Files
fm_be/internal/domain/complaint/model.go
2026-07-16 22:16:45 +07:00

154 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 AD 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)
}