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,128 @@
package helicopter
import (
"gorm.io/gorm"
"time"
filemanager "wucher/internal/domain/file_manager"
"wucher/internal/shared/pkg/uuidv7"
)
const (
StatusAvailable = "available"
StatusBooked = "booked"
StatusAOG = "aog"
StatusMCF = "mcf"
)
const (
AOGSourceUnknown = ""
AOGSourceManual = "manual"
AOGSourceAuto = "auto"
AOGSourceComplaint = "complaint"
)
// StatusRank is the display order for fleet and hold/defect listings — most critical
// first: MCF, then AOG, then Booked, then Available (any unknown status sorts last).
func StatusRank(status string) int {
switch status {
case StatusMCF:
return 0
case StatusAOG:
return 1
case StatusBooked:
return 2
case StatusAvailable:
return 3
default:
return 4
}
}
func IsValidStatus(s string) bool {
switch s {
case StatusAvailable, StatusBooked, StatusAOG, StatusMCF:
return true
default:
return false
}
}
type StatusInput struct {
IsBook bool
IsMCF bool
ComplaintGrounded bool
}
func (h *Helicopter) DeriveStatus(in StatusInput) string {
switch {
case in.IsMCF:
return StatusMCF
case h.AirOnGround || in.ComplaintGrounded:
return StatusAOG
case in.IsBook:
return StatusBooked
default:
return StatusAvailable
}
}
func (h *Helicopter) EnforceAOGInactive() {
if h.AirOnGround {
h.IsActive = false
}
}
func (h *Helicopter) EnforceMCFInactive(mcf bool) {
if mcf {
h.IsActive = false
}
}
type SortKeyPatch struct {
Set bool
Value *int
}
type Helicopter struct {
ID []byte `gorm:"type:binary(16);primaryKey;column:id"`
Designation string `gorm:"type:varchar(100);not null;column:designation"`
Identifier string `gorm:"type:varchar(100);uniqueIndex;not null;column:identifier"`
ReportSequence int `gorm:"not null;default:0;column:report_sequence"`
Type string `gorm:"type:varchar(100);not null;column:type"`
SortKey *int `gorm:"column:sortkey"`
Engine1 string `gorm:"type:varchar(100);column:engine_1"`
Engine2 string `gorm:"type:varchar(100);column:engine_2"`
Consumption float64 `gorm:"type:decimal(10,2);not null;default:0;column:consumption_lt_min"`
NR1 bool `gorm:"type:tinyint(1);not null;default:0;column:nr1"`
NR2 bool `gorm:"type:tinyint(1);not null;default:0;column:nr2"`
LH bool `gorm:"type:tinyint(1);not null;default:0;column:lh"`
RH bool `gorm:"type:tinyint(1);not null;default:0;column:rh"`
MGB bool `gorm:"type:tinyint(1);not null;default:0;column:mgb"`
IGB bool `gorm:"type:tinyint(1);not null;default:0;column:igb"`
TGB bool `gorm:"type:tinyint(1);not null;default:0;column:tgb"`
UTCOffset int `gorm:"not null;default:0;column:utc_offset"`
FotoAttachmentID []byte `gorm:"type:binary(16);index;column:foto_attachment_id"`
Dry bool `gorm:"type:tinyint(1);not null;default:0;column:dry"`
AirOnGround bool `gorm:"type:tinyint(1);not null;default:0;column:air_on_ground"`
AOGReason string `gorm:"type:text;column:aog_reason"`
AOGSource string `gorm:"type:varchar(16);not null;default:'';column:aog_source"`
Note string `gorm:"type:text;column:note"`
IsActive bool `gorm:"type:tinyint(1);index;not null;default:1;column:is_active"`
CreatedBy []byte `gorm:"type:binary(16);column:created_by"`
UpdatedBy []byte `gorm:"type:binary(16);column:updated_by"`
DeletedAt *time.Time `gorm:"column:deleted_at"`
CreatedByName string `gorm:"->;column:created_by_name"`
UpdatedByName string `gorm:"->;column:updated_by_name"`
FotoAttachment *filemanager.Attachment `gorm:"foreignKey:FotoAttachmentID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
CreatedAt time.Time
UpdatedAt time.Time
}
func (Helicopter) TableName() string { return "helicopters" }
func (h *Helicopter) BeforeCreate(tx *gorm.DB) error {
if len(h.ID) == 0 {
h.ID = uuidv7.MustBytes()
}
return nil
}

View File

@@ -0,0 +1,53 @@
package helicopter
import "testing"
func TestHelicopterEnforceAOGInactive(t *testing.T) {
cases := []struct {
name string
airOnGround bool
initActive bool
want bool
}{
{name: "aog forces inactive", airOnGround: true, initActive: true, want: false},
{name: "aog stays inactive", airOnGround: true, initActive: false, want: false},
{name: "not aog keeps manual active", airOnGround: false, initActive: true, want: true},
{name: "not aog keeps manual inactive", airOnGround: false, initActive: false, want: false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
h := Helicopter{AirOnGround: tc.airOnGround, IsActive: tc.initActive}
h.EnforceAOGInactive()
if h.IsActive != tc.want {
t.Fatalf("IsActive = %v, want %v", h.IsActive, tc.want)
}
})
}
}
func TestHelicopterDeriveStatus(t *testing.T) {
cases := []struct {
name string
airOnGround bool
mcf bool
isInUse bool
want string
}{
{name: "available when idle", airOnGround: false, mcf: false, isInUse: false, want: StatusAvailable},
{name: "booked when in use", airOnGround: false, mcf: false, isInUse: true, want: StatusBooked},
{name: "mcf when mcf flagged", airOnGround: false, mcf: true, isInUse: false, want: StatusMCF},
{name: "mcf outranks booked", airOnGround: false, mcf: true, isInUse: true, want: StatusMCF},
{name: "aog when on ground", airOnGround: true, mcf: false, isInUse: false, want: StatusAOG},
{name: "aog outranks booked", airOnGround: true, mcf: false, isInUse: true, want: StatusAOG},
{name: "mcf outranks aog", airOnGround: true, mcf: true, isInUse: false, want: StatusMCF},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
h := Helicopter{AirOnGround: tc.airOnGround}
if got := h.DeriveStatus(StatusInput{IsBook: tc.isInUse, IsMCF: tc.mcf}); got != tc.want {
t.Fatalf("DeriveStatus() = %q, want %q", got, tc.want)
}
})
}
}

View File

@@ -0,0 +1,16 @@
package helicopter
import "context"
type Repository interface {
Create(ctx context.Context, helicopter *Helicopter) error
Update(ctx context.Context, helicopter *Helicopter) error
ExistsByIdentifier(ctx context.Context, identifier string, excludeID []byte) (bool, error)
Delete(ctx context.Context, id []byte) error
GetByID(ctx context.Context, id []byte) (*Helicopter, error)
List(ctx context.Context, filter string, statuses []string, sort string, limit, offset int, groundedIDs [][]byte) ([]Helicopter, int64, error)
NextReportNumber(ctx context.Context, id []byte) (string, int, error)
IsInUse(ctx context.Context, id []byte) (bool, error)
InUseByAircraftIDs(ctx context.Context, ids [][]byte) (map[string]bool, error)
ActiveFlightIDByAircraftIDs(ctx context.Context, ids [][]byte) (map[string][]byte, error)
}

View File

@@ -0,0 +1,17 @@
package helicopter
import "context"
type Service interface {
Create(ctx context.Context, helicopter *Helicopter) error
Update(ctx context.Context, helicopter *Helicopter) error
ExistsByIdentifier(ctx context.Context, identifier string, excludeID []byte) (bool, error)
ApplySortKeyPatch(helicopter *Helicopter, patch SortKeyPatch)
Delete(ctx context.Context, id []byte) error
GetByID(ctx context.Context, id []byte) (*Helicopter, error)
List(ctx context.Context, filter string, statuses []string, sort string, limit, offset int, groundedIDs [][]byte) ([]Helicopter, int64, error)
NextReportNumber(ctx context.Context, id []byte) (string, int, error)
IsInUse(ctx context.Context, id []byte) (bool, error)
InUseByAircraftIDs(ctx context.Context, ids [][]byte) (map[string]bool, error)
ActiveFlightIDByAircraftIDs(ctx context.Context, ids [][]byte) (map[string][]byte, error)
}