357 lines
10 KiB
Go
357 lines
10 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
afterflightinspection "wucher/internal/domain/after_flight_inspection"
|
|
basedomain "wucher/internal/domain/base"
|
|
flightdomain "wucher/internal/domain/flight"
|
|
flightdata "wucher/internal/domain/flight_data"
|
|
"wucher/internal/domain/mission"
|
|
sharedconst "wucher/internal/shared/const"
|
|
"wucher/internal/shared/pkg/util"
|
|
)
|
|
|
|
type MissionService struct {
|
|
repo mission.Repository
|
|
flightSvc flightdomain.FlightService
|
|
flightDataSvc flightdata.Service
|
|
afterFlight afterflightinspection.Service
|
|
}
|
|
|
|
func missionCodePrefix(missionType string, t time.Time) string {
|
|
return strings.ToUpper(strings.TrimSpace(missionType)) + "-" + t.Format("06") + "-"
|
|
}
|
|
|
|
func NewMissionService(repo mission.Repository, deps ...any) *MissionService {
|
|
var flightSvc flightdomain.FlightService
|
|
var fdSvc flightdata.Service
|
|
var afterSvc afterflightinspection.Service
|
|
for _, dep := range deps {
|
|
switch v := dep.(type) {
|
|
case flightdomain.FlightService:
|
|
flightSvc = v
|
|
case flightdata.Service:
|
|
fdSvc = v
|
|
case afterflightinspection.Service:
|
|
afterSvc = v
|
|
}
|
|
}
|
|
return &MissionService{repo: repo, flightSvc: flightSvc, flightDataSvc: fdSvc, afterFlight: afterSvc}
|
|
}
|
|
|
|
func (s *MissionService) Create(ctx context.Context, row *mission.Mission) error {
|
|
if row == nil {
|
|
return mission.ErrRequired
|
|
}
|
|
if len(row.FlightID) == 0 {
|
|
return mission.ErrFlightIDRequired
|
|
}
|
|
row.Type = strings.ToUpper(strings.TrimSpace(row.Type))
|
|
category, err := s.repo.FindCategoryByCode(ctx, row.Type)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if category == nil || len(category.ID) == 0 {
|
|
return mission.ErrTypeInvalid
|
|
}
|
|
row.MissionCategoryID = append([]byte(nil), category.ID...)
|
|
if row.Type == sharedconst.MissionTypeHEMS {
|
|
if len(row.SubtypeID) == 0 {
|
|
return mission.ErrSubtypeRequired
|
|
}
|
|
ok, err := s.repo.SubCategoryBelongsToCategory(ctx, row.SubtypeID, row.MissionCategoryID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ok {
|
|
return mission.ErrSubtypeInvalid
|
|
}
|
|
} else if len(row.SubtypeID) > 0 {
|
|
return mission.ErrSubtypeForbidden
|
|
}
|
|
|
|
startTime, endTime, err := s.resolveMissionTimes(ctx, row.FlightID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
row.StartTime = startTime
|
|
row.EndTime = endTime
|
|
if err := s.ensureNotAfterFlightLocked(ctx, row.FlightID); err != nil {
|
|
return err
|
|
}
|
|
|
|
return s.repo.WithTransaction(ctx, func(txCtx context.Context) error {
|
|
return s.persistMissionCreate(txCtx, row)
|
|
})
|
|
}
|
|
|
|
func (s *MissionService) persistMissionCreate(ctx context.Context, row *mission.Mission) error {
|
|
if strings.TrimSpace(row.Code) == "" {
|
|
prefix := missionCodePrefix(row.Type, time.Now())
|
|
maxSeq, err := s.repo.MaxCodeSeqByPrefix(ctx, prefix)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
row.Code = prefix + strconv.Itoa(maxSeq+1)
|
|
}
|
|
return s.repo.Create(ctx, row)
|
|
}
|
|
|
|
func (s *MissionService) CreateType(ctx context.Context, row *mission.MissionCategory) error {
|
|
if row == nil {
|
|
return mission.ErrRequired
|
|
}
|
|
row.CodeType = strings.ToUpper(strings.TrimSpace(row.CodeType))
|
|
row.TypeName = strings.TrimSpace(row.TypeName)
|
|
if row.CodeType == "" || row.TypeName == "" {
|
|
return mission.ErrTypeInvalid
|
|
}
|
|
return s.repo.CreateCategory(ctx, row)
|
|
}
|
|
|
|
func (s *MissionService) UpdateByID(ctx context.Context, missionID []byte, missionType string, subtypeID []byte, note string, updatedBy []byte) error {
|
|
if len(missionID) == 0 {
|
|
return mission.ErrMissionIDRequired
|
|
}
|
|
missionType = strings.ToUpper(strings.TrimSpace(missionType))
|
|
category, err := s.repo.FindCategoryByCode(ctx, missionType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if category == nil || len(category.ID) == 0 {
|
|
return mission.ErrTypeInvalid
|
|
}
|
|
|
|
if missionType == sharedconst.MissionTypeHEMS {
|
|
if len(subtypeID) == 0 {
|
|
return mission.ErrSubtypeRequired
|
|
}
|
|
ok, err := s.repo.SubCategoryBelongsToCategory(ctx, subtypeID, category.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ok {
|
|
return mission.ErrSubtypeInvalid
|
|
}
|
|
} else if len(subtypeID) > 0 {
|
|
return mission.ErrSubtypeForbidden
|
|
}
|
|
|
|
return s.repo.UpdateByID(ctx, missionID, missionType, category.ID, subtypeID, note, updatedBy)
|
|
}
|
|
|
|
func (s *MissionService) FlightIDByMissionID(ctx context.Context, missionID []byte) ([]byte, error) {
|
|
if len(missionID) != 16 {
|
|
return nil, nil
|
|
}
|
|
row, err := s.repo.GetByID(ctx, missionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if row == nil || len(row.FlightID) != 16 {
|
|
return nil, nil
|
|
}
|
|
return append([]byte(nil), row.FlightID...), nil
|
|
}
|
|
|
|
func (s *MissionService) resolveMissionTimes(ctx context.Context, flightID []byte) (string, string, error) {
|
|
if s.flightSvc == nil {
|
|
return "", "", mission.ErrScheduleMissing
|
|
}
|
|
flightRow, err := s.flightSvc.GetByID(ctx, flightID)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
if flightRow == nil || len(flightRow.ID) != 16 || flightRow.Date.IsZero() {
|
|
return "", "", mission.ErrScheduleMissing
|
|
}
|
|
baseRow := missionBaseFromFlight(flightRow)
|
|
if baseRow == nil {
|
|
return "", "", mission.ErrBaseMissing
|
|
}
|
|
startTime, endTime, err := util.ResolveShiftWindow(baseRow, flightRow.Date)
|
|
if err != nil {
|
|
return "", "", mapBaseShiftError(err)
|
|
}
|
|
return startTime, endTime, nil
|
|
}
|
|
|
|
func mapBaseShiftError(err error) error {
|
|
switch {
|
|
case errors.Is(err, util.ErrShiftCoordinatesInvalid):
|
|
return mission.ErrCoordinatesInvalid
|
|
case errors.Is(err, util.ErrShiftTwilightMissing):
|
|
return mission.ErrTwilightMissing
|
|
case errors.Is(err, util.ErrShiftScheduleMissing):
|
|
return mission.ErrScheduleMissing
|
|
default:
|
|
return err
|
|
}
|
|
}
|
|
|
|
func (s *MissionService) ensureNotAfterFlightLocked(ctx context.Context, flightID []byte) error {
|
|
if s.flightSvc == nil || s.afterFlight == nil || len(flightID) != 16 {
|
|
return nil
|
|
}
|
|
flightRow, err := s.flightSvc.GetByID(ctx, flightID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if flightRow == nil || flightRow.Takeover == nil || flightRow.Takeover.ReserveAc == nil || len(flightRow.Takeover.ReserveAc.InspectionID) != 16 {
|
|
return nil
|
|
}
|
|
afterRow, err := s.afterFlight.GetByFlightInspectionID(ctx, flightRow.Takeover.ReserveAc.InspectionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if afterRow != nil {
|
|
return mission.ErrLockedAfterFlightInspection
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func missionBaseFromFlight(row *flightdomain.Flight) *basedomain.Base {
|
|
if row == nil || row.Takeover == nil {
|
|
return nil
|
|
}
|
|
return row.Takeover.Base
|
|
}
|
|
|
|
func (s *MissionService) DeleteByID(ctx context.Context, missionID []byte, deletedBy []byte) error {
|
|
if len(missionID) == 0 {
|
|
return mission.ErrMissionIDRequired
|
|
}
|
|
return s.repo.DeleteByID(ctx, missionID, deletedBy)
|
|
}
|
|
|
|
func (s *MissionService) DeleteByFlightID(ctx context.Context, flightID []byte, deletedBy []byte) error {
|
|
if len(flightID) == 0 {
|
|
return mission.ErrFlightIDRequired
|
|
}
|
|
return s.repo.DeleteByFlightID(ctx, flightID, deletedBy)
|
|
}
|
|
|
|
func (s *MissionService) AttachFile(ctx context.Context, missionID []byte, fileAttachmentID []byte) error {
|
|
if len(missionID) != 16 {
|
|
return mission.ErrMissionIDRequired
|
|
}
|
|
if len(fileAttachmentID) != 16 {
|
|
return mission.ErrFileAttachmentIDRequired
|
|
}
|
|
return s.repo.AttachFile(ctx, missionID, fileAttachmentID)
|
|
}
|
|
|
|
func (s *MissionService) DetachFile(ctx context.Context, missionID []byte, fileAttachmentID []byte) error {
|
|
if len(missionID) != 16 {
|
|
return mission.ErrMissionIDRequired
|
|
}
|
|
if len(fileAttachmentID) != 16 {
|
|
return mission.ErrFileAttachmentIDRequired
|
|
}
|
|
return s.repo.DetachFile(ctx, missionID, fileAttachmentID)
|
|
}
|
|
|
|
func (s *MissionService) GetByID(ctx context.Context, missionID []byte) (*mission.Mission, error) {
|
|
if len(missionID) == 0 {
|
|
return nil, mission.ErrMissionIDRequired
|
|
}
|
|
row, err := s.repo.GetByID(ctx, missionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.hydrateFlightDataCompletion(ctx, row); err != nil {
|
|
return nil, err
|
|
}
|
|
return row, nil
|
|
}
|
|
|
|
func (s *MissionService) ListByFlightID(ctx context.Context, flightID []byte) ([]mission.Mission, error) {
|
|
if len(flightID) == 0 {
|
|
return nil, mission.ErrFlightIDRequired
|
|
}
|
|
rows, err := s.repo.ListByFlightID(ctx, flightID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
func (s *MissionService) ListByFlightIDs(ctx context.Context, flightIDs [][]byte) ([]mission.Mission, error) {
|
|
rows, err := s.repo.ListByFlightIDs(ctx, flightIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
func (s *MissionService) GetByFlightID(ctx context.Context, flightID []byte) (*mission.Mission, error) {
|
|
if len(flightID) == 0 {
|
|
return nil, mission.ErrFlightIDRequired
|
|
}
|
|
row, err := s.repo.GetByFlightID(ctx, flightID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return row, nil
|
|
}
|
|
|
|
func (s *MissionService) List(ctx context.Context, filter, sort string, flightID []byte, flightDataStatus string, limit, offset int) ([]mission.Mission, int64, error) {
|
|
rows, total, err := s.repo.List(ctx, filter, normalizeMissionSort(sort), flightID, flightDataStatus, limit, offset)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
return rows, total, nil
|
|
}
|
|
|
|
func (s *MissionService) ListDatatable(ctx context.Context, filter mission.ListFilter, limit, offset int) ([]mission.Mission, int64, int64, error) {
|
|
filter.Sort = normalizeMissionSort(filter.Sort)
|
|
rows, total, filtered, err := s.repo.ListDatatable(ctx, filter, limit, offset)
|
|
if err != nil {
|
|
return nil, 0, 0, err
|
|
}
|
|
return rows, total, filtered, nil
|
|
}
|
|
|
|
func (s *MissionService) ListTypes(ctx context.Context) ([]mission.MissionCategory, error) {
|
|
return s.repo.ListCategoriesWithSubCategories(ctx)
|
|
}
|
|
|
|
func (s *MissionService) hydrateFlightDataCompletion(ctx context.Context, row *mission.Mission) error {
|
|
if s.flightDataSvc == nil || row == nil || len(row.ID) == 0 {
|
|
return nil
|
|
}
|
|
fd, err := s.flightDataSvc.GetByMissionID(ctx, row.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if fd == nil {
|
|
return nil
|
|
}
|
|
var details *flightdata.SPODetails
|
|
if strings.ToUpper(strings.TrimSpace(row.Type)) == sharedconst.MissionTypeSPO {
|
|
details, err = s.flightDataSvc.GetSPODetailsByFlightDataID(ctx, fd.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
row.FlightDataStatus = flightdata.DeriveStatus(fd, details)
|
|
return nil
|
|
}
|
|
|
|
func normalizeMissionSort(sort string) string {
|
|
if normalized := normalizeSortByRules(sort,
|
|
sortField("missions.type", "type"),
|
|
sortField("flights.date", "flight_date"),
|
|
sortField("missions.created_at", "created_at"),
|
|
sortField("missions.updated_at", "updated_at"),
|
|
); normalized != "" {
|
|
return normalized
|
|
}
|
|
return "missions.created_at DESC"
|
|
}
|