package mysql import ( "context" "errors" "strings" "time" "gorm.io/gorm" flightinspection "wucher/internal/domain/flight_inspection" "wucher/internal/domain/helicopter" reserveac "wucher/internal/domain/reserve_ac" "wucher/internal/shared/pkg/uuidv7" shareddto "wucher/internal/transport/http/dto/shared" ) type ReserveAcRepository struct { db *gorm.DB } func NewReserveAcRepository(db *gorm.DB) *ReserveAcRepository { return &ReserveAcRepository{db: db} } func (r *ReserveAcRepository) Create(ctx context.Context, row *reserveac.ReserveAc) error { return r.db.WithContext(ctx).Create(row).Error } func (r *ReserveAcRepository) Update(ctx context.Context, row *reserveac.ReserveAc) error { return r.db.WithContext(ctx).Save(row).Error } func (r *ReserveAcRepository) Delete(ctx context.Context, id []byte, deletedBy []byte) error { now := time.Now().UTC() updates := map[string]any{ "deleted_at": now, "deleted_by": deletedBy, "updated_by": deletedBy, } res := r.db.WithContext(ctx). Model(&reserveac.ReserveAc{}). Where("id = ? AND deleted_at IS NULL", id). Updates(updates) if res.Error != nil { return res.Error } if res.RowsAffected == 0 { return gorm.ErrRecordNotFound } return nil } func (r *ReserveAcRepository) GetByID(ctx context.Context, id []byte) (*reserveac.ReserveAc, error) { var row reserveac.ReserveAc err := r.db.WithContext(ctx). Preload("Aircraft"). Preload("Inspection"). 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 *ReserveAcRepository) GetActiveByInspectionID(ctx context.Context, inspectionID []byte) (*reserveac.ReserveAc, error) { if len(inspectionID) != 16 { return nil, nil } var row reserveac.ReserveAc err := r.db.WithContext(ctx). Preload("Aircraft"). Preload("Inspection"). Where("inspection_id = ? AND deleted_at IS NULL", inspectionID). Limit(1). Find(&row).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } if err != nil { return nil, err } if len(row.ID) == 0 { return nil, nil } return &row, nil } func (r *ReserveAcRepository) List(ctx context.Context, filter string, sort string, limit, offset int) ([]reserveac.ReserveAc, int64, error) { rows := make([]reserveac.ReserveAc, 0) var total int64 base := r.db.WithContext(ctx).Model(&reserveac.ReserveAc{}).Where("deleted_at IS NULL") if strings.TrimSpace(filter) != "" { like := "%" + strings.ToLower(strings.TrimSpace(filter)) + "%" base = base.Where( "LOWER(status) LIKE ? OR LOWER(HEX(id)) LIKE ? OR LOWER(HEX(helicopter_id)) LIKE ? OR LOWER(HEX(inspection_id)) LIKE ?", like, like, like, like, ) } query := base if sort != "" { query = query.Order(sort) } if err := base.Count(&total).Error; err != nil { return nil, 0, err } if limit > 0 { query = query.Limit(limit).Offset(offset) } if err := query.Find(&rows).Error; err != nil { return nil, 0, err } return rows, total, nil } func (r *ReserveAcRepository) AircraftExists(ctx context.Context, helicopterID []byte) (bool, error) { if len(helicopterID) != 16 { return false, nil } var total int64 err := r.db.WithContext(ctx). Model(&helicopter.Helicopter{}). Where("id = ?", helicopterID). Count(&total).Error return total > 0, err } func (r *ReserveAcRepository) FlightInspectionExists(ctx context.Context, inspectionID []byte) (bool, error) { if len(inspectionID) != 16 { return false, nil } var total int64 err := r.db.WithContext(ctx). Model(&flightinspection.FlightInspection{}). Where("id = ?", inspectionID). Count(&total).Error return total > 0, err } func (r *ReserveAcRepository) ExistsActiveByInspectionID(ctx context.Context, inspectionID []byte, excludeID []byte) (bool, error) { if len(inspectionID) != 16 { return false, nil } var total int64 q := r.db.WithContext(ctx). Model(&reserveac.ReserveAc{}). Where("inspection_id = ? AND deleted_at IS NULL", inspectionID) if len(excludeID) == 16 { q = q.Where("id <> ?", excludeID) } if err := q.Count(&total).Error; err != nil { return false, err } return total > 0, nil } func (r *ReserveAcRepository) GetLastDailyInspectionByHelicopterID(ctx context.Context, helicopterID []byte) (*shareddto.LastDailyInspection, error) { if len(helicopterID) != 16 { return nil, nil } type row struct { ID []byte `gorm:"column:id"` Name string `gorm:"column:name"` ShortName string `gorm:"column:short_name"` At time.Time `gorm:"column:inspected_at"` } var out row err := r.db.WithContext(ctx). Table("reserve_acs ra"). Select(` fi.id AS id, TRIM(CONCAT(COALESCE(u.first_name, ''), ' ', COALESCE(u.last_name, ''))) AS name, COALESCE(NULLIF(pp.short_name, ''), NULLIF(tp.short_name, ''), '') AS short_name, COALESCE(afi.updated_at, fpc.updated_at, bfi.updated_at) AS inspected_at`). Joins("JOIN flight_inspections fi ON fi.id = ra.inspection_id"). Joins("LEFT JOIN before_flight_inspections bfi ON bfi.flight_inspection_id = fi.id"). Joins("LEFT JOIN flight_prep_checks fpc ON fpc.flight_inspection_id = fi.id"). Joins("LEFT JOIN after_flight_inspections afi ON afi.flight_inspection_id = fi.id"). Joins(`JOIN users u ON u.id = CASE WHEN afi.updated_at IS NOT NULL THEN afi.updated_by WHEN fpc.updated_at IS NOT NULL THEN fpc.updated_by WHEN bfi.updated_at IS NOT NULL THEN bfi.updated_by ELSE NULL END`). Joins("LEFT JOIN pilot_profiles pp ON pp.user_id = u.id"). Joins("LEFT JOIN technician_profiles tp ON tp.user_id = u.id"). Where("ra.helicopter_id = ? AND ra.deleted_at IS NULL", helicopterID). Where("COALESCE(afi.updated_at, fpc.updated_at, bfi.updated_at) IS NOT NULL"). Order("fi.inspection_date DESC"). Order("COALESCE(afi.updated_at, fpc.updated_at, bfi.updated_at) DESC"). Limit(1). Take(&out).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } if err != nil { return nil, err } return &shareddto.LastDailyInspection{ ID: out.ID, Name: strings.TrimSpace(out.Name), ShortName: strings.TrimSpace(out.ShortName), At: out.At.UTC(), }, nil } func (r *ReserveAcRepository) GetLastDailyInspectionSummaryByHelicopterID(ctx context.Context, helicopterID []byte) (*shareddto.LastDailyInspectionSummary, error) { if len(helicopterID) != 16 { return nil, nil } type row struct { InspectionID []byte `gorm:"column:inspection_id"` InspectorID []byte `gorm:"column:inspector_id"` InspectorName string `gorm:"column:inspector_name"` InspectorLicenseNo string `gorm:"column:inspector_license_no"` BaseID []byte `gorm:"column:base_id"` BaseName string `gorm:"column:base_name"` BaseAbbreviation string `gorm:"column:base_abbreviation"` BaseType string `gorm:"column:base_type"` InspectedAt time.Time `gorm:"column:inspected_at"` CompletedCount int `gorm:"column:completed_count"` TotalCount int `gorm:"column:total_count"` } var out row err := r.db.WithContext(ctx). Table("reserve_acs ra"). Select(` ra.inspection_id AS inspection_id, COALESCE(u.id, ra.updated_by) AS inspector_id, COALESCE( NULLIF(TRIM(CONCAT(COALESCE(u.first_name, ''), ' ', COALESCE(u.last_name, ''))), ''), NULLIF(TRIM(u.username), ''), NULLIF(TRIM(u.email), ''), '' ) AS inspector_name, COALESCE(NULLIF(pp.license_no, ''), NULLIF(tp.license_no, ''), '') AS inspector_license_no, ra.base_id AS base_id, COALESCE(TRIM(b.base), '') AS base_name, COALESCE(TRIM(b.base_abbreviation), '') AS base_abbreviation, COALESCE(NULLIF(LOWER(bc.key), ''), 'base') AS base_type, COALESCE(afi.updated_at, fpc.updated_at, bfi.updated_at, ra.updated_at) AS inspected_at, ( SELECT COUNT(*) FROM flight_inspection_file_checklists fic WHERE fic.flight_inspection_id = fi.id ) AS total_count, ( SELECT COUNT(*) FROM flight_inspection_file_checklists fic WHERE fic.flight_inspection_id = fi.id AND fic.is_done = 1 ) AS completed_count `). Joins("JOIN flight_inspections fi ON fi.id = ra.inspection_id"). Joins("LEFT JOIN before_flight_inspections bfi ON bfi.flight_inspection_id = fi.id"). Joins("LEFT JOIN flight_prep_checks fpc ON fpc.flight_inspection_id = fi.id"). Joins("LEFT JOIN after_flight_inspections afi ON afi.flight_inspection_id = fi.id"). Joins(`LEFT JOIN users u ON u.id = CASE WHEN afi.updated_at IS NOT NULL THEN afi.updated_by WHEN fpc.updated_at IS NOT NULL THEN fpc.updated_by WHEN bfi.updated_at IS NOT NULL THEN bfi.updated_by ELSE ra.updated_by END`). Joins("LEFT JOIN pilot_profiles pp ON pp.user_id = u.id"). Joins("LEFT JOIN technician_profiles tp ON tp.user_id = u.id"). Joins("LEFT JOIN bases b ON b.id = ra.base_id AND b.deleted_at IS NULL"). Joins("LEFT JOIN base_categories bc ON bc.id = b.base_category_id"). Where("ra.helicopter_id = ? AND ra.deleted_at IS NULL", helicopterID). Where("COALESCE(afi.updated_at, fpc.updated_at, bfi.updated_at, ra.updated_at) IS NOT NULL"). Order("fi.inspection_date DESC, COALESCE(afi.updated_at, fpc.updated_at, bfi.updated_at, ra.updated_at) DESC"). Limit(1). Take(&out).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } if err != nil { return nil, err } inspectionID, _ := uuidv7.BytesToString(out.InspectionID) inspectorID, _ := uuidv7.BytesToString(out.InspectorID) baseID, _ := uuidv7.BytesToString(out.BaseID) return &shareddto.LastDailyInspectionSummary{ InspectionID: inspectionID, Status: "inspected_cleared", InspectorID: inspectorID, InspectorName: strings.TrimSpace(out.InspectorName), InspectorLicenseNo: strings.TrimSpace(out.InspectorLicenseNo), BaseID: baseID, BaseName: strings.TrimSpace(out.BaseName), BaseAbbreviation: strings.TrimSpace(out.BaseAbbreviation), BaseType: strings.TrimSpace(out.BaseType), InspectedAt: out.InspectedAt.UTC(), InspectionFilesChecklistCompleted: out.CompletedCount, InspectionFilesChecklistTotal: out.TotalCount, }, nil }