package service import ( "bytes" "context" "errors" "gorm.io/gorm" airrescuechecklist "wucher/internal/domain/air_rescue_checklist" ) type AirRescuerChecklistService struct { repo airrescuechecklist.Repository } func NewAirRescuerChecklistService(repo airrescuechecklist.Repository) *AirRescuerChecklistService { return &AirRescuerChecklistService{repo: repo} } func (s *AirRescuerChecklistService) CreateBulk(ctx context.Context, rows []airrescuechecklist.ChecklistCreateInput) error { if len(rows) == 0 { return nil } return s.repo.InTx(ctx, func(txRepo airrescuechecklist.TxRepository) error { for i := range rows { header := &rows[i].Checklist if header.Position <= 0 { nextPos, err := txRepo.NextChecklistPosition(ctx, header.HEMSBaseID, header.ScopeCode) if err != nil { return err } header.Position = nextPos } if err := txRepo.CreateChecklist(ctx, header); err != nil { return err } var nextItemPos int itemNextInitialized := false for j := range rows[i].Items { item := &rows[i].Items[j] item.ChecklistID = append([]byte(nil), header.ID...) if item.Position <= 0 { if !itemNextInitialized { pos, err := txRepo.NextChecklistItemPosition(ctx, header.ID) if err != nil { return err } nextItemPos = pos itemNextInitialized = true } item.Position = nextItemPos nextItemPos++ } if err := txRepo.CreateChecklistItem(ctx, item); err != nil { return err } } } return nil }) } func (s *AirRescuerChecklistService) UpdateChecklist(ctx context.Context, row *airrescuechecklist.AirRescuerChecklist) error { if row == nil { return gorm.ErrInvalidData } row.Position = normalizeChecklistPosition(row.Position) return s.repo.InTx(ctx, func(txRepo airrescuechecklist.TxRepository) error { if _, err := txRepo.LockChecklistByID(ctx, row.ID); err != nil { return err } return txRepo.UpdateChecklist(ctx, row) }) } func (s *AirRescuerChecklistService) UpdateChecklistWithNewItems( ctx context.Context, row *airrescuechecklist.AirRescuerChecklist, newItems []airrescuechecklist.AirRescuerChecklistItem, ) error { if row == nil && len(newItems) == 0 { return gorm.ErrInvalidData } checklistID := []byte(nil) if row != nil { row.Position = normalizeChecklistPosition(row.Position) checklistID = append([]byte(nil), row.ID...) } if len(checklistID) == 0 && len(newItems) > 0 { checklistID = append([]byte(nil), newItems[0].ChecklistID...) } if len(checklistID) == 0 { return gorm.ErrInvalidData } return s.repo.InTx(ctx, func(txRepo airrescuechecklist.TxRepository) error { if _, err := txRepo.LockChecklistByID(ctx, checklistID); err != nil { return err } if row != nil { if err := txRepo.UpdateChecklist(ctx, row); err != nil { return err } } if len(newItems) == 0 { return nil } nextItemPos := 0 itemNextInitialized := false for i := range newItems { newItems[i].ChecklistID = append([]byte(nil), checklistID...) if newItems[i].Position <= 0 { if !itemNextInitialized { pos, err := txRepo.NextChecklistItemPosition(ctx, checklistID) if err != nil { return err } nextItemPos = pos itemNextInitialized = true } newItems[i].Position = nextItemPos nextItemPos++ } } return txRepo.CreateChecklistItems(ctx, newItems) }) } func (s *AirRescuerChecklistService) DeleteChecklist(ctx context.Context, id []byte, deletedBy []byte) error { return s.repo.InTx(ctx, func(txRepo airrescuechecklist.TxRepository) error { if _, err := txRepo.LockChecklistByID(ctx, id); err != nil { return err } if err := txRepo.SoftDeleteChecklist(ctx, id, deletedBy); err != nil { return err } return txRepo.SoftDeleteItemsByChecklistID(ctx, id, deletedBy) }) } func (s *AirRescuerChecklistService) CreateChecklistItem(ctx context.Context, row *airrescuechecklist.AirRescuerChecklistItem) error { if row == nil { return gorm.ErrInvalidData } return s.repo.InTx(ctx, func(txRepo airrescuechecklist.TxRepository) error { if _, err := txRepo.LockChecklistByID(ctx, row.ChecklistID); err != nil { return err } if row.Position <= 0 { nextPos, err := txRepo.NextChecklistItemPosition(ctx, row.ChecklistID) if err != nil { return err } row.Position = nextPos } return txRepo.CreateChecklistItem(ctx, row) }) } func (s *AirRescuerChecklistService) CreateChecklistItems(ctx context.Context, checklistID []byte, rows []airrescuechecklist.AirRescuerChecklistItem) error { if len(rows) == 0 { return nil } return s.repo.InTx(ctx, func(txRepo airrescuechecklist.TxRepository) error { if _, err := txRepo.LockChecklistByID(ctx, checklistID); err != nil { return err } nextItemPos := 0 itemNextInitialized := false for i := range rows { rows[i].ChecklistID = append([]byte(nil), checklistID...) if rows[i].Position <= 0 { if !itemNextInitialized { pos, err := txRepo.NextChecklistItemPosition(ctx, checklistID) if err != nil { return err } nextItemPos = pos itemNextInitialized = true } rows[i].Position = nextItemPos nextItemPos++ } } return txRepo.CreateChecklistItems(ctx, rows) }) } func (s *AirRescuerChecklistService) ReorderChecklistItems( ctx context.Context, checklistID []byte, rows []airrescuechecklist.ChecklistItemReorderInput, updatedBy []byte, ) error { if len(rows) == 0 { return gorm.ErrInvalidData } return s.repo.InTx(ctx, func(txRepo airrescuechecklist.TxRepository) error { if _, err := txRepo.LockChecklistByID(ctx, checklistID); err != nil { return err } seenItemIDs := make(map[string]struct{}, len(rows)) for i := range rows { itemID := rows[i].ID if len(itemID) == 0 { return gorm.ErrInvalidData } itemKey := string(itemID) if _, exists := seenItemIDs[itemKey]; exists { return errors.New("duplicate item_uuid in reorder payload") } seenItemIDs[itemKey] = struct{}{} current, err := txRepo.LockChecklistItemByID(ctx, itemID) if err != nil { return err } if !bytes.Equal(current.ChecklistID, checklistID) { return gorm.ErrRecordNotFound } if err := txRepo.UpdateChecklistItem(ctx, &airrescuechecklist.AirRescuerChecklistItem{ ID: append([]byte(nil), itemID...), Name: current.Name, Position: normalizeChecklistPosition(rows[i].Position), UpdatedBy: append([]byte(nil), updatedBy...), }); err != nil { return err } } return nil }) } func (s *AirRescuerChecklistService) UpdateChecklistItem(ctx context.Context, row *airrescuechecklist.AirRescuerChecklistItem) error { if row == nil { return gorm.ErrInvalidData } row.Position = normalizeChecklistPosition(row.Position) return s.repo.InTx(ctx, func(txRepo airrescuechecklist.TxRepository) error { if _, err := txRepo.LockChecklistItemByID(ctx, row.ID); err != nil { return err } return txRepo.UpdateChecklistItem(ctx, row) }) } func (s *AirRescuerChecklistService) DeleteChecklistItem(ctx context.Context, id []byte, deletedBy []byte) error { return s.repo.InTx(ctx, func(txRepo airrescuechecklist.TxRepository) error { if _, err := txRepo.LockChecklistItemByID(ctx, id); err != nil { return err } return txRepo.SoftDeleteChecklistItem(ctx, id, deletedBy) }) } func (s *AirRescuerChecklistService) GetChecklistByID(ctx context.Context, id []byte) (*airrescuechecklist.ChecklistHeaderView, error) { return s.repo.GetChecklistByID(ctx, id) } func (s *AirRescuerChecklistService) GetChecklistItemByID(ctx context.Context, id []byte) (*airrescuechecklist.ChecklistItemView, error) { return s.repo.GetChecklistItemByID(ctx, id) } func (s *AirRescuerChecklistService) ListChecklistHeaders(ctx context.Context, filter airrescuechecklist.ChecklistHeaderFilter) ([]airrescuechecklist.ChecklistHeaderView, error) { return s.repo.ListChecklistHeaders(ctx, filter) } func (s *AirRescuerChecklistService) ListChecklistItemsByChecklistIDs(ctx context.Context, checklistIDs [][]byte) ([]airrescuechecklist.ChecklistItemView, error) { return s.repo.ListChecklistItemsByChecklistIDs(ctx, checklistIDs) } func (s *AirRescuerChecklistService) ListHEMSBases(ctx context.Context, filter airrescuechecklist.HEMSBaseFilter) ([]airrescuechecklist.HEMSBaseView, error) { return s.repo.ListHEMSBases(ctx, filter) } func normalizeChecklistPosition(v int) int { if v <= 0 { return 1 } return v }