add api daily checklist report

This commit is contained in:
MacBook Air M1
2026-01-07 17:39:17 +07:00
parent e545047165
commit c3f8ae5887
6 changed files with 704 additions and 47 deletions
@@ -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,7 +96,7 @@ 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,
})
}
@@ -128,6 +175,121 @@ func (u *DailyChecklistController) GetSummary(c *fiber.Ctx) error {
})
}
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("idDailyChecklist")
@@ -199,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 {
@@ -28,6 +28,9 @@ type DailyChecklistListDTO struct {
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 {
@@ -59,6 +62,35 @@ 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"`
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"`
@@ -130,17 +162,12 @@ func ToDailyChecklistListDTO(e entity.DailyChecklist) DailyChecklistListDTO {
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)
}
return result
}
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 {
+6 -3
View File
@@ -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,12 +13,15 @@ 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)
@@ -56,5 +59,5 @@ func DailyChecklistRoutes(v1 fiber.Router, u user.UserService, s dailyChecklist.
route.Post("/assignment", ctrl.UpdateAssignment)
route.Patch("/:idDailyChecklist", ctrl.UpdateOne)
route.Delete("/:id", ctrl.DeleteOne)
route.Delete("/:idDailyChecklist", ctrl.DeleteOne)
}
@@ -22,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)
@@ -35,6 +35,7 @@ type DailyChecklistService interface {
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 {
@@ -53,6 +54,20 @@ type DailyChecklistDetail struct {
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
@@ -65,6 +80,34 @@ type DailyChecklistSummary struct {
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,
@@ -78,26 +121,160 @@ func (s dailyChecklistService) withRelations(db *gorm.DB) *gorm.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 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) {
@@ -633,7 +810,7 @@ func (s dailyChecklistService) GetSummary(c *fiber.Ctx, params *validation.Summa
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)
Where("d.date BETWEEN ? AND ? AND d.status = ?", dateFrom, dateTo, "APPROVED")
if params.Category != "" {
db = db.Where("d.category = ?", params.Category)
@@ -673,3 +850,274 @@ func (s dailyChecklistService) GetSummary(c *fiber.Ctx, params *validation.Summa
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
}
@@ -16,6 +16,10 @@ 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"`
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 {
@@ -39,3 +43,15 @@ type SummaryQuery struct {
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) {