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,5 @@
package mcf
import "errors"
var ErrNotFound = errors.New("maintenance check flight not found")

View File

@@ -0,0 +1,89 @@
package mcf
import (
"time"
"gorm.io/gorm"
"wucher/internal/shared/pkg/uuidv7"
)
const (
ResultPassed = "passed"
ResultFailed = "failed"
)
type MaintenanceCheckFlight 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"`
Date *time.Time `gorm:"type:date;column:date"`
AFHours string `gorm:"type:varchar(20);column:af_hours"`
LandingCount int `gorm:"type:int;column:landing_count"`
UTCtime *time.Time `gorm:"type:datetime(3);column:utc_time"`
DecidedBy []byte `gorm:"type:binary(16);column:decided_by"`
DecidedAt *time.Time `gorm:"type:datetime(3);column:decided_at"`
Result *string `gorm:"type:varchar(20);column:result"`
Notes *string `gorm:"type:text;column:notes"`
CompletedAt *time.Time `gorm:"type:datetime(3);column:completed_at"`
CompletedBy []byte `gorm:"type:binary(16);column:completed_by"`
// Cancelled marks a DRAFT MCF that was set to "No" without ever being signed — the
// check flight never happened ("tidak jadi"). It releases the MCF status but the row
// is kept as a cancelled record in the list. A signed MCF is a real event, not cancellable.
CancelledAt *time.Time `gorm:"type:datetime(3);column:cancelled_at"`
CancelledBy []byte `gorm:"type:binary(16);column:cancelled_by"`
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 (MaintenanceCheckFlight) TableName() string { return "maintenance_check_flights" }
func (m *MaintenanceCheckFlight) BeforeCreate(_ *gorm.DB) error {
if len(m.ID) == 0 {
m.ID = uuidv7.MustBytes()
}
return nil
}
func (m *MaintenanceCheckFlight) IsComplete() bool {
return m != nil && m.CompletedAt != nil
}
func (m *MaintenanceCheckFlight) IsCancelled() bool {
return m != nil && m.CancelledAt != nil
}
// IsOpen reports whether the MCF is an active (open) draft: not signed, not cancelled.
func (m *MaintenanceCheckFlight) IsOpen() bool {
return m != nil && m.CompletedAt == nil && m.CancelledAt == nil
}
// IsCleared reports whether the MCF is done, releasing the aircraft from MCF status. Once
// the MCF is SIGNED (completed) it is considered done regardless of pass/fail — the result
// is recorded, but signing itself takes the helicopter out of MCF. A draft (not yet signed)
// is NOT cleared.
func (m *MaintenanceCheckFlight) IsCleared() bool {
return m != nil && m.CompletedAt != nil
}
func (m *MaintenanceCheckFlight) IsPending() bool {
return m != nil && !m.IsCleared()
}
func IsValidResult(s string) bool {
switch s {
case ResultPassed, ResultFailed:
return true
default:
return false
}
}

View File

