package mysql import ( "context" "errors" "strings" "time" "gorm.io/gorm" fleetstatus "wucher/internal/domain/fleet_status" ) type FleetStatusRepository struct { db *gorm.DB } func NewFleetStatusRepository(db *gorm.DB) *FleetStatusRepository { return &FleetStatusRepository{db: db} } func (r *FleetStatusRepository) Create(ctx context.Context, row *fleetstatus.FleetStatus) error { return r.db.WithContext(ctx).Create(row).Error } func (r *FleetStatusRepository) CreateWithDB(ctx context.Context, db *gorm.DB, row *fleetstatus.FleetStatus) error { return db.WithContext(ctx).Create(row).Error } func (r *FleetStatusRepository) Update(ctx context.Context, row *fleetstatus.FleetStatus) error { return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Model(&fleetstatus.FleetStatus{}). Where("id = ? AND deleted_at IS NULL", row.ID). Updates(map[string]any{ "helicopter_id": row.HelicopterID, "status": row.Status, "serviced_at": row.ServicedAt, "updated_by": row.UpdatedBy, }).Error; err != nil { return err } if err := tx.Where("fleet_status_id = ?", row.ID).Delete(&fleetstatus.MaintenanceSchedule{}).Error; err != nil { return err } if len(row.MaintenanceSchedules) > 0 { for i := range row.MaintenanceSchedules { row.MaintenanceSchedules[i].FleetStatusID = row.ID } if err := tx.Create(&row.MaintenanceSchedules).Error; err != nil { return err } } if err := tx.Where("fleet_status_id = ?", row.ID).Delete(&fleetstatus.FleetStatusFile{}).Error; err != nil { return err } if len(row.Files) > 0 { for i := range row.Files { row.Files[i].FleetStatusID = row.ID } if err := tx.Create(&row.Files).Error; err != nil { return err } } return nil }) } func (r *FleetStatusRepository) Delete(ctx context.Context, id []byte, deletedBy []byte) error { now := time.Now().UTC() return r.db.WithContext(ctx). Model(&fleetstatus.FleetStatus{}). Where("id = ? AND deleted_at IS NULL", id). Updates(map[string]any{"deleted_at": now, "deleted_by": deletedBy, "updated_by": deletedBy}).Error } func (r *FleetStatusRepository) GetByID(ctx context.Context, id []byte) (*fleetstatus.FleetStatus, error) { var row fleetstatus.FleetStatus err := r.db.WithContext(ctx). Preload("Helicopter"). Preload("MaintenanceSchedules"). Preload("Files"). Preload("Files.Attachment"). Preload("Files.Attachment.File"). Where("id = ? AND deleted_at IS NULL", id). First(&row).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } return &row, err } func (r *FleetStatusRepository) List(ctx context.Context, filter, sort string, limit, offset int) ([]fleetstatus.FleetStatus, int64, error) { rows := make([]fleetstatus.FleetStatus, 0) var total int64 base := r.db.WithContext(ctx). Model(&fleetstatus.FleetStatus{}). Where("deleted_at IS NULL") if strings.TrimSpace(filter) != "" { like := "%" + strings.ToLower(strings.TrimSpace(filter)) + "%" base = base.Where("LOWER(HEX(id)) LIKE ? OR LOWER(HEX(helicopter_id)) LIKE ?", like, like) } query := base if strings.TrimSpace(sort) != "" { query = query.Order(sort) } else { query = query.Order("created_at DESC") } if err := base.Count(&total).Error; err != nil { return nil, 0, err } if limit > 0 { query = query.Limit(limit).Offset(offset) } if err := query.Preload("Helicopter"). Preload("MaintenanceSchedules"). Preload("Files"). Preload("Files.Attachment"). Preload("Files.Attachment.File"). Find(&rows).Error; err != nil { return nil, 0, err } return rows, total, nil } func (r *FleetStatusRepository) MarkServiced(ctx context.Context, id []byte, servicedAt time.Time, actorID []byte) error { return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var row fleetstatus.FleetStatus if err := tx.Preload("Helicopter"). Preload("MaintenanceSchedules"). Where("id = ? AND deleted_at IS NULL", id). First(&row).Error; err != nil { return err } if len(row.MaintenanceSchedules) > 0 { logs := make([]fleetstatus.FleetStatusServiceLog, 0, len(row.MaintenanceSchedules)) for i := range row.MaintenanceSchedules { item := row.MaintenanceSchedules[i] logs = append(logs, fleetstatus.FleetStatusServiceLog{ FleetStatusID: row.ID, HelicopterID: row.HelicopterID, InspectionType: item.InspectionType, Type: item.Type, Due: item.Due, Ext: item.Ext, ServicedAt: servicedAt, CreatedBy: actorID, }) } if err := tx.Create(&logs).Error; err != nil { return err } } if err := tx.Where("fleet_status_id = ?", row.ID).Delete(&fleetstatus.MaintenanceSchedule{}).Error; err != nil { return err } return tx.Model(&fleetstatus.FleetStatus{}). Where("id = ? AND deleted_at IS NULL", row.ID). Updates(map[string]any{ "status": fleetstatus.StatusServiced, "serviced_at": servicedAt, "updated_by": actorID, }). Error }) } func (r *FleetStatusRepository) LatestByHelicopterIDs(ctx context.Context, helicopterIDs [][]byte) (map[string]*fleetstatus.FleetStatus, error) { out := make(map[string]*fleetstatus.FleetStatus) filtered := make([][]byte, 0, len(helicopterIDs)) seen := make(map[string]struct{}, len(helicopterIDs)) for _, id := range helicopterIDs { if len(id) != 16 { continue } key := string(id) if _, ok := seen[key]; ok { continue } seen[key] = struct{}{} filtered = append(filtered, append([]byte(nil), id...)) } if len(filtered) == 0 { return out, nil } rows := make([]fleetstatus.FleetStatus, 0) // Ordered newest-first so the first row seen per helicopter is its latest. if err := r.db.WithContext(ctx). Where("deleted_at IS NULL"). Where("helicopter_id IN ?", filtered). Order("created_at DESC, id DESC"). Preload("Helicopter"). Preload("MaintenanceSchedules"). Preload("Files"). Preload("Files.Attachment"). Preload("Files.Attachment.File"). Find(&rows).Error; err != nil { return nil, err } for i := range rows { key := string(rows[i].HelicopterID) if _, ok := out[key]; ok { continue } row := rows[i] out[key] = &row } return out, nil } func (r *FleetStatusRepository) ListServiceHistoryByHelicopterID(ctx context.Context, helicopterID []byte, limit, offset int) ([]fleetstatus.FleetStatusServiceLog, int64, error) { rows := make([]fleetstatus.FleetStatusServiceLog, 0) var total int64 base := r.db.WithContext(ctx). Model(&fleetstatus.FleetStatusServiceLog{}). Where("helicopter_id = ?", helicopterID) if err := base.Count(&total).Error; err != nil { return nil, 0, err } query := base.Order("serviced_at DESC, created_at DESC") if limit > 0 { query = query.Limit(limit).Offset(offset) } if err := query.Preload("Helicopter").Find(&rows).Error; err != nil { return nil, 0, err } return rows, total, nil } func (r *FleetStatusRepository) HelicopterIDsWithFleetStatus(ctx context.Context) ([][]byte, error) { type rowT struct { HelicopterID []byte `gorm:"column:helicopter_id"` } rows := make([]rowT, 0) err := r.db.WithContext(ctx). Table("fleet_statuses"). Select("DISTINCT helicopter_id"). Where("deleted_at IS NULL"). Scan(&rows).Error if err != nil { return nil, err } out := make([][]byte, 0, len(rows)) for i := range rows { if len(rows[i].HelicopterID) == 16 { out = append(out, rows[i].HelicopterID) } } return out, nil } func (r *FleetStatusRepository) HelicopterIDByMissionID(ctx context.Context, missionID []byte) ([]byte, error) { if len(missionID) != 16 { return nil, nil } var out struct { HelicopterID []byte `gorm:"column:helicopter_id"` } err := r.db.WithContext(ctx). Table("missions m"). Select("ra.helicopter_id AS helicopter_id"). Joins("JOIN flights f ON f.id = m.flight_id AND f.deleted_at IS NULL"). Joins("JOIN takeover_acs ta ON ta.id = f.takeover_ac_id AND ta.deleted_at IS NULL"). Joins("JOIN reserve_acs ra ON ra.id = ta.reserve_ac_id AND ra.deleted_at IS NULL"). Where("m.id = ? AND m.deleted_at IS NULL", missionID). Limit(1). Scan(&out).Error if err != nil { return nil, err } return out.HelicopterID, nil }