init push
This commit is contained in:
528
internal/repository/mysql/mission_repo.go
Normal file
528
internal/repository/mysql/mission_repo.go
Normal file
@@ -0,0 +1,528 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
flightdata "wucher/internal/domain/flight_data"
|
||||
"wucher/internal/domain/mission"
|
||||
"wucher/internal/shared/pkg/txctx"
|
||||
)
|
||||
|
||||
type MissionRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewMissionRepository(db *gorm.DB) *MissionRepository {
|
||||
return &MissionRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *MissionRepository) Create(ctx context.Context, row *mission.Mission) error {
|
||||
normalizeMissionTimeForDB(row)
|
||||
if err := txctx.DB(ctx, r.db).Create(row).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||
return mission.ErrDuplicateFlight
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *MissionRepository) WithTransaction(ctx context.Context, fn func(context.Context) error) error {
|
||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return fn(txctx.With(ctx, tx))
|
||||
})
|
||||
}
|
||||
|
||||
// MaxCodeSeqByPrefix returns the highest numeric suffix among mission codes that
|
||||
// start with prefix (e.g. "HEMS-26-"), or 0 when none exist. Used to compute the
|
||||
// next per-type, per-year sequence number.
|
||||
func (r *MissionRepository) MaxCodeSeqByPrefix(ctx context.Context, prefix string) (int, error) {
|
||||
var maxSeq *int
|
||||
err := txctx.DB(ctx, r.db).
|
||||
Model(&mission.Mission{}).
|
||||
Where("code LIKE ? AND deleted_at IS NULL", prefix+"%").
|
||||
Select("MAX(CAST(SUBSTRING_INDEX(code, '-', -1) AS UNSIGNED))").
|
||||
Scan(&maxSeq).Error
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if maxSeq == nil {
|
||||
return 0, nil
|
||||
}
|
||||
return *maxSeq, nil
|
||||
}
|
||||
|
||||
func (r *MissionRepository) AttachFile(ctx context.Context, missionID []byte, fileAttachmentID []byte) error {
|
||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var missionRow mission.Mission
|
||||
if err := tx.Where("id = ? AND deleted_at IS NULL", missionID).
|
||||
Take(&missionRow).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
var existing mission.MissionFile
|
||||
err := tx.Where("mission_id = ? AND file_attachment_id = ?", missionID, fileAttachmentID).
|
||||
Take(&existing).Error
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
return tx.Create(&mission.MissionFile{
|
||||
MissionID: append([]byte(nil), missionID...),
|
||||
FileAttachmentID: append([]byte(nil), fileAttachmentID...),
|
||||
}).Error
|
||||
})
|
||||
}
|
||||
|
||||
func (r *MissionRepository) DetachFile(ctx context.Context, missionID []byte, fileAttachmentID []byte) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Where("mission_id = ? AND file_attachment_id = ?", missionID, fileAttachmentID).
|
||||
Delete(&mission.MissionFile{}).Error
|
||||
}
|
||||
|
||||
func (r *MissionRepository) CreateCategory(ctx context.Context, row *mission.MissionCategory) error {
|
||||
return r.db.WithContext(ctx).Create(row).Error
|
||||
}
|
||||
|
||||
func (r *MissionRepository) UpdateFlightDataIDByID(ctx context.Context, missionID, flightDataID, updatedBy []byte) error {
|
||||
patch := map[string]any{
|
||||
"flight_data_id": flightDataID,
|
||||
}
|
||||
if len(updatedBy) == 16 {
|
||||
patch["updated_by"] = updatedBy
|
||||
}
|
||||
res := txctx.DB(ctx, r.db).
|
||||
Model(&mission.Mission{}).
|
||||
Where("id = ? AND deleted_at IS NULL", missionID).
|
||||
Updates(patch)
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *MissionRepository) UpdateByID(ctx context.Context, missionID []byte, missionType string, missionCategoryID []byte, subtypeID []byte, note string, updatedBy []byte) error {
|
||||
patch := map[string]any{
|
||||
"type": missionType,
|
||||
"mission_category_id": missionCategoryID,
|
||||
"subtype_id": nil,
|
||||
"note": strings.TrimSpace(note),
|
||||
}
|
||||
if len(subtypeID) == 16 {
|
||||
patch["subtype_id"] = subtypeID
|
||||
}
|
||||
if len(updatedBy) == 16 {
|
||||
patch["updated_by"] = updatedBy
|
||||
}
|
||||
|
||||
res := r.db.WithContext(ctx).
|
||||
Model(&mission.Mission{}).
|
||||
Where("id = ? AND deleted_at IS NULL", missionID).
|
||||
Updates(patch)
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *MissionRepository) DeleteByID(ctx context.Context, missionID []byte, deletedBy []byte) error {
|
||||
now := time.Now().UTC()
|
||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
patch := map[string]any{
|
||||
"deleted_at": now,
|
||||
"deleted_by": deletedBy,
|
||||
}
|
||||
if len(deletedBy) == 16 {
|
||||
patch["updated_by"] = deletedBy
|
||||
}
|
||||
|
||||
res := tx.Model(&mission.Mission{}).
|
||||
Where("id = ? AND deleted_at IS NULL", missionID).
|
||||
Updates(patch)
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
fdPatch := map[string]any{
|
||||
"deleted_at": now,
|
||||
"deleted_by": deletedBy,
|
||||
}
|
||||
if len(deletedBy) == 16 {
|
||||
fdPatch["updated_by"] = deletedBy
|
||||
}
|
||||
|
||||
if err := tx.Model(&flightdata.FlightData{}).
|
||||
Where("mission_id = ? AND deleted_at IS NULL", missionID).
|
||||
Updates(fdPatch).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *MissionRepository) DeleteByFlightID(ctx context.Context, flightID []byte, deletedBy []byte) error {
|
||||
now := time.Now().UTC()
|
||||
patch := map[string]any{
|
||||
"deleted_at": now,
|
||||
"deleted_by": deletedBy,
|
||||
}
|
||||
if len(deletedBy) == 16 {
|
||||
patch["updated_by"] = deletedBy
|
||||
}
|
||||
|
||||
res := r.db.WithContext(ctx).
|
||||
Model(&mission.Mission{}).
|
||||
Where("flight_id = ? AND deleted_at IS NULL", flightID).
|
||||
Updates(patch)
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *MissionRepository) ListByFlightID(ctx context.Context, flightID []byte) ([]mission.Mission, error) {
|
||||
rows := make([]mission.Mission, 0)
|
||||
err := r.db.WithContext(ctx).
|
||||
Select(r.statusSelectSQL()).
|
||||
Joins("LEFT JOIN mission_subcategories msc ON msc.id = missions.subtype_id").
|
||||
Preload("MissionSubCategory").
|
||||
Preload("Flight").
|
||||
Preload("Files").
|
||||
Preload("Files.FileAttachment").
|
||||
Preload("Files.FileAttachment.File").
|
||||
Where("missions.flight_id = ? AND missions.deleted_at IS NULL", flightID).
|
||||
Order("missions.created_at ASC, missions.id ASC").
|
||||
Find(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (r *MissionRepository) ListByFlightIDs(ctx context.Context, flightIDs [][]byte) ([]mission.Mission, error) {
|
||||
if len(flightIDs) == 0 {
|
||||
return []mission.Mission{}, nil
|
||||
}
|
||||
rows := make([]mission.Mission, 0)
|
||||
err := r.db.WithContext(ctx).
|
||||
Select(r.statusSelectSQL()).
|
||||
Joins("LEFT JOIN mission_subcategories msc ON msc.id = missions.subtype_id").
|
||||
Preload("MissionSubCategory").
|
||||
Preload("Flight").
|
||||
Preload("Files").
|
||||
Preload("Files.FileAttachment").
|
||||
Preload("Files.FileAttachment.File").
|
||||
Where("missions.flight_id IN ? AND missions.deleted_at IS NULL", flightIDs).
|
||||
Order("missions.flight_id ASC, missions.created_at ASC, missions.id ASC").
|
||||
Find(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (r *MissionRepository) GetByID(ctx context.Context, missionID []byte) (*mission.Mission, error) {
|
||||
var row mission.Mission
|
||||
err := r.db.WithContext(ctx).
|
||||
Select(r.statusSelectSQL()).
|
||||
Joins("LEFT JOIN mission_subcategories msc ON msc.id = missions.subtype_id").
|
||||
Preload("MissionSubCategory").
|
||||
Preload("Flight").
|
||||
Preload("Files").
|
||||
Preload("Files.FileAttachment").
|
||||
Preload("Files.FileAttachment.File").
|
||||
Where("missions.id = ? AND missions.deleted_at IS NULL", missionID).
|
||||
First(&row).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &row, nil
|
||||
}
|
||||
|
||||
func (r *MissionRepository) GetByFlightID(ctx context.Context, flightID []byte) (*mission.Mission, error) {
|
||||
var row mission.Mission
|
||||
err := r.db.WithContext(ctx).
|
||||
Select(r.statusSelectSQL()).
|
||||
Joins("LEFT JOIN mission_subcategories msc ON msc.id = missions.subtype_id").
|
||||
Preload("MissionSubCategory").
|
||||
Preload("Flight").
|
||||
Preload("Files").
|
||||
Preload("Files.FileAttachment").
|
||||
Preload("Files.FileAttachment.File").
|
||||
Where("missions.flight_id = ? AND missions.deleted_at IS NULL", flightID).
|
||||
First(&row).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &row, nil
|
||||
}
|
||||
|
||||
func (r *MissionRepository) List(ctx context.Context, filter, sort string, flightID []byte, flightDataStatus string, limit, offset int) ([]mission.Mission, int64, error) {
|
||||
rows, total, _, err := r.listWithFilter(ctx, mission.ListFilter{
|
||||
Search: filter,
|
||||
Sort: sort,
|
||||
FlightID: flightID,
|
||||
FlightDataStatus: flightDataStatus,
|
||||
}, limit, offset, false)
|
||||
return rows, total, err
|
||||
}
|
||||
|
||||
func (r *MissionRepository) ListDatatable(ctx context.Context, filter mission.ListFilter, limit, offset int) ([]mission.Mission, int64, int64, error) {
|
||||
return r.listWithFilter(ctx, filter, limit, offset, true)
|
||||
}
|
||||
|
||||
func (r *MissionRepository) statusSelectSQL() string {
|
||||
return `missions.*,
|
||||
COALESCE((
|
||||
SELECT flight_data.id
|
||||
FROM flight_data
|
||||
WHERE flight_data.mission_id = missions.id
|
||||
AND flight_data.deleted_at IS NULL
|
||||
ORDER BY flight_data.created_at DESC, flight_data.id DESC
|
||||
LIMIT 1
|
||||
), missions.flight_data_id) AS flight_data_id,
|
||||
` + r.flightDataStatusExprSQL() + ` AS flight_data_status`
|
||||
}
|
||||
|
||||
func (r *MissionRepository) flightDataStatusExprSQL() string {
|
||||
completeCondition := `(
|
||||
flight_data.take_off IS NOT NULL
|
||||
AND flight_data.landing IS NOT NULL
|
||||
AND flight_data.duration > 0
|
||||
AND flight_data.red > 0
|
||||
AND flight_data.max_n1 > 0
|
||||
AND flight_data.max_n2 > 0
|
||||
AND flight_data.pax_count > 0
|
||||
AND flight_data.landing_count > 0
|
||||
AND flight_data.rotor_brake_cycle > 0
|
||||
AND COALESCE(flight_data.ticket_no, '') <> ''
|
||||
AND COALESCE(flight_data.engine, '') <> ''
|
||||
AND COALESCE(flight_data.delivery_note_number, '') <> ''
|
||||
AND COALESCE(flight_data.customer_name, '') <> ''
|
||||
AND flight_data.flight_plan_distance > 0
|
||||
AND flight_data.flight_plan_time > 0
|
||||
AND flight_data.flight_plan_true_course > 0
|
||||
AND flight_data.fuel_before_flight > 0
|
||||
AND flight_data.fuel_upload > 0
|
||||
AND flight_data.fuel_after_flight > 0
|
||||
AND flight_data.fuel_planning > 0
|
||||
AND COALESCE(flight_data.other_information, '') <> ''
|
||||
)`
|
||||
return `COALESCE((
|
||||
SELECT CASE
|
||||
WHEN COUNT(flight_data.id) = 0 THEN 'draft'
|
||||
WHEN SUM(CASE WHEN ` + completeCondition + ` THEN 1 ELSE 0 END) = 0 THEN 'draft'
|
||||
WHEN SUM(CASE WHEN ` + completeCondition + ` THEN 1 ELSE 0 END) = COUNT(flight_data.id) THEN 'completed'
|
||||
ELSE 'in_progress'
|
||||
END
|
||||
FROM flight_data
|
||||
WHERE flight_data.mission_id = missions.id
|
||||
AND flight_data.deleted_at IS NULL
|
||||
), 'draft')`
|
||||
}
|
||||
|
||||
func (r *MissionRepository) listWithFilter(ctx context.Context, filter mission.ListFilter, limit, offset int, includeUnfilteredTotal bool) ([]mission.Mission, int64, int64, error) {
|
||||
rows := make([]mission.Mission, 0)
|
||||
var total int64
|
||||
var filteredTotal int64
|
||||
|
||||
base := r.db.WithContext(ctx).
|
||||
Model(&mission.Mission{}).
|
||||
Joins("LEFT JOIN flights ON flights.id = missions.flight_id").
|
||||
Joins("LEFT JOIN mission_subcategories msc ON msc.id = missions.subtype_id").
|
||||
Where("missions.deleted_at IS NULL")
|
||||
|
||||
if includeUnfilteredTotal {
|
||||
if err := base.Session(&gorm.Session{}).Count(&total).Error; err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
} else {
|
||||
total = 0
|
||||
}
|
||||
|
||||
base = r.applyMissionListFilter(base, filter)
|
||||
|
||||
countQuery := base.Session(&gorm.Session{})
|
||||
if err := countQuery.Count(&filteredTotal).Error; err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
query := base.Session(&gorm.Session{}).
|
||||
Select(r.statusSelectSQL()).
|
||||
Preload("MissionSubCategory").
|
||||
Preload("Flight").
|
||||
Preload("Files").
|
||||
Preload("Files.FileAttachment").
|
||||
Preload("Files.FileAttachment.File")
|
||||
if filter.Sort != "" {
|
||||
query = query.Order(filter.Sort)
|
||||
}
|
||||
if limit > 0 {
|
||||
query = query.Limit(limit).Offset(offset)
|
||||
}
|
||||
|
||||
if err := query.Find(&rows).Error; err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
if !includeUnfilteredTotal {
|
||||
total = filteredTotal
|
||||
}
|
||||
return rows, total, filteredTotal, nil
|
||||
}
|
||||
|
||||
func (r *MissionRepository) applyMissionListFilter(query *gorm.DB, filter mission.ListFilter) *gorm.DB {
|
||||
if filter.Search != "" {
|
||||
like := "%" + filter.Search + "%"
|
||||
query = query.Where(
|
||||
"missions.type LIKE ? OR flights.mission_code LIKE ? OR DATE_FORMAT(flights.date, '%Y-%m-%d') LIKE ?",
|
||||
like, like, like,
|
||||
)
|
||||
}
|
||||
if len(filter.FlightID) == 16 {
|
||||
query = query.Where("missions.flight_id = ?", filter.FlightID)
|
||||
}
|
||||
if filter.FlightDataStatus != "" {
|
||||
query = query.Where(r.flightDataStatusExprSQL()+" = ?", filter.FlightDataStatus)
|
||||
}
|
||||
if filter.MissionType != "" {
|
||||
query = query.Where("missions.type = ?", strings.ToUpper(strings.TrimSpace(filter.MissionType)))
|
||||
}
|
||||
if filter.StartDate != nil {
|
||||
query = query.Where("flights.date >= ?", filter.StartDate.UTC().Format("2006-01-02"))
|
||||
}
|
||||
if filter.EndDate != nil {
|
||||
query = query.Where("flights.date <= ?", filter.EndDate.UTC().Format("2006-01-02"))
|
||||
}
|
||||
if len(filter.PilotID) == 16 {
|
||||
query = query.Where(`
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM duty_rosters dr
|
||||
JOIN duty_roster_crews c ON c.roster_id = dr.id
|
||||
WHERE dr.flight_id = missions.flight_id
|
||||
AND dr.deleted_at IS NULL
|
||||
AND c.deleted_at IS NULL
|
||||
AND c.role_code = 'pilot'
|
||||
AND c.user_id = ?
|
||||
)`, filter.PilotID)
|
||||
}
|
||||
if len(filter.HelicopterID) == 16 {
|
||||
query = query.Where(`
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM takeover_acs ta
|
||||
JOIN reserve_acs ra ON ra.id = ta.reserve_ac_id
|
||||
WHERE ta.id = flights.takeover_ac_id
|
||||
AND ta.deleted_at IS NULL
|
||||
AND ra.deleted_at IS NULL
|
||||
AND ra.helicopter_id = ?
|
||||
)`, filter.HelicopterID)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
func normalizeMissionTimeForDB(row *mission.Mission) {
|
||||
if row == nil {
|
||||
return
|
||||
}
|
||||
row.StartTime = normalizeMissionDBClock(row.StartTime)
|
||||
row.EndTime = normalizeMissionDBClock(row.EndTime)
|
||||
}
|
||||
|
||||
func normalizeMissionDBClock(raw string) string {
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
if trimmed == "" {
|
||||
return ""
|
||||
}
|
||||
if len(trimmed) == 5 {
|
||||
return trimmed + ":00"
|
||||
}
|
||||
if len(trimmed) == 8 {
|
||||
return trimmed
|
||||
}
|
||||
if parsed, err := time.Parse("15:04", trimmed); err == nil {
|
||||
return parsed.Format("15:04:05")
|
||||
}
|
||||
if parsed, err := time.Parse("15:04:05", trimmed); err == nil {
|
||||
return parsed.Format("15:04:05")
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
func (r *MissionRepository) ListCategoriesWithSubCategories(ctx context.Context) ([]mission.MissionCategory, error) {
|
||||
var rows []mission.MissionCategory
|
||||
if err := r.db.WithContext(ctx).
|
||||
Preload("SubCategory", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Order("sub_type ASC")
|
||||
}).
|
||||
Order("code_type ASC").
|
||||
Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range rows {
|
||||
rows[i].CodeType = strings.TrimSpace(rows[i].CodeType)
|
||||
rows[i].TypeName = strings.TrimSpace(rows[i].TypeName)
|
||||
for j := range rows[i].SubCategory {
|
||||
rows[i].SubCategory[j].SubCodeType = strings.TrimSpace(rows[i].SubCategory[j].SubCodeType)
|
||||
rows[i].SubCategory[j].SubTypeName = strings.TrimSpace(rows[i].SubCategory[j].SubTypeName)
|
||||
rows[i].SubCategory[j].TaskName = strings.TrimSpace(rows[i].SubCategory[j].TaskName)
|
||||
}
|
||||
}
|
||||
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (r *MissionRepository) FindCategoryByCode(ctx context.Context, code string) (*mission.MissionCategory, error) {
|
||||
var row mission.MissionCategory
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("code_type = ?", code).
|
||||
Take(&row).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &row, nil
|
||||
}
|
||||
|
||||
func (r *MissionRepository) SubCategoryBelongsToCategory(ctx context.Context, subCategoryID, categoryID []byte) (bool, error) {
|
||||
if len(subCategoryID) != 16 || len(categoryID) != 16 {
|
||||
return false, nil
|
||||
}
|
||||
var cnt int64
|
||||
if err := r.db.WithContext(ctx).
|
||||
Table("mission_subcategories").
|
||||
Where("id = ? AND mission_category_id = ?", subCategoryID, categoryID).
|
||||
Count(&cnt).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return cnt > 0, nil
|
||||
}
|
||||
Reference in New Issue
Block a user