mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
adjust softdelete daily checklist; add empty kandang
This commit is contained in:
@@ -122,6 +122,13 @@ type DailyChecklistReportCategory struct {
|
||||
Baik int
|
||||
}
|
||||
|
||||
const (
|
||||
dailyChecklistDateLayout = "2006-01-02"
|
||||
dailyChecklistCategoryEmptyKandang = "empty_kandang"
|
||||
dailyChecklistStatusRejected = "REJECTED"
|
||||
dailyChecklistStatusDraft = "DRAFT"
|
||||
)
|
||||
|
||||
func NewDailyChecklistService(repo repository.DailyChecklistRepository, phaseRepo phaseRepo.PhasesRepository, validate *validator.Validate, documentSvc commonSvc.DocumentService) DailyChecklistService {
|
||||
return &dailyChecklistService{
|
||||
Log: utils.Log,
|
||||
@@ -146,7 +153,8 @@ func (s dailyChecklistService) ensureChecklistAccess(c *fiber.Ctx, checklistID u
|
||||
Joins("JOIN kandang_groups k ON k.id = dc.kandang_id").
|
||||
Joins("JOIN locations loc ON loc.id = k.location_id").
|
||||
Joins("JOIN areas a ON a.id = loc.area_id").
|
||||
Where("dc.id = ?", checklistID)
|
||||
Where("dc.id = ?", checklistID).
|
||||
Where("dc.deleted_at IS NULL")
|
||||
|
||||
scopedDB, err := m.ApplyLocationAreaScope(c, db, "loc.id", "a.id")
|
||||
if err != nil {
|
||||
@@ -196,7 +204,7 @@ func (s dailyChecklistService) ensureTaskAccess(c *fiber.Ctx, taskID uint) error
|
||||
|
||||
db := s.Repository.DB().WithContext(c.Context()).
|
||||
Table("daily_checklist_activity_tasks t").
|
||||
Joins("JOIN daily_checklists dc ON dc.id = t.checklist_id").
|
||||
Joins("JOIN daily_checklists dc ON dc.id = t.checklist_id AND dc.deleted_at IS NULL").
|
||||
Joins("JOIN kandang_groups k ON k.id = dc.kandang_id").
|
||||
Joins("JOIN locations loc ON loc.id = k.location_id").
|
||||
Joins("JOIN areas a ON a.id = loc.area_id").
|
||||
@@ -228,7 +236,8 @@ func (s dailyChecklistService) GetAll(c *fiber.Ctx, params *validation.Query) ([
|
||||
Table("daily_checklists dc").
|
||||
Joins("JOIN kandang_groups k ON k.id = dc.kandang_id").
|
||||
Joins("JOIN locations loc ON loc.id = k.location_id").
|
||||
Joins("JOIN areas a ON a.id = loc.area_id")
|
||||
Joins("JOIN areas a ON a.id = loc.area_id").
|
||||
Where("dc.deleted_at IS NULL")
|
||||
|
||||
var scopeErr error
|
||||
db, scopeErr = m.ApplyLocationAreaScope(c, db, "loc.id", "a.id")
|
||||
@@ -501,66 +510,39 @@ func (s *dailyChecklistService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
date, err := time.Parse("2006-01-02", req.Date)
|
||||
date, err := time.Parse(dailyChecklistDateLayout, strings.TrimSpace(req.Date))
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "invalid date format, use YYYY-MM-DD")
|
||||
}
|
||||
|
||||
status := req.Status
|
||||
category := req.Category
|
||||
endDate := date
|
||||
|
||||
if req.EmptyKandang {
|
||||
if strings.TrimSpace(req.EmptyKandangEndDate) == "" {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "empty_kandang_end_date is required when empty_kandang is true")
|
||||
}
|
||||
|
||||
endDate, err = time.Parse(dailyChecklistDateLayout, strings.TrimSpace(req.EmptyKandangEndDate))
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "invalid empty_kandang_end_date format, use YYYY-MM-DD")
|
||||
}
|
||||
if endDate.Before(date) {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "empty_kandang_end_date must be greater than or equal to date")
|
||||
}
|
||||
|
||||
category = dailyChecklistCategoryEmptyKandang
|
||||
}
|
||||
|
||||
targetID := uint(0)
|
||||
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
||||
existing := new(entity.DailyChecklist)
|
||||
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("date = ? AND kandang_id = ? AND category = ? AND (status IS NULL OR status <> ?)", date, req.KandangId, category, "REJECTED").
|
||||
Take(existing).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
if req.EmptyKandang {
|
||||
return s.createBulkDailyChecklists(tx, req.KandangId, date, endDate, category, status, &targetID)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if err := tx.Model(&entity.DailyChecklist{}).
|
||||
Where("id = ?", existing.Id).
|
||||
Update("updated_at", time.Now()).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetID = existing.Id
|
||||
return nil
|
||||
}
|
||||
|
||||
createStatus := status
|
||||
var rejectedCount int64
|
||||
if err := tx.Model(&entity.DailyChecklist{}).
|
||||
Where("date = ? AND kandang_id = ? AND category = ? AND status = ?", date, req.KandangId, category, "REJECTED").
|
||||
Count(&rejectedCount).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if rejectedCount > 0 {
|
||||
createStatus = "DRAFT"
|
||||
}
|
||||
|
||||
createBody := &entity.DailyChecklist{
|
||||
KandangId: req.KandangId,
|
||||
Date: date,
|
||||
Category: category,
|
||||
Status: &createStatus,
|
||||
}
|
||||
|
||||
if err := tx.Create(createBody).Error; err != nil {
|
||||
// Handle concurrent insert for active checklist with same key.
|
||||
if findErr := tx.
|
||||
Where("date = ? AND kandang_id = ? AND category = ? AND (status IS NULL OR status <> ?)", date, req.KandangId, category, "REJECTED").
|
||||
Take(existing).Error; findErr == nil {
|
||||
targetID = existing.Id
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
targetID = createBody.Id
|
||||
return nil
|
||||
return s.createOrReuseSingleDailyChecklist(tx, req.KandangId, date, category, status, &targetID)
|
||||
})
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to create/upsert dailyChecklist: %+v", err)
|
||||
@@ -570,6 +552,109 @@ func (s *dailyChecklistService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
||||
return s.GetOne(c, targetID)
|
||||
}
|
||||
|
||||
func (s *dailyChecklistService) createOrReuseSingleDailyChecklist(tx *gorm.DB, kandangID uint, date time.Time, category, status string, targetID *uint) error {
|
||||
existing := new(entity.DailyChecklist)
|
||||
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("date = ? AND kandang_id = ? AND category = ? AND (status IS NULL OR status <> ?) AND deleted_at IS NULL", date, kandangID, category, dailyChecklistStatusRejected).
|
||||
Take(existing).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if err := tx.Model(&entity.DailyChecklist{}).
|
||||
Where("id = ?", existing.Id).
|
||||
Update("updated_at", time.Now()).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*targetID = existing.Id
|
||||
return nil
|
||||
}
|
||||
|
||||
createStatus := status
|
||||
var rejectedCount int64
|
||||
if err := tx.Model(&entity.DailyChecklist{}).
|
||||
Where("date = ? AND kandang_id = ? AND category = ? AND status = ? AND deleted_at IS NULL", date, kandangID, category, dailyChecklistStatusRejected).
|
||||
Count(&rejectedCount).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if rejectedCount > 0 {
|
||||
createStatus = dailyChecklistStatusDraft
|
||||
}
|
||||
|
||||
createBody := &entity.DailyChecklist{
|
||||
KandangId: kandangID,
|
||||
Date: date,
|
||||
Category: category,
|
||||
Status: &createStatus,
|
||||
}
|
||||
|
||||
if err := tx.Create(createBody).Error; err != nil {
|
||||
// Handle concurrent insert for active checklist with same key.
|
||||
if findErr := tx.
|
||||
Where("date = ? AND kandang_id = ? AND category = ? AND (status IS NULL OR status <> ?) AND deleted_at IS NULL", date, kandangID, category, dailyChecklistStatusRejected).
|
||||
Take(existing).Error; findErr == nil {
|
||||
*targetID = existing.Id
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
*targetID = createBody.Id
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dailyChecklistService) createBulkDailyChecklists(tx *gorm.DB, kandangID uint, startDate, endDate time.Time, category, status string, targetID *uint) error {
|
||||
var conflictCount int64
|
||||
if err := tx.Model(&entity.DailyChecklist{}).
|
||||
Where("kandang_id = ? AND category = ? AND date BETWEEN ? AND ? AND (status IS NULL OR status <> ?) AND deleted_at IS NULL", kandangID, category, startDate, endDate, dailyChecklistStatusRejected).
|
||||
Count(&conflictCount).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if conflictCount > 0 {
|
||||
return fiber.NewError(fiber.StatusConflict, "DailyChecklist already exists for at least one date in range")
|
||||
}
|
||||
|
||||
for currentDate := startDate; !currentDate.After(endDate); currentDate = currentDate.AddDate(0, 0, 1) {
|
||||
createStatus := status
|
||||
var rejectedCount int64
|
||||
if err := tx.Model(&entity.DailyChecklist{}).
|
||||
Where("date = ? AND kandang_id = ? AND category = ? AND status = ? AND deleted_at IS NULL", currentDate, kandangID, category, dailyChecklistStatusRejected).
|
||||
Count(&rejectedCount).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if rejectedCount > 0 {
|
||||
createStatus = dailyChecklistStatusDraft
|
||||
}
|
||||
|
||||
createBody := &entity.DailyChecklist{
|
||||
KandangId: kandangID,
|
||||
Date: currentDate,
|
||||
Category: category,
|
||||
Status: &createStatus,
|
||||
}
|
||||
|
||||
if err := tx.Create(createBody).Error; err != nil {
|
||||
// Handle concurrent insert for active checklist in same date range.
|
||||
var existingActiveCount int64
|
||||
checkErr := tx.Model(&entity.DailyChecklist{}).
|
||||
Where("date = ? AND kandang_id = ? AND category = ? AND (status IS NULL OR status <> ?) AND deleted_at IS NULL", currentDate, kandangID, category, dailyChecklistStatusRejected).
|
||||
Count(&existingActiveCount).Error
|
||||
if checkErr == nil && existingActiveCount > 0 {
|
||||
return fiber.NewError(fiber.StatusConflict, "DailyChecklist already exists for at least one date in range")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if currentDate.Equal(startDate) {
|
||||
*targetID = createBody.Id
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s dailyChecklistService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.DailyChecklist, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
@@ -712,7 +797,35 @@ func (s dailyChecklistService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
if err := s.ensureChecklistAccess(c, id); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
|
||||
actorID, err := m.ActorIDFromContext(c)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Failed to get actor ID from context")
|
||||
}
|
||||
|
||||
if err := s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
||||
updateResult := tx.Model(&entity.DailyChecklist{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]any{
|
||||
"deleted_by": actorID,
|
||||
"updated_at": time.Now(),
|
||||
})
|
||||
if updateResult.Error != nil {
|
||||
return updateResult.Error
|
||||
}
|
||||
if updateResult.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
deleteResult := tx.Delete(&entity.DailyChecklist{}, id)
|
||||
if deleteResult.Error != nil {
|
||||
return deleteResult.Error
|
||||
}
|
||||
if deleteResult.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
|
||||
}
|
||||
@@ -1152,7 +1265,7 @@ func (s dailyChecklistService) GetSummary(c *fiber.Ctx, params *validation.Summa
|
||||
SUM(CASE WHEN NOT a.checked THEN 1 ELSE 0 END) AS activity_left,
|
||||
MAX(a.updated_at) AS last_activity`).
|
||||
Joins("JOIN daily_checklist_activity_tasks t ON t.id = a.task_id").
|
||||
Joins("JOIN daily_checklists d ON d.id = t.checklist_id").
|
||||
Joins("JOIN daily_checklists d ON d.id = t.checklist_id AND d.deleted_at IS NULL").
|
||||
Joins("JOIN kandang_groups k ON k.id = d.kandang_id").
|
||||
Joins("JOIN employees e ON e.id = a.employee_id").
|
||||
Joins("JOIN locations loc ON loc.id = k.location_id").
|
||||
@@ -1224,7 +1337,7 @@ func (s dailyChecklistService) GetReport(c *fiber.Ctx, params *validation.Report
|
||||
db := s.Repository.DB().WithContext(c.Context()).
|
||||
Table("daily_checklist_activity_task_assignments AS dca").
|
||||
Joins("JOIN daily_checklist_activity_tasks dcat ON dcat.id = dca.task_id").
|
||||
Joins("JOIN daily_checklists dc ON dc.id = dcat.checklist_id").
|
||||
Joins("JOIN daily_checklists dc ON dc.id = dcat.checklist_id AND dc.deleted_at IS NULL").
|
||||
Joins("JOIN employees e ON e.id = dca.employee_id").
|
||||
Joins("JOIN kandang_groups k ON k.id = dc.kandang_id").
|
||||
Joins("JOIN locations loc ON loc.id = k.location_id").
|
||||
|
||||
Reference in New Issue
Block a user