mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 05:21:57 +00:00
Merge branch 'dev/daily-checklist' into 'development'
add module daily checklist See merge request mbugroup/lti-api!137
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/dto"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/services"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/validations"
|
||||
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
@@ -28,6 +29,18 @@ func (u *DailyChecklistController) GetAll(c *fiber.Ctx) error {
|
||||
Limit: c.QueryInt("limit", 10),
|
||||
Search: c.Query("search", ""),
|
||||
}
|
||||
query.DateFrom = c.Query("date_from", "")
|
||||
query.DateTo = c.Query("date_to", "")
|
||||
query.Status = c.Query("status", "")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if query.Page < 1 || query.Limit < 1 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||
@@ -38,6 +51,40 @@ func (u *DailyChecklistController) GetAll(c *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
responseData := make([]dto.DailyChecklistListDTO, len(result))
|
||||
for i, item := range result {
|
||||
var name string
|
||||
if item.Name != nil {
|
||||
name = *item.Name
|
||||
}
|
||||
|
||||
var status string
|
||||
if item.Status != nil {
|
||||
status = *item.Status
|
||||
}
|
||||
|
||||
var kandang *kandangDTO.KandangRelationDTO
|
||||
if item.Kandang.Id != 0 {
|
||||
mapped := kandangDTO.ToKandangRelationDTO(item.Kandang)
|
||||
kandang = &mapped
|
||||
}
|
||||
|
||||
responseData[i] = dto.DailyChecklistListDTO{
|
||||
Id: item.ID,
|
||||
Name: name,
|
||||
Status: status,
|
||||
Category: item.Category,
|
||||
Date: item.Date,
|
||||
Kandang: kandang,
|
||||
CreatedUser: nil,
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
TotalPhase: item.TotalPhase,
|
||||
TotalActivity: item.TotalActivity,
|
||||
Progress: item.Progress,
|
||||
}
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.DailyChecklistListDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
@@ -49,19 +96,209 @@ func (u *DailyChecklistController) GetAll(c *fiber.Ctx) error {
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: dto.ToDailyChecklistListDTOs(result),
|
||||
Data: responseData,
|
||||
})
|
||||
}
|
||||
|
||||
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) GetReport(c *fiber.Ctx) error {
|
||||
query := &validation.ReportQuery{
|
||||
Page: c.QueryInt("page", 1),
|
||||
Limit: c.QueryInt("limit", 10),
|
||||
Month: c.QueryInt("bulan", 0),
|
||||
Year: c.QueryInt("tahun", 0),
|
||||
}
|
||||
|
||||
parseUintParam := func(param string) (*uint, error) {
|
||||
if param == "" {
|
||||
return nil, nil
|
||||
}
|
||||
value, err := strconv.ParseUint(param, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u := uint(value)
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
if val, err := parseUintParam(c.Query("area_id", "")); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid area_id")
|
||||
} else {
|
||||
query.AreaID = val
|
||||
}
|
||||
|
||||
if val, err := parseUintParam(c.Query("location_id", "")); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid location_id")
|
||||
} else {
|
||||
query.LocationID = val
|
||||
}
|
||||
|
||||
if val, err := parseUintParam(c.Query("kandang_id", "")); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid kandang_id")
|
||||
} else {
|
||||
query.KandangID = val
|
||||
}
|
||||
|
||||
if val, err := parseUintParam(c.Query("employee_id", "")); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid employee_id")
|
||||
} else {
|
||||
query.EmployeeID = val
|
||||
}
|
||||
|
||||
if val, err := parseUintParam(c.Query("phase_id", "")); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid phase_id")
|
||||
} else {
|
||||
query.PhaseID = val
|
||||
}
|
||||
|
||||
if query.Month == 0 || query.Year == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "bulan and tahun are required")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
responseData := make([]dto.DailyChecklistReportDTO, len(result))
|
||||
for i, item := range result {
|
||||
responseData[i] = dto.DailyChecklistReportDTO{
|
||||
Area: dto.DailyChecklistReportEntityDTO{
|
||||
Id: item.AreaID,
|
||||
Name: item.AreaName,
|
||||
},
|
||||
Farm: dto.DailyChecklistReportEntityDTO{
|
||||
Id: item.LocationID,
|
||||
Name: item.LocationName,
|
||||
},
|
||||
Kandang: dto.DailyChecklistReportEntityDTO{
|
||||
Id: item.KandangID,
|
||||
Name: item.KandangName,
|
||||
},
|
||||
ABK: dto.DailyChecklistReportEntityDTO{
|
||||
Id: item.EmployeeID,
|
||||
Name: item.EmployeeName,
|
||||
},
|
||||
Phase: item.PhaseName,
|
||||
DailyActivities: withoutActivities(item.DailyActivities),
|
||||
Summary: dto.DailyChecklistReportSummaryDTO{
|
||||
TotalChecklist: item.Summary.TotalChecklist,
|
||||
JumlahHariEfektif: item.Summary.JumlahHariEfektif,
|
||||
AbkPercentage: item.Summary.AbkPercentage,
|
||||
KandangPercentage: item.Summary.KandangPercentage,
|
||||
Kategori: dto.DailyChecklistReportCategoryDTO{
|
||||
Kurang: item.Summary.Category.Kurang,
|
||||
Cukup: item.Summary.Category.Cukup,
|
||||
Baik: item.Summary.Category.Baik,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.DailyChecklistReportDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get daily checklist report successfully",
|
||||
Meta: response.Meta{
|
||||
Page: query.Page,
|
||||
Limit: query.Limit,
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: responseData,
|
||||
})
|
||||
}
|
||||
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 +308,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),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -98,7 +335,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 {
|
||||
@@ -124,7 +361,7 @@ func (u *DailyChecklistController) UpdateOne(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
func (u *DailyChecklistController) DeleteOne(c *fiber.Ctx) error {
|
||||
param := c.Params("id")
|
||||
param := c.Params("idDailyChecklist")
|
||||
|
||||
id, err := strconv.Atoi(param)
|
||||
if err != nil {
|
||||
@@ -217,6 +454,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 == "" {
|
||||
@@ -241,3 +504,21 @@ func (u *DailyChecklistController) GetAllTasks(c *fiber.Ctx) error {
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *DailyChecklistController) UpdateAssignment(c *fiber.Ctx) error {
|
||||
req := new(validation.UpdateAssignment)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
if err := u.DailyChecklistService.UpdateAssignment(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Assignment updated successfully",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,100 @@ 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"`
|
||||
TotalPhase int `json:"total_phase"`
|
||||
TotalActivity int `json:"total_activity"`
|
||||
Progress int `json:"progress"`
|
||||
}
|
||||
|
||||
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 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 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"`
|
||||
Summary DailyChecklistReportSummaryDTO `json:"summary"`
|
||||
}
|
||||
|
||||
type DailyChecklistReportEntityDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type DailyChecklistReportSummaryDTO struct {
|
||||
TotalChecklist int `json:"total_checklist"`
|
||||
JumlahHariEfektif int `json:"jumlah_hari_efektif"`
|
||||
AbkPercentage int `json:"abk_percentage"`
|
||||
KandangPercentage int `json:"kandang_percentage"`
|
||||
Kategori DailyChecklistReportCategoryDTO `json:"kategori"`
|
||||
}
|
||||
|
||||
type DailyChecklistReportCategoryDTO struct {
|
||||
Kurang int `json:"kurang"`
|
||||
Cukup int `json:"cukup"`
|
||||
Baik int `json:"baik"`
|
||||
}
|
||||
|
||||
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,25 +141,92 @@ 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,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
CreatedUser: createdUser,
|
||||
Id: e.Id,
|
||||
Name: name,
|
||||
Status: status,
|
||||
Category: e.Category,
|
||||
Date: e.Date,
|
||||
Kandang: kandang,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
CreatedUser: createdUser,
|
||||
TotalPhase: 0,
|
||||
TotalActivity: 0,
|
||||
Progress: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func ToDailyChecklistListDTOs(e []entity.DailyChecklist) []DailyChecklistListDTO {
|
||||
result := make([]DailyChecklistListDTO, len(e))
|
||||
for i, r := range e {
|
||||
result[i] = ToDailyChecklistListDTO(r)
|
||||
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 result
|
||||
}
|
||||
|
||||
func ToDailyChecklistDetailDTO(e entity.DailyChecklist) DailyChecklistDetailDTO {
|
||||
return DailyChecklistDetailDTO{
|
||||
DailyChecklistListDTO: ToDailyChecklistListDTO(e),
|
||||
DailyChecklistListDTO: ToDailyChecklistListDTO(checklist),
|
||||
Phases: phaseDTOs,
|
||||
Tasks: taskDTOs,
|
||||
AssignedEmployees: assignedDTOs,
|
||||
TotalActivity: totalActivities,
|
||||
Progress: progress,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package dailyChecklists
|
||||
|
||||
import (
|
||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/controllers"
|
||||
dailyChecklist "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/services"
|
||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
@@ -13,23 +13,51 @@ func DailyChecklistRoutes(v1 fiber.Router, u user.UserService, s dailyChecklist.
|
||||
ctrl := controller.NewDailyChecklistController(s)
|
||||
|
||||
route := v1.Group("/daily-checklists")
|
||||
route.Use(m.Auth(u))
|
||||
// route.Use(m.Auth(u))
|
||||
|
||||
route.Get("/", ctrl.GetAll)
|
||||
route.Get("/report", ctrl.GetReport)
|
||||
|
||||
route.Get("/summary", ctrl.GetSummary)
|
||||
|
||||
route.Get("/report", ctrl.GetReport)
|
||||
|
||||
// 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
|
||||
*/
|
||||
route.Post("/phase/:idDailyChecklist", ctrl.CreateDailyChecklistPhase)
|
||||
|
||||
// create assigment
|
||||
/*
|
||||
ketika add ABK
|
||||
*/
|
||||
route.Post("/assignment/:idDailyChecklist", ctrl.CreateAssignment)
|
||||
|
||||
// remove assignment
|
||||
/*
|
||||
ketika remove ABK
|
||||
*/
|
||||
route.Delete("/:idDailyChecklist/assignments/:idEmployee", ctrl.RemoveAssignment)
|
||||
|
||||
//get all tasks
|
||||
route.Get("/tasks", ctrl.GetAllTasks)
|
||||
|
||||
route.Get("/:id", ctrl.GetOne)
|
||||
route.Patch("/:id", ctrl.UpdateOne)
|
||||
route.Delete("/:id", ctrl.DeleteOne)
|
||||
// update assignment
|
||||
/*
|
||||
ketika check dan uncheck tugas oleh ABK
|
||||
*/
|
||||
route.Post("/assignment", ctrl.UpdateAssignment)
|
||||
|
||||
route.Patch("/:idDailyChecklist", ctrl.UpdateOne)
|
||||
route.Delete("/:idDailyChecklist", ctrl.DeleteOne)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -20,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
type DailyChecklistService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.DailyChecklist, int64, error)
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]DailyChecklistListItem, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.DailyChecklist, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.DailyChecklist, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.DailyChecklist, error)
|
||||
@@ -29,6 +31,11 @@ type DailyChecklistService interface {
|
||||
AssignTasks(ctx *fiber.Ctx, id uint, req *validation.AssignTask) error
|
||||
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)
|
||||
GetSummary(ctx *fiber.Ctx, params *validation.SummaryQuery) ([]DailyChecklistSummary, error)
|
||||
GetReport(ctx *fiber.Ctx, params *validation.ReportQuery) ([]DailyChecklistReportItem, int64, error)
|
||||
}
|
||||
|
||||
type dailyChecklistService struct {
|
||||
@@ -38,6 +45,69 @@ 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
|
||||
}
|
||||
|
||||
type DailyChecklistListItem struct {
|
||||
ID uint
|
||||
Name *string
|
||||
Date time.Time
|
||||
Category string
|
||||
Status *string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Kandang entity.Kandang
|
||||
TotalPhase int
|
||||
TotalActivity int
|
||||
Progress int
|
||||
}
|
||||
|
||||
type DailyChecklistSummary struct {
|
||||
EmployeeID uint
|
||||
EmployeeName string
|
||||
KandangID uint
|
||||
KandangName string
|
||||
TotalActivity int
|
||||
ActivityDone int
|
||||
ActivityLeft int
|
||||
CompletionRate int
|
||||
LastActivity *time.Time
|
||||
}
|
||||
|
||||
type DailyChecklistReportItem struct {
|
||||
AreaID uint
|
||||
AreaName string
|
||||
LocationID uint
|
||||
LocationName string
|
||||
KandangID uint
|
||||
KandangName string
|
||||
EmployeeID uint
|
||||
EmployeeName string
|
||||
PhaseName string
|
||||
DailyActivities map[string]int
|
||||
Summary DailyChecklistReportSummary
|
||||
}
|
||||
|
||||
type DailyChecklistReportSummary struct {
|
||||
TotalChecklist int
|
||||
JumlahHariEfektif int
|
||||
AbkPercentage int
|
||||
KandangPercentage int
|
||||
Category DailyChecklistReportCategory
|
||||
}
|
||||
|
||||
type DailyChecklistReportCategory struct {
|
||||
Kurang int
|
||||
Cukup int
|
||||
Baik int
|
||||
}
|
||||
|
||||
func NewDailyChecklistService(repo repository.DailyChecklistRepository, phaseRepo phaseRepo.PhasesRepository, validate *validator.Validate) DailyChecklistService {
|
||||
return &dailyChecklistService{
|
||||
Log: utils.Log,
|
||||
@@ -48,29 +118,163 @@ 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) {
|
||||
func (s dailyChecklistService) GetAll(c *fiber.Ctx, params *validation.Query) ([]DailyChecklistListItem, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
dailyChecklists, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||
db = s.withRelations(db)
|
||||
if params.Search != "" {
|
||||
return db.Where("name LIKE ?", "%"+params.Search+"%")
|
||||
}
|
||||
return db.Order("created_at DESC").Order("updated_at DESC")
|
||||
})
|
||||
db := s.Repository.DB().WithContext(c.Context()).
|
||||
Table("daily_checklists dc").
|
||||
Joins("JOIN kandangs k ON k.id = dc.kandang_id")
|
||||
|
||||
if err != nil {
|
||||
if params.DateFrom != "" {
|
||||
dateFrom, err := time.Parse("2006-01-02", params.DateFrom)
|
||||
if err != nil {
|
||||
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "invalid date_from format, use YYYY-MM-DD")
|
||||
}
|
||||
db = db.Where("dc.date >= ?", dateFrom)
|
||||
}
|
||||
|
||||
if params.DateTo != "" {
|
||||
dateTo, err := time.Parse("2006-01-02", params.DateTo)
|
||||
if err != nil {
|
||||
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "invalid date_to format, use YYYY-MM-DD")
|
||||
}
|
||||
db = db.Where("dc.date <= ?", dateTo)
|
||||
}
|
||||
|
||||
if params.KandangID != nil {
|
||||
db = db.Where("dc.kandang_id = ?", *params.KandangID)
|
||||
}
|
||||
|
||||
if params.Status != "" {
|
||||
db = db.Where("dc.status = ?", params.Status)
|
||||
}
|
||||
|
||||
if params.Search != "" {
|
||||
like := "%" + params.Search + "%"
|
||||
db = db.Where("(k.name ILIKE ? OR dc.category ILIKE ?)", like, like)
|
||||
}
|
||||
|
||||
countDB := db.Session(&gorm.Session{})
|
||||
var total int64
|
||||
if err := countDB.Count(&total).Error; err != nil {
|
||||
s.Log.Errorf("Failed to count dailyChecklists: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
type dailyChecklistListRow struct {
|
||||
ID uint
|
||||
Name *string
|
||||
Date time.Time
|
||||
Category string
|
||||
Status *string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
KandangID uint
|
||||
TotalPhase int64
|
||||
TotalActivity int64
|
||||
TotalAssignments int64
|
||||
CompletedAssignments int64
|
||||
}
|
||||
|
||||
rows := make([]dailyChecklistListRow, 0)
|
||||
selectDB := db.Session(&gorm.Session{})
|
||||
if err := selectDB.
|
||||
Select(`
|
||||
dc.id,
|
||||
dc.name,
|
||||
dc.date,
|
||||
dc.category,
|
||||
dc.status,
|
||||
dc.created_at,
|
||||
dc.updated_at,
|
||||
dc.kandang_id,
|
||||
COALESCE((
|
||||
SELECT COUNT(*)
|
||||
FROM daily_checklist_phases dcp
|
||||
WHERE dcp.checklist_id = dc.id
|
||||
), 0) AS total_phase,
|
||||
COALESCE((
|
||||
SELECT COUNT(pa.id)
|
||||
FROM daily_checklist_phases dcp
|
||||
JOIN phase_activities pa ON pa.phase_id = dcp.phase_id
|
||||
WHERE dcp.checklist_id = dc.id AND pa.deleted_at IS NULL
|
||||
), 0) AS total_activity,
|
||||
COALESCE((
|
||||
SELECT COUNT(*)
|
||||
FROM daily_checklist_activity_task_assignments dca
|
||||
JOIN daily_checklist_activity_tasks dcat ON dcat.id = dca.task_id
|
||||
WHERE dcat.checklist_id = dc.id
|
||||
), 0) AS total_assignments,
|
||||
COALESCE((
|
||||
SELECT COUNT(*)
|
||||
FROM daily_checklist_activity_task_assignments dca
|
||||
JOIN daily_checklist_activity_tasks dcat ON dcat.id = dca.task_id
|
||||
WHERE dcat.checklist_id = dc.id AND dca.checked
|
||||
), 0) AS completed_assignments`).
|
||||
Order("dc.date DESC, dc.created_at DESC").
|
||||
Offset(offset).
|
||||
Limit(params.Limit).
|
||||
Scan(&rows).Error; err != nil {
|
||||
s.Log.Errorf("Failed to get dailyChecklists: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return dailyChecklists, total, nil
|
||||
|
||||
kandangIDs := make([]uint, 0, len(rows))
|
||||
seen := make(map[uint]struct{})
|
||||
for _, row := range rows {
|
||||
if _, ok := seen[row.KandangID]; !ok {
|
||||
seen[row.KandangID] = struct{}{}
|
||||
kandangIDs = append(kandangIDs, row.KandangID)
|
||||
}
|
||||
}
|
||||
|
||||
kandangMap := make(map[uint]entity.Kandang)
|
||||
if len(kandangIDs) > 0 {
|
||||
var kandangs []entity.Kandang
|
||||
if err := s.Repository.DB().WithContext(c.Context()).
|
||||
Where("id IN ?", kandangIDs).
|
||||
Preload("Location").
|
||||
Preload("Pic").
|
||||
Preload("CreatedUser").
|
||||
Find(&kandangs).Error; err != nil {
|
||||
s.Log.Errorf("Failed to get kandangs for daily checklist list: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
for _, kandang := range kandangs {
|
||||
kandangMap[kandang.Id] = kandang
|
||||
}
|
||||
}
|
||||
|
||||
items := make([]DailyChecklistListItem, len(rows))
|
||||
for i, row := range rows {
|
||||
progress := 0
|
||||
if row.TotalAssignments > 0 {
|
||||
progress = int(math.Round(float64(row.CompletedAssignments) / float64(row.TotalAssignments) * 100))
|
||||
}
|
||||
|
||||
items[i] = DailyChecklistListItem{
|
||||
ID: row.ID,
|
||||
Name: row.Name,
|
||||
Date: row.Date,
|
||||
Category: row.Category,
|
||||
Status: row.Status,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
Kandang: kandangMap[row.KandangID],
|
||||
TotalPhase: int(row.TotalPhase),
|
||||
TotalActivity: int(row.TotalActivity),
|
||||
Progress: progress,
|
||||
}
|
||||
}
|
||||
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
func (s dailyChecklistService) GetOne(c *fiber.Ctx, id uint) (*entity.DailyChecklist, error) {
|
||||
@@ -85,6 +289,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
|
||||
@@ -122,14 +392,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 {
|
||||
@@ -296,6 +564,71 @@ 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
|
||||
}
|
||||
|
||||
task := new(entity.DailyChecklistActivityTask)
|
||||
if err := s.Repository.DB().WithContext(c.Context()).First(task, req.TaskID).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "Task not found")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if req.EmployeeID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid employee id")
|
||||
}
|
||||
|
||||
updates := map[string]any{"updated_at": time.Now()}
|
||||
if req.Checked != nil {
|
||||
updates["checked"] = *req.Checked
|
||||
}
|
||||
if req.Note != nil {
|
||||
updates["note"] = *req.Note
|
||||
}
|
||||
|
||||
return s.Repository.DB().WithContext(c.Context()).Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "task_id"}, {Name: "employee_id"}},
|
||||
DoUpdates: clause.Assignments(updates),
|
||||
}).Create(&entity.DailyChecklistActivityTaskAssignment{
|
||||
TaskId: req.TaskID,
|
||||
EmployeeId: req.EmployeeID,
|
||||
Checked: req.Checked != nil && *req.Checked,
|
||||
Note: req.Note,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func parsePhaseIDs(raw string) ([]uint, error) {
|
||||
parts := strings.Split(raw, ",")
|
||||
result := make([]uint, 0, len(parts))
|
||||
@@ -355,6 +688,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
|
||||
@@ -408,3 +767,357 @@ 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 ? AND d.status = ?", dateFrom, dateTo, "APPROVED")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (s dailyChecklistService) GetReport(c *fiber.Ctx, params *validation.ReportQuery) ([]DailyChecklistReportItem, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
buildBase := func() *gorm.DB {
|
||||
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 employees e ON e.id = dca.employee_id").
|
||||
Joins("JOIN kandangs 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 phases p ON p.id = dcat.phase_id").
|
||||
Where("EXTRACT(MONTH FROM dc.date) = ?", params.Month).
|
||||
Where("EXTRACT(YEAR FROM dc.date) = ?", params.Year)
|
||||
|
||||
if params.AreaID != nil {
|
||||
db = db.Where("a.id = ?", *params.AreaID)
|
||||
}
|
||||
if params.LocationID != nil {
|
||||
db = db.Where("loc.id = ?", *params.LocationID)
|
||||
}
|
||||
if params.KandangID != nil {
|
||||
db = db.Where("k.id = ?", *params.KandangID)
|
||||
}
|
||||
if params.EmployeeID != nil {
|
||||
db = db.Where("dca.employee_id = ?", *params.EmployeeID)
|
||||
}
|
||||
if params.PhaseID != nil {
|
||||
db = db.Where("p.id = ?", *params.PhaseID)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
buildGroupedQuery := func() *gorm.DB {
|
||||
return buildBase().
|
||||
Select(`
|
||||
a.id AS area_id,
|
||||
a.name AS area_name,
|
||||
loc.id AS location_id,
|
||||
loc.name AS location_name,
|
||||
k.id AS kandang_id,
|
||||
k.name AS kandang_name,
|
||||
e.id AS employee_id,
|
||||
e.name AS employee_name,
|
||||
p.id AS phase_id,
|
||||
p.name AS phase_name,
|
||||
SUM(CASE WHEN dca.checked THEN 1 ELSE 0 END) AS completed_assignments,
|
||||
COUNT(*) AS total_assignments`).
|
||||
Group("a.id, a.name, loc.id, loc.name, k.id, k.name, e.id, e.name, p.id, p.name")
|
||||
}
|
||||
|
||||
var total int64
|
||||
groupedForCount := buildGroupedQuery()
|
||||
if err := s.Repository.DB().WithContext(c.Context()).
|
||||
Table("(?) AS grouped", groupedForCount).
|
||||
Count(&total).Error; err != nil {
|
||||
s.Log.Errorf("Failed to count report data: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
type reportRow struct {
|
||||
AreaID uint
|
||||
AreaName string
|
||||
LocationID uint
|
||||
LocationName string
|
||||
KandangID uint
|
||||
KandangName string
|
||||
EmployeeID uint
|
||||
EmployeeName string
|
||||
PhaseID uint
|
||||
PhaseName string
|
||||
CompletedAssignments int64
|
||||
TotalAssignments int64
|
||||
}
|
||||
|
||||
rows := make([]reportRow, 0)
|
||||
if err := buildGroupedQuery().
|
||||
Order("a.name, loc.name, k.name, e.name").
|
||||
Offset(offset).
|
||||
Limit(params.Limit).
|
||||
Scan(&rows).Error; err != nil {
|
||||
s.Log.Errorf("Failed to fetch report data: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
return []DailyChecklistReportItem{}, total, nil
|
||||
}
|
||||
|
||||
type comboKey struct {
|
||||
EmployeeID uint
|
||||
KandangID uint
|
||||
PhaseID uint
|
||||
}
|
||||
|
||||
employeeIDs := make([]uint, 0)
|
||||
kandangIDs := make([]uint, 0)
|
||||
phaseIDs := make([]uint, 0)
|
||||
comboSet := make(map[comboKey]struct{})
|
||||
employeeSet := make(map[uint]struct{})
|
||||
kandangSet := make(map[uint]struct{})
|
||||
phaseSet := make(map[uint]struct{})
|
||||
|
||||
for _, row := range rows {
|
||||
key := comboKey{EmployeeID: row.EmployeeID, KandangID: row.KandangID, PhaseID: row.PhaseID}
|
||||
comboSet[key] = struct{}{}
|
||||
if _, ok := employeeSet[row.EmployeeID]; !ok {
|
||||
employeeSet[row.EmployeeID] = struct{}{}
|
||||
employeeIDs = append(employeeIDs, row.EmployeeID)
|
||||
}
|
||||
if _, ok := kandangSet[row.KandangID]; !ok {
|
||||
kandangSet[row.KandangID] = struct{}{}
|
||||
kandangIDs = append(kandangIDs, row.KandangID)
|
||||
}
|
||||
if _, ok := phaseSet[row.PhaseID]; !ok {
|
||||
phaseSet[row.PhaseID] = struct{}{}
|
||||
phaseIDs = append(phaseIDs, row.PhaseID)
|
||||
}
|
||||
}
|
||||
|
||||
dailyActivityMap := make(map[comboKey]map[string]int)
|
||||
if len(employeeIDs) > 0 {
|
||||
var dailyRows []struct {
|
||||
EmployeeID uint
|
||||
KandangID uint
|
||||
PhaseID uint
|
||||
Date time.Time
|
||||
Completed int64
|
||||
}
|
||||
|
||||
dailyQuery := buildBase().
|
||||
Where("dca.employee_id IN ?", employeeIDs).
|
||||
Where("dc.kandang_id IN ?", kandangIDs).
|
||||
Where("dcat.phase_id IN ?", phaseIDs).
|
||||
Select(`
|
||||
dca.employee_id,
|
||||
dc.kandang_id,
|
||||
dcat.phase_id,
|
||||
dc.date,
|
||||
SUM(CASE WHEN dca.checked THEN 1 ELSE 0 END) AS completed`).
|
||||
Group("dca.employee_id, dc.kandang_id, dcat.phase_id, dc.date")
|
||||
|
||||
if err := dailyQuery.Scan(&dailyRows).Error; err != nil {
|
||||
s.Log.Errorf("Failed to fetch daily activities for report: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
for _, row := range dailyRows {
|
||||
key := comboKey{EmployeeID: row.EmployeeID, KandangID: row.KandangID, PhaseID: row.PhaseID}
|
||||
if _, ok := comboSet[key]; !ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := dailyActivityMap[key]; !ok {
|
||||
dailyActivityMap[key] = make(map[string]int)
|
||||
}
|
||||
day := strconv.Itoa(row.Date.Day())
|
||||
dailyActivityMap[key][day] = int(row.Completed)
|
||||
}
|
||||
}
|
||||
|
||||
employeeStats := make(map[uint]struct {
|
||||
Completed int64
|
||||
Total int64
|
||||
})
|
||||
var employeeRows []struct {
|
||||
EmployeeID uint
|
||||
Completed int64
|
||||
Total int64
|
||||
}
|
||||
if err := buildBase().
|
||||
Select(`
|
||||
dca.employee_id,
|
||||
SUM(CASE WHEN dca.checked THEN 1 ELSE 0 END) AS completed,
|
||||
COUNT(*) AS total`).
|
||||
Group("dca.employee_id").
|
||||
Scan(&employeeRows).Error; err != nil {
|
||||
s.Log.Errorf("Failed to fetch employee stats for report: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
for _, row := range employeeRows {
|
||||
employeeStats[row.EmployeeID] = struct {
|
||||
Completed int64
|
||||
Total int64
|
||||
}{Completed: row.Completed, Total: row.Total}
|
||||
}
|
||||
|
||||
kandangStats := make(map[uint]struct {
|
||||
Completed int64
|
||||
Total int64
|
||||
})
|
||||
var kandangRows []struct {
|
||||
KandangID uint
|
||||
Completed int64
|
||||
Total int64
|
||||
}
|
||||
if err := buildBase().
|
||||
Select(`
|
||||
dc.kandang_id,
|
||||
SUM(CASE WHEN dca.checked THEN 1 ELSE 0 END) AS completed,
|
||||
COUNT(*) AS total`).
|
||||
Group("dc.kandang_id").
|
||||
Scan(&kandangRows).Error; err != nil {
|
||||
s.Log.Errorf("Failed to fetch kandang stats for report: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
for _, row := range kandangRows {
|
||||
kandangStats[row.KandangID] = struct {
|
||||
Completed int64
|
||||
Total int64
|
||||
}{Completed: row.Completed, Total: row.Total}
|
||||
}
|
||||
|
||||
items := make([]DailyChecklistReportItem, len(rows))
|
||||
for i, row := range rows {
|
||||
key := comboKey{EmployeeID: row.EmployeeID, KandangID: row.KandangID, PhaseID: row.PhaseID}
|
||||
|
||||
activities := dailyActivityMap[key]
|
||||
if activities == nil {
|
||||
activities = map[string]int{}
|
||||
}
|
||||
|
||||
totalChecklist := 0
|
||||
for _, count := range activities {
|
||||
totalChecklist += count
|
||||
}
|
||||
|
||||
employeeStat := employeeStats[row.EmployeeID]
|
||||
abkPercentage := 0
|
||||
if employeeStat.Total > 0 {
|
||||
abkPercentage = int(math.Round(float64(employeeStat.Completed) / float64(employeeStat.Total) * 100))
|
||||
}
|
||||
|
||||
kandangStat := kandangStats[row.KandangID]
|
||||
kandangPercentage := 0
|
||||
if kandangStat.Total > 0 {
|
||||
kandangPercentage = int(math.Round(float64(kandangStat.Completed) / float64(kandangStat.Total) * 100))
|
||||
}
|
||||
|
||||
items[i] = DailyChecklistReportItem{
|
||||
AreaID: row.AreaID,
|
||||
AreaName: row.AreaName,
|
||||
LocationID: row.LocationID,
|
||||
LocationName: row.LocationName,
|
||||
KandangID: row.KandangID,
|
||||
KandangName: row.KandangName,
|
||||
EmployeeID: row.EmployeeID,
|
||||
EmployeeName: row.EmployeeName,
|
||||
PhaseName: row.PhaseName,
|
||||
DailyActivities: activities,
|
||||
Summary: DailyChecklistReportSummary{
|
||||
TotalChecklist: totalChecklist,
|
||||
JumlahHariEfektif: len(activities),
|
||||
AbkPercentage: abkPercentage,
|
||||
KandangPercentage: kandangPercentage,
|
||||
Category: DailyChecklistReportCategory{
|
||||
Kurang: 0,
|
||||
Cukup: 0,
|
||||
Baik: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
@@ -8,13 +8,18 @@ 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 {
|
||||
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
||||
Search string `query:"search" validate:"omitempty,max=50"`
|
||||
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
||||
Search string `query:"search" validate:"omitempty,max=50"`
|
||||
DateFrom string `query:"date_from" validate:"omitempty"`
|
||||
DateTo string `query:"date_to" validate:"omitempty"`
|
||||
Status string `query:"status" validate:"omitempty"`
|
||||
KandangID *uint `query:"kandang_id" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type AssignPhases struct {
|
||||
@@ -24,3 +29,29 @@ type AssignPhases struct {
|
||||
type AssignTask struct {
|
||||
EmployeeIDs string `json:"employee_ids" validate:"required"`
|
||||
}
|
||||
|
||||
type UpdateAssignment struct {
|
||||
TaskID uint `json:"task_id" validate:"required"`
|
||||
EmployeeID uint `json:"employee_id" validate:"required"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type ReportQuery struct {
|
||||
Page int `query:"page" validate:"required,number,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"required,number,min=1,max=100,gt=0"`
|
||||
Month int `query:"bulan" validate:"required,number,min=1,max=12"`
|
||||
Year int `query:"tahun" validate:"required,number,min=1900"`
|
||||
AreaID *uint `query:"area_id" validate:"omitempty"`
|
||||
LocationID *uint `query:"location_id" validate:"omitempty"`
|
||||
KandangID *uint `query:"kandang_id" validate:"omitempty"`
|
||||
EmployeeID *uint `query:"employee_id" validate:"omitempty"`
|
||||
PhaseID *uint `query:"phase_id" validate:"omitempty"`
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@ func NewPhaseActivityService(repo repository.PhaseActivityRepository, phaseRepo
|
||||
}
|
||||
|
||||
func (s phaseActivityService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
return db
|
||||
return db.Joins("JOIN phases ON phases.id = phase_activities.phase_id").
|
||||
Where("phases.deleted_at IS NULL")
|
||||
}
|
||||
|
||||
func (s phaseActivityService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.PhaseActivity, int64, error) {
|
||||
|
||||
Reference in New Issue
Block a user