feat[BE-375]: get api closing data produksi

This commit is contained in:
MacBook Air M1
2025-12-18 13:32:48 +07:00
parent 21d22c20a3
commit d675b1e826
5 changed files with 451 additions and 0 deletions
@@ -3,6 +3,7 @@ package service
import (
"context"
"errors"
"math"
"strconv"
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
@@ -30,6 +31,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.SapronakQuery) ([]dto.ClosingSapronakItemDTO, int64, error)
}
@@ -379,3 +381,219 @@ func (s closingService) GetOverhead(c *fiber.Ctx, projectFlockID uint) (*dto.Ove
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
}
}
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 juga
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
}
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
}
chickenSales := dto.ClosingSalesDTO{
SalesPopulation: int(chickenSalesQty),
SalesWeight: chickenSalesWeight,
AverageWeight: chickenAverageWeight,
AverageSellingPrice: chickenAverageSellingPrice,
}
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")
}
chickenDepletion := population - chickenSalesQty
if chickenDepletion < 0 {
chickenDepletion = 0
}
eggDepletion := harvestEggQty - eggSalesQty
if eggDepletion < 0 {
eggDepletion = 0
}
chickenPerformance := calculatePerformanceMetrics(chickenAverageWeight, chickenSalesWeight, feedUsed, population, chickenDepletion, age, standards)
eggPerformance := calculatePerformanceMetrics(averageEggWeight, eggSalesWeight, feedUsed, harvestEggQty, eggDepletion, age, standards)
sales := dto.ClosingSalesGroupDTO{
ChickenProduction: chickenSales,
EggProduction: eggSales,
}
performance := dto.ClosingPerformanceDTO{
Depletion: chickenPerformance.Depletion,
Age: age,
MortalityStd: chickenPerformance.MortalityStd,
MortalityAct: chickenPerformance.MortalityAct,
DeffMortality: chickenPerformance.DeffMortality,
FcrStd: eggPerformance.FcrStd,
FcrAct: eggPerformance.FcrAct,
DeffFcr: eggPerformance.DeffFcr,
Adg: eggPerformance.Adg,
}
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
adg := 0.0
if age > 0 {
adg = averageWeight / age
}
return dto.ClosingPerformanceDTO{
Depletion: depletion,
Age: age,
MortalityStd: mortalityStd,
MortalityAct: mortalityAct,
DeffMortality: deffMortality,
FcrStd: fcrStd,
FcrAct: fcrAct,
DeffFcr: deffFcr,
Adg: adg,
}
}
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
}