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