@@ -0,0 +1,81 @@
package mcf
import (
"testing"
"time"
)
func result(v string) *string { return &v }
func TestMaintenanceCheckFlightState(t *testing.T) {
now := time.Now()
cases := []struct {
name string
row *MaintenanceCheckFlight
wantComplete bool
wantCleared bool
wantPending bool
}{
{name: "nil", row: nil, wantComplete: false, wantCleared: false, wantPending: false},
{name: "draft (not flown)", row: &MaintenanceCheckFlight{}, wantComplete: false, wantCleared: false, wantPending: true},
// Once signed the MCF is done regardless of result — signing clears MCF status.
{name: "signed failed is done", row: &MaintenanceCheckFlight{CompletedAt: &now, Result: result(ResultFailed)}, wantComplete: true, wantCleared: true, wantPending: false},
{name: "signed passed is done", row: &MaintenanceCheckFlight{CompletedAt: &now, Result: result(ResultPassed)}, wantComplete: true, wantCleared: true, wantPending: false},
{name: "result set without completedAt still pending", row: &MaintenanceCheckFlight{Result: result(ResultFailed)}, wantComplete: false, wantCleared: false, wantPending: true},
// A draft edited with result=passed but NOT yet signed must NOT clear the aircraft.
{name: "passed result on unsigned draft is NOT cleared", row: &MaintenanceCheckFlight{Result: result(ResultPassed)}, wantComplete: false, wantCleared: false, wantPending: true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := tc.row.IsComplete(); got != tc.wantComplete {
t.Errorf("IsComplete() = %v, want %v", got, tc.wantComplete)
}
if got := tc.row.IsCleared(); got != tc.wantCleared {
t.Errorf("IsCleared() = %v, want %v", got, tc.wantCleared)
}
if got := tc.row.IsPending(); got != tc.wantPending {
t.Errorf("IsPending() = %v, want %v", got, tc.wantPending)
}
})
}
}
func TestIsValidResult(t *testing.T) {
cases := []struct {
in string
want bool
}{
{ResultPassed, true},
{ResultFailed, true},
{"partial", false},
{"", false},
{"PASSED", false},
}
for _, tc := range cases {
t.Run(tc.in, func(t *testing.T) {
if got := IsValidResult(tc.in); got != tc.want {
t.Fatalf("IsValidResult(%q) = %v, want %v", tc.in, got, tc.want)
}
})
}
}
func TestMCFTableNameAndBeforeCreate(t *testing.T) {
if (MaintenanceCheckFlight{}).TableName() != "maintenance_check_flights" {
t.Fatalf("unexpected table name")
}
m := &MaintenanceCheckFlight{}
if err := m.BeforeCreate(nil); err != nil {
t.Fatalf("BeforeCreate err: %v", err)
}
if len(m.ID) == 0 {
t.Fatal("BeforeCreate should set ID")
}
prev := string(m.ID)
if err := m.BeforeCreate(nil); err != nil {
t.Fatalf("BeforeCreate err: %v", err)
}
if string(m.ID) != prev {
t.Fatal("BeforeCreate must not overwrite an existing ID")
}
}

View File

@@ -0,0 +1,17 @@
package mcf
import "context"
type Repository interface {
Create(ctx context.Context, row *MaintenanceCheckFlight) error
Update(ctx context.Context, row *MaintenanceCheckFlight) error
Delete(ctx context.Context, id []byte, deletedBy []byte) error
GetByID(ctx context.Context, id []byte) (*MaintenanceCheckFlight, error)
ListByHelicopter(ctx context.Context, helicopterID []byte) ([]MaintenanceCheckFlight, error)
GetLatestByHelicopter(ctx context.Context, helicopterID []byte) (*MaintenanceCheckFlight, error)
LatestByHelicopterIDs(ctx context.Context, helicopterIDs [][]byte) (map[string]*MaintenanceCheckFlight, error)
PendingHelicopterIDs(ctx context.Context, helicopterIDs [][]byte) (map[string]bool, error)
// HelicopterHasOpenMCF reports whether the helicopter has a draft (not yet
// completed/signed) MCF — used to enforce one open MCF per helicopter.
HelicopterHasOpenMCF(ctx context.Context, helicopterID []byte) (bool, error)
}

View File

@@ -0,0 +1,15 @@
package mcf
import "context"
type Service interface {
Create(ctx context.Context, row *MaintenanceCheckFlight) error
Update(ctx context.Context, row *MaintenanceCheckFlight) error
Delete(ctx context.Context, id []byte, deletedBy []byte) error
GetByID(ctx context.Context, id []byte) (*MaintenanceCheckFlight, error)
ListByHelicopter(ctx context.Context, helicopterID []byte) ([]MaintenanceCheckFlight, error)
GetLatestByHelicopter(ctx context.Context, helicopterID []byte) (*MaintenanceCheckFlight, error)
LatestByHelicopterIDs(ctx context.Context, helicopterIDs [][]byte) (map[string]*MaintenanceCheckFlight, error)
PendingHelicopterIDs(ctx context.Context, helicopterIDs [][]byte) (map[string]bool, error)
HelicopterHasOpenMCF(ctx context.Context, helicopterID []byte) (bool, error)
}