632 lines
20 KiB
Go
632 lines
20 KiB
Go
package mysql
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
|
|
basedomain "wucher/internal/domain/base"
|
|
dutyroster "wucher/internal/domain/duty_roster"
|
|
)
|
|
|
|
type DutyRosterRepository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewDutyRosterRepository(db *gorm.DB) *DutyRosterRepository {
|
|
return &DutyRosterRepository{db: db}
|
|
}
|
|
|
|
func (r *DutyRosterRepository) InTx(ctx context.Context, fn func(txRepo dutyroster.TxRepository) error) error {
|
|
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
return fn(&DutyRosterRepository{db: tx})
|
|
})
|
|
}
|
|
|
|
func (r *DutyRosterRepository) GetRosterByID(ctx context.Context, id []byte) (*dutyroster.DutyRoster, error) {
|
|
row := &dutyroster.DutyRoster{}
|
|
if err := r.db.WithContext(ctx).
|
|
Where("id = ? AND deleted_at IS NULL", id).
|
|
Take(row).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return row, nil
|
|
}
|
|
|
|
func (r *DutyRosterRepository) LockRosterByID(ctx context.Context, id []byte) (*dutyroster.DutyRoster, error) {
|
|
row := &dutyroster.DutyRoster{}
|
|
if err := r.db.WithContext(ctx).
|
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
|
Where("id = ? AND deleted_at IS NULL", id).
|
|
Take(row).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return row, nil
|
|
}
|
|
|
|
func (r *DutyRosterRepository) CreateHeader(ctx context.Context, row *dutyroster.DutyRoster) error {
|
|
return r.db.WithContext(ctx).Create(row).Error
|
|
}
|
|
|
|
func (r *DutyRosterRepository) UpdateHeader(ctx context.Context, row *dutyroster.DutyRoster) error {
|
|
updates := map[string]any{
|
|
"base_id": row.BaseID,
|
|
"duty_date": row.DutyDate,
|
|
"updated_by": row.UpdatedBy,
|
|
}
|
|
res := r.db.WithContext(ctx).Model(&dutyroster.DutyRoster{}).
|
|
Where("id = ? AND deleted_at IS NULL", row.ID).
|
|
Updates(updates)
|
|
if res.Error != nil {
|
|
return res.Error
|
|
}
|
|
if res.RowsAffected == 0 {
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *DutyRosterRepository) SoftDeleteHeader(ctx context.Context, id []byte, deletedBy []byte) error {
|
|
now := time.Now().UTC()
|
|
res := r.db.WithContext(ctx).Model(&dutyroster.DutyRoster{}).
|
|
Where("id = ? AND deleted_at IS NULL", id).
|
|
Updates(map[string]any{"deleted_at": now, "deleted_by": deletedBy, "updated_by": deletedBy})
|
|
if res.Error != nil {
|
|
return res.Error
|
|
}
|
|
if res.RowsAffected == 0 {
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *DutyRosterRepository) SoftDeleteCrewsByRosterID(ctx context.Context, rosterID []byte, deletedBy []byte) error {
|
|
now := time.Now().UTC()
|
|
return r.db.WithContext(ctx).Model(&dutyroster.DutyRosterCrew{}).
|
|
Where("roster_id = ? AND deleted_at IS NULL", rosterID).
|
|
Updates(map[string]any{"deleted_at": now, "deleted_by": deletedBy, "updated_by": deletedBy}).Error
|
|
}
|
|
|
|
func (r *DutyRosterRepository) HardDeleteHeader(ctx context.Context, id []byte) error {
|
|
return r.db.WithContext(ctx).
|
|
Where("id = ?", id).
|
|
Delete(&dutyroster.DutyRoster{}).Error
|
|
}
|
|
|
|
func (r *DutyRosterRepository) HardDeleteCrewsByRosterID(ctx context.Context, rosterID []byte) error {
|
|
return r.db.WithContext(ctx).
|
|
Where("roster_id = ?", rosterID).
|
|
Delete(&dutyroster.DutyRosterCrew{}).Error
|
|
}
|
|
|
|
func (r *DutyRosterRepository) ListActiveCrewsByRosterID(ctx context.Context, rosterID []byte) ([]dutyroster.DutyRosterCrew, error) {
|
|
rows := make([]dutyroster.DutyRosterCrew, 0)
|
|
if err := r.db.WithContext(ctx).
|
|
Where("roster_id = ? AND deleted_at IS NULL", rosterID).
|
|
Find(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
func (r *DutyRosterRepository) InsertCrew(ctx context.Context, row *dutyroster.DutyRosterCrew) error {
|
|
return r.db.WithContext(ctx).Create(row).Error
|
|
}
|
|
|
|
func (r *DutyRosterRepository) CrewExists(ctx context.Context, row dutyroster.DutyRosterCrew) (bool, error) {
|
|
return crewExists(r.db.WithContext(ctx), row)
|
|
}
|
|
|
|
func (r *DutyRosterRepository) SoftDeleteCrewByUser(ctx context.Context, rosterID []byte, roleCode, crewType string, userID, updatedBy []byte) error {
|
|
return softReplaceCrewByUser(r.db.WithContext(ctx), rosterID, roleCode, crewType, userID, updatedBy)
|
|
}
|
|
|
|
func (r *DutyRosterRepository) FindRosterIDByBaseDate(ctx context.Context, baseID []byte, dutyDate time.Time) ([]byte, error) {
|
|
date := dateOnlyUTC(dutyDate)
|
|
var row dutyroster.DutyRoster
|
|
err := r.db.WithContext(ctx).
|
|
Where("base_id = ? AND duty_date = ? AND deleted_at IS NULL", baseID, date).
|
|
Take(&row).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return row.ID, nil
|
|
}
|
|
|
|
func (r *DutyRosterRepository) FindRosterIDsByBaseDate(ctx context.Context, baseID []byte, dutyDate time.Time) ([][]byte, error) {
|
|
return findRosterIDsByBaseAndDate(r.db.WithContext(ctx), baseID, dutyDate)
|
|
}
|
|
|
|
func (r *DutyRosterRepository) MatchCrewByFilter(ctx context.Context, rosterIDs [][]byte, crew dutyroster.DutyRosterCrew) ([]dutyroster.DeletedCrewResult, error) {
|
|
return matchCrewByFilter(r.db.WithContext(ctx), rosterIDs, crew)
|
|
}
|
|
|
|
func (r *DutyRosterRepository) SoftDeleteCrewRows(ctx context.Context, rows []dutyroster.DeletedCrewResult, deletedBy []byte) error {
|
|
return softDeleteCrewByIDs(r.db.WithContext(ctx), rows, time.Now().UTC(), deletedBy)
|
|
}
|
|
|
|
func (r *DutyRosterRepository) SoftDeleteCrewForDate(ctx context.Context, baseID []byte, dutyDate time.Time, crew dutyroster.DutyRosterCrew, updatedBy []byte) error {
|
|
return softDeleteCrewForDate(r.db.WithContext(ctx), baseID, dutyDate, crew, time.Now().UTC(), updatedBy)
|
|
}
|
|
|
|
func (r *DutyRosterRepository) GetBaseDefaultShift(ctx context.Context, baseID []byte, baseType string) (string, error) {
|
|
hasDefaultShiftTypes := r.hasBaseDefaultShiftTypeColumns()
|
|
q := r.db.WithContext(ctx).
|
|
Table("bases b").
|
|
Where("b.id = ?", baseID)
|
|
if hasDefaultShiftTypes {
|
|
q = q.Select("b.default_start_time_type, b.default_end_time_type, b.default_shift_start, b.default_shift_end")
|
|
} else {
|
|
q = q.Select("b.default_shift_start, b.default_shift_end")
|
|
}
|
|
if strings.TrimSpace(baseType) != "" {
|
|
q = q.Joins("JOIN base_categories bc ON bc.id = b.base_category_id").
|
|
Where("bc.key = ?", rosterBaseCategoryKey(baseType))
|
|
}
|
|
var out struct {
|
|
DefaultStartTimeType string
|
|
DefaultEndTimeType string
|
|
DefaultShiftStart string
|
|
DefaultShiftEnd string
|
|
}
|
|
err := q.Take(&out).Error
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
start := ""
|
|
end := ""
|
|
if !hasDefaultShiftTypes || normalizeBaseShiftTimeType(out.DefaultStartTimeType) == basedomain.ShiftTimeTypeFixed {
|
|
start = strings.TrimSpace(out.DefaultShiftStart)
|
|
}
|
|
if !hasDefaultShiftTypes || normalizeBaseShiftTimeType(out.DefaultEndTimeType) == basedomain.ShiftTimeTypeFixed {
|
|
end = strings.TrimSpace(out.DefaultShiftEnd)
|
|
}
|
|
if start != "" || end != "" {
|
|
switch {
|
|
case start != "" && end != "":
|
|
return start + "-" + end, nil
|
|
case start != "":
|
|
return start, nil
|
|
default:
|
|
return end, nil
|
|
}
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
func (r *DutyRosterRepository) GetHeaderByID(ctx context.Context, id []byte) (*dutyroster.HeaderView, error) {
|
|
return r.GetHeaderByIDByBaseType(ctx, id, "hems")
|
|
}
|
|
|
|
func (r *DutyRosterRepository) GetHeaderByIDByBaseType(ctx context.Context, id []byte, baseType string) (*dutyroster.HeaderView, error) {
|
|
categoryKey := rosterBaseCategoryKey(baseType)
|
|
out := &dutyroster.HeaderView{}
|
|
defaultShiftStartExpr := r.baseDefaultShiftDisplayExpr("hb", "default_shift_start", "default_start_time_type")
|
|
defaultShiftEndExpr := r.baseDefaultShiftDisplayExpr("hb", "default_shift_end", "default_end_time_type")
|
|
query := r.db.WithContext(ctx).
|
|
Table("duty_rosters hdr").
|
|
Select(`
|
|
hdr.id,
|
|
hdr.base_id AS base_id,
|
|
hb.base AS base_name,
|
|
hb.base_abbreviation,
|
|
`+defaultShiftStartExpr+` AS default_shift_start,
|
|
`+defaultShiftEndExpr+` AS default_shift_end,
|
|
'' AS helicopter_id,
|
|
'' AS helicopter_type,
|
|
'' AS helicopter_designation,
|
|
'' AS helicopter_identifier,
|
|
hdr.duty_date,
|
|
TIME_FORMAT(hdr.shift_start, '%H:%i:%s') AS shift_start,
|
|
TIME_FORMAT(hdr.shift_end, '%H:%i:%s') AS shift_end,
|
|
hdr.created_at,
|
|
hdr.created_by,
|
|
hdr.updated_at`).
|
|
Joins("LEFT JOIN bases hb ON hb.id = hdr.base_id").
|
|
Joins("LEFT JOIN base_categories bc ON bc.id = hb.base_category_id").
|
|
Where("hdr.id = ? AND hdr.deleted_at IS NULL", id)
|
|
err := query.Where("bc.key = ?", categoryKey).
|
|
Where("hb.id IS NOT NULL").
|
|
Take(out).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) || len(out.ID) == 0 {
|
|
out = &dutyroster.HeaderView{}
|
|
err = r.db.WithContext(ctx).
|
|
Table("duty_rosters hdr").
|
|
Select(`
|
|
hdr.id,
|
|
hdr.base_id AS base_id,
|
|
hb.base AS base_name,
|
|
hb.base_abbreviation,
|
|
`+defaultShiftStartExpr+` AS default_shift_start,
|
|
`+defaultShiftEndExpr+` AS default_shift_end,
|
|
'' AS helicopter_id,
|
|
'' AS helicopter_type,
|
|
'' AS helicopter_designation,
|
|
'' AS helicopter_identifier,
|
|
hdr.duty_date,
|
|
TIME_FORMAT(hdr.shift_start, '%H:%i:%s') AS shift_start,
|
|
TIME_FORMAT(hdr.shift_end, '%H:%i:%s') AS shift_end,
|
|
hdr.created_at,
|
|
hdr.created_by,
|
|
hdr.updated_at`).
|
|
Joins("LEFT JOIN bases hb ON hb.id = hdr.base_id").
|
|
Where("hdr.id = ? AND hdr.deleted_at IS NULL", id).
|
|
Where("hb.id IS NOT NULL").
|
|
Take(out).Error
|
|
}
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (r *DutyRosterRepository) hasBaseDefaultShiftTypeColumns() bool {
|
|
if r == nil || r.db == nil {
|
|
return false
|
|
}
|
|
return r.db.Migrator().HasColumn("bases", "default_start_time_type") &&
|
|
r.db.Migrator().HasColumn("bases", "default_end_time_type")
|
|
}
|
|
|
|
func (r *DutyRosterRepository) baseDefaultShiftDisplayExpr(alias, shiftColumn, typeColumn string) string {
|
|
qualifiedShift := alias + "." + shiftColumn
|
|
if !r.hasBaseDefaultShiftTypeColumns() {
|
|
return "CASE WHEN " + qualifiedShift + " IS NULL THEN '' ELSE TIME_FORMAT(" + qualifiedShift + ", '%H:%i:%s') END"
|
|
}
|
|
qualifiedType := alias + "." + typeColumn
|
|
return "CASE WHEN COALESCE(" + qualifiedType + ", 'FIXED') <> 'FIXED' OR " + qualifiedShift + " IS NULL THEN '' ELSE TIME_FORMAT(" + qualifiedShift + ", '%H:%i:%s') END"
|
|
}
|
|
|
|
func (r *DutyRosterRepository) GetCrewsByRosterIDs(ctx context.Context, rosterIDs [][]byte) ([]dutyroster.CrewView, error) {
|
|
if len(rosterIDs) == 0 {
|
|
return []dutyroster.CrewView{}, nil
|
|
}
|
|
rows := make([]dutyroster.CrewView, 0)
|
|
err := r.db.WithContext(ctx).
|
|
Table("duty_roster_crews c").
|
|
Select(`
|
|
c.id,
|
|
c.roster_id,
|
|
c.user_id,
|
|
COALESCE(NULLIF(c.name_label, ''), TRIM(CONCAT(COALESCE(u.first_name, ''), ' ', COALESCE(u.last_name, ''))), '') AS name,
|
|
COALESCE(NULLIF(r.name, ''), c.role_code) AS role,
|
|
c.role_code,
|
|
c.crew_type,
|
|
COALESCE(NULLIF(c.mobile_phone, ''), u.mobile_phone, '') AS mobile_phone,
|
|
COALESCE(NULLIF(c.email, ''), u.email, '') AS email,
|
|
c.date_start,
|
|
c.date_end,
|
|
CASE
|
|
WHEN c.shift_start IS NULL OR TIME_FORMAT(c.shift_start, '%H:%i:%s') = '00:00:00' THEN ''
|
|
ELSE TIME_FORMAT(c.shift_start, '%H:%i:%s')
|
|
END AS shift_start,
|
|
CASE
|
|
WHEN c.shift_end IS NULL OR TIME_FORMAT(c.shift_end, '%H:%i:%s') = '00:00:00' THEN ''
|
|
ELSE TIME_FORMAT(c.shift_end, '%H:%i:%s')
|
|
END AS shift_end,
|
|
c.flight_instructor,
|
|
c.line_checker,
|
|
c.supervisor,
|
|
c.examiner,
|
|
c.co_pilot`).
|
|
Joins("LEFT JOIN users u ON u.id = c.user_id").
|
|
Joins("LEFT JOIN roles r ON r.id = u.role_id").
|
|
Where("c.roster_id IN ? AND c.deleted_at IS NULL", rosterIDs).
|
|
Order("c.role_code ASC, c.crew_type ASC, c.created_at ASC").
|
|
Scan(&rows).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
func (r *DutyRosterRepository) ListHeadersByRange(ctx context.Context, baseID []byte, from, to time.Time) ([]dutyroster.HeaderView, error) {
|
|
return r.ListHeadersByRangeByBaseType(ctx, baseID, from, to, "hems")
|
|
}
|
|
|
|
func (r *DutyRosterRepository) ListHeadersByRangeByBaseType(ctx context.Context, baseID []byte, from, to time.Time, baseType string) ([]dutyroster.HeaderView, error) {
|
|
categoryKey := rosterBaseCategoryKey(baseType)
|
|
rows := make([]dutyroster.HeaderView, 0)
|
|
q := r.db.WithContext(ctx).
|
|
Table("duty_rosters hdr").
|
|
Select(`
|
|
hdr.id,
|
|
hdr.base_id AS base_id,
|
|
hb.base AS base_name,
|
|
hb.base_abbreviation,
|
|
'' AS helicopter_id,
|
|
'' AS helicopter_type,
|
|
'' AS helicopter_designation,
|
|
'' AS helicopter_identifier,
|
|
hdr.duty_date,
|
|
TIME_FORMAT(hdr.shift_start, '%H:%i:%s') AS shift_start,
|
|
TIME_FORMAT(hdr.shift_end, '%H:%i:%s') AS shift_end,
|
|
hdr.created_at,
|
|
hdr.created_by,
|
|
hdr.updated_at`).
|
|
Joins("LEFT JOIN bases hb ON hb.id = hdr.base_id").
|
|
Joins("LEFT JOIN base_categories bc ON bc.id = hb.base_category_id").
|
|
Where("hdr.deleted_at IS NULL AND hdr.duty_date BETWEEN ? AND ?", from, to).
|
|
Where("bc.key = ?", categoryKey).
|
|
Where("hb.id IS NOT NULL")
|
|
if len(baseID) == 16 {
|
|
q = q.Where("hdr.base_id = ?", baseID)
|
|
}
|
|
err := q.Order("hb.base ASC, hdr.duty_date ASC").Scan(&rows).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
func (r *DutyRosterRepository) ListHEMSBases(ctx context.Context, baseID []byte) ([]dutyroster.BaseView, error) {
|
|
return r.ListBasesByType(ctx, baseID, "hems")
|
|
}
|
|
|
|
func (r *DutyRosterRepository) ListBasesByType(ctx context.Context, baseID []byte, baseType string) ([]dutyroster.BaseView, error) {
|
|
categoryKey := rosterBaseCategoryKey(baseType)
|
|
rows := make([]dutyroster.BaseView, 0)
|
|
q := r.db.WithContext(ctx).
|
|
Table("bases b").
|
|
Select(`
|
|
b.id,
|
|
b.base AS base_name,
|
|
b.base_abbreviation`).
|
|
Joins("JOIN base_categories bc ON bc.id = b.base_category_id").
|
|
Where("b.is_active = ?", true).
|
|
Where("bc.key = ?", categoryKey)
|
|
|
|
if len(baseID) == 16 {
|
|
q = q.Where("b.id = ?", baseID)
|
|
}
|
|
|
|
if err := q.Order("CASE WHEN b.sortkey IS NULL THEN 1 ELSE 0 END ASC, b.sortkey ASC, b.base ASC").Scan(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
func rosterBaseCategoryKey(baseType string) string {
|
|
switch strings.ToLower(strings.TrimSpace(baseType)) {
|
|
case "base":
|
|
return basedomain.CategoryKeyRegular
|
|
default:
|
|
return basedomain.CategoryKeyHEMS
|
|
}
|
|
}
|
|
|
|
func dateOnlyUTC(t time.Time) time.Time {
|
|
return time.Date(t.UTC().Year(), t.UTC().Month(), t.UTC().Day(), 0, 0, 0, 0, time.UTC)
|
|
}
|
|
|
|
func softReplaceCrewByUser(tx *gorm.DB, rosterID []byte, roleCode, crewType string, userID []byte, updatedBy []byte) error {
|
|
if len(rosterID) == 0 || len(userID) == 0 {
|
|
return nil
|
|
}
|
|
now := time.Now().UTC()
|
|
return tx.Model(&dutyroster.DutyRosterCrew{}).
|
|
Where("roster_id = ? AND role_code = ? AND crew_type = ? AND user_id = ? AND deleted_at IS NULL", rosterID, roleCode, crewType, userID).
|
|
Updates(map[string]any{
|
|
"deleted_at": now,
|
|
"deleted_by": updatedBy,
|
|
"updated_by": updatedBy,
|
|
}).Error
|
|
}
|
|
|
|
func crewExists(tx *gorm.DB, row dutyroster.DutyRosterCrew) (bool, error) {
|
|
q := tx.Model(&dutyroster.DutyRosterCrew{}).
|
|
Where("roster_id = ? AND role_code = ? AND crew_type = ? AND deleted_at IS NULL", row.RosterID, row.RoleCode, row.CrewType).
|
|
Where("name_label = ? AND mobile_phone = ? AND email = ?", row.NameLabel, row.MobilePhone, row.Email).
|
|
Where("shift_start = ? AND shift_end = ?", row.ShiftStart, row.ShiftEnd)
|
|
|
|
if len(row.UserID) == 0 {
|
|
q = q.Where("user_id IS NULL")
|
|
} else {
|
|
q = q.Where("user_id = ?", row.UserID)
|
|
}
|
|
if row.DateStart == nil {
|
|
q = q.Where("date_start IS NULL")
|
|
} else {
|
|
q = q.Where("date_start = ?", dateOnlyUTC(*row.DateStart))
|
|
}
|
|
if row.DateEnd == nil {
|
|
q = q.Where("date_end IS NULL")
|
|
} else {
|
|
q = q.Where("date_end = ?", dateOnlyUTC(*row.DateEnd))
|
|
}
|
|
|
|
var count int64
|
|
if err := q.Count(&count).Error; err != nil {
|
|
return false, err
|
|
}
|
|
return count > 0, nil
|
|
}
|
|
|
|
func softDeleteCrewForDate(
|
|
tx *gorm.DB,
|
|
baseID []byte,
|
|
dutyDate time.Time,
|
|
crew dutyroster.DutyRosterCrew,
|
|
now time.Time,
|
|
updatedBy []byte,
|
|
) error {
|
|
date := dateOnlyUTC(dutyDate)
|
|
var rosterIDs [][]byte
|
|
if err := tx.
|
|
Table("duty_rosters").
|
|
Select("id").
|
|
Where("base_id = ? AND duty_date = ? AND deleted_at IS NULL", baseID, date).
|
|
Scan(&rosterIDs).Error; err != nil {
|
|
return err
|
|
}
|
|
if len(rosterIDs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
q := tx.Model(&dutyroster.DutyRosterCrew{}).
|
|
Where("roster_id IN ? AND role_code = ? AND crew_type = ? AND deleted_at IS NULL", rosterIDs, crew.RoleCode, crew.CrewType).
|
|
Where("name_label = ? AND mobile_phone = ? AND email = ? AND shift_start = ? AND shift_end = ?",
|
|
crew.NameLabel,
|
|
crew.MobilePhone,
|
|
crew.Email,
|
|
crew.ShiftStart,
|
|
crew.ShiftEnd,
|
|
)
|
|
if len(crew.UserID) == 0 {
|
|
q = q.Where("user_id IS NULL")
|
|
} else {
|
|
q = q.Where("user_id = ?", crew.UserID)
|
|
}
|
|
if crew.DateStart == nil {
|
|
q = q.Where("date_start IS NULL")
|
|
} else {
|
|
q = q.Where("date_start = ?", dateOnlyUTC(*crew.DateStart))
|
|
}
|
|
if crew.DateEnd == nil {
|
|
q = q.Where("date_end IS NULL")
|
|
} else {
|
|
q = q.Where("date_end = ?", dateOnlyUTC(*crew.DateEnd))
|
|
}
|
|
|
|
return q.Updates(map[string]any{
|
|
"deleted_at": now,
|
|
"deleted_by": updatedBy,
|
|
"updated_by": updatedBy,
|
|
}).Error
|
|
}
|
|
|
|
func findRosterIDsByBaseAndDate(tx *gorm.DB, baseID []byte, dutyDate time.Time) ([][]byte, error) {
|
|
type rowID struct {
|
|
ID []byte `gorm:"column:id"`
|
|
}
|
|
date := dateOnlyUTC(dutyDate)
|
|
rows := make([]rowID, 0)
|
|
if err := tx.
|
|
Table("duty_rosters").
|
|
Select("id").
|
|
Where("base_id = ? AND duty_date = ? AND deleted_at IS NULL", baseID, date).
|
|
Scan(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([][]byte, 0, len(rows))
|
|
for i := range rows {
|
|
out = append(out, rows[i].ID)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func softDeleteCrewByFilter(
|
|
tx *gorm.DB,
|
|
rosterIDs [][]byte,
|
|
crew dutyroster.DutyRosterCrew,
|
|
now time.Time,
|
|
updatedBy []byte,
|
|
) error {
|
|
if len(rosterIDs) == 0 {
|
|
return nil
|
|
}
|
|
q := tx.Model(&dutyroster.DutyRosterCrew{}).
|
|
Table("duty_roster_crews c").
|
|
Where("c.roster_id IN ? AND c.role_code = ? AND c.deleted_at IS NULL", rosterIDs, crew.RoleCode)
|
|
|
|
if strings.TrimSpace(crew.CrewType) != "" {
|
|
q = q.Where("c.crew_type = ?", crew.CrewType)
|
|
}
|
|
if len(crew.UserID) > 0 {
|
|
q = q.Where("c.user_id = ?", crew.UserID)
|
|
} else if strings.TrimSpace(crew.NameLabel) != "" {
|
|
q = q.Where("c.name_label = ?", crew.NameLabel)
|
|
}
|
|
if crew.DateStart != nil {
|
|
q = q.Where("c.date_start = ?", dateOnlyUTC(*crew.DateStart))
|
|
}
|
|
if crew.DateEnd != nil {
|
|
q = q.Where("c.date_end = ?", dateOnlyUTC(*crew.DateEnd))
|
|
}
|
|
if strings.TrimSpace(crew.ShiftStart) != "" {
|
|
q = q.Where("c.shift_start = ?", crew.ShiftStart)
|
|
}
|
|
if strings.TrimSpace(crew.ShiftEnd) != "" {
|
|
q = q.Where("c.shift_end = ?", crew.ShiftEnd)
|
|
}
|
|
|
|
return q.Updates(map[string]any{
|
|
"deleted_at": now,
|
|
"deleted_by": updatedBy,
|
|
"updated_by": updatedBy,
|
|
}).Error
|
|
}
|
|
|
|
func matchCrewByFilter(tx *gorm.DB, rosterIDs [][]byte, crew dutyroster.DutyRosterCrew) ([]dutyroster.DeletedCrewResult, error) {
|
|
if len(rosterIDs) == 0 {
|
|
return []dutyroster.DeletedCrewResult{}, nil
|
|
}
|
|
q := tx.Model(&dutyroster.DutyRosterCrew{}).
|
|
Select(`
|
|
c.id,
|
|
c.roster_id,
|
|
c.user_id,
|
|
COALESCE(NULLIF(c.name_label, ''), TRIM(CONCAT(COALESCE(u.first_name, ''), ' ', COALESCE(u.last_name, ''))), '') AS name,
|
|
c.role_code,
|
|
c.crew_type,
|
|
c.date_start,
|
|
c.date_end`).
|
|
Table("duty_roster_crews c").
|
|
Joins("LEFT JOIN users u ON u.id = c.user_id").
|
|
Where("roster_id IN ? AND role_code = ? AND deleted_at IS NULL", rosterIDs, crew.RoleCode)
|
|
|
|
if strings.TrimSpace(crew.CrewType) != "" {
|
|
q = q.Where("crew_type = ?", crew.CrewType)
|
|
}
|
|
if len(crew.UserID) > 0 {
|
|
q = q.Where("user_id = ?", crew.UserID)
|
|
} else if strings.TrimSpace(crew.NameLabel) != "" {
|
|
q = q.Where("name_label = ?", crew.NameLabel)
|
|
}
|
|
if crew.DateStart != nil {
|
|
q = q.Where("date_start = ?", dateOnlyUTC(*crew.DateStart))
|
|
}
|
|
if crew.DateEnd != nil {
|
|
q = q.Where("date_end = ?", dateOnlyUTC(*crew.DateEnd))
|
|
}
|
|
if strings.TrimSpace(crew.ShiftStart) != "" {
|
|
q = q.Where("shift_start = ?", crew.ShiftStart)
|
|
}
|
|
if strings.TrimSpace(crew.ShiftEnd) != "" {
|
|
q = q.Where("shift_end = ?", crew.ShiftEnd)
|
|
}
|
|
|
|
out := make([]dutyroster.DeletedCrewResult, 0)
|
|
if err := q.Scan(&out).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func softDeleteCrewByIDs(tx *gorm.DB, rows []dutyroster.DeletedCrewResult, now time.Time, updatedBy []byte) error {
|
|
if len(rows) == 0 {
|
|
return nil
|
|
}
|
|
ids := make([][]byte, 0, len(rows))
|
|
for i := range rows {
|
|
ids = append(ids, rows[i].ID)
|
|
}
|
|
return tx.Model(&dutyroster.DutyRosterCrew{}).
|
|
Where("id IN ? AND deleted_at IS NULL", ids).
|
|
Updates(map[string]any{
|
|
"deleted_at": now,
|
|
"deleted_by": updatedBy,
|
|
"updated_by": updatedBy,
|
|
}).Error
|
|
}
|