From 590df26a1f5d7d1de04a1110454c586b3a540c48 Mon Sep 17 00:00:00 2001 From: MacBook Air M1 Date: Tue, 13 Jan 2026 14:43:37 +0700 Subject: [PATCH] adjust api closing production data --- .../controllers/closing.controller.go | 12 +- internal/modules/closings/dto/closing.dto.go | 33 ++- internal/modules/closings/module.go | 5 +- .../repositories/closing.repository.go | 18 ++ .../closings/services/closing.service.go | 214 +++++++++++++++--- .../repositories/recording.repository.go | 73 ++++++ 6 files changed, 313 insertions(+), 42 deletions(-) diff --git a/internal/modules/closings/controllers/closing.controller.go b/internal/modules/closings/controllers/closing.controller.go index 6ab2d398..8f129521 100644 --- a/internal/modules/closings/controllers/closing.controller.go +++ b/internal/modules/closings/controllers/closing.controller.go @@ -338,7 +338,17 @@ func (u *ClosingController) GetClosingDataProduksi(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Invalid projectFlockId") } - result, err := u.ClosingService.GetClosingDataProduksi(c, uint(id)) + var kandangID *uint + if raw := c.Query("kandang_id"); raw != "" { + kandangInt, convErr := strconv.Atoi(raw) + if convErr != nil || kandangInt <= 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid kandang_id") + } + kandangUint := uint(kandangInt) + kandangID = &kandangUint + } + + result, err := u.ClosingService.GetClosingDataProduksi(c, uint(id), kandangID) if err != nil { return err } diff --git a/internal/modules/closings/dto/closing.dto.go b/internal/modules/closings/dto/closing.dto.go index ac172c83..05a606ca 100644 --- a/internal/modules/closings/dto/closing.dto.go +++ b/internal/modules/closings/dto/closing.dto.go @@ -65,33 +65,44 @@ type ClosingPurchaseDTO struct { FinalPopulation int `json:"final_population"` FeedIn float64 `json:"feed_in"` FeedUsed float64 `json:"feed_used"` - FeedUsedPerHead float64 `json:"feed_used_per_head"` + // 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"` + AverageWeight float64 `json:"avg_weight"` + AverageSellingPrice float64 `json:"avg_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"` + EggMassKg float64 `json:"egg_mass"` + AverageEggWeightKg float64 `json:"avg_egg_weight"` + AverageSellingPrice float64 `json:"avg_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"` + MortalityStd float64 `json:"mor_std"` + MortalityAct float64 `json:"mor_act"` + DeffMortality float64 `json:"mor_diff"` FcrStd float64 `json:"fcr_std"` FcrAct float64 `json:"fcr_act"` - DeffFcr float64 `json:"deff_fcr"` - Awg float64 `json:"awg"` + DeffFcr float64 `json:"fcr_diff"` + AwgAct float64 `json:"awg_act"` + AwgStd float64 `json:"awg_std"` + FeedIntake float64 `json:"feed_intake"` + FeedIntakeStd float64 `json:"feed_intake_std"` + HenDayAct *float64 `json:"hen_day_act,omitempty"` + HendayStd *float64 `json:"hen_day_std,omitempty"` + EggMass *float64 `json:"egg_mass,omitempty"` + EggMassStd *float64 `json:"egg_mass_std,omitempty"` + EggWeight *float64 `json:"egg_weight,omitempty"` + EggWeightStd *float64 `json:"egg_weight_std,omitempty"` + HenHouseAct *float64 `json:"hen_housed_act,omitempty"` + HenHouseStd *float64 `json:"hen_housed_std,omitempty"` } type ClosingSalesGroupDTO struct { diff --git a/internal/modules/closings/module.go b/internal/modules/closings/module.go index c89e6125..87ce815a 100644 --- a/internal/modules/closings/module.go +++ b/internal/modules/closings/module.go @@ -11,6 +11,7 @@ import ( sClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/services" rExpenseRealization "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories" rMarketings "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories" + rProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories" rChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories" rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" rRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories" @@ -33,11 +34,13 @@ func (ClosingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate * expenseRealizationRepo := rExpenseRealization.NewExpenseRealizationRepository(db) chickinRepo := rChickin.NewChickinRepository(db) recordingRepo := rRecording.NewRecordingRepository(db) + standardGrowthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db) + productionStandardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db) purchaseRepo := rPurchase.NewPurchaseRepository(db) approvalRepo := commonRepo.NewApprovalRepository(db) approvalService := commonSvc.NewApprovalService(approvalRepo) - closingService := sClosing.NewClosingService(closingRepo, projectFlockRepo, marketingRepo, marketingDeliveryProductRepo, approvalService, expenseRealizationRepo, projectBudgetRepo, chickinRepo, purchaseRepo, recordingRepo, validate) + closingService := sClosing.NewClosingService(closingRepo, projectFlockRepo, marketingRepo, marketingDeliveryProductRepo, approvalService, expenseRealizationRepo, projectBudgetRepo, chickinRepo, purchaseRepo, recordingRepo, standardGrowthDetailRepo, productionStandardDetailRepo, validate) sapronakService := sClosing.NewSapronakService(closingRepo, projectFlockKandangRepo, validate) userService := sUser.NewUserService(userRepo, validate) diff --git a/internal/modules/closings/repositories/closing.repository.go b/internal/modules/closings/repositories/closing.repository.go index 9d08d083..2ce3e496 100644 --- a/internal/modules/closings/repositories/closing.repository.go +++ b/internal/modules/closings/repositories/closing.repository.go @@ -18,6 +18,7 @@ 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) + SumProjectChickinUsageByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (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) @@ -166,6 +167,23 @@ func (r *ClosingRepositoryImpl) SumFeedPurchaseAndUsedByProjectFlockKandangIDs(c return purchaseAgg.TotalIn, usageAgg.TotalUsed, nil } +func (r *ClosingRepositoryImpl) SumProjectChickinUsageByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) { + if len(projectFlockKandangIDs) == 0 { + return 0, nil + } + + var total float64 + if err := r.DB().WithContext(ctx). + Model(&entity.ProjectChickin{}). + Where("project_flock_kandang_id IN ?", projectFlockKandangIDs). + Select("COALESCE(SUM(usage_qty), 0)"). + Scan(&total).Error; err != nil { + return 0, err + } + + return total, nil +} + func (r *ClosingRepositoryImpl) SumClaimCullingByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) { if len(projectFlockKandangIDs) == 0 { return 0, nil diff --git a/internal/modules/closings/services/closing.service.go b/internal/modules/closings/services/closing.service.go index 245fd24c..8b00475f 100644 --- a/internal/modules/closings/services/closing.service.go +++ b/internal/modules/closings/services/closing.service.go @@ -16,6 +16,7 @@ import ( expenseRealizationRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories" marketingDeliveryProductRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories" marketingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories" + productionStandardRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories" chickinRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories" projectflockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" recordingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories" @@ -35,7 +36,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) + GetClosingDataProduksi(ctx *fiber.Ctx, projectFlockID uint, kandangID *uint) (*dto.ClosingProductionReportDTO, error) GetClosingSapronak(ctx *fiber.Ctx, projectFlockID uint, params *validation.ClosingSapronakQuery) ([]dto.ClosingSapronakItemDTO, int64, error) GetClosingKeuangan(ctx *fiber.Ctx, projectFlockID uint) (*dto.ReportResponse, error) GetExpeditionHPP(ctx *fiber.Ctx, projectFlockID uint, projectFlockKandangID *uint) (*dto.ExpeditionHPPDTO, error) @@ -54,9 +55,11 @@ type closingService struct { ChickinRepo chickinRepository.ProjectChickinRepository PurchaseRepo purchaseRepository.PurchaseRepository RecordingRepo recordingRepository.RecordingRepository + StandardGrowthDetailRepo productionStandardRepository.StandardGrowthDetailRepository + ProductionStandardDetailRepo productionStandardRepository.ProductionStandardDetailRepository } -func NewClosingService(repo repository.ClosingRepository, projectFlockRepo projectflockRepository.ProjectflockRepository, marketingRepo marketingRepository.MarketingRepository, marketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository, approvalSvc commonSvc.ApprovalService, expenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository, projectBudgetRepo projectflockRepository.ProjectBudgetRepository, chickinRepo chickinRepository.ProjectChickinRepository, purchaseRepo purchaseRepository.PurchaseRepository, recordingRepo recordingRepository.RecordingRepository, validate *validator.Validate) ClosingService { +func NewClosingService(repo repository.ClosingRepository, projectFlockRepo projectflockRepository.ProjectflockRepository, marketingRepo marketingRepository.MarketingRepository, marketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository, approvalSvc commonSvc.ApprovalService, expenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository, projectBudgetRepo projectflockRepository.ProjectBudgetRepository, chickinRepo chickinRepository.ProjectChickinRepository, purchaseRepo purchaseRepository.PurchaseRepository, recordingRepo recordingRepository.RecordingRepository, standardGrowthDetailRepo productionStandardRepository.StandardGrowthDetailRepository, productionStandardDetailRepo productionStandardRepository.ProductionStandardDetailRepository, validate *validator.Validate) ClosingService { return &closingService{ Log: utils.Log, Validate: validate, @@ -70,6 +73,8 @@ func NewClosingService(repo repository.ClosingRepository, projectFlockRepo proje ChickinRepo: chickinRepo, PurchaseRepo: purchaseRepo, RecordingRepo: recordingRepo, + StandardGrowthDetailRepo: standardGrowthDetailRepo, + ProductionStandardDetailRepo: productionStandardDetailRepo, } } @@ -231,7 +236,7 @@ func (s closingService) GetClosingSapronak(c *fiber.Ctx, projectFlockID uint, pa var projectFlockKandangIDs []uint if params.Type == validation.SapronakTypeOutgoing { - projectFlockKandangIDs, err = s.getProjectFlockKandangIDs(c.Context(), projectFlockID) + projectFlockKandangIDs, err = s.getProjectFlockKandangIDs(c.Context(), projectFlockID, nil) if err != nil { s.Log.Errorf("Failed to fetch project flock kandang IDs for project flock %d: %+v", projectFlockID, err) return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandang") @@ -311,12 +316,15 @@ func (s closingService) getWarehouseIDsByProjectFlock(ctx context.Context, proje return ids, nil } -func (s closingService) getProjectFlockKandangIDs(ctx context.Context, projectFlockID uint) ([]uint, error) { +func (s closingService) getProjectFlockKandangIDs(ctx context.Context, projectFlockID uint, kandangID *uint) ([]uint, error) { var ids []uint - err := s.Repository.DB().WithContext(ctx). + query := s.Repository.DB().WithContext(ctx). Model(&entity.ProjectFlockKandang{}). - Where("project_flock_id = ?", projectFlockID). - Pluck("id", &ids).Error + Where("project_flock_id = ?", projectFlockID) + if kandangID != nil { + query = query.Where("kandang_id = ?", *kandangID) + } + err := query.Order("id ASC").Pluck("id", &ids).Error if err != nil { return nil, err } @@ -520,12 +528,22 @@ 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) { +func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint, kandangID *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) + projectFlockKandangIDs, err := s.getProjectFlockKandangIDs(c.Context(), projectFlockID, kandangID) + 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") + } + + if len(projectFlockKandangIDs) == 0 { + return nil, fiber.NewError(fiber.StatusNotFound, "No project flock kandang found") + } + + project, err := s.Repository.GetByID(c.Context(), projectFlockID, s.withRelations) if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Project flock not found") } @@ -534,19 +552,29 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint 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 - } + population, err := s.Repository.SumProjectChickinUsageByProjectFlockKandangIDs(c.Context(), projectFlockKandangIDs) + if err != nil { + s.Log.Errorf("Failed to sum population for project flock %d: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch population data") } isGrowing := strings.EqualFold(project.Category, string(utils.ProjectFlockCategoryGrowing)) - projectFlockKandangIDs, err := s.getProjectFlockKandangIDs(c.Context(), projectFlockID) + currentWeek, err := s.determineProductionWeek(c.Context(), projectFlockKandangIDs) 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") + s.Log.Errorf("Failed to determine production week for project flock %d: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to determine production week") + } + + targetAverages, err := s.RecordingRepo.GetAverageTargetMetricsByProjectFlockKandangID(c.Context(), projectFlockKandangIDs[0], !isGrowing) + if err != nil { + s.Log.Errorf("Failed to calculate target metrics for project flock %d: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch target metrics data") + } + var fcrActFromRecording *float64 + if targetAverages.FcrCount > 0 { + fcrAvg := targetAverages.FcrAvg + fcrActFromRecording = &fcrAvg } feedIn, feedUsed, err := s.Repository.SumFeedPurchaseAndUsedByProjectFlockKandangIDs(c.Context(), projectFlockKandangIDs) @@ -555,6 +583,30 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch feed purchase data") } + averageFeedIntake := targetAverages.FeedIntakeAvg + + feedIntakeStd := 0.0 + if project.ProductionStandardId > 0 && currentWeek > 0 && s.StandardGrowthDetailRepo != nil { + feedIntakeStd, err = s.calculateFeedIntakeStd(c.Context(), project.ProductionStandardId, currentWeek) + if err != nil { + s.Log.Errorf("Failed to compute feed intake std for project flock %d: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch feed intake standard data") + } + } + + var productionStandardDetail *entity.ProductionStandardDetail + if project.ProductionStandardId > 0 && currentWeek > 0 && s.ProductionStandardDetailRepo != nil { + productionStandardDetail, err = s.ProductionStandardDetailRepo.GetByStandardIDAndWeek(c.Context(), project.ProductionStandardId, currentWeek) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + productionStandardDetail = nil + } else { + s.Log.Errorf("Failed to fetch production standard detail for project flock %d: %+v", projectFlockID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch production standard detail 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) @@ -577,10 +629,10 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch sales age data") } - feedUsedPerHead := 0.0 - if population > 0 { - feedUsedPerHead = feedUsed / population - } + // feedUsedPerHead := 0.0 + // if population > 0 { + // feedUsedPerHead = feedUsed / population + // } purchase := dto.ClosingPurchaseDTO{ InitialPopulation: int(population), @@ -588,7 +640,7 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint FinalPopulation: int(finalPopulation), FeedIn: feedIn, FeedUsed: feedUsed, - FeedUsedPerHead: feedUsedPerHead, + // FeedUsedPerHead: feedUsedPerHead, } chickenFlagNames := []string{string(utils.FlagPullet)} @@ -621,6 +673,9 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint } chickenPerformance := calculatePerformanceMetrics(chickenAverageWeight, chickenSalesWeight, feedUsed, population, chickenDepletion, age, standards) + if fcrActFromRecording != nil { + chickenPerformance.FcrAct = *fcrActFromRecording + } var eggSales *dto.ClosingEggSalesDTO var eggPerformance *dto.ClosingPerformanceDTO @@ -668,6 +723,9 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint } eggPerf := calculatePerformanceMetrics(averageEggWeight, eggSalesWeight, feedUsed, harvestEggQty, eggDepletion, age, standards) + if fcrActFromRecording != nil { + eggPerf.FcrAct = *fcrActFromRecording + } eggPerformance = &eggPerf } @@ -684,15 +742,55 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint DeffMortality: chickenPerformance.DeffMortality, } if eggPerformance != nil { - performance.FcrStd = eggPerformance.FcrStd + // performance.FcrStd = eggPerformance.FcrStd performance.FcrAct = eggPerformance.FcrAct - performance.DeffFcr = eggPerformance.DeffFcr - performance.Awg = eggPerformance.Awg + // performance.DeffFcr = eggPerformance.DeffFcr + performance.AwgAct = eggPerformance.AwgAct } else { - performance.FcrStd = chickenPerformance.FcrStd + // performance.FcrStd = chickenPerformance.FcrStd performance.FcrAct = chickenPerformance.FcrAct - performance.DeffFcr = chickenPerformance.DeffFcr - performance.Awg = chickenPerformance.Awg + // performance.DeffFcr = chickenPerformance.DeffFcr + performance.AwgAct = chickenPerformance.AwgAct + } + performance.FeedIntake = averageFeedIntake + performance.FeedIntakeStd = feedIntakeStd + if !isGrowing { + if targetAverages.HenDayCount > 0 { + henDayAct := targetAverages.HenDayAvg + performance.HenDayAct = &henDayAct + } + if targetAverages.HenHouseCount > 0 { + henHouseAct := targetAverages.HenHouseAvg + performance.HenHouseAct = &henHouseAct + } + if targetAverages.EggWeightCount > 0 { + eggWeight := targetAverages.EggWeightAvg + performance.EggWeight = &eggWeight + } + if targetAverages.EggMassCount > 0 { + eggMass := targetAverages.EggMassAvg + performance.EggMass = &eggMass + } + } + performance.DeffFcr = performance.FcrStd - performance.FcrAct + if productionStandardDetail != nil { + if productionStandardDetail.StandardFCR != nil { + performance.FcrStd = *productionStandardDetail.StandardFCR + } + if !isGrowing { + if productionStandardDetail.TargetHenDayProduction != nil { + performance.HendayStd = productionStandardDetail.TargetHenDayProduction + } + if productionStandardDetail.TargetHenHouseProduction != nil { + performance.HenHouseStd = productionStandardDetail.TargetHenHouseProduction + } + if productionStandardDetail.TargetEggWeight != nil { + performance.EggWeightStd = productionStandardDetail.TargetEggWeight + } + if productionStandardDetail.TargetEggMass != nil { + performance.EggMassStd = productionStandardDetail.TargetEggMass + } + } } result := dto.ClosingProductionReportDTO{ @@ -738,6 +836,64 @@ func (s closingService) calculateAverageSalesAge(ctx context.Context, projectFlo return totalAgeWeeks / totalQty, nil } +func (s closingService) determineProductionWeek(ctx context.Context, projectFlockKandangIDs []uint) (int, error) { + if len(projectFlockKandangIDs) == 0 { + return 0, nil + } + + firstKandangID := projectFlockKandangIDs[0] + + var chickin entity.ProjectChickin + if err := s.Repository.DB().WithContext(ctx). + Where("project_flock_kandang_id = ?", firstKandangID). + Order("chick_in_date ASC"). + First(&chickin).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return 0, nil + } + return 0, err + } + + recording, err := s.RecordingRepo.GetLatestByProjectFlockKandangID(ctx, firstKandangID) + if err != nil { + return 0, err + } + if recording == nil { + return 0, nil + } + + if recording.RecordDatetime.Before(chickin.ChickInDate) { + return 0, nil + } + + elapsed := recording.RecordDatetime.Sub(chickin.ChickInDate) + weekFloat := elapsed.Hours() / (24 * 7) + week := int(math.Ceil(weekFloat)) + if week <= 0 { + week = 1 + } + + return week, nil +} + +func (s closingService) calculateFeedIntakeStd(ctx context.Context, productionStandardID uint, week int) (float64, error) { + if productionStandardID == 0 || week <= 0 || s.StandardGrowthDetailRepo == nil { + return 0, nil + } + + detail, err := s.StandardGrowthDetailRepo.GetByStandardIDAndWeek(ctx, productionStandardID, week) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return 0, nil + } + return 0, err + } + if detail == nil || detail.FeedIntake == nil { + return 0, nil + } + return *detail.FeedIntake, nil +} + func calculatePerformanceMetrics(averageWeight, totalWeight, feedUsed, basePopulation, depletion, age float64, standards []entity.FcrStandard) dto.ClosingPerformanceDTO { mortalityStd, fcrStd := closestFcrValues(standards, averageWeight) @@ -768,7 +924,7 @@ func calculatePerformanceMetrics(averageWeight, totalWeight, feedUsed, basePopul FcrStd: fcrStd, FcrAct: fcrAct, DeffFcr: deffFcr, - Awg: awg, + AwgAct: awg, } } diff --git a/internal/modules/production/recordings/repositories/recording.repository.go b/internal/modules/production/recordings/repositories/recording.repository.go index 941d4507..d15b934d 100644 --- a/internal/modules/production/recordings/repositories/recording.repository.go +++ b/internal/modules/production/recordings/repositories/recording.repository.go @@ -48,12 +48,28 @@ type RecordingRepository interface { GetTotalDepletionByProjectFlockID(ctx context.Context, projectFlockID uint) (totalDepletion float64, err error) GetLatestAvgWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (avgWeight float64, err error) GetTotalEggProductionWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (totalWeightKg float64, err error) + GetAverageTargetMetricsByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint, includeTargets bool) (RecordingTargetAverages, error) } type RecordingRepositoryImpl struct { *repository.BaseRepositoryImpl[entity.Recording] } +type RecordingTargetAverages struct { + HenDayAvg float64 + HenDayCount int64 + HenHouseAvg float64 + HenHouseCount int64 + EggWeightAvg float64 + EggWeightCount int64 + EggMassAvg float64 + EggMassCount int64 + FeedIntakeAvg float64 + FeedIntakeCount int64 + FcrAvg float64 + FcrCount int64 +} + func NewRecordingRepository(db *gorm.DB) RecordingRepository { return &RecordingRepositoryImpl{ BaseRepositoryImpl: repository.NewBaseRepository[entity.Recording](db), @@ -433,6 +449,63 @@ func (r *RecordingRepositoryImpl) GetTotalEggProductionWeightByProjectFlockID(ct return result, err } +func (r *RecordingRepositoryImpl) GetAverageTargetMetricsByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint, includeTargets bool) (RecordingTargetAverages, error) { + var row struct { + HenDayTotal float64 + HenHouseTotal float64 + EggWeightTotal float64 + EggMassTotal float64 + FeedIntakeTotal float64 + FcrTotal float64 + TotalCount int64 + } + + selectParts := []string{ + "COALESCE(SUM(feed_intake), 0) AS feed_intake_total", + "COALESCE(SUM(fcr_value), 0) AS fcr_total", + "COUNT(*) AS total_count", + } + if includeTargets { + selectParts = append([]string{ + "COALESCE(SUM(hen_day), 0) AS hen_day_total", + "COALESCE(SUM(hen_house), 0) AS hen_house_total", + "COALESCE(SUM(egg_weight), 0) AS egg_weight_total", + "COALESCE(SUM(egg_mass), 0) AS egg_mass_total", + }, selectParts...) + } + + if err := r.DB().WithContext(ctx). + Table("recordings"). + Select(strings.Join(selectParts, ", ")). + Where("project_flock_kandangs_id = ? AND deleted_at IS NULL", projectFlockKandangID). + Scan(&row).Error; err != nil { + return RecordingTargetAverages{}, err + } + + result := RecordingTargetAverages{ + FeedIntakeCount: row.TotalCount, + FcrCount: row.TotalCount, + } + if includeTargets { + result.HenDayCount = row.TotalCount + result.HenHouseCount = row.TotalCount + result.EggWeightCount = row.TotalCount + result.EggMassCount = row.TotalCount + } + if row.TotalCount > 0 { + if includeTargets { + result.HenDayAvg = row.HenDayTotal / float64(row.TotalCount) + result.HenHouseAvg = row.HenHouseTotal / float64(row.TotalCount) + result.EggWeightAvg = row.EggWeightTotal / float64(row.TotalCount) + result.EggMassAvg = row.EggMassTotal / float64(row.TotalCount) + } + result.FeedIntakeAvg = row.FeedIntakeTotal / float64(row.TotalCount) + result.FcrAvg = row.FcrTotal / float64(row.TotalCount) + } + + return result, nil +} + func nextRecordingDay(days []int) int { if len(days) == 0 { return 1