mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 05:21:57 +00:00
adjust empty kandang daily checklist
This commit is contained in:
@@ -238,17 +238,17 @@ func (u *DailyChecklistController) GetReport(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
result, totalResults, err := u.DailyChecklistService.GetReport(c, query)
|
||||
withoutActivities := func(src map[string]int) map[string]int {
|
||||
if src == nil {
|
||||
return map[string]int{}
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
withoutActivities := func(src map[string]any) map[string]any {
|
||||
if src == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
responseData := make([]dto.DailyChecklistReportDTO, len(result))
|
||||
for i, item := range result {
|
||||
responseData[i] = dto.DailyChecklistReportDTO{
|
||||
|
||||
@@ -72,13 +72,14 @@ type DailyChecklistPerformanceOverviewDTO struct {
|
||||
ActivityLeft int `json:"activity_left"`
|
||||
}
|
||||
|
||||
|
||||
type DailyChecklistReportDTO struct {
|
||||
Area DailyChecklistReportEntityDTO `json:"area"`
|
||||
Farm DailyChecklistReportEntityDTO `json:"farm"`
|
||||
Kandang DailyChecklistReportEntityDTO `json:"kandang"`
|
||||
ABK DailyChecklistReportEntityDTO `json:"abk"`
|
||||
Phase string `json:"phase"`
|
||||
DailyActivities map[string]int `json:"daily_activities"`
|
||||
DailyActivities map[string]any `json:"daily_activities"`
|
||||
Summary DailyChecklistReportSummaryDTO `json:"summary"`
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ type DailyChecklistReportItem struct {
|
||||
EmployeeID uint
|
||||
EmployeeName string
|
||||
PhaseName string
|
||||
DailyActivities map[string]int
|
||||
DailyActivities map[string]any
|
||||
Summary DailyChecklistReportSummary
|
||||
}
|
||||
|
||||
@@ -123,12 +123,13 @@ type DailyChecklistReportCategory struct {
|
||||
}
|
||||
|
||||
const (
|
||||
dailyChecklistDateLayout = "2006-01-02"
|
||||
dailyChecklistCategoryEmptyKandang = "empty_kandang"
|
||||
dailyChecklistStatusRejected = "REJECTED"
|
||||
dailyChecklistStatusDraft = "DRAFT"
|
||||
dailyChecklistErrEmptyKandangExist = "DailyChecklist cannot be created because empty_kandang already exists for at least one date in range"
|
||||
dailyChecklistErrDateOverlapExist = "DailyChecklist cannot be created because at least one date in range already has a checklist"
|
||||
dailyChecklistDateLayout = "2006-01-02"
|
||||
dailyChecklistCategoryEmptyKandang = "empty_kandang"
|
||||
dailyChecklistStatusRejected = "REJECTED"
|
||||
dailyChecklistStatusDraft = "DRAFT"
|
||||
dailyChecklistErrEmptyKandangExist = "DailyChecklist cannot be created because empty_kandang already exists for at least one date in range"
|
||||
dailyChecklistErrDateOverlapExist = "DailyChecklist cannot be created because at least one date in range already has a checklist"
|
||||
dailyChecklistErrDeletedNonEmptyKandangExists = "DailyChecklist cannot be created as empty_kandang because a deleted non-empty_kandang checklist exists for this date"
|
||||
)
|
||||
|
||||
func NewDailyChecklistService(repo repository.DailyChecklistRepository, phaseRepo phaseRepo.PhasesRepository, validate *validator.Validate, documentSvc commonSvc.DocumentService) DailyChecklistService {
|
||||
@@ -519,21 +520,8 @@ func (s *dailyChecklistService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -544,15 +532,17 @@ func (s *dailyChecklistService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
||||
return err
|
||||
}
|
||||
|
||||
if req.EmptyKandang {
|
||||
if err := s.validateNoChecklistOverlapForEmptyKandang(tx, req.KandangId, date, endDate); err != nil {
|
||||
if category == dailyChecklistCategoryEmptyKandang {
|
||||
if err := s.validateNoChecklistOverlapForEmptyKandang(tx, req.KandangId, date, date); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.validateNoDeletedNonEmptyKandangForDate(tx, req.KandangId, date); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := s.validateNoEmptyKandangConflict(tx, req.KandangId, date, date); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.createBulkDailyChecklists(tx, req.KandangId, date, endDate, category, status, &targetID)
|
||||
}
|
||||
|
||||
if err := s.validateNoEmptyKandangConflict(tx, req.KandangId, date, endDate); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.createOrReuseSingleDailyChecklist(tx, req.KandangId, date, category, status, &targetID)
|
||||
@@ -615,6 +605,22 @@ func (s *dailyChecklistService) validateNoEmptyKandangConflict(tx *gorm.DB, kand
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dailyChecklistService) validateNoDeletedNonEmptyKandangForDate(tx *gorm.DB, kandangID uint, date time.Time) error {
|
||||
var conflictCount int64
|
||||
if err := tx.Model(&entity.DailyChecklist{}).
|
||||
Unscoped().
|
||||
Where("kandang_id = ? AND date = ? AND deleted_at IS NULL AND category != ?", kandangID, date, dailyChecklistCategoryEmptyKandang).
|
||||
Count(&conflictCount).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if conflictCount > 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, dailyChecklistErrDeletedNonEmptyKandangExists)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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"}).
|
||||
@@ -1659,7 +1665,7 @@ func (s dailyChecklistService) GetReport(c *fiber.Ctx, params *validation.Report
|
||||
|
||||
totalChecklist := 0
|
||||
categoryCounts := DailyChecklistReportCategory{}
|
||||
activityOutput := make(map[string]int, len(activities))
|
||||
activityOutput := make(map[string]any, len(activities))
|
||||
|
||||
for day, stat := range activities {
|
||||
activityOutput[day] = stat.Completed
|
||||
@@ -1717,5 +1723,109 @@ func (s dailyChecklistService) GetReport(c *fiber.Ctx, params *validation.Report
|
||||
}
|
||||
}
|
||||
|
||||
// Flag empty kandang days within this report month
|
||||
if len(kandangIDs) > 0 {
|
||||
firstDay := time.Date(params.Year, time.Month(params.Month), 1, 0, 0, 0, 0, time.UTC)
|
||||
lastDay := firstDay.AddDate(0, 1, 0).AddDate(0, 0, -1)
|
||||
today := time.Now().UTC().Truncate(24 * time.Hour)
|
||||
|
||||
type emptyKandangRec struct {
|
||||
KandangID uint
|
||||
Date time.Time
|
||||
}
|
||||
var emptyRecs []emptyKandangRec
|
||||
if err := s.Repository.DB().WithContext(c.Context()).
|
||||
Model(&entity.DailyChecklist{}).
|
||||
Where("kandang_id IN ? AND category = ? AND date <= ? AND deleted_at IS NULL",
|
||||
kandangIDs, dailyChecklistCategoryEmptyKandang, lastDay).
|
||||
Select("kandang_id, date").
|
||||
Scan(&emptyRecs).Error; err != nil {
|
||||
s.Log.Errorf("Failed to get empty kandang records for report: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
emptyDaysByKandang := make(map[uint]map[int]struct{})
|
||||
|
||||
if len(emptyRecs) > 0 {
|
||||
minEmptyDate := emptyRecs[0].Date
|
||||
for _, rec := range emptyRecs[1:] {
|
||||
if rec.Date.Before(minEmptyDate) {
|
||||
minEmptyDate = rec.Date
|
||||
}
|
||||
}
|
||||
|
||||
type checklistDateRec struct {
|
||||
KandangID uint
|
||||
Date time.Time
|
||||
}
|
||||
var nextDates []checklistDateRec
|
||||
if err := s.Repository.DB().WithContext(c.Context()).
|
||||
Model(&entity.DailyChecklist{}).
|
||||
Where("kandang_id IN ? AND category != ? AND date > ? AND (status IS NULL OR status != ?) AND deleted_at IS NULL",
|
||||
kandangIDs, dailyChecklistCategoryEmptyKandang, minEmptyDate, dailyChecklistStatusRejected).
|
||||
Select("kandang_id, date").
|
||||
Order("kandang_id ASC, date ASC").
|
||||
Scan(&nextDates).Error; err != nil {
|
||||
s.Log.Errorf("Failed to get next checklist dates for empty kandang: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
nextDatesByKandang := make(map[uint][]time.Time)
|
||||
for _, row := range nextDates {
|
||||
nextDatesByKandang[row.KandangID] = append(nextDatesByKandang[row.KandangID], row.Date)
|
||||
}
|
||||
|
||||
for _, rec := range emptyRecs {
|
||||
var nextDate time.Time
|
||||
for _, d := range nextDatesByKandang[rec.KandangID] {
|
||||
if d.After(rec.Date) {
|
||||
nextDate = d
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If no next checklist, cap empty period at today (not end of month)
|
||||
ceiling := lastDay
|
||||
if today.Before(lastDay) {
|
||||
ceiling = today
|
||||
}
|
||||
periodEnd := ceiling
|
||||
if !nextDate.IsZero() {
|
||||
periodEnd = nextDate.AddDate(0, 0, -1)
|
||||
}
|
||||
|
||||
effectiveStart := rec.Date
|
||||
if effectiveStart.Before(firstDay) {
|
||||
effectiveStart = firstDay
|
||||
}
|
||||
effectiveEnd := periodEnd
|
||||
if effectiveEnd.After(lastDay) {
|
||||
effectiveEnd = lastDay
|
||||
}
|
||||
|
||||
if effectiveStart.After(effectiveEnd) {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := emptyDaysByKandang[rec.KandangID]; !ok {
|
||||
emptyDaysByKandang[rec.KandangID] = make(map[int]struct{})
|
||||
}
|
||||
for d := effectiveStart; !d.After(effectiveEnd); d = d.AddDate(0, 0, 1) {
|
||||
emptyDaysByKandang[rec.KandangID][d.Day()] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, item := range items {
|
||||
daySet := emptyDaysByKandang[item.KandangID]
|
||||
for day := range daySet {
|
||||
key := strconv.Itoa(day)
|
||||
if _, exists := items[i].DailyActivities[key]; !exists {
|
||||
items[i].DailyActivities[key] = "Kandang kosong"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ type Create struct {
|
||||
KandangId uint `json:"kandang_id" validate:"required"`
|
||||
Category string `json:"category" validate:"required"`
|
||||
Status string `json:"status" validate:"required"`
|
||||
EmptyKandang bool `json:"empty_kandang"`
|
||||
EmptyKandangEndDate string `json:"empty_kandang_end_date"`
|
||||
EmptyKandang bool `json:"empty_kandang"`
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
|
||||
Reference in New Issue
Block a user