init push

This commit is contained in:
2026-07-16 22:16:45 +07:00
commit 8b068bdb10
1021 changed files with 332816 additions and 0 deletions

View File

@@ -0,0 +1,332 @@
package service
import (
"context"
"strings"
afterflightinspection "wucher/internal/domain/after_flight_inspection"
flightdomain "wucher/internal/domain/flight"
flightdata "wucher/internal/domain/flight_data"
fmreport "wucher/internal/domain/fm_report"
sharedconst "wucher/internal/shared/const"
"wucher/internal/shared/pkg/uuidv7"
)
type aogRecalculator interface {
RecalculateAOGByMission(ctx context.Context, missionID []byte) error
}
type missionFlightResolver interface {
FlightIDByMissionID(ctx context.Context, missionID []byte) ([]byte, error)
}
type fmReportLookup interface {
GetByFlightID(ctx context.Context, flightID []byte) (*fmreport.Report, error)
}
type flightLookup interface {
GetByID(context.Context, []byte) (*flightdomain.Flight, error)
}
type afterFlightInspectionLookup interface {
GetByFlightInspectionID(context.Context, []byte) (*afterflightinspection.AfterFlightInspection, error)
}
type FlightDataService struct {
repo flightdata.Repository
aog aogRecalculator
mission missionFlightResolver
flight flightLookup
fmReports fmReportLookup
afterFlight afterFlightInspectionLookup
}
func NewFlightDataService(repo flightdata.Repository) *FlightDataService {
return &FlightDataService{repo: repo}
}
func (s *FlightDataService) WithAOGRecalculator(a aogRecalculator) *FlightDataService {
s.aog = a
return s
}
func (s *FlightDataService) WithMissionResolver(resolver missionFlightResolver) *FlightDataService {
s.mission = resolver
return s
}
func (s *FlightDataService) WithFlightLookup(lookup flightLookup) *FlightDataService {
s.flight = lookup
return s
}
func (s *FlightDataService) WithFMReportLookup(lookup fmReportLookup) *FlightDataService {
s.fmReports = lookup
return s
}
func (s *FlightDataService) WithAfterFlightInspectionLookup(lookup afterFlightInspectionLookup) *FlightDataService {
s.afterFlight = lookup
return s
}
func (s *FlightDataService) recalcAOG(ctx context.Context, missionID []byte) {
if s.aog == nil || len(missionID) == 0 {
return
}
_ = s.aog.RecalculateAOGByMission(ctx, missionID)
}
func (s *FlightDataService) Create(ctx context.Context, row *flightdata.FlightData) error {
if row == nil {
return flightdata.ErrRequired
}
if err := validateFlightDataLocationPair(row.FromICAOID, row.FromHospitalID, true); err != nil {
return err
}
if err := validateFlightDataLocationPair(row.ToICAOID, row.ToHospitalID, false); err != nil {
return err
}
if err := s.canCreateForMission(ctx, row.MissionID); err != nil {
return err
}
row.Status = flightdata.StatusInProgress
if err := s.repo.Create(ctx, row); err != nil {
return err
}
s.recalcAOG(ctx, row.MissionID)
if err := s.syncStatus(ctx, row.ID); err != nil {
return err
}
return nil
}
func (s *FlightDataService) CreatePlaceholder(ctx context.Context, row *flightdata.FlightData) error {
if row == nil {
return flightdata.ErrRequired
}
if err := s.canCreateForMission(ctx, row.MissionID); err != nil {
return err
}
row.Status = flightdata.StatusInProgress
if err := s.repo.Create(ctx, row); err != nil {
return err
}
return nil
}
func (s *FlightDataService) Update(ctx context.Context, row *flightdata.FlightData) error {
if row == nil {
return flightdata.ErrRequired
}
if err := validateFlightDataLocationPair(row.FromICAOID, row.FromHospitalID, true); err != nil {
return err
}
if err := validateFlightDataLocationPair(row.ToICAOID, row.ToHospitalID, false); err != nil {
return err
}
if err := s.ensureEditable(ctx, row.MissionID); err != nil {
return err
}
if err := s.repo.Update(ctx, row); err != nil {
return err
}
s.recalcAOG(ctx, row.MissionID)
if err := s.syncStatus(ctx, row.ID); err != nil {
return err
}
return nil
}
func (s *FlightDataService) Delete(ctx context.Context, id []byte, deletedBy []byte) error {
var missionID []byte
if row, err := s.repo.GetByID(ctx, id); err == nil && row != nil {
missionID = row.MissionID
if err := s.ensureAfterFlightInspectionNotLocked(ctx, missionID); err != nil {
return err
}
if err := s.ensureEditable(ctx, missionID); err != nil {
return err
}
}
if err := s.repo.Delete(ctx, id, deletedBy); err != nil {
return err
}
s.recalcAOG(ctx, missionID)
return nil
}
func (s *FlightDataService) GetByID(ctx context.Context, id []byte) (*flightdata.FlightData, error) {
return s.repo.GetByID(ctx, id)
}
func (s *FlightDataService) GetByFlightID(ctx context.Context, flightID []byte) (*flightdata.FlightData, error) {
return s.repo.GetByFlightID(ctx, flightID)
}
func (s *FlightDataService) GetByMissionID(ctx context.Context, missionID []byte) (*flightdata.FlightData, error) {
return s.repo.GetByMissionID(ctx, missionID)
}
func (s *FlightDataService) ListByMissionID(ctx context.Context, missionID []byte) ([]flightdata.FlightData, error) {
return s.repo.ListByMissionID(ctx, missionID)
}
func (s *FlightDataService) List(ctx context.Context, date string, missionID, flightDataID []byte, limit, offset int) ([]flightdata.FlightData, int64, error) {
return s.repo.List(ctx, date, missionID, flightDataID, limit, offset)
}
func (s *FlightDataService) GetSPODetailsByFlightDataID(ctx context.Context, flightDataID []byte) (*flightdata.SPODetails, error) {
return s.repo.GetSPODetailsByFlightDataID(ctx, flightDataID)
}
func (s *FlightDataService) UpsertSPODetails(ctx context.Context, flightDataID []byte, data *flightdata.SPOUpsertData) error {
return s.repo.UpsertSPODetails(ctx, flightDataID, data)
}
func (s *FlightDataService) CanCreateForMission(ctx context.Context, missionID []byte) error {
return s.canCreateForMission(ctx, missionID)
}
func (s *FlightDataService) canCreateForMission(ctx context.Context, missionID []byte) error {
if err := s.ensureAfterFlightInspectionNotLocked(ctx, missionID); err != nil {
return err
}
flightID, err := s.flightIDByMissionID(ctx, missionID)
if err != nil {
return err
}
if len(flightID) != 16 || s.fmReports == nil {
return nil
}
report, err := s.fmReports.GetByFlightID(ctx, flightID)
if err != nil {
return err
}
if report != nil {
return flightdata.ErrCreateBlocked
}
return nil
}
func (s *FlightDataService) ensureEditable(ctx context.Context, missionID []byte) error {
flightID, err := s.flightIDByMissionID(ctx, missionID)
if err != nil {
return err
}
if len(flightID) != 16 || s.fmReports == nil {
return nil
}
report, err := s.fmReports.GetByFlightID(ctx, flightID)
if err != nil {
return err
}
if report != nil && report.CompletedAt != nil {
return flightdata.ErrLocked
}
return nil
}
func (s *FlightDataService) ensureAfterFlightInspectionNotLocked(ctx context.Context, missionID []byte) error {
flightID, err := s.flightIDByMissionID(ctx, missionID)
if err != nil {
return err
}
if len(flightID) != 16 || s.flight == nil || s.afterFlight == nil {
return nil
}
flightRow, err := s.flight.GetByID(ctx, flightID)
if err != nil {
return err
}
if flightRow == nil || flightRow.Takeover == nil || flightRow.Takeover.ReserveAc == nil || len(flightRow.Takeover.ReserveAc.InspectionID) != 16 {
return nil
}
afterRow, err := s.afterFlight.GetByFlightInspectionID(ctx, flightRow.Takeover.ReserveAc.InspectionID)
if err != nil {
return err
}
if afterRow != nil {
return flightdata.ErrLockedAfterFlightInspection
}
return nil
}
func (s *FlightDataService) flightIDByMissionID(ctx context.Context, missionID []byte) ([]byte, error) {
if len(missionID) != 16 || s.mission == nil {
return nil, nil
}
flightID, err := s.mission.FlightIDByMissionID(ctx, missionID)
if err != nil {
return nil, err
}
return flightID, nil
}
func (s *FlightDataService) AttachmentReferenceType() string {
return sharedconst.FlightDataResourceType
}
func (s *FlightDataService) AttachmentReferenceID(row *flightdata.FlightData) string {
if row == nil {
return ""
}
id, err := uuidv7.BytesToString(row.ID)
if err != nil {
return ""
}
return id
}
func (s *FlightDataService) syncStatus(ctx context.Context, id []byte) error {
if len(id) != 16 {
return nil
}
row, err := s.repo.GetByID(ctx, id)
if err != nil || row == nil {
return err
}
if row.Mission == nil && len(row.MissionID) == 16 {
if byMissionID, lookupErr := s.repo.GetByMissionID(ctx, row.MissionID); lookupErr == nil && byMissionID != nil {
row = byMissionID
}
}
details, err := s.loadSPODetails(ctx, row)
if err != nil {
return err
}
desired := flightdata.DeriveStatus(row, details)
if strings.EqualFold(strings.TrimSpace(row.Status), desired) {
return nil
}
row.Status = desired
return s.repo.Update(ctx, row)
}
func (s *FlightDataService) loadSPODetails(ctx context.Context, row *flightdata.FlightData) (*flightdata.SPODetails, error) {
if row == nil || row.Mission == nil {
return nil, nil
}
if !strings.EqualFold(strings.TrimSpace(row.Mission.Type), sharedconst.MissionTypeSPO) {
return nil, nil
}
details, err := s.repo.GetSPODetailsByFlightDataID(ctx, row.ID)
if err != nil {
return nil, err
}
return details, nil
}
func validateFlightDataLocationPair(a, b []byte, isOrigin bool) error {
hasA := len(a) > 0
hasB := len(b) > 0
if hasA == hasB {
if isOrigin {
return flightdata.ErrOriginLocationReference
}
return flightdata.ErrDestinationLocationReference
}
return nil
}