init push
This commit is contained in:
9
internal/domain/easa_release/errors.go
Normal file
9
internal/domain/easa_release/errors.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package easarelease
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("easa release not found")
|
||||
ErrAlreadySigned = errors.New("easa release already signed")
|
||||
ErrIncompleteForSign = errors.New("easa release required fields are incomplete")
|
||||
)
|
||||
77
internal/domain/easa_release/model.go
Normal file
77
internal/domain/easa_release/model.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package easarelease
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
complaint "wucher/internal/domain/complaint"
|
||||
flightdomain "wucher/internal/domain/flight"
|
||||
helicopter "wucher/internal/domain/helicopter"
|
||||
"wucher/internal/shared/pkg/uuidv7"
|
||||
)
|
||||
|
||||
type EASARelease struct {
|
||||
ID []byte `gorm:"type:binary(16);primaryKey;column:id"`
|
||||
HelicopterID []byte `gorm:"type:binary(16);not null;index;column:helicopter_id"`
|
||||
|
||||
// ComplaintID scopes the release to the specific complaint it closes (optional).
|
||||
// When set, the release is fetched via get-by-complaint and closes that complaint.
|
||||
// When NULL the release is standalone (aircraft cleared with no open defect).
|
||||
ComplaintID []byte `gorm:"type:binary(16);index;column:complaint_id"`
|
||||
|
||||
// FlightID — optional flight context. Retained for the standalone (no-complaint)
|
||||
// sign-off gate, which is still flight-scoped. Not required for complaint releases.
|
||||
FlightID []byte `gorm:"type:binary(16);index;column:flight_id"`
|
||||
|
||||
EASADate *time.Time `gorm:"type:date;column:easa_date"`
|
||||
EASAACHours *string `gorm:"type:varchar(20);column:easa_ac_hours"`
|
||||
EASALocation *string `gorm:"type:varchar(255);column:easa_location"`
|
||||
EASAWONo *string `gorm:"type:varchar(100);column:easa_wo_no"`
|
||||
EASAMaintManualRevAirframe *string `gorm:"type:varchar(100);column:easa_maint_manual_rev_airframe"`
|
||||
EASAMaintManualRevEngine *string `gorm:"type:varchar(100);column:easa_maint_manual_rev_engine"`
|
||||
EASASignerID *string `gorm:"type:varchar(100);column:easa_signer_id"`
|
||||
EASAPermitNo *string `gorm:"type:varchar(100);column:easa_permit_no"`
|
||||
|
||||
SignedAt *time.Time `gorm:"type:datetime(3);column:signed_at"`
|
||||
SignedBy []byte `gorm:"type:binary(16);column:signed_by"`
|
||||
|
||||
Helicopter *helicopter.Helicopter `gorm:"foreignKey:HelicopterID;references:ID"`
|
||||
Flight *flightdomain.Flight `gorm:"foreignKey:FlightID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
|
||||
Complaint *complaint.Complaint `gorm:"foreignKey:ComplaintID;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 (EASARelease) TableName() string { return "easa_releases" }
|
||||
|
||||
func (e *EASARelease) BeforeCreate(_ *gorm.DB) error {
|
||||
if len(e.ID) == 0 {
|
||||
e.ID = uuidv7.MustBytes()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EASARelease) IsSigned() bool { return e != nil && e.SignedAt != nil }
|
||||
|
||||
func (e *EASARelease) IsActive() bool { return e.IsSigned() }
|
||||
|
||||
// MissingSignFields reports which mandatory fields are still empty for signing.
|
||||
// Only the signer is required — it is auto-filled from the duty pilot. The other
|
||||
// document fields (date, A/C hours, location, WO no, manual revisions) are
|
||||
// adjustable free text and optional, so a release can be signed without them.
|
||||
func (e *EASARelease) MissingSignFields() []string {
|
||||
if e == nil {
|
||||
return []string{"easa_signer_id"}
|
||||
}
|
||||
var missing []string
|
||||
if e.EASASignerID == nil || *e.EASASignerID == "" {
|
||||
missing = append(missing, "easa_signer_id")
|
||||
}
|
||||
return missing
|
||||
}
|
||||
83
internal/domain/easa_release/model_test.go
Normal file
83
internal/domain/easa_release/model_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package easarelease
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func str(s string) *string { return &s }
|
||||
|
||||
func TestEASAReleaseIsSigned(t *testing.T) {
|
||||
now := time.Now()
|
||||
if (&EASARelease{}).IsSigned() {
|
||||
t.Fatal("unsigned release reported as signed")
|
||||
}
|
||||
if !(&EASARelease{SignedAt: &now}).IsSigned() {
|
||||
t.Fatal("signed release reported as unsigned")
|
||||
}
|
||||
var nilRel *EASARelease
|
||||
if nilRel.IsSigned() {
|
||||
t.Fatal("nil release reported as signed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEASAReleaseMissingSignFields(t *testing.T) {
|
||||
// Only the auto-filled signer is required to sign; the adjustable document
|
||||
// fields (date, A/C hours, location, WO no, manual revisions) are optional.
|
||||
onlySigner := []string{"easa_signer_id"}
|
||||
now := time.Now()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
row *EASARelease
|
||||
want []string
|
||||
}{
|
||||
{name: "nil requires signer", row: nil, want: onlySigner},
|
||||
{name: "empty requires signer", row: &EASARelease{}, want: onlySigner},
|
||||
{name: "signer present lists none", row: &EASARelease{EASASignerID: str("019d61db-53c6-7280-87ec-e65f205c6d3b")}, want: nil},
|
||||
{name: "other fields empty but signer set lists none", row: &EASARelease{EASASignerID: str("019d61db-53c6-7280-87ec-e65f205c6d3b"), EASADate: &now}, want: nil},
|
||||
{name: "blank signer counts as missing", row: &EASARelease{EASASignerID: str("")}, want: onlySigner},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := tc.row.MissingSignFields()
|
||||
if len(got) == 0 && len(tc.want) == 0 {
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tc.want) {
|
||||
t.Fatalf("MissingSignFields() = %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEASAReleaseIsActiveAndTableName(t *testing.T) {
|
||||
if (EASARelease{}).TableName() != "easa_releases" {
|
||||
t.Fatalf("unexpected table name")
|
||||
}
|
||||
now := time.Now()
|
||||
if (&EASARelease{}).IsActive() {
|
||||
t.Fatal("unsigned release must not be active")
|
||||
}
|
||||
if !(&EASARelease{SignedAt: &now}).IsActive() {
|
||||
t.Fatal("signed release must be active")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEASAReleaseBeforeCreate(t *testing.T) {
|
||||
e := &EASARelease{}
|
||||
if err := e.BeforeCreate(nil); err != nil {
|
||||
t.Fatalf("BeforeCreate err: %v", err)
|
||||
}
|
||||
if len(e.ID) == 0 {
|
||||
t.Fatal("BeforeCreate should set ID")
|
||||
}
|
||||
prev := string(e.ID)
|
||||
if err := e.BeforeCreate(nil); err != nil {
|
||||
t.Fatalf("BeforeCreate err: %v", err)
|
||||
}
|
||||
if string(e.ID) != prev {
|
||||
t.Fatal("BeforeCreate must not overwrite an existing ID")
|
||||
}
|
||||
}
|
||||
16
internal/domain/easa_release/repository.go
Normal file
16
internal/domain/easa_release/repository.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package easarelease
|
||||
|
||||
import "context"
|
||||
|
||||
type Repository interface {
|
||||
Create(ctx context.Context, row *EASARelease) error
|
||||
Update(ctx context.Context, row *EASARelease) error
|
||||
Delete(ctx context.Context, id []byte, deletedBy []byte) error
|
||||
GetByID(ctx context.Context, id []byte) (*EASARelease, error)
|
||||
GetByFlightID(ctx context.Context, flightID []byte) (*EASARelease, error)
|
||||
GetByComplaintID(ctx context.Context, complaintID []byte) (*EASARelease, error)
|
||||
ListByHelicopter(ctx context.Context, helicopterID []byte) ([]EASARelease, error)
|
||||
GetLatestByHelicopter(ctx context.Context, helicopterID []byte) (*EASARelease, error)
|
||||
GetActiveByHelicopter(ctx context.Context, helicopterID []byte) (*EASARelease, error)
|
||||
VoidByID(ctx context.Context, id []byte, reason string, actor []byte) error
|
||||
}
|
||||
16
internal/domain/easa_release/service.go
Normal file
16
internal/domain/easa_release/service.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package easarelease
|
||||
|
||||
import "context"
|
||||
|
||||
type Service interface {
|
||||
Create(ctx context.Context, row *EASARelease) error
|
||||
Update(ctx context.Context, row *EASARelease) error
|
||||
Delete(ctx context.Context, id []byte, deletedBy []byte) error
|
||||
GetByID(ctx context.Context, id []byte) (*EASARelease, error)
|
||||
GetByFlightID(ctx context.Context, flightID []byte) (*EASARelease, error)
|
||||
GetByComplaintID(ctx context.Context, complaintID []byte) (*EASARelease, error)
|
||||
ListByHelicopter(ctx context.Context, helicopterID []byte) ([]EASARelease, error)
|
||||
GetActiveByHelicopter(ctx context.Context, helicopterID []byte) (*EASARelease, error)
|
||||
Sign(ctx context.Context, id []byte, signerID []byte) error
|
||||
Amend(ctx context.Context, currentID []byte, reason string, actor []byte) (newID []byte, err error)
|
||||
}
|
||||
Reference in New Issue
Block a user