Files
fm_be/internal/repository/mysql/flight_data_repo.go
2026-07-16 22:16:45 +07:00

595 lines
18 KiB
Go

package mysql
import (
"context"
"errors"
"log"
"time"
"gorm.io/gorm"
flightdata "wucher/internal/domain/flight_data"
"wucher/internal/shared/pkg/txctx"
"wucher/internal/shared/pkg/uuidv7"
)
type FlightDataRepository struct {
db *gorm.DB
hasHesloFlightsTable bool
hasHesloSlingsTable bool
hasLoggingSlingsTable bool
hasHecFlightsTable bool
hasHecSlingsTable bool
hasHecLoadsTable bool
}
func NewFlightDataRepository(db *gorm.DB) *FlightDataRepository {
schema := newSchemaCache(db)
return &FlightDataRepository{
db: db,
hasHesloFlightsTable: schema.HasTable("heslo_flights"),
hasHesloSlingsTable: schema.HasTable("heslo_slings"),
hasLoggingSlingsTable: schema.HasTable("logging_slings"),
hasHecFlightsTable: schema.HasTable("hec_flights"),
hasHecSlingsTable: schema.HasTable("hec_slings"),
hasHecLoadsTable: schema.HasTable("hec_loads"),
}
}
func (r *FlightDataRepository) Create(ctx context.Context, row *flightdata.FlightData) error {
if row == nil {
return nil
}
now := time.Now().UTC()
if len(row.ID) != 16 {
row.ID = uuidv7.MustBytes()
}
if row.CreatedAt.IsZero() {
row.CreatedAt = now
}
if row.UpdatedAt.IsZero() {
row.UpdatedAt = row.CreatedAt
}
return txctx.DB(ctx, r.db).Table("flight_data").Create(flightDataPersistenceValues(row)).Error
}
func (r *FlightDataRepository) Update(ctx context.Context, row *flightdata.FlightData) error {
if row == nil {
return nil
}
row.UpdatedAt = time.Now().UTC()
updates := flightDataPersistenceValues(row)
delete(updates, "id")
delete(updates, "created_at")
delete(updates, "created_by")
return r.db.WithContext(ctx).
Model(&flightdata.FlightData{}).
Where("id = ?", row.ID).
Updates(updates).Error
}
func (r *FlightDataRepository) 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,
}
return r.db.WithContext(ctx).
Model(&flightdata.FlightData{}).
Where("id = ? AND deleted_at IS NULL", id).
Updates(updates).Error
}
func (r *FlightDataRepository) GetByID(ctx context.Context, id []byte) (*flightdata.FlightData, error) {
var row flightdata.FlightData
err := r.db.WithContext(ctx).
Preload("Mission").
Preload("Mission.Flight").
Preload("Mission.Flight.Takeover").
Preload("Mission.Flight.Takeover.RosterCrews").
Preload("Mission.Flight.Takeover.OtherPeople").
Preload("CoPilot").
Preload("FromICAO").
Preload("FromHospital").
Preload("ToICAO").
Preload("ToHospital").
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 *FlightDataRepository) GetByMissionID(ctx context.Context, missionID []byte) (*flightdata.FlightData, error) {
var row flightdata.FlightData
err := r.db.WithContext(ctx).
Preload("Mission").
Preload("Mission.Flight").
Preload("Mission.Flight.Takeover").
Preload("Mission.Flight.Takeover.RosterCrews").
Preload("Mission.Flight.Takeover.OtherPeople").
Preload("CoPilot").
Preload("FromICAO").
Preload("FromHospital").
Preload("ToICAO").
Preload("ToHospital").
Where("mission_id = ? AND deleted_at IS NULL", missionID).
Order("created_at DESC").
First(&row).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return &row, err
}
func (r *FlightDataRepository) ListByMissionID(ctx context.Context, missionID []byte) ([]flightdata.FlightData, error) {
rows := make([]flightdata.FlightData, 0)
err := r.db.WithContext(ctx).
Preload("Mission").
Preload("Mission.Flight").
Preload("Mission.Flight.Takeover").
Preload("Mission.Flight.Takeover.RosterCrews").
Preload("Mission.Flight.Takeover.OtherPeople").
Preload("CoPilot").
Preload("FromICAO").
Preload("FromHospital").
Preload("ToICAO").
Preload("ToHospital").
Where("mission_id = ? AND deleted_at IS NULL", missionID).
Order("created_at ASC, id ASC").
Find(&rows).Error
return rows, err
}
func (r *FlightDataRepository) GetByFlightID(ctx context.Context, flightID []byte) (*flightdata.FlightData, error) {
var row flightdata.FlightData
err := r.db.WithContext(ctx).
Preload("Mission").
Preload("CoPilot").
Preload("FromICAO").
Preload("FromHospital").
Preload("ToICAO").
Preload("ToHospital").
Where("mission_id IN (SELECT id FROM missions WHERE flight_id = ? AND deleted_at IS NULL) AND deleted_at IS NULL", flightID).
Order("created_at DESC").
First(&row).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return &row, err
}
func (r *FlightDataRepository) List(ctx context.Context, date string, missionID, flightDataID []byte, limit, offset int) ([]flightdata.FlightData, int64, error) {
rows := make([]flightdata.FlightData, 0)
var total int64
base := r.db.WithContext(ctx).
Model(&flightdata.FlightData{}).
Preload("Mission").
Preload("Mission.Flight").
Preload("Mission.Flight.Takeover").
Preload("Mission.Flight.Takeover.RosterCrews").
Preload("Mission.Flight.Takeover.OtherPeople").
Preload("CoPilot").
Preload("FromICAO").
Preload("FromHospital").
Preload("ToICAO").
Preload("ToHospital").
Where("deleted_at IS NULL")
if date != "" {
base = base.Where("DATE(take_off) = ?", date)
}
if len(missionID) == 16 {
base = base.Where("mission_id = ?", missionID)
}
if len(flightDataID) == 16 {
base = base.Where("id = ?", flightDataID)
}
if err := base.Count(&total).Error; err != nil {
return nil, 0, err
}
query := base.Order("take_off DESC, created_at DESC")
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 *FlightDataRepository) GetSPODetailsByFlightDataID(ctx context.Context, flightDataID []byte) (*flightdata.SPODetails, error) {
details := &flightdata.SPODetails{
HESLO: flightdata.SPOHESLODetails{
Flights: make([]flightdata.SPOHESLOFlight, 0),
Slings: make([]flightdata.SPOHESLOSling, 0),
},
Logging: flightdata.SPOLoggingDetails{
Slings: make([]flightdata.SPOLoggingSling, 0),
},
HEC: flightdata.SPOHECDetails{
Flights: make([]flightdata.SPOHECFlight, 0),
Slings: make([]flightdata.SPOHECSling, 0),
Loads: make([]flightdata.SPOHECLoad, 0),
},
}
if len(flightDataID) != 16 {
return details, nil
}
db := r.db.WithContext(ctx)
if r.hasHesloFlightsTable {
type hesloFlightRow struct {
ID []byte `gorm:"column:id"`
ROT int `gorm:"column:rot"`
FuelTruckID []byte `gorm:"column:fuel_truck_id"`
FuelTruckName string `gorm:"column:fuel_truck_name"`
LoggingSlingID []byte `gorm:"column:logging_sling_id"`
LoggingSlingName string `gorm:"column:logging_sling_name"`
}
rows := make([]hesloFlightRow, 0)
if err := db.Table("heslo_flights hf").
Select("hf.id, hf.rot, hf.fuel_truck_id, fuel.name AS fuel_truck_name, hf.logging_sling_id, log.name AS logging_sling_name").
Joins("LEFT JOIN facilities fuel ON fuel.id = hf.fuel_truck_id").
Joins("LEFT JOIN facilities log ON log.id = hf.logging_sling_id").
Where("hf.flight_data_id = ?", flightDataID).
Order("hf.created_at ASC").
Scan(&rows).Error; err != nil {
return nil, err
}
for i := range rows {
details.HESLO.Flights = append(details.HESLO.Flights, flightdata.SPOHESLOFlight{
ID: rows[i].ID,
ROT: rows[i].ROT,
FuelTruckID: rows[i].FuelTruckID,
FuelTruckName: rows[i].FuelTruckName,
LoggingSlingID: rows[i].LoggingSlingID,
LoggingSlingName: rows[i].LoggingSlingName,
})
}
}
if r.hasHesloSlingsTable && r.hasHesloFlightsTable {
type hesloSlingRow struct {
ID []byte `gorm:"column:id"`
HESLOFlightID []byte `gorm:"column:heslo_flight_id"`
SlingID []byte `gorm:"column:facility_sling_id"`
SlingName string `gorm:"column:sling_name"`
}
rows := make([]hesloSlingRow, 0)
if err := db.Table("heslo_slings hs").
Select("hs.id, hs.heslo_flight_id, hs.facility_sling_id, sling.name AS sling_name").
Joins("JOIN heslo_flights hf ON hf.id = hs.heslo_flight_id").
Joins("LEFT JOIN facilities sling ON sling.id = hs.facility_sling_id").
Where("hf.flight_data_id = ?", flightDataID).
Order("hs.created_at ASC").
Scan(&rows).Error; err != nil {
return nil, err
}
for i := range rows {
details.HESLO.Slings = append(details.HESLO.Slings, flightdata.SPOHESLOSling{
ID: rows[i].ID,
HESLOFlightID: rows[i].HESLOFlightID,
SlingID: rows[i].SlingID,
SlingName: rows[i].SlingName,
})
}
}
if r.hasLoggingSlingsTable {
type loggingSlingRow struct {
ID []byte `gorm:"column:id"`
SlingID []byte `gorm:"column:facility_sling_id"`
SlingName string `gorm:"column:sling_name"`
}
rows := make([]loggingSlingRow, 0)
if err := db.Table("logging_slings ls").
Select("ls.id, ls.facility_sling_id, sling.name AS sling_name").
Joins("LEFT JOIN facilities sling ON sling.id = ls.facility_sling_id").
Where("ls.flight_data_id = ?", flightDataID).
Order("ls.created_at ASC").
Scan(&rows).Error; err != nil {
return nil, err
}
for i := range rows {
details.Logging.Slings = append(details.Logging.Slings, flightdata.SPOLoggingSling{
ID: rows[i].ID,
SlingID: rows[i].SlingID,
SlingName: rows[i].SlingName,
})
}
}
if r.hasHecFlightsTable {
type hecFlightRow struct {
ID []byte `gorm:"column:id"`
ROT int `gorm:"column:rot"`
HECCycle int `gorm:"column:hec_cycle"`
EquipmentID []byte `gorm:"column:equipment_id"`
EquipmentName string `gorm:"column:equipment_name"`
}
rows := make([]hecFlightRow, 0)
if err := db.Table("hec_flights hf").
Select("hf.id, hf.rot, hf.hec_cycle, hf.equipment_id, eq.name AS equipment_name").
Joins("LEFT JOIN facilities eq ON eq.id = hf.equipment_id").
Where("hf.flight_data_id = ?", flightDataID).
Order("hf.created_at ASC").
Scan(&rows).Error; err != nil {
return nil, err
}
for i := range rows {
details.HEC.Flights = append(details.HEC.Flights, flightdata.SPOHECFlight{
ID: rows[i].ID,
ROT: rows[i].ROT,
HECCycle: rows[i].HECCycle,
EquipmentID: rows[i].EquipmentID,
EquipmentName: rows[i].EquipmentName,
})
}
}
if r.hasHecSlingsTable {
type hecSlingRow struct {
ID []byte `gorm:"column:id"`
SlingID []byte `gorm:"column:hec_sling_id"`
SlingName string `gorm:"column:sling_name"`
}
rows := make([]hecSlingRow, 0)
if err := db.Table("hec_slings hs").
Select("hs.id, hs.hec_sling_id, sling.name AS sling_name").
Joins("LEFT JOIN facilities sling ON sling.id = hs.hec_sling_id").
Where("hs.flight_data_id = ?", flightDataID).
Order("hs.created_at ASC").
Scan(&rows).Error; err != nil {
return nil, err
}
for i := range rows {
details.HEC.Slings = append(details.HEC.Slings, flightdata.SPOHECSling{
ID: rows[i].ID,
SlingID: rows[i].SlingID,
SlingName: rows[i].SlingName,
})
}
}
if r.hasHecLoadsTable {
type hecLoadRow struct {
ID []byte `gorm:"column:id"`
LoadCategory string `gorm:"column:load_category"`
Quantity int `gorm:"column:quantity"`
}
rows := make([]hecLoadRow, 0)
if err := db.Table("hec_loads").
Select("id, load_category, quantity").
Where("flight_data_id = ?", flightDataID).
Order("created_at ASC").
Scan(&rows).Error; err != nil {
return nil, err
}
for i := range rows {
details.HEC.Loads = append(details.HEC.Loads, flightdata.SPOHECLoad{
ID: rows[i].ID,
LoadCategory: rows[i].LoadCategory,
Quantity: rows[i].Quantity,
})
}
}
return details, nil
}
func (r *FlightDataRepository) UpsertSPODetails(ctx context.Context, flightDataID []byte, data *flightdata.SPOUpsertData) error {
if len(flightDataID) != 16 || data == nil {
return nil
}
log.Printf("[UpsertSPODetails] start: heslo=%v logging=%v hec=%v hasHesloFlights=%v hasLogging=%v hasHecFlights=%v",
data.HESLO != nil, data.Logging != nil, data.HEC != nil,
r.hasHesloFlightsTable, r.hasLoggingSlingsTable, r.hasHecFlightsTable)
txErr := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
now := time.Now().UTC()
if data.HESLO != nil && r.hasHesloFlightsTable {
if r.hasHesloSlingsTable {
if err := tx.Exec(
"DELETE hs FROM heslo_slings hs JOIN heslo_flights hf ON hf.id = hs.heslo_flight_id WHERE hf.flight_data_id = ?",
flightDataID,
).Error; err != nil {
return err
}
}
if err := tx.Exec("DELETE FROM heslo_flights WHERE flight_data_id = ?", flightDataID).Error; err != nil {
return err
}
for _, f := range data.HESLO.Flights {
hf := &flightdata.HESLOFlight{
ID: uuidv7.MustBytes(),
FlightDataID: flightDataID,
ROT: f.ROT,
FacilityFuelTruckID: f.FuelTruckID,
LoggingSlingID: f.LoggingSlingID,
CreatedAt: now,
}
if err := tx.Omit("FlightData", "FacilityFuelTruck", "LoggingSling").Create(hf).Error; err != nil {
return err
}
if r.hasHesloSlingsTable {
for _, s := range f.Slings {
if len(s.SlingID) != 16 {
continue
}
hs := &flightdata.HESLOSling{
ID: uuidv7.MustBytes(),
HESLOFlightID: hf.ID,
FacilitySlingID: s.SlingID,
CreatedAt: now,
}
if err := tx.Omit("HESLOFlight", "FacilitySling").Create(hs).Error; err != nil {
return err
}
}
}
}
}
if data.Logging != nil && r.hasLoggingSlingsTable {
if err := tx.Exec("DELETE FROM logging_slings WHERE flight_data_id = ?", flightDataID).Error; err != nil {
return err
}
for _, s := range data.Logging.Slings {
if len(s.SlingID) != 16 {
continue
}
ls := &flightdata.LoggingSling{
ID: uuidv7.MustBytes(),
FlightDataID: flightDataID,
FacilitySlingID: s.SlingID,
CreatedAt: now,
}
if err := tx.Omit("FlightData", "FacilitySling").Create(ls).Error; err != nil {
return err
}
}
}
if data.HEC != nil {
if r.hasHecSlingsTable {
if err := tx.Exec("DELETE FROM hec_slings WHERE flight_data_id = ?", flightDataID).Error; err != nil {
return err
}
}
if r.hasHecLoadsTable {
if err := tx.Exec("DELETE FROM hec_loads WHERE flight_data_id = ?", flightDataID).Error; err != nil {
return err
}
}
if r.hasHecFlightsTable {
if err := tx.Exec("DELETE FROM hec_flights WHERE flight_data_id = ?", flightDataID).Error; err != nil {
return err
}
for _, f := range data.HEC.Flights {
hf := &flightdata.HECFlight{
ID: uuidv7.MustBytes(),
FlightDataID: flightDataID,
ROT: f.ROT,
HECCycle: f.HECCycle,
EquipmentID: f.EquipmentID,
CreatedAt: now,
}
if err := tx.Omit("FlightData", "Equipment").Create(hf).Error; err != nil {
return err
}
}
}
if r.hasHecSlingsTable {
for _, s := range data.HEC.Slings {
if len(s.SlingID) != 16 {
continue
}
hs := &flightdata.HECSling{
ID: uuidv7.MustBytes(),
FlightDataID: flightDataID,
HECSlingID: s.SlingID,
CreatedAt: now,
}
if err := tx.Omit("FlightData", "HECSling").Create(hs).Error; err != nil {
return err
}
}
}
if r.hasHecLoadsTable {
for _, l := range data.HEC.Loads {
hl := &flightdata.HECLoads{
ID: uuidv7.MustBytes(),
FlightDataID: flightDataID,
LoadCategory: l.LoadCategory,
Quantity: l.Quantity,
CreatedAt: now,
}
if err := tx.Omit("FlightData").Create(hl).Error; err != nil {
return err
}
}
}
}
return nil
})
log.Printf("[UpsertSPODetails] done: err=%v", txErr)
return txErr
}
func flightDataPersistenceValues(row *flightdata.FlightData) map[string]any {
if row == nil {
return map[string]any{}
}
return map[string]any{
"id": nullableBytesValue(row.ID),
"mission_id": nullableBytesValue(row.MissionID),
"co_pilot_id": nullableBytesValue(row.CoPilotID),
"from_icao_id": nullableBytesValue(row.FromICAOID),
"from_hospital_id": nullableBytesValue(row.FromHospitalID),
"to_icao_id": nullableBytesValue(row.ToICAOID),
"to_hospital_id": nullableBytesValue(row.ToHospitalID),
"take_off": nullableTimeValue(row.FlightTakeOff),
"landing": nullableTimeValue(row.FlightLanding),
"duration": row.FlightDuration,
"flight_type": row.FlightType,
"red": row.FlightRED,
"max_n1": row.MaxN1,
"max_n2": row.MaxN2,
"pax_count": row.PaxCount,
"ticket_no": row.TicketNo,
"engine": row.Engine,
"landing_count": row.LandingCount,
"rotor_brake_cycle": row.RotorBrakeCycle,
"hook_releases": row.HookReleases,
"delivery_note_number": row.DeliveryNoteNumber,
"customer_name": row.CustomerName,
"flight_plan_distance": row.FlightPlanDistance,
"flight_plan_time": row.FlightPlanTime,
"flight_plan_true_course": row.FlightPlanTrueCourse,
"fuel_before_flight": row.FuelBeforeFlight,
"fuel_upload": row.FuelUpload,
"fuel_after_flight": row.FuelAfterFlight,
"fuel_planning": row.FuelPlanning,
"flight_position": row.FlightPositioning,
"other_information": row.OtherInformation,
"status": row.Status,
"created_at": row.CreatedAt,
"created_by": nullableBytesValue(row.CreatedBy),
"updated_at": row.UpdatedAt,
"updated_by": nullableBytesValue(row.UpdatedBy),
"deleted_at": nullableTimePtrValue(row.DeletedAt),
"deleted_by": nullableBytesValue(row.DeletedBy),
}
}
func nullableBytesValue(v []byte) any {
if len(v) == 0 {
return nil
}
return v
}
func nullableTimeValue(v time.Time) any {
if v.IsZero() {
return nil
}
return v
}
func nullableTimePtrValue(v *time.Time) any {
if v == nil {
return nil
}
return *v
}