package complaint import ( "time" "gorm.io/gorm" flightdomain "wucher/internal/domain/flight" helicopter "wucher/internal/domain/helicopter" "wucher/internal/shared/pkg/uuidv7" ) const ( MELSeverityNonMEL int8 = 0 MELSeverityA int8 = 1 MELSeverityB int8 = 2 MELSeverityC int8 = 3 MELSeverityD int8 = 4 ) const ( StatusPendingMEL = "pending_mel" StatusActiveNonMEL = "active_non_mel" StatusActiveMEL = "active_mel" StatusServiced = "serviced" ) var melGraceDays = map[int8]int{ MELSeverityA: 1, MELSeverityB: 3, MELSeverityC: 10, MELSeverityD: 120, } type ListFilter struct { Search string DateFrom string DateTo string Person string HelicopterID []byte FlightID []byte HoldItemsOnly bool } func MELGraceDays(severity int8) int { return melGraceDays[severity] } type Complaint struct { ID []byte `gorm:"type:binary(16);primaryKey;column:id"` HelicopterID []byte `gorm:"type:binary(16);not null;index;column:helicopter_id"` FlightID []byte `gorm:"type:binary(16);index;column:flight_id"` Description string `gorm:"type:text;not null;column:description"` ReportedBy []byte `gorm:"type:binary(16);not null;column:reported_by"` ReportedAt time.Time `gorm:"type:datetime(3);not null;column:reported_at"` AircraftHoursAtReport *string `gorm:"type:varchar(20);column:aircraft_hours_at_report"` MELSeverity int8 `gorm:"type:tinyint;not null;default:0;column:mel_severity"` MELClassifiedAt *time.Time `gorm:"type:datetime(3);column:mel_classified_at"` MELClassifiedBy []byte `gorm:"type:binary(16);column:mel_classified_by"` IsNSR bool `gorm:"type:boolean;not null;default:false;column:is_nsr"` NSRDecidedAt *time.Time `gorm:"type:datetime(3);column:nsr_decided_at"` NSRDecidedBy []byte `gorm:"type:binary(16);column:nsr_decided_by"` NSRReason *string `gorm:"type:text;column:nsr_reason"` ActionTaken *string `gorm:"type:text;column:action_taken"` AircraftHoursAtFix *string `gorm:"type:varchar(20);column:aircraft_hours_at_fix"` FixedAt *time.Time `gorm:"type:datetime(3);column:fixed_at"` FixedByEASAID []byte `gorm:"type:binary(16);column:fixed_by_easa_id"` Helicopter *helicopter.Helicopter `gorm:"foreignKey:HelicopterID;references:ID"` Flight *flightdomain.Flight `gorm:"foreignKey:FlightID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"` CreatedAt time.Time `gorm:"type:datetime(3);column:created_at"` CreatedBy []byte `gorm:"type:binary(16);column:created_by"` UpdatedAt time.Time `gorm:"type:datetime(3);column:updated_at"` UpdatedBy []byte `gorm:"type:binary(16);column:updated_by"` DeletedAt *time.Time `gorm:"type:datetime(3);index;column:deleted_at"` DeletedBy []byte `gorm:"type:binary(16);column:deleted_by"` } func (Complaint) TableName() string { return "complaints" } func (c *Complaint) BeforeCreate(_ *gorm.DB) error { if len(c.ID) == 0 { c.ID = uuidv7.MustBytes() } return nil } func (c *Complaint) Status() string { if c == nil { return StatusActiveNonMEL } if c.FixedAt != nil { return StatusServiced } // mel_severity is optional at creation; until it is classified (mel_classified_at set) // the complaint is "pending_mel" — its severity value (0) is a default, not "Non-MEL". if c.MELClassifiedAt == nil { return StatusPendingMEL } if c.MELSeverity > 0 { return StatusActiveMEL } return StatusActiveNonMEL } func (c *Complaint) MELDeadline() *time.Time { if c == nil || c.MELSeverity <= 0 { return nil } grace := MELGraceDays(c.MELSeverity) if grace <= 0 || c.ReportedAt.IsZero() { return nil } // MEL grace is measured in days: A=1, B=3, C=10, D=120 days from report. deadline := c.ReportedAt.Add(time.Duration(grace) * 24 * time.Hour) return &deadline } func (c *Complaint) IsMELAOG(now time.Time) bool { if c == nil || c.IsNSR { return false } deadline := c.MELDeadline() if deadline == nil { return false } return !now.Before(*deadline) } // IsGrounding reports whether this complaint currently grounds the aircraft (AOG). // Returning to service requires a formal release: only a Non-Safety Release (NSR / // MEL deferral) or a signed EASA release (CRS, sets FixedAt) lifts the grounding. // Recording action_taken alone does NOT lift it — corrective work is not a release. // // A pending (not-yet-classified) complaint does NOT ground: an unassessed report is not // yet a grounding defect. Grounding starts at classification — NON-MEL grounds // immediately, MEL A–D grounds once its grace deadline lapses. func (c *Complaint) IsGrounding(now time.Time) bool { if c == nil || c.IsNSR || c.FixedAt != nil { return false } if c.MELClassifiedAt == nil { return false } if c.MELSeverity <= 0 { return true } return c.IsMELAOG(now) }