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,142 @@
package dutyroster
import (
"time"
"gorm.io/gorm"
"wucher/internal/domain/auth"
"wucher/internal/domain/base"
"wucher/internal/domain/takeover"
"wucher/internal/shared/pkg/uuidv7"
)
// DutyRoster stores roster header per base and duty date.
type DutyRoster struct {
ID []byte `gorm:"type:binary(16);primaryKey;column:id"`
FlightID []byte `gorm:"type:binary(16);index:idx_roster_flight_id,unique;column:flight_id"`
BaseID []byte `gorm:"type:binary(16);not null;index:idx_roster_base_date;column:base_id"`
DutyDate time.Time `gorm:"type:date;not null;index:idx_roster_base_date;column:duty_date"`
CreatedAt time.Time `gorm:"column:created_at"`
CreatedBy []byte `gorm:"type:binary(16);column:created_by"`
UpdatedAt time.Time `gorm:"column:updated_at"`
UpdatedBy []byte `gorm:"type:binary(16);column:updated_by"`
DeletedAt *time.Time `gorm:"column:deleted_at"`
DeletedBy []byte `gorm:"type:binary(16);column:deleted_by"`
Base *base.Base `gorm:"foreignKey:BaseID;references:ID;-:migration"`
Crews []DutyRosterCrew `gorm:"foreignKey:RosterID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
}
func (DutyRoster) TableName() string { return "duty_rosters" }
func (h *DutyRoster) BeforeCreate(tx *gorm.DB) error {
if len(h.ID) == 0 {
h.ID = uuidv7.MustBytes()
}
return nil
}
type DutyRosterCrew struct {
ID []byte `gorm:"type:binary(16);primaryKey;column:id"`
RosterID []byte `gorm:"type:binary(16);not null;index:idx_roster_crew_role;index:idx_roster_crew_unique,unique;column:roster_id"`
UserID []byte `gorm:"type:binary(16);index:idx_roster_crew_unique,unique;column:user_id"`
TakeoverAcID []byte `gorm:"type:binary(16);index;column:takeover_ac_id"`
RoleCode string `gorm:"type:varchar(64);not null;index:idx_roster_crew_role;index:idx_roster_crew_unique,unique;check:chk_roster_crew_role_code,role_code IN ('pilot','doctor','rescuer','helper','other_person','air_rescuer','flight_assistant');column:role_code"`
CrewType string `gorm:"type:varchar(32);not null;default:main;index:idx_roster_crew_unique,unique;check:chk_roster_crew_type,crew_type IN ('main','additional');column:crew_type"`
NameLabel string `gorm:"type:varchar(191);index:idx_roster_crew_unique,unique;column:name_label"`
MobilePhone string `gorm:"type:varchar(64);column:mobile_phone"`
Email string `gorm:"type:varchar(191);column:email"`
ConfirmAt *time.Time `gorm:"column:confirm_at"`
DateStart *time.Time `gorm:"type:date;index:idx_roster_crew_unique,unique;check:chk_roster_crew_date_range,date_start IS NULL OR date_end IS NULL OR date_start <= date_end;column:date_start"`
DateEnd *time.Time `gorm:"type:date;index:idx_roster_crew_unique,unique;column:date_end"`
ShiftStart string `gorm:"type:time;index:idx_roster_crew_unique,unique;column:shift_start"`
ShiftEnd string `gorm:"type:time;index:idx_roster_crew_unique,unique;column:shift_end"`
FlightInstructor bool `gorm:"type:tinyint(1);not null;default:0;column:flight_instructor"`
LineChecker bool `gorm:"type:tinyint(1);not null;default:0;column:line_checker"`
Supervisor bool `gorm:"type:tinyint(1);not null;default:0;column:supervisor"`
Examiner bool `gorm:"type:tinyint(1);not null;default:0;column:examiner"`
CoPilot bool `gorm:"type:tinyint(1);not null;default:0;column:co_pilot"`
Roster *DutyRoster `gorm:"foreignKey:RosterID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
User *auth.User `gorm:"foreignKey:UserID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
TakeoverAc *takeover.TakeoverAc `gorm:"foreignKey:TakeoverAcID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
CreatedAt time.Time `gorm:"column:created_at"`
CreatedBy []byte `gorm:"type:binary(16);column:created_by"`
UpdatedAt time.Time `gorm:"column:updated_at"`
UpdatedBy []byte `gorm:"type:binary(16);column:updated_by"`
DeletedAt *time.Time `gorm:"column:deleted_at"`
DeletedBy []byte `gorm:"type:binary(16);column:deleted_by"`
}
func (DutyRosterCrew) TableName() string { return "duty_roster_crews" }
func (h *DutyRosterCrew) BeforeCreate(tx *gorm.DB) error {
if len(h.ID) == 0 {
h.ID = uuidv7.MustBytes()
}
return nil
}
// HeaderView is a denormalized header row for list/get responses.
type HeaderView struct {
ID []byte
BaseID []byte
BaseName string
BaseAbbreviation string
DefaultShiftStart string
DefaultShiftEnd string
HelicopterID []byte
HelicopterType string
HelicopterDesignation string
HelicopterIdentifier string
DutyDate time.Time
ShiftStart string
ShiftEnd string
CreatedAt time.Time
CreatedBy []byte
UpdatedAt time.Time
}
// BaseView is a lightweight base projection used in roster listing.
type BaseView struct {
ID []byte
BaseName string
BaseAbbreviation string
}
// CrewView is a denormalized crew row for list/get responses.
type CrewView struct {
ID []byte
RosterID []byte
UserID []byte
Name string
Role string
RoleCode string
CrewType string
MobilePhone string
Email string
DateStart *time.Time
DateEnd *time.Time
ShiftStart string
ShiftEnd string
FlightInstructor bool
LineChecker bool
Supervisor bool
Examiner bool
CoPilot bool
}
type DeletedCrewResult struct {
ID []byte
RosterID []byte
UserID []byte
Name string
RoleCode string
CrewType string
DateStart *time.Time
DateEnd *time.Time
}

