290 lines
8.1 KiB
Go
290 lines
8.1 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"wucher/internal/domain/flight"
|
|
fmreport "wucher/internal/domain/fm_report"
|
|
"wucher/internal/shared/pkg/appctx"
|
|
"wucher/internal/shared/pkg/uuidv7"
|
|
)
|
|
|
|
const reportCodeMaxAttempts = 5
|
|
|
|
type FMReportService struct {
|
|
repo fmreport.Repository
|
|
historyBuilder fmreport.FleetHistoryBuilder
|
|
codeResolver fmreport.ReportCodeResolver
|
|
usage interface {
|
|
RefreshAll(context.Context) error
|
|
}
|
|
}
|
|
|
|
func NewFMReportService(repo fmreport.Repository) *FMReportService {
|
|
return &FMReportService{repo: repo}
|
|
}
|
|
|
|
func (s *FMReportService) WithFleetHistoryBuilder(builder fmreport.FleetHistoryBuilder) *FMReportService {
|
|
s.historyBuilder = builder
|
|
return s
|
|
}
|
|
|
|
func (s *FMReportService) WithReportCodeResolver(resolver fmreport.ReportCodeResolver) *FMReportService {
|
|
s.codeResolver = resolver
|
|
return s
|
|
}
|
|
|
|
func (s *FMReportService) WithUsageRefresher(refresher interface {
|
|
RefreshAll(context.Context) error
|
|
}) *FMReportService {
|
|
s.usage = refresher
|
|
return s
|
|
}
|
|
|
|
func (s *FMReportService) resolveHelicopterForReportCode(ctx context.Context, takeoverID []byte) ([]byte, string) {
|
|
if s.codeResolver == nil || len(takeoverID) != 16 {
|
|
return nil, ""
|
|
}
|
|
helicopterID, reportPrefix := s.codeResolver.ResolveHelicopterForReportCode(ctx, takeoverID)
|
|
reportPrefix = strings.ToUpper(strings.TrimSpace(reportPrefix))
|
|
if len(helicopterID) != 16 || reportPrefix == "" {
|
|
return nil, ""
|
|
}
|
|
return helicopterID, reportPrefix
|
|
}
|
|
|
|
func (s *FMReportService) assignReportCode(ctx context.Context, row *fmreport.Report, helicopterID []byte, reportPrefix string) {
|
|
if len(helicopterID) != 16 || reportPrefix == "" || len(row.ID) != 16 {
|
|
return
|
|
}
|
|
for attempt := 0; attempt < reportCodeMaxAttempts; attempt++ {
|
|
maxSeq, err := s.repo.MaxReportSeqByHelicopter(ctx, helicopterID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
code := reportPrefix + "-" + fmt.Sprintf("%04d", maxSeq+1)
|
|
if err := s.repo.SetReportCode(ctx, row.ID, code); err != nil {
|
|
if errors.Is(err, fmreport.ErrDuplicateReportCode) {
|
|
continue
|
|
}
|
|
return
|
|
}
|
|
row.ReportCode = &code
|
|
return
|
|
}
|
|
}
|
|
|
|
func (s *FMReportService) GetFleetHistory(ctx context.Context, reportID []byte) (*fmreport.FleetHistory, error) {
|
|
if len(reportID) != 16 {
|
|
return nil, fmt.Errorf("report id must be a valid UUID")
|
|
}
|
|
return s.repo.GetFleetHistoryByReportID(ctx, reportID)
|
|
}
|
|
|
|
func (s *FMReportService) UpsertForFlight(ctx context.Context, flightRow *flight.Flight, flightInspectionID []byte) (*fmreport.Report, error) {
|
|
if flightRow == nil || len(flightRow.ID) != 16 {
|
|
return nil, fmt.Errorf("flight is required")
|
|
}
|
|
if len(flightRow.TakeoverAcID) != 16 {
|
|
return nil, fmt.Errorf("takeover id is required")
|
|
}
|
|
if len(flightInspectionID) != 16 {
|
|
return nil, fmt.Errorf("flight inspection id is required")
|
|
}
|
|
|
|
row := &fmreport.Report{
|
|
FlightID: flightRow.ID,
|
|
TakeoverID: flightRow.TakeoverAcID,
|
|
FlightInspectionID: flightInspectionID,
|
|
}
|
|
if userID := appctx.GetUserID(ctx); len(userID) == 16 {
|
|
row.CreatedBy = userID
|
|
row.UpdatedBy = userID
|
|
}
|
|
isNew := true
|
|
if existing, err := s.repo.GetByFlightID(ctx, flightRow.ID); err == nil && existing != nil && len(existing.ID) == 16 {
|
|
isNew = false
|
|
if existing.CompletedAt != nil {
|
|
return existing, nil
|
|
}
|
|
row.ID = existing.ID
|
|
row.CreatedAt = existing.CreatedAt
|
|
row.CreatedBy = existing.CreatedBy
|
|
row.HelicopterID = existing.HelicopterID
|
|
row.ReportCode = existing.ReportCode
|
|
row.Engine1GpcN1 = existing.Engine1GpcN1
|
|
row.Engine1PtcN2 = existing.Engine1PtcN2
|
|
row.Engine2GpcN1 = existing.Engine2GpcN1
|
|
row.Engine2PtcN2 = existing.Engine2PtcN2
|
|
}
|
|
|
|
var helicopterID []byte
|
|
var reportPrefix string
|
|
if isNew {
|
|
helicopterID, reportPrefix = s.resolveHelicopterForReportCode(ctx, flightRow.TakeoverAcID)
|
|
if len(helicopterID) == 16 {
|
|
row.HelicopterID = helicopterID
|
|
}
|
|
}
|
|
if err := s.repo.Upsert(ctx, row); err != nil {
|
|
return nil, fmt.Errorf("failed to upsert fm report: %w", err)
|
|
}
|
|
if isNew {
|
|
s.assignReportCode(ctx, row, helicopterID, reportPrefix)
|
|
}
|
|
|
|
// The fleet-status snapshot is frozen at COMPLETION (see Complete), not here — while the
|
|
// report is open its fleet_status is shown live so fleet updates reflect.
|
|
return row, nil
|
|
}
|
|
|
|
// snapshotFleetStatus freezes the current fleet status for a report (idempotent: no-op if a
|
|
// snapshot already exists for the report).
|
|
func (s *FMReportService) snapshotFleetStatus(ctx context.Context, row *fmreport.Report) {
|
|
if s.historyBuilder == nil || len(row.ID) != 16 {
|
|
return
|
|
}
|
|
if existing, _ := s.repo.GetFleetHistoryByReportID(ctx, row.ID); existing != nil {
|
|
return
|
|
}
|
|
if history := s.historyBuilder.BuildFleetHistory(ctx, row.TakeoverID); history != nil {
|
|
history.FMReportID = row.ID
|
|
_ = s.repo.CreateFleetHistory(ctx, history)
|
|
}
|
|
}
|
|
|
|
func (s *FMReportService) UpdateForFlight(ctx context.Context, flightID []byte, engine1GpcN1, engine1PtcN2, engine2GpcN1, engine2PtcN2 *string) (*fmreport.Report, error) {
|
|
if len(flightID) != 16 {
|
|
return nil, fmt.Errorf("flight id must be a valid UUID")
|
|
}
|
|
row, err := s.repo.GetByFlightID(ctx, flightID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if row == nil || len(row.ID) != 16 {
|
|
return nil, fmreport.ErrNotFound
|
|
}
|
|
if row.CompletedAt != nil {
|
|
return nil, fmreport.ErrCompleted
|
|
}
|
|
if engine1GpcN1 != nil {
|
|
v := strings.TrimSpace(*engine1GpcN1)
|
|
row.Engine1GpcN1 = &v
|
|
}
|
|
if engine1PtcN2 != nil {
|
|
v := strings.TrimSpace(*engine1PtcN2)
|
|
row.Engine1PtcN2 = &v
|
|
}
|
|
if engine2GpcN1 != nil {
|
|
v := strings.TrimSpace(*engine2GpcN1)
|
|
row.Engine2GpcN1 = &v
|
|
}
|
|
if engine2PtcN2 != nil {
|
|
v := strings.TrimSpace(*engine2PtcN2)
|
|
row.Engine2PtcN2 = &v
|
|
}
|
|
if userID := appctx.GetUserID(ctx); len(userID) == 16 {
|
|
row.UpdatedBy = userID
|
|
}
|
|
if err := s.repo.Upsert(ctx, row); err != nil {
|
|
return nil, fmt.Errorf("failed to update fm report: %w", err)
|
|
}
|
|
return row, nil
|
|
}
|
|
|
|
func (s *FMReportService) Complete(ctx context.Context, flightID []byte) (*fmreport.Report, error) {
|
|
if len(flightID) != 16 {
|
|
return nil, fmt.Errorf("flight id must be a valid UUID")
|
|
}
|
|
row, err := s.repo.GetByFlightID(ctx, flightID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if row == nil || len(row.ID) != 16 {
|
|
return nil, fmreport.ErrNotFound
|
|
}
|
|
if row.CompletedAt != nil {
|
|
if s.usage != nil {
|
|
if err := s.usage.RefreshAll(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return row, nil
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
row.CompletedAt = &now
|
|
if userID := appctx.GetUserID(ctx); len(userID) == 16 {
|
|
row.CompletedBy = userID
|
|
row.UpdatedBy = userID
|
|
}
|
|
if err := s.repo.Upsert(ctx, row); err != nil {
|
|
return nil, fmt.Errorf("failed to complete fm report: %w", err)
|
|
}
|
|
// Freeze the fleet-status snapshot at the moment of completion.
|
|
s.snapshotFleetStatus(ctx, row)
|
|
if s.usage != nil {
|
|
if err := s.usage.RefreshAll(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return row, nil
|
|
}
|
|
|
|
func (s *FMReportService) GetByID(ctx context.Context, id []byte) (*fmreport.Report, error) {
|
|
if len(id) != 16 {
|
|
return nil, fmt.Errorf("id must be a valid UUID")
|
|
}
|
|
return s.repo.GetByID(ctx, id)
|
|
}
|
|
|
|
func (s *FMReportService) GetByFlightID(ctx context.Context, flightID []byte) (*fmreport.Report, error) {
|
|
if len(flightID) != 16 {
|
|
return nil, fmt.Errorf("flight id must be a valid UUID")
|
|
}
|
|
row, err := s.repo.GetByFlightID(ctx, flightID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return row, nil
|
|
}
|
|
|
|
func (s *FMReportService) List(ctx context.Context, filter fmreport.ListFilter, limit, offset int) ([]fmreport.Report, int64, error) {
|
|
return s.repo.List(ctx, filter, limit, offset)
|
|
}
|
|
|
|
func (s *FMReportService) Delete(ctx context.Context, id []byte) error {
|
|
if len(id) != 16 {
|
|
return fmt.Errorf("id must be a valid UUID")
|
|
}
|
|
row, err := s.repo.GetByID(ctx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if row == nil || len(row.ID) != 16 {
|
|
return fmreport.ErrNotFound
|
|
}
|
|
var deletedBy []byte
|
|
if userID := appctx.GetUserID(ctx); len(userID) == 16 {
|
|
deletedBy = userID
|
|
}
|
|
return s.repo.Delete(ctx, id, deletedBy)
|
|
}
|
|
|
|
func (s *FMReportService) AttachmentReferenceType() string { return "fm_report" }
|
|
|
|
func (s *FMReportService) AttachmentReferenceID(row *fmreport.Report) string {
|
|
if row == nil {
|
|
return ""
|
|
}
|
|
id, err := uuidv7.BytesToString(row.ID)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(id)
|
|
}
|