diff --git a/.air.toml b/.air.toml index 0c534172..c463b5b2 100644 --- a/.air.toml +++ b/.air.toml @@ -3,7 +3,7 @@ root = "." tmp_dir = "tmp" [build] -cmd = "go build -o ./tmp/main ./cmd/api" +cmd = "go build -buildvcs=false -o ./tmp/main ./cmd/api" bin = "tmp/main" full_bin = "APP_ENV=dev ./tmp/main" include_ext = ["go", "tpl", "tmpl", "html"] diff --git a/internal/modules/closings/controllers/closing.controller.go b/internal/modules/closings/controllers/closing.controller.go index 113aa667..aa191500 100644 --- a/internal/modules/closings/controllers/closing.controller.go +++ b/internal/modules/closings/controllers/closing.controller.go @@ -307,3 +307,25 @@ func (u *ClosingController) GetExpeditionHPPByKandang(c *fiber.Ctx) error { Data: result, }) } + +func (u *ClosingController) GetClosingDataProduksi(c *fiber.Ctx) error { + param := c.Params("projectFlockId") + + id, err := strconv.Atoi(param) + if err != nil || id <= 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid projectFlockId") + } + + result, err := u.ClosingService.GetClosingDataProduksi(c, uint(id)) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Retrieved production data successfully", + Data: result, + }) +} diff --git a/internal/modules/closings/dto/closing.dto.go b/internal/modules/closings/dto/closing.dto.go index 1f1cb492..429495b7 100644 --- a/internal/modules/closings/dto/closing.dto.go +++ b/internal/modules/closings/dto/closing.dto.go @@ -58,6 +58,52 @@ type ClosingSummaryDTO struct { StatusClosing string `json:"closing_status"` } +type ClosingPurchaseDTO struct { + InitialPopulation int `json:"initial_population"` + ClaimCulling int `json:"claim_culling"` + FinalPopulation int `json:"final_population"` + FeedIn float64 `json:"feed_in"` + FeedUsed float64 `json:"feed_used"` + FeedUsedPerHead float64 `json:"feed_used_per_head"` +} + +type ClosingSalesDTO struct { + SalesPopulation int `json:"sales_population"` + SalesWeight float64 `json:"sales_weight"` + AverageWeight float64 `json:"average_weight"` + AverageSellingPrice float64 `json:"chicken_average_selling_price"` +} + +type ClosingEggSalesDTO struct { + EggPieces int `json:"egg_pieces"` + EggMassKg float64 `json:"egg_mass_kg"` + AverageEggWeightKg float64 `json:"average_egg_weight_kg"` + AverageSellingPrice float64 `json:"egg_average_selling_price"` +} + +type ClosingPerformanceDTO struct { + Depletion float64 `json:"depletion"` + Age float64 `json:"age_day"` + MortalityStd float64 `json:"mortality_std"` + MortalityAct float64 `json:"mortality_act"` + DeffMortality float64 `json:"deff_mortality"` + FcrStd float64 `json:"fcr_std"` + FcrAct float64 `json:"fcr_act"` + DeffFcr float64 `json:"deff_fcr"` + Awg float64 `json:"awg"` +} + +type ClosingSalesGroupDTO struct { + Chicken ClosingSalesDTO `json:"chicken"` + Egg *ClosingEggSalesDTO `json:"egg,omitempty"` +} + +type ClosingProductionReportDTO struct { + Purchase ClosingPurchaseDTO `json:"purchase"` + Sales ClosingSalesGroupDTO `json:"sales"` + Performance ClosingPerformanceDTO `json:"performance"` +} + func ToClosingSummaryDTO(project entity.ProjectFlock, statusProject, statusClosing string) ClosingSummaryDTO { history := project.KandangHistory diff --git a/internal/modules/closings/repositories/closing.repository.go b/internal/modules/closings/repositories/closing.repository.go index 0214d739..6a59c5f9 100644 --- a/internal/modules/closings/repositories/closing.repository.go +++ b/internal/modules/closings/repositories/closing.repository.go @@ -16,6 +16,12 @@ import ( type ClosingRepository interface { repository.BaseRepository[entity.ProjectFlock] GetSapronak(ctx context.Context, params SapronakQueryParams) ([]SapronakRow, int64, error) + SumFeedPurchaseAndUsedByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, error) + SumClaimCullingByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) + SumMarketingWeightAndQtyByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, float64, error) + SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, float64, float64, error) + SumRecordingEggQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, error) + GetFcrStandardsByFcrID(ctx context.Context, fcrID uint) ([]entity.FcrStandard, error) GetExpeditionHPP(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]ExpeditionHPPRow, error) FetchSapronakIncoming(ctx context.Context, kandangID uint) ([]SapronakIncomingRow, error) FetchSapronakIncomingDetails(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, error) @@ -117,6 +123,163 @@ func (r *ClosingRepositoryImpl) GetSapronak(ctx context.Context, params Sapronak return rows, totalResults, nil } +func (r *ClosingRepositoryImpl) SumFeedPurchaseAndUsedByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, error) { + if len(projectFlockKandangIDs) == 0 { + return 0, 0, nil + } + + var purchaseAgg struct { + TotalIn float64 `gorm:"column:total_in"` + } + + err := r.DB().WithContext(ctx). + Table("purchase_items pi"). + Joins("JOIN flags f ON f.flagable_id = pi.product_id AND f.flagable_type = 'products'"). + Where("f.name = ?", "PAKAN"). + Where("pi.project_flock_kandang_id IN ?", projectFlockKandangIDs). + Select("COALESCE(SUM(pi.total_qty), 0) AS total_in"). + Scan(&purchaseAgg).Error + if err != nil { + return 0, 0, err + } + + var usageAgg struct { + TotalUsed float64 `gorm:"column:total_used"` + } + + err = r.DB().WithContext(ctx). + Table("recording_stocks rs"). + Joins("JOIN product_warehouses pw ON pw.id = rs.product_warehouse_id"). + Joins("JOIN products prod ON prod.id = pw.product_id"). + Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products"). + Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs). + Where("f.name = ?", "PAKAN"). + Select("COALESCE(SUM(COALESCE(rs.usage_qty, 0) + COALESCE(rs.pending_qty, 0)), 0) AS total_used"). + Scan(&usageAgg).Error + if err != nil { + return 0, 0, err + } + + return purchaseAgg.TotalIn, usageAgg.TotalUsed, nil +} + +func (r *ClosingRepositoryImpl) SumClaimCullingByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) { + if len(projectFlockKandangIDs) == 0 { + return 0, nil + } + + var agg struct { + Total float64 `gorm:"column:total_culling"` + } + + err := r.DB().WithContext(ctx). + Table("recording_depletions rd"). + Joins("JOIN product_warehouses pw ON pw.id = rd.product_warehouse_id"). + Joins("JOIN products prod ON prod.id = pw.product_id"). + Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products"). + Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs). + Where("f.name = ?", utils.FlagAyamCulling). + Select("COALESCE(SUM(rd.qty), 0) AS total_culling"). + Scan(&agg).Error + if err != nil { + return 0, err + } + + return agg.Total, nil +} + +func (r *ClosingRepositoryImpl) SumMarketingWeightAndQtyByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, float64, error) { + if len(projectFlockKandangIDs) == 0 { + return 0, 0, 0, nil + } + + var agg struct { + TotalWeight float64 `gorm:"column:total_weight"` + TotalQty float64 `gorm:"column:total_qty"` + TotalPrice float64 `gorm:"column:total_price"` + } + + err := r.DB().WithContext(ctx). + Table("marketing_products mp"). + Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id"). + Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs). + Select("COALESCE(SUM(mp.total_weight), 0) AS total_weight, COALESCE(SUM(mp.qty), 0) AS total_qty, COALESCE(SUM(mp.total_price), 0) AS total_price"). + Scan(&agg).Error + if err != nil { + return 0, 0, 0, err + } + + return agg.TotalWeight, agg.TotalQty, agg.TotalPrice, nil +} + +func (r *ClosingRepositoryImpl) SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, float64, float64, error) { + if len(projectFlockKandangIDs) == 0 || len(flagNames) == 0 { + return 0, 0, 0, nil + } + + var agg struct { + TotalWeight float64 `gorm:"column:total_weight"` + TotalQty float64 `gorm:"column:total_qty"` + TotalPrice float64 `gorm:"column:total_price"` + } + + err := r.DB().WithContext(ctx). + Table("marketing_products mp"). + Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id"). + Joins("JOIN products prod ON prod.id = pw.product_id"). + Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products"). + Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs). + Where("f.name IN ?", flagNames). + Select("COALESCE(SUM(mp.total_weight), 0) AS total_weight, COALESCE(SUM(mp.qty), 0) AS total_qty, COALESCE(SUM(mp.total_price), 0) AS total_price"). + Scan(&agg).Error + if err != nil { + return 0, 0, 0, err + } + + return agg.TotalWeight, agg.TotalQty, agg.TotalPrice, nil +} + +func (r *ClosingRepositoryImpl) SumRecordingEggQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, error) { + if len(projectFlockKandangIDs) == 0 || len(flagNames) == 0 { + return 0, nil + } + + var agg struct { + TotalQty float64 `gorm:"column:total_qty"` + } + + err := r.DB().WithContext(ctx). + Table("recording_eggs re"). + Joins("JOIN product_warehouses pw ON pw.id = re.product_warehouse_id"). + Joins("JOIN products prod ON prod.id = pw.product_id"). + Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products"). + Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs). + Where("f.name IN ?", flagNames). + Select("COALESCE(SUM(re.qty), 0) AS total_qty"). + Scan(&agg).Error + if err != nil { + return 0, err + } + + return agg.TotalQty, nil +} + +func (r *ClosingRepositoryImpl) GetFcrStandardsByFcrID(ctx context.Context, fcrID uint) ([]entity.FcrStandard, error) { + if fcrID == 0 { + return []entity.FcrStandard{}, nil + } + + var standards []entity.FcrStandard + if err := r.DB().WithContext(ctx). + Where("fcr_id = ?", fcrID). + Order("weight ASC"). + Find(&standards).Error; err != nil { + return nil, err + } + + return standards, nil +} + func (r *ClosingRepositoryImpl) GetExpeditionHPP(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]ExpeditionHPPRow, error) { db := r.DB().WithContext(ctx) @@ -305,7 +468,6 @@ type SapronakDetailRow struct { Price float64 } - func (r *ClosingRepositoryImpl) withCtx(ctx context.Context) *gorm.DB { return r.DB().WithContext(ctx) } func applyJoins(db *gorm.DB, joins ...string) *gorm.DB { @@ -368,7 +530,7 @@ func (r *ClosingRepositoryImpl) usageQuery( `) db = applyJoins(db, joins...) return db. - Joins("JOIN product_warehouses pw ON " + pwJoinCond). + Joins("JOIN product_warehouses pw ON "+pwJoinCond). Joins("JOIN products p ON p.id = pw.product_id"). Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct). Where(where, args...) @@ -401,7 +563,7 @@ func (r *ClosingRepositoryImpl) detailQuery( ) *gorm.DB { db := r.withCtx(ctx). Table(table). - Joins("JOIN product_warehouses pw ON " + pwJoinCond). + Joins("JOIN product_warehouses pw ON "+pwJoinCond). Joins("JOIN products p ON p.id = pw.product_id"). Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct) @@ -495,7 +657,6 @@ func (r *ClosingRepositoryImpl) FetchSapronakChickinUsageDetails(ctx context.Con ) } - func (r *ClosingRepositoryImpl) incomingPurchaseBase(ctx context.Context, kandangID uint) *gorm.DB { return r.withCtx(ctx). Table("purchase_items AS pi"). @@ -572,7 +733,7 @@ func (r *ClosingRepositoryImpl) fetchStockLogs(ctx context.Context, kandangID ui COALESCE(sl.increase,0) AS increase, COALESCE(sl.decrease,0) AS decrease, COALESCE(p.product_price,0) AS price, - ` + movementSelect + ` + `+movementSelect+` `). Joins("JOIN product_warehouses pw ON pw.id = sl.product_warehouse_id"). Joins("JOIN products p ON p.id = pw.product_id"). diff --git a/internal/modules/closings/route.go b/internal/modules/closings/route.go index 8a155bd0..08b6e725 100644 --- a/internal/modules/closings/route.go +++ b/internal/modules/closings/route.go @@ -29,4 +29,5 @@ func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService route.Get("/:projectFlockId/sapronak", ctrl.GetClosingSapronak) route.Get("/:project_flock_id/expedition-hpp", ctrl.GetExpeditionHPP) route.Get("/:project_flock_id/:project_flock_kandang_id/expedition-hpp", ctrl.GetExpeditionHPPByKandang) + route.Get("/:projectFlockId/data-produksi", ctrl.GetClosingDataProduksi) } diff --git a/internal/modules/closings/services/closing.service.go b/internal/modules/closings/services/closing.service.go index afba0a9d..895f05f7 100644 --- a/internal/modules/closings/services/closing.service.go +++ b/internal/modules/closings/services/closing.service.go @@ -3,7 +3,9 @@ package service import ( "context" "errors" + "math" "strconv" + "strings" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" @@ -30,6 +32,7 @@ type ClosingService interface { GetPenjualan(ctx *fiber.Ctx, projectFlockID uint) ([]entity.MarketingDeliveryProduct, error) GetClosingSummary(ctx *fiber.Ctx, projectFlockID uint) (*dto.ClosingSummaryDTO, error) GetOverhead(ctx *fiber.Ctx, projectFlockID uint) (*dto.OverheadListDTO, error) + GetClosingDataProduksi(ctx *fiber.Ctx, projectFlockID uint) (*dto.ClosingProductionReportDTO, error) GetClosingSapronak(ctx *fiber.Ctx, projectFlockID uint, params *validation.ClosingSapronakQuery) ([]dto.ClosingSapronakItemDTO, int64, error) GetExpeditionHPP(ctx *fiber.Ctx, projectFlockID uint, projectFlockKandangID *uint) (*dto.ExpeditionHPPDTO, error) } @@ -414,3 +417,236 @@ func (s closingService) GetExpeditionHPP(c *fiber.Ctx, projectFlockID uint, proj return result, nil } + +func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint) (*dto.ClosingProductionReportDTO, error) { + if projectFlockID == 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id") + } + + project, err := s.Repository.GetByID(c.Context(), projectFlockID, s.withClosingRelations) + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "Project flock not found") + } + if err != nil { + s.Log.Errorf("Failed get project flock %d for closing data produksi: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock") + } + + var population float64 + for _, history := range project.KandangHistory { + for _, chickin := range history.Chickins { + population += chickin.UsageQty + chickin.PendingUsageQty + } + } + + isGrowing := strings.EqualFold(project.Category, string(utils.ProjectFlockCategoryGrowing)) + + projectFlockKandangIDs, err := s.getProjectFlockKandangIDs(c.Context(), projectFlockID) + if err != nil { + s.Log.Errorf("Failed to fetch project flock kandangs for %d: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandangs") + } + + feedIn, feedUsed, err := s.Repository.SumFeedPurchaseAndUsedByProjectFlockKandangIDs(c.Context(), projectFlockKandangIDs) + if err != nil { + s.Log.Errorf("Failed to sum feed purchase/used qty for project flock %d: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch feed purchase data") + } + + claimCulling, err := s.Repository.SumClaimCullingByProjectFlockKandangIDs(c.Context(), projectFlockKandangIDs) + if err != nil { + s.Log.Errorf("Failed to sum claim culling for project flock %d: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch claim culling data") + } + + finalPopulation := population - claimCulling + + var standards []entity.FcrStandard + if project.FcrId > 0 { + standards, err = s.Repository.GetFcrStandardsByFcrID(c.Context(), project.FcrId) + if err != nil { + s.Log.Errorf("Failed to fetch FCR standards for project flock %d: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch FCR standard data") + } + } + // masih dummy, karena tab penjualan agenya masih dummy + age := 1.0 + + feedUsedPerHead := 0.0 + if population > 0 { + feedUsedPerHead = feedUsed / population + } + + purchase := dto.ClosingPurchaseDTO{ + InitialPopulation: int(population), + ClaimCulling: int(claimCulling), + FinalPopulation: int(finalPopulation), + FeedIn: feedIn, + FeedUsed: feedUsed, + FeedUsedPerHead: feedUsedPerHead, + } + + chickenFlagNames := []string{string(utils.FlagPullet)} + chickenSalesWeight, chickenSalesQty, chickenSalesPrice, err := s.Repository.SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(c.Context(), projectFlockKandangIDs, chickenFlagNames) + if err != nil { + s.Log.Errorf("Failed to fetch chicken sales data for project flock %d: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch chicken sales data") + } + + var chickenAverageWeight float64 + if chickenSalesQty > 0 { + chickenAverageWeight = chickenSalesWeight / chickenSalesQty + } + + var chickenAverageSellingPrice float64 + if chickenSalesWeight > 0 { + chickenAverageSellingPrice = chickenSalesPrice / chickenSalesWeight + } + + chickenSales := dto.ClosingSalesDTO{ + SalesPopulation: int(chickenSalesQty), + SalesWeight: chickenSalesWeight, + AverageWeight: chickenAverageWeight, + AverageSellingPrice: chickenAverageSellingPrice, + } + + chickenDepletion := population - chickenSalesQty + if chickenDepletion < 0 { + chickenDepletion = 0 + } + + chickenPerformance := calculatePerformanceMetrics(chickenAverageWeight, chickenSalesWeight, feedUsed, population, chickenDepletion, age, standards) + + var eggSales *dto.ClosingEggSalesDTO + var eggPerformance *dto.ClosingPerformanceDTO + if !isGrowing { + eggFlagNames := []string{ + string(utils.FlagTelur), + string(utils.FlagTelurUtuh), + string(utils.FlagTelurPecah), + string(utils.FlagTelurPutih), + string(utils.FlagTelurRetak), + } + + eggSalesWeight, eggSalesQty, eggSalesPrice, err := s.Repository.SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(c.Context(), projectFlockKandangIDs, eggFlagNames) + if err != nil { + s.Log.Errorf("Failed to fetch egg sales data for project flock %d: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch egg sales data") + } + + var averageEggWeight float64 + if eggSalesQty > 0 { + averageEggWeight = eggSalesWeight / eggSalesQty + } + + var averageEggSellingPrice float64 + if eggSalesWeight > 0 { + averageEggSellingPrice = eggSalesPrice / eggSalesWeight + } + + eggSales = &dto.ClosingEggSalesDTO{ + EggPieces: int(eggSalesQty), + EggMassKg: eggSalesWeight, + AverageEggWeightKg: averageEggWeight, + AverageSellingPrice: averageEggSellingPrice, + } + + harvestEggQty, err := s.Repository.SumRecordingEggQtyByProjectFlockKandangIDsAndFlagNames(c.Context(), projectFlockKandangIDs, eggFlagNames) + if err != nil { + s.Log.Errorf("Failed to fetch recording egg qty for project flock %d: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch egg harvest data") + } + + eggDepletion := harvestEggQty - eggSalesQty + if eggDepletion < 0 { + eggDepletion = 0 + } + + eggPerf := calculatePerformanceMetrics(averageEggWeight, eggSalesWeight, feedUsed, harvestEggQty, eggDepletion, age, standards) + eggPerformance = &eggPerf + } + + sales := dto.ClosingSalesGroupDTO{ + Chicken: chickenSales, + Egg: eggSales, + } + + performance := dto.ClosingPerformanceDTO{ + Depletion: chickenPerformance.Depletion, + Age: age, + MortalityStd: chickenPerformance.MortalityStd, + MortalityAct: chickenPerformance.MortalityAct, + DeffMortality: chickenPerformance.DeffMortality, + } + if eggPerformance != nil { + performance.FcrStd = eggPerformance.FcrStd + performance.FcrAct = eggPerformance.FcrAct + performance.DeffFcr = eggPerformance.DeffFcr + performance.Awg = eggPerformance.Awg + } else { + performance.FcrStd = chickenPerformance.FcrStd + performance.FcrAct = chickenPerformance.FcrAct + performance.DeffFcr = chickenPerformance.DeffFcr + performance.Awg = chickenPerformance.Awg + } + + result := dto.ClosingProductionReportDTO{ + Purchase: purchase, + Sales: sales, + Performance: performance, + } + + return &result, nil +} + +func calculatePerformanceMetrics(averageWeight, totalWeight, feedUsed, basePopulation, depletion, age float64, standards []entity.FcrStandard) dto.ClosingPerformanceDTO { + mortalityStd, fcrStd := closestFcrValues(standards, averageWeight) + + fcrAct := 0.0 + if totalWeight > 0 { + fcrAct = feedUsed / totalWeight + } + + mortalityAct := 0.0 + if basePopulation > 0 { + mortalityAct = (depletion / basePopulation) * 100 + } + + deffMortality := mortalityStd - mortalityAct + deffFcr := fcrStd - fcrAct + + awg := 0.0 + if age > 0 { + awg = averageWeight / age + } + + return dto.ClosingPerformanceDTO{ + Depletion: depletion, + Age: age, + MortalityStd: mortalityStd, + MortalityAct: mortalityAct, + DeffMortality: deffMortality, + FcrStd: fcrStd, + FcrAct: fcrAct, + DeffFcr: deffFcr, + Awg: awg, + } +} + +func closestFcrValues(standards []entity.FcrStandard, averageWeight float64) (float64, float64) { + if len(standards) == 0 || averageWeight <= 0 { + return 0, 0 + } + + closest := standards[0] + minDiff := math.Abs(closest.Weight - averageWeight) + for _, std := range standards[1:] { + diff := math.Abs(std.Weight - averageWeight) + if diff < minDiff { + minDiff = diff + closest = std + } + } + + return closest.Mortality, closest.FcrNumber +} diff --git a/internal/modules/inventory/product-stocks/services/product-stock.service.go b/internal/modules/inventory/product-stocks/services/product-stock.service.go index a0765d84..11475109 100644 --- a/internal/modules/inventory/product-stocks/services/product-stock.service.go +++ b/internal/modules/inventory/product-stocks/services/product-stock.service.go @@ -64,11 +64,18 @@ func (s productStockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e offset := (params.Page - 1) * params.Limit productStocks, total, err := s.ProductRepository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { + db = db.Where(`EXISTS ( + SELECT 1 + FROM product_warehouses pw + WHERE pw.product_id = products.id + AND pw.qty > 0 + )`) + db = s.withRelations(db) if params.Search != "" { - return db.Where("name ILIKE ?", "%"+params.Search+"%") + db = db.Where("products.name ILIKE ?", "%"+params.Search+"%") } - return db.Order("created_at DESC").Order("updated_at DESC") + return db.Order("products.created_at DESC").Order("products.updated_at DESC") }) if err != nil { diff --git a/internal/modules/repports/controllers/repport.controller.go b/internal/modules/repports/controllers/repport.controller.go index 21d3c49a..039854c8 100644 --- a/internal/modules/repports/controllers/repport.controller.go +++ b/internal/modules/repports/controllers/repport.controller.go @@ -97,3 +97,53 @@ func (c *RepportController) GetMarketing(ctx *fiber.Ctx) error { Data: result, }) } + +func (c *RepportController) GetPurchaseSupplier(ctx *fiber.Ctx) error { + query := &validation.PurchaseSupplierQuery{ + Page: ctx.QueryInt("page", 1), + Limit: ctx.QueryInt("limit", 10), + AreaId: int64(ctx.QueryInt("area_id", 0)), + SupplierId: int64(ctx.QueryInt("supplier_id", 0)), + ProductId: int64(ctx.QueryInt("product_id", 0)), + ProductCategoryId: int64(ctx.QueryInt("product_category_id", 0)), + StartDate: ctx.Query("start_date", ""), + EndDate: ctx.Query("end_date", ""), + SortBy: ctx.Query("sort_by", ""), + FilterBy: ctx.Query("filter_by", ""), + } + + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + + result, totalResults, err := c.RepportService.GetPurchaseSupplier(ctx, query) + if err != nil { + return err + } + + filters := map[string]interface{}{ + "area_id": query.AreaId, + "supplier_id": query.SupplierId, + "product_id": query.ProductId, + "product_category_id": query.ProductCategoryId, + "start_date": query.StartDate, + "end_date": query.EndDate, + "sort_by": query.SortBy, + "filter_by": query.FilterBy, + } + + return ctx.Status(fiber.StatusOK). + JSON(response.SuccessWithPaginate[dto.PurchaseSupplierDTO]{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get supplier purchase recap successfully", + Meta: response.Meta{ + Page: query.Page, + Limit: query.Limit, + TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), + TotalResults: totalResults, + Filters: filters, + }, + Data: result, + }) +} diff --git a/internal/modules/repports/dto/repportPurchase.dto.go b/internal/modules/repports/dto/repportPurchase.dto.go new file mode 100644 index 00000000..830a076f --- /dev/null +++ b/internal/modules/repports/dto/repportPurchase.dto.go @@ -0,0 +1,159 @@ +package dto + +import ( + "math" + "time" + + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto" + supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto" + warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto" +) + +type PurchaseSupplierRowDTO struct { + ReceiveDate string `json:"receive_date"` + PoDate string `json:"po_date"` + PoNumber string `json:"po_number"` + Product *productDTO.ProductRelationDTO `json:"product,omitempty"` + Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"` + Qty float64 `json:"qty"` + UnitPrice float64 `json:"unit_price"` + PurchaseValue float64 `json:"purchase_value"` + TransportUnitPrice float64 `json:"transport_unit_price"` + TransportValue float64 `json:"transport_value"` + TotalAmount float64 `json:"total_amount"` + Expedition string `json:"expedition"` + DeliveryNumber string `json:"delivery_number"` +} + +type PurchaseSupplierSummaryDTO struct { + TotalQty float64 `json:"total_qty"` + TotalPurchaseValue float64 `json:"total_purchase_value"` + TotalTransportValue float64 `json:"total_transport_value"` + TotalAmount float64 `json:"total_amount"` + TotalUnitPrice float64 `json:"total_unit_price"` + TotalTransportUnitPrice float64 `json:"total_transport_unit_price"` +} + +type PurchaseSupplierDTO struct { + Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"` + Rows []PurchaseSupplierRowDTO `json:"rows"` + Summary PurchaseSupplierSummaryDTO `json:"summary"` +} + +func formatDatePtr(t *time.Time) string { + if t == nil || t.IsZero() { + return "" + } + return t.Format("02-Jan-2006") +} + +func ToPurchaseSupplierRowDTO(item *entity.PurchaseItem) PurchaseSupplierRowDTO { + row := PurchaseSupplierRowDTO{ + ReceiveDate: formatDatePtr(item.ReceivedDate), + Qty: item.TotalQty, + UnitPrice: item.Price, + } + + if item.Purchase != nil { + row.PoDate = formatDatePtr(item.Purchase.PoDate) + if item.Purchase.PoNumber != nil { + row.PoNumber = *item.Purchase.PoNumber + } + } + + if item.Product != nil && item.Product.Id != 0 { + product := productDTO.ToProductRelationDTO(*item.Product) + row.Product = &product + } + + if item.Warehouse != nil && item.Warehouse.Id != 0 { + warehouse := warehouseDTO.ToWarehouseRelationDTO(*item.Warehouse) + row.Warehouse = &warehouse + } + + qty := row.Qty + if qty < 0 { + qty = 0 + } + + row.PurchaseValue = row.UnitPrice * qty + + var transportUnit float64 + var expeditionName string + + if item.ExpenseNonstock != nil { + transportUnit = item.ExpenseNonstock.Price + + if item.ExpenseNonstock.Expense != nil && + item.ExpenseNonstock.Expense.Supplier != nil && + item.ExpenseNonstock.Expense.Supplier.Id != 0 { + expSupplier := item.ExpenseNonstock.Expense.Supplier + expeditionName = expSupplier.Name + } + } + + row.TransportUnitPrice = transportUnit + row.TransportValue = transportUnit * qty + row.TotalAmount = row.PurchaseValue + row.TransportValue + + if expeditionName == "" { + row.Expedition = "-" + } else { + row.Expedition = expeditionName + } + + if item.TravelNumber != nil && *item.TravelNumber != "" { + row.DeliveryNumber = *item.TravelNumber + } else { + row.DeliveryNumber = "-" + } + + return row +} + +func ToPurchaseSupplierDTO(supplier entity.Supplier, items []entity.PurchaseItem) PurchaseSupplierDTO { + var supplierDTORef *supplierDTO.SupplierRelationDTO + if supplier.Id != 0 { + mapped := supplierDTO.ToSupplierRelationDTO(supplier) + supplierDTORef = &mapped + } + + rows := make([]PurchaseSupplierRowDTO, 0, len(items)) + summary := PurchaseSupplierSummaryDTO{} + + var unitPriceSum float64 + var unitPriceCount int + var transportUnitPriceSum float64 + var transportUnitPriceCount int + + for i := range items { + row := ToPurchaseSupplierRowDTO(&items[i]) + rows = append(rows, row) + + summary.TotalQty += row.Qty + summary.TotalPurchaseValue += row.PurchaseValue + summary.TotalTransportValue += row.TransportValue + summary.TotalAmount += row.TotalAmount + + unitPriceSum += row.UnitPrice + unitPriceCount++ + + transportUnitPriceSum += row.TransportUnitPrice + transportUnitPriceCount++ + } + + if unitPriceCount > 0 { + summary.TotalUnitPrice = math.Round(unitPriceSum / float64(unitPriceCount)) + } + + if transportUnitPriceCount > 0 { + summary.TotalTransportUnitPrice = math.Round(transportUnitPriceSum / float64(transportUnitPriceCount)) + } + + return PurchaseSupplierDTO{ + Supplier: supplierDTORef, + Rows: rows, + Summary: summary, + } +} diff --git a/internal/modules/repports/module.go b/internal/modules/repports/module.go index 4479b733..f3798f6a 100644 --- a/internal/modules/repports/module.go +++ b/internal/modules/repports/module.go @@ -7,6 +7,7 @@ import ( commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" approvalService "gitlab.com/mbugroup/lti-api.git/internal/common/service" + repportRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/repositories" sRepport "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/services" expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories" @@ -20,9 +21,10 @@ func (RepportModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate * expenseRealizationRepository := expenseRepo.NewExpenseRealizationRepository(db) marketingDeliveryProductRepository := marketingRepo.NewMarketingDeliveryProductRepository(db) approvalRepository := commonRepo.NewApprovalRepository(db) + purchaseSupplierRepository := repportRepo.NewPurchaseSupplierRepository(db) approvalSvc := approvalService.NewApprovalService(approvalRepository) - repportService := sRepport.NewRepportService(validate, expenseRealizationRepository, marketingDeliveryProductRepository, approvalSvc) + repportService := sRepport.NewRepportService(validate, expenseRealizationRepository, marketingDeliveryProductRepository, approvalSvc, purchaseSupplierRepository) RepportRoutes(router, repportService) } diff --git a/internal/modules/repports/repositories/purchase_supplier.repository.go b/internal/modules/repports/repositories/purchase_supplier.repository.go new file mode 100644 index 00000000..979623fc --- /dev/null +++ b/internal/modules/repports/repositories/purchase_supplier.repository.go @@ -0,0 +1,195 @@ +package repositories + +import ( + "context" + "fmt" + "strings" + + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/validations" + "gitlab.com/mbugroup/lti-api.git/internal/utils" + + "gorm.io/gorm" +) + +type PurchaseSupplierRepository interface { + GetSuppliersWithPurchases(ctx context.Context, offset, limit int, filters *validation.PurchaseSupplierQuery) ([]entity.Supplier, int64, error) + GetItemsBySuppliers(ctx context.Context, supplierIDs []uint, filters *validation.PurchaseSupplierQuery) ([]entity.PurchaseItem, error) +} + +type purchaseSupplierRepositoryImpl struct { + db *gorm.DB +} + +func NewPurchaseSupplierRepository(db *gorm.DB) PurchaseSupplierRepository { + return &purchaseSupplierRepositoryImpl{db: db} +} + +func (r *purchaseSupplierRepositoryImpl) baseSupplierQuery(ctx context.Context, filters *validation.PurchaseSupplierQuery) *gorm.DB { + dateColumn := "purchase_items.received_date" + switch strings.ToLower(strings.TrimSpace(filters.FilterBy)) { + case "po_date": + dateColumn = "purchases.po_date" + case "receive_date", "": + dateColumn = "purchase_items.received_date" + } + + db := r.db.WithContext(ctx). + Model(&entity.Supplier{}). + Joins("JOIN purchases ON purchases.supplier_id = suppliers.id"). + Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id") + + if filters.SupplierId > 0 { + db = db.Where("suppliers.id = ?", filters.SupplierId) + } + + if filters.ProductId > 0 { + db = db.Where("purchase_items.product_id = ?", filters.ProductId) + } + + if filters.ProductCategoryId > 0 { + db = db. + Joins("JOIN products ON products.id = purchase_items.product_id"). + Where("products.product_category_id = ?", filters.ProductCategoryId) + } + + if filters.AreaId > 0 { + db = db. + Joins("JOIN warehouses ON warehouses.id = purchase_items.warehouse_id"). + Where("warehouses.area_id = ?", filters.AreaId) + } + + if filters.StartDate != "" { + if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil { + db = db.Where(fmt.Sprintf("DATE(%s) >= ?", dateColumn), dateFrom) + } + } + + if filters.EndDate != "" { + if dateTo, err := utils.ParseDateString(filters.EndDate); err == nil { + db = db.Where(fmt.Sprintf("DATE(%s) <= ?", dateColumn), dateTo) + } + } + + return db +} + +func (r *purchaseSupplierRepositoryImpl) GetSuppliersWithPurchases(ctx context.Context, offset, limit int, filters *validation.PurchaseSupplierQuery) ([]entity.Supplier, int64, error) { + query := r.baseSupplierQuery(ctx, filters) + + var totalSuppliers int64 + if err := query. + Distinct("suppliers.id"). + Count(&totalSuppliers).Error; err != nil { + return nil, 0, err + } + + if totalSuppliers == 0 { + return []entity.Supplier{}, 0, nil + } + + if offset < 0 { + offset = 0 + } + + var supplierIDs []uint + if err := query. + Select("suppliers.id"). + Order("suppliers.id ASC"). + Offset(offset). + Limit(limit). + Pluck("suppliers.id", &supplierIDs).Error; err != nil { + return nil, 0, err + } + + if len(supplierIDs) == 0 { + return []entity.Supplier{}, totalSuppliers, nil + } + + var suppliers []entity.Supplier + if err := r.db.WithContext(ctx). + Where("id IN ?", supplierIDs). + Find(&suppliers).Error; err != nil { + return nil, 0, err + } + + return suppliers, totalSuppliers, nil +} + +func (r *purchaseSupplierRepositoryImpl) GetItemsBySuppliers(ctx context.Context, supplierIDs []uint, filters *validation.PurchaseSupplierQuery) ([]entity.PurchaseItem, error) { + if len(supplierIDs) == 0 { + return []entity.PurchaseItem{}, nil + } + + // Tentukan kolom tanggal yang akan dipakai untuk filter & sort + dateColumn := "purchase_items.received_date" + switch strings.ToLower(strings.TrimSpace(filters.FilterBy)) { + case "po_date": + dateColumn = "purchases.po_date" + case "receive_date", "": + dateColumn = "purchase_items.received_date" + } + + orderDirection := "ASC" + switch strings.ToUpper(strings.TrimSpace(filters.SortBy)) { + case "DESC": + orderDirection = "DESC" + case "ASC", "": + orderDirection = "ASC" + } + + db := r.db.WithContext(ctx). + Model(&entity.PurchaseItem{}). + Preload("Purchase"). + Preload("Purchase.Supplier"). + Preload("Product"). + Preload("Product.ProductCategory"). + Preload("Warehouse"). + Preload("Warehouse.Area"). + Preload("Warehouse.Location"). + Preload("Warehouse.Kandang"). + Preload("ExpenseNonstock"). + Preload("ExpenseNonstock.Expense"). + Preload("ExpenseNonstock.Expense.Supplier"). + Joins("JOIN purchases ON purchases.id = purchase_items.purchase_id"). + Where("purchases.supplier_id IN ?", supplierIDs) + + if filters.ProductId > 0 { + db = db.Where("purchase_items.product_id = ?", filters.ProductId) + } + + if filters.ProductCategoryId > 0 { + db = db. + Joins("JOIN products ON products.id = purchase_items.product_id"). + Where("products.product_category_id = ?", filters.ProductCategoryId) + } + + if filters.AreaId > 0 { + db = db. + Joins("JOIN warehouses ON warehouses.id = purchase_items.warehouse_id"). + Where("warehouses.area_id = ?", filters.AreaId) + } + + if filters.StartDate != "" { + if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil { + db = db.Where(fmt.Sprintf("DATE(%s) >= ?", dateColumn), dateFrom) + } + } + + if filters.EndDate != "" { + if dateTo, err := utils.ParseDateString(filters.EndDate); err == nil { + db = db.Where(fmt.Sprintf("DATE(%s) <= ?", dateColumn), dateTo) + } + } + + // Urutkan berdasarkan kolom tanggal yang dipilih dan arah sort + db = db.Order(fmt.Sprintf("%s %s", dateColumn, orderDirection)). + Order("purchase_items.id ASC") + + var items []entity.PurchaseItem + if err := db.Find(&items).Error; err != nil { + return nil, err + } + + return items, nil +} diff --git a/internal/modules/repports/route.go b/internal/modules/repports/route.go index 4aea831c..d24caac5 100644 --- a/internal/modules/repports/route.go +++ b/internal/modules/repports/route.go @@ -14,4 +14,5 @@ func RepportRoutes(v1 fiber.Router, s repport.RepportService) { route.Get("/expense", ctrl.GetExpense) route.Get("/marketing", ctrl.GetMarketing) + route.Get("/purchase-supplier", ctrl.GetPurchaseSupplier) } diff --git a/internal/modules/repports/services/repport.service.go b/internal/modules/repports/services/repport.service.go index 3adc5c0a..aa649871 100644 --- a/internal/modules/repports/services/repport.service.go +++ b/internal/modules/repports/services/repport.service.go @@ -2,6 +2,7 @@ package service import ( "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto" + repportRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" @@ -10,6 +11,8 @@ import ( expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories" marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" "github.com/sirupsen/logrus" @@ -19,6 +22,7 @@ import ( type RepportService interface { GetExpense(ctx *fiber.Ctx, params *validation.ExpenseQuery) ([]dto.RepportExpenseListDTO, int64, error) GetMarketing(ctx *fiber.Ctx, params *validation.MarketingQuery) ([]dto.RepportMarketingListDTO, int64, error) + GetPurchaseSupplier(ctx *fiber.Ctx, params *validation.PurchaseSupplierQuery) ([]dto.PurchaseSupplierDTO, int64, error) } type repportService struct { @@ -27,15 +31,23 @@ type repportService struct { ExpenseRealizationRepo expenseRepo.ExpenseRealizationRepository MarketingDeliveryRepo marketingRepo.MarketingDeliveryProductRepository ApprovalSvc approvalService.ApprovalService + PurchaseSupplierRepo repportRepo.PurchaseSupplierRepository } -func NewRepportService(validate *validator.Validate, expenseRealizationRepo expenseRepo.ExpenseRealizationRepository, marketingDeliveryRepo marketingRepo.MarketingDeliveryProductRepository, approvalSvc approvalService.ApprovalService) RepportService { +func NewRepportService( + validate *validator.Validate, + expenseRealizationRepo expenseRepo.ExpenseRealizationRepository, + marketingDeliveryRepo marketingRepo.MarketingDeliveryProductRepository, + approvalSvc approvalService.ApprovalService, + purchaseSupplierRepo repportRepo.PurchaseSupplierRepository, +) RepportService { return &repportService{ Log: utils.Log, Validate: validate, ExpenseRealizationRepo: expenseRealizationRepo, MarketingDeliveryRepo: marketingDeliveryRepo, ApprovalSvc: approvalSvc, + PurchaseSupplierRepo: purchaseSupplierRepo, } } @@ -113,3 +125,58 @@ func (s *repportService) GetMarketing(c *fiber.Ctx, params *validation.Marketing return dto.ToRepportMarketingListDTOs(deliveryProducts), total, nil } + +func (s *repportService) GetPurchaseSupplier(c *fiber.Ctx, params *validation.PurchaseSupplierQuery) ([]dto.PurchaseSupplierDTO, int64, error) { + if err := s.Validate.Struct(params); err != nil { + return nil, 0, err + } + + offset := (params.Page - 1) * params.Limit + if offset < 0 { + offset = 0 + } + + suppliers, totalSuppliers, err := s.PurchaseSupplierRepo.GetSuppliersWithPurchases(c.Context(), offset, params.Limit, params) + if err != nil { + return nil, 0, err + } + + if totalSuppliers == 0 || len(suppliers) == 0 { + return []dto.PurchaseSupplierDTO{}, totalSuppliers, nil + } + + supplierMap := make(map[uint]entity.Supplier, len(suppliers)) + supplierIDs := make([]uint, 0, len(suppliers)) + for _, supplier := range suppliers { + supplierMap[supplier.Id] = supplier + supplierIDs = append(supplierIDs, supplier.Id) + } + + items, err := s.PurchaseSupplierRepo.GetItemsBySuppliers(c.Context(), supplierIDs, params) + if err != nil { + return nil, 0, err + } + + itemsBySupplier := make(map[uint][]entity.PurchaseItem) + for _, item := range items { + if item.Purchase == nil { + continue + } + supplierID := item.Purchase.SupplierId + itemsBySupplier[supplierID] = append(itemsBySupplier[supplierID], item) + } + + result := make([]dto.PurchaseSupplierDTO, 0, len(supplierIDs)) + for _, supplierID := range supplierIDs { + supplier, exists := supplierMap[supplierID] + if !exists { + continue + } + + supplierItems := itemsBySupplier[supplierID] + dtoItem := dto.ToPurchaseSupplierDTO(supplier, supplierItems) + result = append(result, dtoItem) + } + + return result, totalSuppliers, nil +} diff --git a/internal/modules/repports/validations/repport.validation.go b/internal/modules/repports/validations/repport.validation.go index 7efc51f9..a69e7716 100644 --- a/internal/modules/repports/validations/repport.validation.go +++ b/internal/modules/repports/validations/repport.validation.go @@ -27,3 +27,16 @@ type MarketingQuery struct { SalesPersonId int64 `query:"sales_person_id" validate:"omitempty"` MarketingId int64 `query:"marketing_id" validate:"omitempty"` } + +type PurchaseSupplierQuery struct { + Page int `query:"page" validate:"omitempty,min=1,gt=0"` + Limit int `query:"limit" validate:"omitempty,min=1,gt=0"` + AreaId int64 `query:"area_id" validate:"omitempty"` + SupplierId int64 `query:"supplier_id" validate:"omitempty"` + ProductId int64 `query:"product_id" validate:"omitempty"` + ProductCategoryId int64 `query:"product_category_id" validate:"omitempty"` + StartDate string `query:"start_date" validate:"omitempty"` + EndDate string `query:"end_date" validate:"omitempty"` + SortBy string `query:"sort_by" validate:"omitempty"` + FilterBy string `query:"filter_by" validate:"omitempty"` +} diff --git a/internal/response/response.go b/internal/response/response.go index c4ecca0f..710d320e 100644 --- a/internal/response/response.go +++ b/internal/response/response.go @@ -14,10 +14,11 @@ type Success struct { } type Meta struct { - Page int `json:"page"` - Limit int `json:"limit"` - TotalPages int64 `json:"total_pages"` - TotalResults int64 `json:"total_results"` + Page int `json:"page"` + Limit int `json:"limit"` + TotalPages int64 `json:"total_pages"` + TotalResults int64 `json:"total_results"` + Filters interface{} `json:"filters,omitempty"` } type SuccessWithPaginate[T any] struct { diff --git a/internal/utils/constant.go b/internal/utils/constant.go index b09bc187..d4f6ec02 100644 --- a/internal/utils/constant.go +++ b/internal/utils/constant.go @@ -29,6 +29,18 @@ const ( FlagVitamin FlagType = "VITAMIN" FlagKimia FlagType = "KIMIA" FlagEkspedisi FlagType = "EKSPEDISI" + + // flag ayam + FlagAyamAfkir FlagType = "AYAM-AFKIR" + FlagAyamCulling FlagType = "AYAM-CULLING" + FlagAyamMati FlagType = "AYAM-MATI" + + //flag telur + FlagTelur FlagType = "TELUR" + FlagTelurUtuh FlagType = "TELUR-UTUH" + FlagTelurPecah FlagType = "TELUR-PECAH" + FlagTelurPutih FlagType = "TELUR-PUTIH" + FlagTelurRetak FlagType = "TELUR-RETAK" ) const ( @@ -221,8 +233,8 @@ const ( ) var RecordingApprovalSteps = map[approvalutils.ApprovalStep]string{ - RecordingStepPengajuan: "Pengajuan", - RecordingStepDisetujui: "Disetujui", + RecordingStepPengajuan: "Pengajuan", + RecordingStepDisetujui: "Disetujui", } // -------------------------------------------------------------------