View File

@@ -0,0 +1,38 @@
package dutyroster
import (
"context"
"time"
)
type TxRepository interface {
GetRosterByID(ctx context.Context, id []byte) (*DutyRoster, error)
LockRosterByID(ctx context.Context, id []byte) (*DutyRoster, error)
CreateHeader(ctx context.Context, row *DutyRoster) error
UpdateHeader(ctx context.Context, row *DutyRoster) error
SoftDeleteHeader(ctx context.Context, id []byte, deletedBy []byte) error
SoftDeleteCrewsByRosterID(ctx context.Context, rosterID []byte, deletedBy []byte) error
ListActiveCrewsByRosterID(ctx context.Context, rosterID []byte) ([]DutyRosterCrew, error)
InsertCrew(ctx context.Context, row *DutyRosterCrew) error
CrewExists(ctx context.Context, row DutyRosterCrew) (bool, error)
SoftDeleteCrewByUser(ctx context.Context, rosterID []byte, roleCode, crewType string, userID, updatedBy []byte) error
FindRosterIDByBaseDate(ctx context.Context, baseID []byte, dutyDate time.Time) ([]byte, error)
FindRosterIDsByBaseDate(ctx context.Context, baseID []byte, dutyDate time.Time) ([][]byte, error)
MatchCrewByFilter(ctx context.Context, rosterIDs [][]byte, crew DutyRosterCrew) ([]DeletedCrewResult, error)
SoftDeleteCrewRows(ctx context.Context, rows []DeletedCrewResult, deletedBy []byte) error
SoftDeleteCrewForDate(ctx context.Context, baseID []byte, dutyDate time.Time, crew DutyRosterCrew, updatedBy []byte) error
HardDeleteHeader(ctx context.Context, id []byte) error
HardDeleteCrewsByRosterID(ctx context.Context, rosterID []byte) error
}
type Repository interface {
InTx(ctx context.Context, fn func(txRepo TxRepository) error) error
GetBaseDefaultShift(ctx context.Context, baseID []byte, baseType string) (string, error)
GetHeaderByID(ctx context.Context, id []byte) (*HeaderView, error)
GetHeaderByIDByBaseType(ctx context.Context, id []byte, baseType string) (*HeaderView, error)
GetCrewsByRosterIDs(ctx context.Context, rosterIDs [][]byte) ([]CrewView, error)
ListHeadersByRange(ctx context.Context, baseID []byte, from, to time.Time) ([]HeaderView, error)
ListHeadersByRangeByBaseType(ctx context.Context, baseID []byte, from, to time.Time, baseType string) ([]HeaderView, error)
ListHEMSBases(ctx context.Context, baseID []byte) ([]BaseView, error)
ListBasesByType(ctx context.Context, baseID []byte, baseType string) ([]BaseView, error)
}

View File

@@ -0,0 +1,23 @@
package dutyroster
import (
"context"
"time"
)
type Service interface {
Create(ctx context.Context, row *DutyRoster, crews []DutyRosterCrew) error
Update(ctx context.Context, row *DutyRoster, crews []DutyRosterCrew) error
Delete(ctx context.Context, id []byte, deletedBy []byte) error
HardDelete(ctx context.Context, id []byte) error
DeleteCrews(ctx context.Context, id []byte, crews []DutyRosterCrew, deletedBy []byte) ([]DeletedCrewResult, error)
GetBaseDefaultShift(ctx context.Context, baseID []byte, baseType string) (string, error)
ResolveBaseDefaultShiftRange(ctx context.Context, baseID []byte, baseType string) (string, string, error)
GetHeaderByID(ctx context.Context, id []byte) (*HeaderView, error)
GetHeaderByIDByBaseType(ctx context.Context, id []byte, baseType string) (*HeaderView, error)
GetCrewsByRosterIDs(ctx context.Context, rosterIDs [][]byte) ([]CrewView, error)
ListHeadersByRange(ctx context.Context, baseID []byte, from, to time.Time) ([]HeaderView, error)
ListHeadersByRangeByBaseType(ctx context.Context, baseID []byte, from, to time.Time, baseType string) ([]HeaderView, error)
ListHEMSBases(ctx context.Context, baseID []byte) ([]BaseView, error)
ListBasesByType(ctx context.Context, baseID []byte, baseType string) ([]BaseView, error)
}