From e545047165f53ffedce77369333ca495ba180a19 Mon Sep 17 00:00:00 2001 From: MacBook Air M1 Date: Wed, 7 Jan 2026 12:02:44 +0700 Subject: [PATCH] add api summary and update status --- .../controllers/daily-checklist.controller.go | 77 ++++++++++++- .../dto/daily-checklist.dto.go | 20 ++++ internal/modules/daily-checklists/route.go | 4 +- .../services/daily-checklist.service.go | 106 +++++++++++++++++- .../validations/daily-checklist.validation.go | 10 +- 5 files changed, 208 insertions(+), 9 deletions(-) diff --git a/internal/modules/daily-checklists/controllers/daily-checklist.controller.go b/internal/modules/daily-checklists/controllers/daily-checklist.controller.go index a965208f..7f2aabe9 100644 --- a/internal/modules/daily-checklists/controllers/daily-checklist.controller.go +++ b/internal/modules/daily-checklists/controllers/daily-checklist.controller.go @@ -53,6 +53,81 @@ func (u *DailyChecklistController) GetAll(c *fiber.Ctx) error { }) } +func (u *DailyChecklistController) GetSummary(c *fiber.Ctx) error { + query := &validation.SummaryQuery{ + DateFrom: c.Query("date_from"), + DateTo: c.Query("date_to"), + Category: c.Query("category"), + } + + if query.DateFrom == "" || query.DateTo == "" { + return fiber.NewError(fiber.StatusBadRequest, "date_from and date_to are required") + } + + if kandangParam := c.Query("kandang_id"); kandangParam != "" { + kandangID, err := strconv.ParseUint(kandangParam, 10, 64) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid kandang_id") + } + value := uint(kandangID) + query.KandangID = &value + } + + result, err := u.DailyChecklistService.GetSummary(c, query) + if err != nil { + return err + } + + type summaryResponse struct { + PerformanceOverview []dto.DailyChecklistPerformanceOverviewDTO `json:"performance_overview"` + TrackingABK []dto.DailyChecklistSummaryDTO `json:"tracking_abk"` + } + + performanceMap := make(map[uint]*dto.DailyChecklistPerformanceOverviewDTO) + tracking := make([]dto.DailyChecklistSummaryDTO, len(result)) + + for i, summary := range result { + tracking[i] = dto.DailyChecklistSummaryDTO{ + EmployeeID: summary.EmployeeID, + EmployeeName: summary.EmployeeName, + KandangID: summary.KandangID, + KandangName: summary.KandangName, + TotalActivity: summary.TotalActivity, + ActivityDone: summary.ActivityDone, + ActivityLeft: summary.ActivityLeft, + CompletionRate: summary.CompletionRate, + LastActivity: summary.LastActivity, + } + + if _, ok := performanceMap[summary.EmployeeID]; !ok { + performanceMap[summary.EmployeeID] = &dto.DailyChecklistPerformanceOverviewDTO{ + EmployeeID: summary.EmployeeID, + EmployeeName: summary.EmployeeName, + } + } + + performanceMap[summary.EmployeeID].TotalActivity += summary.TotalActivity + performanceMap[summary.EmployeeID].ActivityDone += summary.ActivityDone + performanceMap[summary.EmployeeID].ActivityLeft += summary.ActivityLeft + } + + performance := make([]dto.DailyChecklistPerformanceOverviewDTO, 0, len(performanceMap)) + for _, v := range performanceMap { + performance = append(performance, *v) + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get daily checklist summary successfully", + Data: summaryResponse{ + PerformanceOverview: performance, + TrackingABK: tracking, + }, + }) +} + func (u *DailyChecklistController) GetOne(c *fiber.Ctx) error { param := c.Params("idDailyChecklist") @@ -98,7 +173,7 @@ func (u *DailyChecklistController) CreateOne(c *fiber.Ctx) error { func (u *DailyChecklistController) UpdateOne(c *fiber.Ctx) error { req := new(validation.Update) - param := c.Params("id") + param := c.Params("idDailyChecklist") id, err := strconv.Atoi(param) if err != nil { diff --git a/internal/modules/daily-checklists/dto/daily-checklist.dto.go b/internal/modules/daily-checklists/dto/daily-checklist.dto.go index 14c8ad7a..eea51184 100644 --- a/internal/modules/daily-checklists/dto/daily-checklist.dto.go +++ b/internal/modules/daily-checklists/dto/daily-checklist.dto.go @@ -39,6 +39,26 @@ type DailyChecklistDetailDTO struct { Progress float64 `json:"progress"` } +type DailyChecklistSummaryDTO struct { + EmployeeID uint `json:"employee_id"` + EmployeeName string `json:"employee_name"` + KandangID uint `json:"kandang_id"` + KandangName string `json:"kandang_name"` + TotalActivity int `json:"total_activity"` + ActivityDone int `json:"activity_done"` + ActivityLeft int `json:"activity_left"` + CompletionRate int `json:"completion_rate"` + LastActivity *time.Time `json:"last_activity,omitempty"` +} + +type DailyChecklistPerformanceOverviewDTO struct { + EmployeeID uint `json:"employee_id"` + EmployeeName string `json:"employee_name"` + TotalActivity int `json:"total_activity"` + ActivityDone int `json:"activity_done"` + ActivityLeft int `json:"activity_left"` +} + type DailyChecklistPhaseDTO struct { Id uint `json:"id"` PhaseId uint `json:"phase_id"` diff --git a/internal/modules/daily-checklists/route.go b/internal/modules/daily-checklists/route.go index d907143d..72396092 100644 --- a/internal/modules/daily-checklists/route.go +++ b/internal/modules/daily-checklists/route.go @@ -17,6 +17,8 @@ func DailyChecklistRoutes(v1 fiber.Router, u user.UserService, s dailyChecklist. route.Get("/", ctrl.GetAll) + route.Get("/summary", ctrl.GetSummary) + // create daily checklist route.Post("/", ctrl.CreateOne) @@ -53,6 +55,6 @@ func DailyChecklistRoutes(v1 fiber.Router, u user.UserService, s dailyChecklist. */ route.Post("/assignment", ctrl.UpdateAssignment) - route.Patch("/:id", ctrl.UpdateOne) + route.Patch("/:idDailyChecklist", ctrl.UpdateOne) route.Delete("/:id", ctrl.DeleteOne) } diff --git a/internal/modules/daily-checklists/services/daily-checklist.service.go b/internal/modules/daily-checklists/services/daily-checklist.service.go index 97ea4523..067b39b3 100644 --- a/internal/modules/daily-checklists/services/daily-checklist.service.go +++ b/internal/modules/daily-checklists/services/daily-checklist.service.go @@ -34,6 +34,7 @@ type DailyChecklistService interface { UpdateAssignment(ctx *fiber.Ctx, req *validation.UpdateAssignment) error GetChecklistPhaseIDs(ctx *fiber.Ctx, checklistID uint) ([]uint, error) GetDetail(ctx *fiber.Ctx, id uint) (*DailyChecklistDetail, error) + GetSummary(ctx *fiber.Ctx, params *validation.SummaryQuery) ([]DailyChecklistSummary, error) } type dailyChecklistService struct { @@ -52,6 +53,18 @@ type DailyChecklistDetail struct { Progress float64 } +type DailyChecklistSummary struct { + EmployeeID uint + EmployeeName string + KandangID uint + KandangName string + TotalActivity int + ActivityDone int + ActivityLeft int + CompletionRate int + LastActivity *time.Time +} + func NewDailyChecklistService(repo repository.DailyChecklistRepository, phaseRepo phaseRepo.PhasesRepository, validate *validator.Validate) DailyChecklistService { return &dailyChecklistService{ Log: utils.Log, @@ -202,14 +215,12 @@ func (s dailyChecklistService) UpdateOne(c *fiber.Ctx, req *validation.Update, i return nil, err } - updateBody := make(map[string]any) - - if req.Name != nil { - updateBody["name"] = *req.Name + updateBody := map[string]any{ + "status": req.Status, } - if len(updateBody) == 0 { - return s.GetOne(c, id) + if req.RejectReason != nil { + updateBody["reject_reason"] = *req.RejectReason } if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil { @@ -579,3 +590,86 @@ func (s dailyChecklistService) AssignTasks(c *fiber.Ctx, id uint, req *validatio return nil } + +func (s dailyChecklistService) GetSummary(c *fiber.Ctx, params *validation.SummaryQuery) ([]DailyChecklistSummary, error) { + if err := s.Validate.Struct(params); err != nil { + return nil, err + } + + dateFrom, err := time.Parse("2006-01-02", params.DateFrom) + if err != nil { + return nil, fiber.NewError(fiber.StatusBadRequest, "invalid date_from format, use YYYY-MM-DD") + } + + dateTo, err := time.Parse("2006-01-02", params.DateTo) + if err != nil { + return nil, fiber.NewError(fiber.StatusBadRequest, "invalid date_to format, use YYYY-MM-DD") + } + + type summaryRow struct { + EmployeeID uint + EmployeeName string + KandangID uint + KandangName string + TotalActivity int64 + ActivityDone int64 + ActivityLeft int64 + LastActivity *time.Time + } + + rows := make([]summaryRow, 0) + db := s.Repository.DB().WithContext(c.Context()). + Table("daily_checklist_activity_task_assignments AS a"). + Select(` + a.employee_id, + e.name AS employee_name, + d.kandang_id, + k.name AS kandang_name, + COUNT(*) AS total_activity, + SUM(CASE WHEN a.checked THEN 1 ELSE 0 END) AS activity_done, + 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 kandangs k ON k.id = d.kandang_id"). + Joins("JOIN employees e ON e.id = a.employee_id"). + Where("d.date BETWEEN ? AND ?", dateFrom, dateTo) + + if params.Category != "" { + db = db.Where("d.category = ?", params.Category) + } + + if params.KandangID != nil { + db = db.Where("d.kandang_id = ?", *params.KandangID) + } + + if err := db. + Group("a.employee_id, e.name, d.kandang_id, k.name"). + Order("e.name ASC"). + Find(&rows).Error; err != nil { + s.Log.Errorf("Failed to get daily checklist summary: %+v", err) + return nil, err + } + + summaries := make([]DailyChecklistSummary, len(rows)) + for i, row := range rows { + completionRate := 0 + if row.TotalActivity > 0 { + completionRate = int(math.Round(float64(row.ActivityDone) / float64(row.TotalActivity) * 100)) + } + + summaries[i] = DailyChecklistSummary{ + EmployeeID: row.EmployeeID, + EmployeeName: row.EmployeeName, + KandangID: row.KandangID, + KandangName: row.KandangName, + TotalActivity: int(row.TotalActivity), + ActivityDone: int(row.ActivityDone), + ActivityLeft: int(row.ActivityLeft), + CompletionRate: completionRate, + LastActivity: row.LastActivity, + } + } + + return summaries, nil +} diff --git a/internal/modules/daily-checklists/validations/daily-checklist.validation.go b/internal/modules/daily-checklists/validations/daily-checklist.validation.go index 61e8a455..969c04e2 100644 --- a/internal/modules/daily-checklists/validations/daily-checklist.validation.go +++ b/internal/modules/daily-checklists/validations/daily-checklist.validation.go @@ -8,7 +8,8 @@ type Create struct { } type Update struct { - Name *string `json:"name,omitempty" validate:"omitempty"` + Status string `json:"status" validate:"required"` + RejectReason *string `json:"reject_reason" validate:"required"` } type Query struct { @@ -31,3 +32,10 @@ type UpdateAssignment struct { Checked *bool `json:"checked,omitempty"` Note *string `json:"note,omitempty"` } + +type SummaryQuery struct { + DateFrom string `query:"date_from" validate:"required"` + DateTo string `query:"date_to" validate:"required"` + Category string `query:"category" validate:"omitempty"` + KandangID *uint `query:"kandang_id" validate:"omitempty"` +}