add api summary and update status

This commit is contained in:
MacBook Air M1
2026-01-07 12:02:44 +07:00
parent 42aa6829c5
commit e545047165
5 changed files with 208 additions and 9 deletions
@@ -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 {
@@ -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"`
+3 -1
View File
@@ -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)
}
@@ -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
}
@@ -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"`
}