diff --git a/internal/modules/daily-checklists/controllers/daily-checklist.controller.go b/internal/modules/daily-checklists/controllers/daily-checklist.controller.go index 351c408b..a965208f 100644 --- a/internal/modules/daily-checklists/controllers/daily-checklist.controller.go +++ b/internal/modules/daily-checklists/controllers/daily-checklist.controller.go @@ -54,14 +54,14 @@ func (u *DailyChecklistController) GetAll(c *fiber.Ctx) error { } func (u *DailyChecklistController) GetOne(c *fiber.Ctx) error { - param := c.Params("id") + param := c.Params("idDailyChecklist") id, err := strconv.Atoi(param) if err != nil { return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") } - result, err := u.DailyChecklistService.GetOne(c, uint(id)) + detail, err := u.DailyChecklistService.GetDetail(c, uint(id)) if err != nil { return err } @@ -71,7 +71,7 @@ func (u *DailyChecklistController) GetOne(c *fiber.Ctx) error { Code: fiber.StatusOK, Status: "success", Message: "Get dailyChecklist successfully", - Data: dto.ToDailyChecklistListDTO(*result), + Data: dto.ToDailyChecklistDetailDTO(detail.Checklist, detail.Phases, detail.Tasks, detail.AssignedEmployees, detail.TotalActivities, detail.Progress), }) } @@ -217,6 +217,32 @@ func (u *DailyChecklistController) RemoveAssignment(c *fiber.Ctx) error { }) } +func (u *DailyChecklistController) GetPhaseByIdChecklist(c *fiber.Ctx) error { + param := c.Params("idDailyChecklist") + id, err := strconv.Atoi(param) + if err != nil || id <= 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid daily checklist id") + } + + phaseIDs, err := u.DailyChecklistService.GetChecklistPhaseIDs(c, uint(id)) + if err != nil { + return err + } + + responseData := make([]map[string]uint, len(phaseIDs)) + for i, phaseID := range phaseIDs { + responseData[i] = map[string]uint{"phase_id": phaseID} + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get phases successfully", + Data: responseData, + }) +} + func (u *DailyChecklistController) GetAllTasks(c *fiber.Ctx) error { checklistParam := c.Query("checklist_id", "") if checklistParam == "" { diff --git a/internal/modules/daily-checklists/dto/daily-checklist.dto.go b/internal/modules/daily-checklists/dto/daily-checklist.dto.go index 31953def..14c8ad7a 100644 --- a/internal/modules/daily-checklists/dto/daily-checklist.dto.go +++ b/internal/modules/daily-checklists/dto/daily-checklist.dto.go @@ -4,6 +4,10 @@ import ( "time" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + employeeDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees/dto" + kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto" + phaseActivityDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phase-activities/dto" + phasesDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/dto" userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" ) @@ -15,15 +19,48 @@ type DailyChecklistRelationDTO struct { } type DailyChecklistListDTO struct { - Id uint `json:"id"` - Name string `json:"name"` - CreatedUser *userDTO.UserRelationDTO `json:"created_user"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + Id uint `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Category string `json:"category"` + Date time.Time `json:"date"` + Kandang *kandangDTO.KandangRelationDTO `json:"kandang,omitempty"` + CreatedUser *userDTO.UserRelationDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } type DailyChecklistDetailDTO struct { DailyChecklistListDTO + Phases []DailyChecklistPhaseDTO `json:"phases"` + Tasks []DailyChecklistActivityTaskDTO `json:"tasks"` + AssignedEmployees []employeeDTO.EmployeesRelationDTO `json:"assigned_employees"` + TotalActivity int `json:"total_activity"` + Progress float64 `json:"progress"` +} + +type DailyChecklistPhaseDTO struct { + Id uint `json:"id"` + PhaseId uint `json:"phase_id"` + Phase phasesDTO.PhasesListDTO `json:"phase"` +} + +type DailyChecklistActivityTaskDTO struct { + Id uint `json:"id"` + ChecklistId uint `json:"checklist_id"` + PhaseId uint `json:"phase_id"` + PhaseActivityId uint `json:"phase_activity_id"` + TimeType *string `json:"time_type"` + Notes *string `json:"notes"` + Phase phasesDTO.PhasesListDTO `json:"phase"` + PhaseActivity phaseActivityDTO.PhaseActivityListDTO `json:"phase_activity"` + Assignments []DailyChecklistAssignmentDTO `json:"assignments"` +} + +type DailyChecklistAssignmentDTO struct { + Employee employeeDTO.EmployeesRelationDTO `json:"employee"` + Checked bool `json:"checked"` + Note *string `json:"note"` } // === Mapper Functions === @@ -52,9 +89,24 @@ func ToDailyChecklistListDTO(e entity.DailyChecklist) DailyChecklistListDTO { name = *e.Name } + var status string + if e.Status != nil { + status = *e.Status + } + + var kandang *kandangDTO.KandangRelationDTO + if e.Kandang.Id != 0 { + mapped := kandangDTO.ToKandangRelationDTO(e.Kandang) + kandang = &mapped + } + return DailyChecklistListDTO{ Id: e.Id, Name: name, + Status: status, + Category: e.Category, + Date: e.Date, + Kandang: kandang, CreatedAt: e.CreatedAt, UpdatedAt: e.UpdatedAt, CreatedUser: createdUser, @@ -69,8 +121,65 @@ func ToDailyChecklistListDTOs(e []entity.DailyChecklist) []DailyChecklistListDTO return result } -func ToDailyChecklistDetailDTO(e entity.DailyChecklist) DailyChecklistDetailDTO { +func ToDailyChecklistDetailDTO(checklist entity.DailyChecklist, phases []entity.DailyChecklistPhase, tasks []entity.DailyChecklistActivityTask, assignedEmployees []entity.Employee, totalActivities int, progress float64) DailyChecklistDetailDTO { + phaseDTOs := make([]DailyChecklistPhaseDTO, 0, len(phases)) + for _, phase := range phases { + phaseDTOs = append(phaseDTOs, DailyChecklistPhaseDTO{ + Id: phase.Id, + PhaseId: phase.PhaseId, + Phase: phasesDTO.ToPhasesListDTO(phase.Phase), + }) + } + + taskDTOs := make([]DailyChecklistActivityTaskDTO, 0, len(tasks)) + for _, task := range tasks { + mappedAssignments := make([]DailyChecklistAssignmentDTO, 0, len(task.Assignments)) + for _, assignment := range task.Assignments { + if assignment.Employee.Id == 0 { + continue + } + mapped := DailyChecklistAssignmentDTO{ + Employee: employeeDTO.ToEmployeesRelationDTO(assignment.Employee), + Checked: assignment.Checked, + Note: assignment.Note, + } + mappedAssignments = append(mappedAssignments, mapped) + } + + phaseDTO := phasesDTO.PhasesListDTO{} + if task.Phase.Id != 0 { + phaseDTO = phasesDTO.ToPhasesListDTO(task.Phase) + } + + activityDTO := phaseActivityDTO.PhaseActivityListDTO{} + if task.PhaseActivity.Id != 0 { + activityDTO = phaseActivityDTO.ToPhaseActivityListDTO(task.PhaseActivity) + } + + taskDTOs = append(taskDTOs, DailyChecklistActivityTaskDTO{ + Id: task.Id, + ChecklistId: task.ChecklistId, + PhaseId: task.PhaseId, + PhaseActivityId: task.PhaseActivityId, + TimeType: task.TimeType, + Notes: task.Notes, + Phase: phaseDTO, + PhaseActivity: activityDTO, + Assignments: mappedAssignments, + }) + } + + assignedDTOs := make([]employeeDTO.EmployeesRelationDTO, 0, len(assignedEmployees)) + for _, emp := range assignedEmployees { + assignedDTOs = append(assignedDTOs, employeeDTO.ToEmployeesRelationDTO(emp)) + } + return DailyChecklistDetailDTO{ - DailyChecklistListDTO: ToDailyChecklistListDTO(e), + DailyChecklistListDTO: ToDailyChecklistListDTO(checklist), + Phases: phaseDTOs, + Tasks: taskDTOs, + AssignedEmployees: assignedDTOs, + TotalActivity: totalActivities, + Progress: progress, } } diff --git a/internal/modules/daily-checklists/route.go b/internal/modules/daily-checklists/route.go index bffb5013..d907143d 100644 --- a/internal/modules/daily-checklists/route.go +++ b/internal/modules/daily-checklists/route.go @@ -16,8 +16,16 @@ func DailyChecklistRoutes(v1 fiber.Router, u user.UserService, s dailyChecklist. route.Use(m.Auth(u)) route.Get("/", ctrl.GetAll) + + // create daily checklist route.Post("/", ctrl.CreateOne) + // get detail data daily checklist by id + route.Get("/relation/:idDailyChecklist", ctrl.GetOne) + + // get phases by daily checklist id + route.Get("/phase/:idDailyChecklist", ctrl.GetPhaseByIdChecklist) + // create task /* ketika add phase @@ -45,7 +53,6 @@ func DailyChecklistRoutes(v1 fiber.Router, u user.UserService, s dailyChecklist. */ route.Post("/assignment", ctrl.UpdateAssignment) - route.Get("/:id", ctrl.GetOne) route.Patch("/:id", 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 63c3cc9c..97ea4523 100644 --- a/internal/modules/daily-checklists/services/daily-checklist.service.go +++ b/internal/modules/daily-checklists/services/daily-checklist.service.go @@ -2,6 +2,8 @@ package service import ( "errors" + "math" + "sort" "strconv" "strings" "time" @@ -30,6 +32,8 @@ type DailyChecklistService interface { RemoveAssignment(ctx *fiber.Ctx, id uint, employeeID uint) error GetTasks(ctx *fiber.Ctx, checklistID uint) ([]entity.DailyChecklistActivityTask, error) UpdateAssignment(ctx *fiber.Ctx, req *validation.UpdateAssignment) error + GetChecklistPhaseIDs(ctx *fiber.Ctx, checklistID uint) ([]uint, error) + GetDetail(ctx *fiber.Ctx, id uint) (*DailyChecklistDetail, error) } type dailyChecklistService struct { @@ -39,6 +43,15 @@ type dailyChecklistService struct { PhaseRepo phaseRepo.PhasesRepository } +type DailyChecklistDetail struct { + Checklist entity.DailyChecklist + Phases []entity.DailyChecklistPhase + Tasks []entity.DailyChecklistActivityTask + AssignedEmployees []entity.Employee + TotalActivities int + Progress float64 +} + func NewDailyChecklistService(repo repository.DailyChecklistRepository, phaseRepo phaseRepo.PhasesRepository, validate *validator.Validate) DailyChecklistService { return &dailyChecklistService{ Log: utils.Log, @@ -49,7 +62,7 @@ func NewDailyChecklistService(repo repository.DailyChecklistRepository, phaseRep } func (s dailyChecklistService) withRelations(db *gorm.DB) *gorm.DB { - return db + return db.Preload("Kandang") } func (s dailyChecklistService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.DailyChecklist, int64, error) { @@ -86,6 +99,72 @@ func (s dailyChecklistService) GetOne(c *fiber.Ctx, id uint) (*entity.DailyCheck return dailyChecklist, nil } +func (s dailyChecklistService) GetDetail(c *fiber.Ctx, id uint) (*DailyChecklistDetail, error) { + checklist, err := s.GetOne(c, id) + if err != nil { + return nil, err + } + + db := s.Repository.DB().WithContext(c.Context()) + + var phases []entity.DailyChecklistPhase + if err := db. + Where("checklist_id = ?", id). + Preload("Phase", func(tx *gorm.DB) *gorm.DB { + return tx.Preload("Activities") + }). + Order("created_at ASC"). + Find(&phases).Error; err != nil { + s.Log.Errorf("Failed to get phases for daily checklist %d: %+v", id, err) + return nil, err + } + + var tasks []entity.DailyChecklistActivityTask + if err := db. + Where("checklist_id = ?", id). + Preload("Phase"). + Preload("PhaseActivity"). + Preload("Assignments", func(tx *gorm.DB) *gorm.DB { + return tx.Preload("Employee") + }). + Order("created_at ASC"). + Find(&tasks).Error; err != nil { + s.Log.Errorf("Failed to get tasks for daily checklist %d: %+v", id, err) + return nil, err + } + + assignedEmployees := collectAssignedEmployees(tasks) + + totalActivities := 0 + for _, phase := range phases { + totalActivities += len(phase.Phase.Activities) + } + + var totalAssignments, completedAssignments int + for _, task := range tasks { + for _, assignment := range task.Assignments { + totalAssignments++ + if assignment.Checked { + completedAssignments++ + } + } + } + + var progress float64 + if totalAssignments > 0 { + progress = math.Round((float64(completedAssignments) / float64(totalAssignments)) * 100) + } + + return &DailyChecklistDetail{ + Checklist: *checklist, + Phases: phases, + Tasks: tasks, + AssignedEmployees: assignedEmployees, + TotalActivities: totalActivities, + Progress: progress, + }, nil +} + func (s *dailyChecklistService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.DailyChecklist, error) { if err := s.Validate.Struct(req); err != nil { return nil, err @@ -297,6 +376,35 @@ func (s dailyChecklistService) GetTasks(c *fiber.Ctx, checklistID uint) ([]entit return tasks, nil } +func (s dailyChecklistService) GetChecklistPhaseIDs(c *fiber.Ctx, checklistID uint) ([]uint, error) { + if checklistID == 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "checklist_id is required") + } + + if _, err := s.Repository.GetByID(c.Context(), checklistID, nil); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found") + } + return nil, err + } + + var phases []entity.DailyChecklistPhase + if err := s.Repository.DB().WithContext(c.Context()). + Where("checklist_id = ?", checklistID). + Order("created_at ASC"). + Find(&phases).Error; err != nil { + s.Log.Errorf("Failed to get daily checklist phases: %+v", err) + return nil, err + } + + phaseIDs := make([]uint, len(phases)) + for i, p := range phases { + phaseIDs[i] = p.PhaseId + } + + return phaseIDs, nil +} + func (s dailyChecklistService) UpdateAssignment(c *fiber.Ctx, req *validation.UpdateAssignment) error { if err := s.Validate.Struct(req); err != nil { return err @@ -392,6 +500,32 @@ func collectTaskIDs(tasks []entity.DailyChecklistActivityTask) []uint { } return result } + +func collectAssignedEmployees(tasks []entity.DailyChecklistActivityTask) []entity.Employee { + employeeMap := make(map[uint]entity.Employee) + for _, task := range tasks { + for _, assignment := range task.Assignments { + if assignment.Employee.Id == 0 { + continue + } + if _, exists := employeeMap[assignment.Employee.Id]; exists { + continue + } + employeeMap[assignment.Employee.Id] = assignment.Employee + } + } + + employees := make([]entity.Employee, 0, len(employeeMap)) + for _, emp := range employeeMap { + employees = append(employees, emp) + } + + sort.Slice(employees, func(i, j int) bool { + return employees[i].Id < employees[j].Id + }) + + return employees +} func (s dailyChecklistService) AssignTasks(c *fiber.Ctx, id uint, req *validation.AssignTask) error { if err := s.Validate.Struct(req); err != nil { return err