init push
This commit is contained in:
289
internal/service/fm_report_service.go
Normal file
289
internal/service/fm_report_service.go
Normal file
@@ -0,0 +1,289 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user