diff --git a/internal/common/repository/common.hpp.repository.go b/internal/common/repository/common.hpp.repository.go new file mode 100644 index 00000000..37094c16 --- /dev/null +++ b/internal/common/repository/common.hpp.repository.go @@ -0,0 +1,279 @@ +package repository + +import ( + "context" + "time" + + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gitlab.com/mbugroup/lti-api.git/internal/utils" + "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo" + "gorm.io/gorm" +) + +type HppCostRepository interface { + GetProjectFlockKandangIDs(ctx context.Context, projectFlockId uint) ([]uint, error) + GetDocCost(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) + GetBudgetCostByProjectFlockId(ctx context.Context, projectFlockId uint) (float64, error) + GetExpedisionCost(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) + GetFeedUsageCost(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, error) + GetOvkUsageCost(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, error) + GetTotalPopulation(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) + GetPulletCost(ctx context.Context, projectFlockKandangId uint) (float64, error) + GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, float64, error) + GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, float64, error) + GetProjectFlockIDByProjectFlockKandangID(ctx context.Context, projectFlockKandangId uint) (uint, error) + GetTransferSourceSummary(ctx context.Context, projectFlockKandangId uint) (uint, float64, error) +} + +type HppRepositoryImpl struct { + db *gorm.DB +} + +func NewHppCostRepository(db *gorm.DB) HppCostRepository { + return &HppRepositoryImpl{db: db} +} + +func (r *HppRepositoryImpl) GetProjectFlockKandangIDs(ctx context.Context, projectFlockId uint) ([]uint, error) { + var ids []uint + err := r.db.WithContext(ctx). + Table("project_flock_kandangs"). + Select("id"). + Where("project_flock_id = ?", projectFlockId). + Scan(&ids).Error + if err != nil { + return nil, err + } + + return ids, nil +} + +func (r *HppRepositoryImpl) GetDocCost(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) { + var total float64 + err := r.db.WithContext(ctx). + Table("project_chickins AS pc"). + Select("COALESCE(SUM(pc.usage_qty * COALESCE(pi.price, 0)), 0)"). + Joins("JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = pc.id AND sa.stockable_type = ?", fifo.UsableKeyProjectChickin.String(), fifo.StockableKeyPurchaseItems.String()). + Joins("JOIN purchase_items AS pi ON pi.id = sa.stockable_id"). + Where("pc.project_flock_kandang_id IN (?)", projectFlockKandangIDs). + Scan(&total).Error + if err != nil { + return 0, err + } + + return total, nil +} + +func (r *HppRepositoryImpl) GetBudgetCostByProjectFlockId(ctx context.Context, projectFlockId uint) (float64, error) { + var total float64 + err := r.db.WithContext(ctx). + Table("project_budgets AS pb"). + Select("COALESCE(SUM(pb.qty * pb.price), 0)"). + Where("pb.project_flock_id = ?", projectFlockId). + Scan(&total).Error + if err != nil { + return 0, err + } + + return total, nil +} + +func (r *HppRepositoryImpl) GetExpedisionCost(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) { + var total float64 + err := r.db.WithContext(ctx). + Table("expense_nonstocks AS en"). + Select("COALESCE(SUM(er.qty * er.price), 0)"). + Joins("JOIN expense_realizations AS er ON er.expense_nonstock_id = en.id"). + Joins("JOIN flags AS f ON f.flagable_id = en.nonstock_id AND f.flagable_type = ?", entity.FlagableTypeNonstock). + Where("en.project_flock_kandang_id IN (?)", projectFlockKandangIDs). + Where("f.name = ?", utils.FlagEkspedisi). + Scan(&total).Error + if err != nil { + return 0, err + } + + return total, nil +} + +func (r *HppRepositoryImpl) GetFeedUsageCost(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, error) { + if date == nil { + now := time.Now() + date = &now + } + + var total float64 + err := r.db.WithContext(ctx). + Table("recordings AS r"). + Select("COALESCE(SUM(rs.usage_qty * COALESCE(pi.price, 0)), 0)"). + Joins("JOIN recording_stocks AS rs ON rs.recording_id = r.id"). + Joins("JOIN product_warehouses AS pw ON pw.id = rs.product_warehouse_id"). + Joins("JOIN flags AS f ON f.flagable_id = pw.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct). + Joins("JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND sa.stockable_type = ?", fifo.UsableKeyRecordingStock.String(), fifo.StockableKeyPurchaseItems.String()). + Joins("JOIN purchase_items AS pi ON pi.id = sa.stockable_id"). + Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs). + Where("r.record_datetime <= ?", *date). + Where("f.name = ?", utils.FlagPakan). + Scan(&total).Error + if err != nil { + return 0, err + } + + return total, nil +} + +func (r *HppRepositoryImpl) GetOvkUsageCost(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, error) { + if date == nil { + now := time.Now() + date = &now + } + + flags := []utils.FlagType{ + utils.FlagOVK, + utils.FlagObat, + utils.FlagVitamin, + utils.FlagKimia, + } + + var total float64 + err := r.db.WithContext(ctx). + Table("recordings AS r"). + Select("COALESCE(SUM(rs.usage_qty * COALESCE(pi.price, 0)), 0)"). + Joins("JOIN recording_stocks AS rs ON rs.recording_id = r.id"). + Joins("JOIN product_warehouses AS pw ON pw.id = rs.product_warehouse_id"). + Joins("JOIN flags AS f ON f.flagable_id = pw.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct). + Joins("JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND sa.stockable_type = ?", fifo.UsableKeyRecordingStock.String(), fifo.StockableKeyPurchaseItems.String()). + Joins("JOIN purchase_items AS pi ON pi.id = sa.stockable_id"). + Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs). + Where("r.record_datetime <= ?", *date). + Where("f.name IN ?", flags). + Scan(&total).Error + if err != nil { + return 0, err + } + + return total, nil +} + +func (r *HppRepositoryImpl) GetTotalPopulation(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) { + var total float64 + err := r.db.WithContext(ctx). + Table("project_chickins AS pc"). + Select("COALESCE(SUM(pc.usage_qty), 0)"). + Where("pc.project_flock_kandang_id IN (?)", projectFlockKandangIDs). + Scan(&total).Error + if err != nil { + return 0, err + } + + return total, nil +} + +func (r *HppRepositoryImpl) GetPulletCost(ctx context.Context, projectFlockKandangId uint) (float64, error) { + stockablePurchase := fifo.StockableKeyPurchaseItems.String() + stockableTransferIn := fifo.StockableKeyStockTransferIn.String() + usableProjectChickin := fifo.UsableKeyProjectChickin.String() + + var total float64 + err := r.db.WithContext(ctx). + Table("project_chickins AS pc"). + Select(` + COALESCE(SUM(pc.usage_qty * CASE + WHEN sa.stockable_type = ? THEN COALESCE(pi.price, 0) + WHEN sa.stockable_type = ? THEN COALESCE(tpi.price, 0) + ELSE 0 + END), 0)`, + stockablePurchase, stockableTransferIn). + Joins("JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = pc.id", usableProjectChickin). + Joins("LEFT JOIN purchase_items AS pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", stockablePurchase). + Joins("LEFT JOIN stock_allocations AS tsa ON tsa.usable_type = ? AND tsa.usable_id = sa.stockable_id AND sa.stockable_type = ? AND tsa.stockable_type = ?", stockableTransferIn, stockableTransferIn, stockablePurchase). + Joins("LEFT JOIN purchase_items AS tpi ON tpi.id = tsa.stockable_id"). + Where("pc.project_flock_kandang_id = ?", projectFlockKandangId). + Scan(&total).Error + if err != nil { + return 0, err + } + + return total, nil +} + +func (r *HppRepositoryImpl) GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, float64, error) { + if date == nil { + now := time.Now() + date = &now + } + + var totals struct { + TotalPieces float64 + TotalWeightKg float64 + } + err := r.db.WithContext(ctx). + Table("recordings AS r"). + Select("COALESCE(SUM(re.qty), 0) AS total_pieces, COALESCE(SUM(re.weight), 0)AS total_weight_kg"). + Joins("JOIN recording_eggs AS re ON re.recording_id = r.id"). + Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs). + Where("r.record_datetime <= ?", *date). + Scan(&totals).Error + if err != nil { + return 0, 0, err + } + + return totals.TotalPieces, totals.TotalWeightKg, nil +} + +func (r *HppRepositoryImpl) GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, float64, error) { + if date == nil { + now := time.Now() + date = &now + } + + var totals struct { + TotalPieces float64 + TotalWeight float64 + } + err := r.db.WithContext(ctx). + Table("recordings AS r"). + Select("COALESCE(SUM(mdp.usage_qty), 0) AS total_pieces, COALESCE(SUM(mdp.total_weight), 0) AS total_weight"). + Joins("JOIN recording_eggs AS re ON re.recording_id = r.id"). + Joins("JOIN stock_allocations AS sa ON sa.stockable_type = ? AND sa.stockable_id = re.id AND sa.usable_type = ?", fifo.StockableKeyRecordingEgg.String(), fifo.UsableKeyMarketingDelivery.String()). + Joins("JOIN marketing_delivery_products AS mdp ON mdp.id = sa.usable_id"). + Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs). + Where("r.record_datetime <= ?", *date). + Scan(&totals).Error + if err != nil { + return 0, 0, err + } + + return totals.TotalPieces, totals.TotalWeight, nil +} + +func (r *HppRepositoryImpl) GetProjectFlockIDByProjectFlockKandangID(ctx context.Context, projectFlockKandangId uint) (uint, error) { + var projectFlockID uint + err := r.db.WithContext(ctx). + Table("project_flock_kandangs"). + Select("project_flock_id"). + Where("id = ?", projectFlockKandangId). + Scan(&projectFlockID).Error + if err != nil { + return 0, err + } + + return projectFlockID, nil +} + +func (r *HppRepositoryImpl) GetTransferSourceSummary(ctx context.Context, projectFlockKandangId uint) (uint, float64, error) { + var summary struct { + ProjectFlockID uint + TotalQty float64 + } + err := r.db.WithContext(ctx). + Table("laying_transfer_targets AS ltt"). + Select("lt.from_project_flock_id AS project_flock_id, COALESCE(SUM(ltt.total_qty), 0) AS total_qty"). + Joins("JOIN laying_transfers AS lt ON lt.id = ltt.laying_transfer_id"). + Where("ltt.target_project_flock_kandang_id = ?", projectFlockKandangId). + Group("lt.from_project_flock_id"). + Scan(&summary).Error + if err != nil { + return 0, 0, err + } + + return summary.ProjectFlockID, summary.TotalQty, nil +} diff --git a/internal/common/service/common.hpp.service.go b/internal/common/service/common.hpp.service.go new file mode 100644 index 00000000..44f2dd5f --- /dev/null +++ b/internal/common/service/common.hpp.service.go @@ -0,0 +1,268 @@ +package service + +import ( + "context" + "math" + "time" + + commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" +) + +type HppService interface { + CalculateHppCost(projectFlockKandangId uint, date *time.Time) (*HppCostResponse, error) + GetTotalDepresiasiFlockGrowing(sourceProjectFlockID uint, date *time.Time) (float64, error) + GetTotalProductionCost(projectFlockKandangId uint, date *time.Time, totalDepresiasiGrowing float64) (float64, error) + GetBudgetKandangLaying(projectFlockKandangId uint, date *time.Time) (float64, error) + GetDepresiasiTransfer(projectFlockKandangId uint, date *time.Time) (float64, error) + GetHppEstimationDanRealisasi(totalProductionCost float64, projectFlockKandangId uint, date *time.Time) (*HppCostResponse, error) +} + +type HppCostResponse struct { + Estimation HppCostDetail `json:"estimation"` + Real HppCostDetail `json:"real"` +} + +type HppCostDetail struct { + HargaKg float64 `json:"harga_kg"` + HargaButir float64 `json:"harga_butir"` + Total float64 `json:"total"` + Kg float64 `json:"kg"` + Butir float64 `json:"butir"` +} + +type hppService struct { + hppRepo commonRepo.HppCostRepository +} + +func NewHppService(hppRepo commonRepo.HppCostRepository) HppService { + return &hppService{hppRepo: hppRepo} +} + +func (s *hppService) CalculateHppCost(projectFlockKandangId uint, date *time.Time) (*HppCostResponse, error) { + if date == nil { + now := time.Now() + date = &now + } + + depresiasiTransfer, err := s.GetDepresiasiTransfer(projectFlockKandangId, date) + if err != nil { + return nil, err + } + + totalProductionCost, err := s.GetTotalProductionCost(projectFlockKandangId, date, depresiasiTransfer) + if err != nil { + return nil, err + } + + return s.GetHppEstimationDanRealisasi(totalProductionCost, projectFlockKandangId, date) + +} + +func (s *hppService) GetTotalDepresiasiFlockGrowing(sourceProjectFlockID uint, date *time.Time) (float64, error) { + if date == nil { + now := time.Now() + date = &now + } + + if s.hppRepo == nil { + return 0, nil + } + + kandangIDs, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), sourceProjectFlockID) + if err != nil { + return 0, err + } + + docCost, err := s.hppRepo.GetDocCost(context.Background(), kandangIDs) + if err != nil { + return 0, err + } + + budgetCost, err := s.hppRepo.GetBudgetCostByProjectFlockId(context.Background(), sourceProjectFlockID) + if err != nil { + return 0, err + } + + expedisionCost, err := s.hppRepo.GetExpedisionCost(context.Background(), kandangIDs) + if err != nil { + return 0, err + } + + feedCost, err := s.hppRepo.GetFeedUsageCost(context.Background(), kandangIDs, date) + if err != nil { + return 0, err + } + + ovkCost, err := s.hppRepo.GetOvkUsageCost(context.Background(), kandangIDs, date) + if err != nil { + return 0, err + } + + return docCost + budgetCost + expedisionCost + feedCost + ovkCost, nil +} + +func (s *hppService) GetTotalProductionCost(projectFlockKandangId uint, date *time.Time, depresiasiTransfer float64) (float64, error) { + if date == nil { + now := time.Now() + date = &now + } + + costPullet, err := s.hppRepo.GetPulletCost(context.Background(), projectFlockKandangId) + if err != nil { + return 0, err + } + + costFeed, err := s.hppRepo.GetFeedUsageCost(context.Background(), []uint{projectFlockKandangId}, date) + if err != nil { + return 0, err + } + + costOvk, err := s.hppRepo.GetOvkUsageCost(context.Background(), []uint{projectFlockKandangId}, date) + if err != nil { + return 0, err + } + + costExpedision, err := s.hppRepo.GetExpedisionCost(context.Background(), []uint{projectFlockKandangId}) + if err != nil { + return 0, err + } + + costBudget, err := s.GetBudgetKandangLaying(projectFlockKandangId, date) + if err != nil { + return 0, err + } + + return depresiasiTransfer + costPullet + costFeed + costOvk + costExpedision + costBudget, nil +} + +func (s *hppService) GetBudgetKandangLaying(projectFlockKandangId uint, date *time.Time) (float64, error) { + if date == nil { + now := time.Now() + date = &now + } + + if s.hppRepo == nil { + return 0, nil + } + + projectFlockId, err := s.hppRepo.GetProjectFlockIDByProjectFlockKandangID(context.Background(), projectFlockKandangId) + if err != nil { + return 0, err + } + + projectFlockKandangIds, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), projectFlockId) + if err != nil { + return 0, err + } + + eggProduksiPiecesFlock, _, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), projectFlockKandangIds, date) + if err != nil { + return 0, err + } + + eggProduksiPiecesKandang, _, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, date) + if err != nil { + return 0, err + } + + totalBudgetCost, err := s.hppRepo.GetBudgetCostByProjectFlockId(context.Background(), projectFlockId) + if err != nil { + return 0, err + } + + if eggProduksiPiecesFlock == 0 { + return 0, nil + } + + return (totalBudgetCost * eggProduksiPiecesKandang) / eggProduksiPiecesFlock, nil +} + +func (s *hppService) GetDepresiasiTransfer(projectFlockKandangId uint, date *time.Time) (float64, error) { + if date == nil { + now := time.Now() + date = &now + } + + if s.hppRepo == nil { + return 0, nil + } + + sourceProjectFlockID, transferTotalQty, err := s.hppRepo.GetTransferSourceSummary(context.Background(), projectFlockKandangId) + if err != nil { + return 0, err + } + + kandangIDsGrowing, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), sourceProjectFlockID) + if err != nil { + return 0, err + } + + totalPopulationFlockGrowing, err := s.hppRepo.GetTotalPopulation(context.Background(), kandangIDsGrowing) + if err != nil { + return 0, err + } + if totalPopulationFlockGrowing == 0 { + return 0, nil + } + + totalDepresiasiFlockGrowing, err := s.GetTotalDepresiasiFlockGrowing(sourceProjectFlockID, date) + if err != nil { + return 0, err + } + + return (totalDepresiasiFlockGrowing * transferTotalQty) / totalPopulationFlockGrowing, nil +} + +func (s *hppService) GetHppEstimationDanRealisasi(totalProductionCost float64, projectFlockKandangId uint, date *time.Time) (*HppCostResponse, error) { + if date == nil { + now := time.Now() + date = &now + } + + if s.hppRepo == nil { + return &HppCostResponse{}, nil + } + + estimPieces, estimWeightKg, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, date) + if err != nil { + return nil, err + } + + realPieces, realWeightKg, err := s.hppRepo.GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, date) + if err != nil { + return nil, err + } + + estimation := HppCostDetail{ + Total: totalProductionCost, + Kg: estimWeightKg, + Butir: estimPieces, + } + if estimWeightKg > 0 { + estimation.HargaKg = roundToTwoDecimals(totalProductionCost / estimWeightKg) + } + if estimPieces > 0 { + estimation.HargaButir = roundToTwoDecimals(totalProductionCost / estimPieces) + } + + real := HppCostDetail{ + Total: totalProductionCost, + Kg: realWeightKg, + Butir: realPieces, + } + if realWeightKg > 0 { + real.HargaKg = roundToTwoDecimals(totalProductionCost / realWeightKg) + } + if realPieces > 0 { + real.HargaButir = roundToTwoDecimals(totalProductionCost / realPieces) + } + + return &HppCostResponse{ + Estimation: estimation, + Real: real, + }, nil +} + +func roundToTwoDecimals(value float64) float64 { + return math.Round(value*100) / 100 +} diff --git a/internal/modules/repports/module.go b/internal/modules/repports/module.go index 60345d5b..9a64b806 100644 --- a/internal/modules/repports/module.go +++ b/internal/modules/repports/module.go @@ -32,6 +32,7 @@ func (RepportModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate * chickinRepository := chickinRepo.NewChickinRepository(db) recordingRepository := recordingRepo.NewRecordingRepository(db) approvalRepository := commonRepo.NewApprovalRepository(db) + hppCostRepository := commonRepo.NewHppCostRepository(db) purchaseSupplierRepository := repportRepo.NewPurchaseSupplierRepository(db) debtSupplierRepository := repportRepo.NewDebtSupplierRepository(db) hppPerKandangRepository := repportRepo.NewHppPerKandangRepository(db) @@ -43,7 +44,8 @@ func (RepportModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate * userRepository := rUser.NewUserRepository(db) approvalSvc := approvalService.NewApprovalService(approvalRepository) - repportService := sRepport.NewRepportService(db, validate, expenseRealizationRepository, marketingDeliveryProductRepository, purchaseRepository, chickinRepository, recordingRepository, approvalSvc, purchaseSupplierRepository, debtSupplierRepository, hppPerKandangRepository, productionResultRepository, customerPaymentRepository, customerRepository, standardGrowthDetailRepository, productionStandardDetailRepository) + hppSvc := approvalService.NewHppService(hppCostRepository) + repportService := sRepport.NewRepportService(db, validate, expenseRealizationRepository, marketingDeliveryProductRepository, purchaseRepository, chickinRepository, recordingRepository, approvalSvc, hppSvc, purchaseSupplierRepository, debtSupplierRepository, hppPerKandangRepository, productionResultRepository, customerPaymentRepository, customerRepository, standardGrowthDetailRepository, productionStandardDetailRepository) userService := sUser.NewUserService(userRepository, validate) RepportRoutes(router, userService, repportService) diff --git a/internal/modules/repports/repositories/hpp_per_kandang.repository.go b/internal/modules/repports/repositories/hpp_per_kandang.repository.go index 1135efbf..03d56fc6 100644 --- a/internal/modules/repports/repositories/hpp_per_kandang.repository.go +++ b/internal/modules/repports/repositories/hpp_per_kandang.repository.go @@ -6,7 +6,6 @@ import ( entity "gitlab.com/mbugroup/lti-api.git/internal/entities" "gitlab.com/mbugroup/lti-api.git/internal/utils" - "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo" "gorm.io/gorm" ) @@ -24,9 +23,9 @@ type HppPerKandangRow struct { // RemainingChickenBirds float64 // RemainingChickenWeight float64 EggProductionWeightKgRemaining float64 - EggProductionPiecesRemaining float64 - EggProductionTotalWeightKg float64 - EggProductionTotalPieces float64 + // EggProductionPiecesRemaining float64 + // EggProductionTotalWeightKg float64 + // EggProductionTotalPieces float64 } type HppPerKandangCostRow struct { @@ -50,7 +49,7 @@ type HppPerKandangSupplierRow struct { type HppPerKandangRepository interface { GetRowsByPeriod(ctx context.Context, start, end time.Time, areaIDs, locationIDs, kandangIDs []int64) ([]HppPerKandangRow, error) GetFeedOvkDocCostByPeriod(ctx context.Context, start, end time.Time, projectFlockKandangIDs []uint) ([]HppPerKandangCostRow, []HppPerKandangSupplierRow, error) - GetEggProductionByProjectFlockKandangIDs(ctx context.Context, start, end time.Time, projectFlockKandangIDs []uint) (map[uint]HppPerKandangRow, error) + GetWeightRemainingByProjectFlockKandangIDs(ctx context.Context, start, end time.Time, projectFlockKandangIDs []uint) (map[uint]HppPerKandangRow, error) } type hppPerKandangRepository struct { @@ -133,60 +132,6 @@ func (r *hppPerKandangRepository) GetRowsByPeriod(ctx context.Context, start, en func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context, start, end time.Time, projectFlockKandangIDs []uint) ([]HppPerKandangCostRow, []HppPerKandangSupplierRow, error) { var rows []HppPerKandangCostRow - purchaseStockableKey := fifo.StockableKeyPurchaseItems.String() - transferStockableKey := fifo.StockableKeyStockTransferIn.String() - - latestApproval := r.db.WithContext(ctx). - Table("approvals AS a"). - Select("a.approvable_id, a.action"). - Joins(` - JOIN ( - SELECT approvable_id, MAX(action_at) AS latest_action_at - FROM approvals - WHERE approvable_type = ? - GROUP BY approvable_id - ) AS la ON la.approvable_id = a.approvable_id AND la.latest_action_at = a.action_at`, - string(utils.ApprovalWorkflowRecording), - ) - - query := r.db.WithContext(ctx). - Table("recordings AS r"). - Select(` - pfk.id AS project_flock_kandang_id, - COALESCE(SUM(CASE - WHEN f.name = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0) - WHEN sa.stockable_type = ? AND tf.name = ? THEN COALESCE(std.total_qty, 0) * COALESCE(tpi.price, 0) - ELSE 0 - END), 0) AS feed_cost, - COALESCE(SUM(CASE - WHEN f.name = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0) - WHEN sa.stockable_type = ? AND tf.name = ? THEN COALESCE(std.total_qty, 0) * COALESCE(tpi.price, 0) - ELSE 0 - END), 0) AS ovk_cost`, - utils.FlagPakan, transferStockableKey, utils.FlagPakan, - utils.FlagOVK, transferStockableKey, utils.FlagOVK). - Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). - Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). - Joins("LEFT JOIN (?) AS la ON la.approvable_id = r.id", latestApproval). - Joins("LEFT JOIN recording_stocks AS rs ON rs.recording_id = r.id"). - Joins("LEFT JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND sa.status = ?", fifo.UsableKeyRecordingStock.String(), entity.StockAllocationStatusActive). - Joins("LEFT JOIN purchase_items AS pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", purchaseStockableKey). - Joins("LEFT JOIN stock_transfer_details AS std ON std.id = sa.stockable_id AND sa.stockable_type = ?", transferStockableKey). - Joins("LEFT JOIN stock_transfers AS st ON st.id = std.stock_transfer_id"). - Joins("LEFT JOIN purchase_items AS tpi ON tpi.product_id = std.product_id AND tpi.warehouse_id = st.from_warehouse_id"). - Joins("LEFT JOIN flags AS f ON f.flagable_id = pi.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct). - Joins("LEFT JOIN flags AS tf ON tf.flagable_id = std.product_id AND tf.flagable_type = ?", entity.FlagableTypeProduct). - Where("r.project_flock_kandangs_id IN ?", projectFlockKandangIDs). - Where("r.record_datetime < ?", end). - Where("r.deleted_at IS NULL"). - Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)) - - query = query.Group("pfk.id").Order("pfk.id ASC") - - if err := query.Scan(&rows).Error; err != nil { - return nil, nil, err - } - docRows := make([]struct { ProjectFlockKandangID uint DocCost float64 @@ -262,130 +207,10 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context, } } - budgetRows := make([]struct { - ProjectFlockKandangID uint - BudgetCost float64 - }, 0) - - pfkUsageSub := r.db. - Table("project_chickins AS pc"). - Select(` - pc.project_flock_kandang_id, - SUM(pc.usage_qty) AS kandang_usage_qty`). - Group("pc.project_flock_kandang_id") - - projectUsageSub := r.db. - Table("project_chickins AS pc"). - Select(` - pfk.project_flock_id, - SUM(pc.usage_qty) AS project_usage_qty`). - Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = pc.project_flock_kandang_id"). - Group("pfk.project_flock_id") - - budgetQuery := r.db.WithContext(ctx). - Table("project_flock_kandangs AS pfk"). - Select(` - pfk.id AS project_flock_kandang_id, - COALESCE(SUM((pb.qty * pb.price) * COALESCE(k_usage.kandang_usage_qty, 0) / NULLIF(p_usage.project_usage_qty, 0)), 0) AS budget_cost`). - Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). - Joins("JOIN locations AS loc ON loc.id = k.location_id"). - Joins("JOIN project_budgets AS pb ON pb.project_flock_id = pfk.project_flock_id"). - Joins("LEFT JOIN (?) AS k_usage ON k_usage.project_flock_kandang_id = pfk.id", pfkUsageSub). - Joins("LEFT JOIN (?) AS p_usage ON p_usage.project_flock_id = pfk.project_flock_id", projectUsageSub). - Where("pfk.id IN (?)", projectFlockKandangIDs). - Group("pfk.id") - // budgetQuery = applyLocationFilters(budgetQuery, areaIDs, locationIDs, kandangIDs) - - if err := budgetQuery.Scan(&budgetRows).Error; err != nil { - return nil, nil, err - } - - for _, budget := range budgetRows { - entry, ok := costMap[budget.ProjectFlockKandangID] - if !ok { - rows = append(rows, HppPerKandangCostRow{ - ProjectFlockKandangID: budget.ProjectFlockKandangID, - }) - entry = &rows[len(rows)-1] - costMap[budget.ProjectFlockKandangID] = entry - } - entry.BudgetCost += budget.BudgetCost - } - - expenseRows := make([]struct { - ProjectFlockKandangID uint - ExpenseCost float64 - }, 0) - - expenseQuery := r.db.WithContext(ctx). - Table("project_flock_kandangs AS pfk"). - Select(` - pfk.id AS project_flock_kandang_id, - COALESCE(SUM(er.qty * er.price), 0) AS expense_cost`). - Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). - Joins("JOIN locations AS loc ON loc.id = k.location_id"). - Joins("JOIN expense_nonstocks AS en ON en.project_flock_kandang_id = pfk.id"). - Joins("JOIN expense_realizations AS er ON er.expense_nonstock_id = en.id"). - Where("pfk.id IN (?)", projectFlockKandangIDs). - Group("pfk.id") - // expenseQuery = applyLocationFilters(expenseQuery, areaIDs, locationIDs, kandangIDs) - - if err := expenseQuery.Scan(&expenseRows).Error; err != nil { - return nil, nil, err - } - - for _, exp := range expenseRows { - entry, ok := costMap[exp.ProjectFlockKandangID] - if !ok { - rows = append(rows, HppPerKandangCostRow{ - ProjectFlockKandangID: exp.ProjectFlockKandangID, - }) - entry = &rows[len(rows)-1] - costMap[exp.ProjectFlockKandangID] = entry - } - entry.ExpenseCost += exp.ExpenseCost - } - - feedSuppliers := make([]HppPerKandangSupplierRow, 0) - - feedQuery := r.db.WithContext(ctx). - Table("recordings AS r"). - Select("DISTINCT pfk.id AS project_flock_kandang_id, s.id AS supplier_id, s.name AS supplier_name, s.alias AS supplier_alias"). - Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). - Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). - Joins("JOIN locations AS loc ON loc.id = k.location_id"). - Joins("LEFT JOIN recording_stocks AS rs ON rs.recording_id = r.id"). - Joins("LEFT JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND sa.status = ?", fifo.UsableKeyRecordingStock.String(), entity.StockAllocationStatusActive). - Joins("LEFT JOIN purchase_items AS pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", purchaseStockableKey). - Joins("LEFT JOIN purchases AS pur ON pur.id = pi.purchase_id"). - Joins("LEFT JOIN suppliers AS s ON s.id = pur.supplier_id"). - Joins("LEFT JOIN flags AS f ON f.flagable_id = pi.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct). - Where("f.name IN ?", []utils.FlagType{utils.FlagPakan, utils.FlagOVK}). - Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs). - Where("r.record_datetime < ?", end). - Where("r.deleted_at IS NULL") - // feedQuery = applyLocationFilters(feedQuery, areaIDs, locationIDs, kandangIDs) - - if err := feedQuery.Scan(&feedSuppliers).Error; err != nil { - return nil, nil, err - } - - for i := range feedSuppliers { - if _, exists := costMap[feedSuppliers[i].ProjectFlockKandangID]; !exists { - rows = append(rows, HppPerKandangCostRow{ - ProjectFlockKandangID: feedSuppliers[i].ProjectFlockKandangID, - }) - costMap[feedSuppliers[i].ProjectFlockKandangID] = &rows[len(rows)-1] - } - feedSuppliers[i].Category = "FEED" - } - - supplierRows := append(docSuppliers, feedSuppliers...) - - return rows, supplierRows, nil + return rows, docSuppliers, nil } -func (r *hppPerKandangRepository) GetEggProductionByProjectFlockKandangIDs(ctx context.Context, start, end time.Time, projectFlockKandangIDs []uint) (map[uint]HppPerKandangRow, error) { +func (r *hppPerKandangRepository) GetWeightRemainingByProjectFlockKandangIDs(ctx context.Context, start, end time.Time, projectFlockKandangIDs []uint) (map[uint]HppPerKandangRow, error) { if len(projectFlockKandangIDs) == 0 { return map[uint]HppPerKandangRow{}, nil } @@ -406,9 +231,9 @@ func (r *hppPerKandangRepository) GetEggProductionByProjectFlockKandangIDs(ctx c type eggRow struct { ProjectFlockKandangID uint EggProductionWeightKgRemaining float64 - EggProductionPiecesRemaining float64 - EggProductionTotalWeightKg float64 - EggProductionTotalPieces float64 + // EggProductionPiecesRemaining float64 + // EggProductionTotalWeightKg float64 + // EggProductionTotalPieces float64 } eggRows := make([]eggRow, 0) @@ -416,10 +241,7 @@ func (r *hppPerKandangRepository) GetEggProductionByProjectFlockKandangIDs(ctx c Table("recordings AS r"). Select(` r.project_flock_kandangs_id AS project_flock_kandang_id, - COALESCE(SUM((re.total_qty - re.total_used) * re.weight / 1000), 0) AS egg_production_weight_kg_remaining, - COALESCE(SUM(re.total_qty - re.total_used), 0) AS egg_production_pieces_remaining, - COALESCE(SUM(re.weight / 1000), 0) AS egg_production_total_weight_kg, - COALESCE(SUM(re.total_qty), 0) AS egg_production_total_pieces`). + COALESCE((SUM(re.weight) / NULLIF(SUM(re.total_qty), 0)) * SUM(re.total_qty - re.total_used), 0) AS egg_production_weight_kg_remaining`). Joins("LEFT JOIN (?) AS la ON la.approvable_id = r.id", latestApproval). Joins("LEFT JOIN recording_eggs AS re ON re.recording_id = r.id"). Where("r.project_flock_kandangs_id IN ?", projectFlockKandangIDs). @@ -437,9 +259,9 @@ func (r *hppPerKandangRepository) GetEggProductionByProjectFlockKandangIDs(ctx c result[row.ProjectFlockKandangID] = HppPerKandangRow{ ProjectFlockKandangID: row.ProjectFlockKandangID, EggProductionWeightKgRemaining: row.EggProductionWeightKgRemaining, - EggProductionPiecesRemaining: row.EggProductionPiecesRemaining, - EggProductionTotalWeightKg: row.EggProductionTotalWeightKg, - EggProductionTotalPieces: row.EggProductionTotalPieces, + // EggProductionPiecesRemaining: row.EggProductionPiecesRemaining, + // EggProductionTotalWeightKg: row.EggProductionTotalWeightKg, + // EggProductionTotalPieces: row.EggProductionTotalPieces, } } diff --git a/internal/modules/repports/services/repport.service.go b/internal/modules/repports/services/repport.service.go index bbe1d111..9c976138 100644 --- a/internal/modules/repports/services/repport.service.go +++ b/internal/modules/repports/services/repport.service.go @@ -57,6 +57,7 @@ type repportService struct { ChickinRepo chickinRepo.ProjectChickinRepository RecordingRepo recordingRepo.RecordingRepository ApprovalSvc approvalService.ApprovalService + HppSvc approvalService.HppService PurchaseSupplierRepo repportRepo.PurchaseSupplierRepository DebtSupplierRepo repportRepo.DebtSupplierRepository HppPerKandangRepo repportRepo.HppPerKandangRepository @@ -85,6 +86,7 @@ func NewRepportService( chickinRepo chickinRepo.ProjectChickinRepository, recordingRepo recordingRepo.RecordingRepository, approvalSvc approvalService.ApprovalService, + hppSvc approvalService.HppService, purchaseSupplierRepo repportRepo.PurchaseSupplierRepository, debtSupplierRepo repportRepo.DebtSupplierRepository, hppPerKandangRepo repportRepo.HppPerKandangRepository, @@ -104,6 +106,7 @@ func NewRepportService( ChickinRepo: chickinRepo, RecordingRepo: recordingRepo, ApprovalSvc: approvalSvc, + HppSvc: hppSvc, PurchaseSupplierRepo: purchaseSupplierRepo, DebtSupplierRepo: debtSupplierRepo, HppPerKandangRepo: hppPerKandangRepo, @@ -1573,16 +1576,16 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes return nil, nil, err } - eggMap, err := s.HppPerKandangRepo.GetEggProductionByProjectFlockKandangIDs(ctx.Context(), startOfDay, endOfDay, validPfkIDs) + eggMap, err := s.HppPerKandangRepo.GetWeightRemainingByProjectFlockKandangIDs(ctx.Context(), startOfDay, endOfDay, validPfkIDs) if err != nil { return nil, nil, err } for pfkID, egg := range eggMap { if rowIdx, ok := pfkIndex[pfkID]; ok { repoRows[rowIdx].EggProductionWeightKgRemaining = egg.EggProductionWeightKgRemaining - repoRows[rowIdx].EggProductionPiecesRemaining = egg.EggProductionPiecesRemaining - repoRows[rowIdx].EggProductionTotalWeightKg = egg.EggProductionTotalWeightKg - repoRows[rowIdx].EggProductionTotalPieces = egg.EggProductionTotalPieces + // repoRows[rowIdx].EggProductionPiecesRemaining = egg.EggProductionPiecesRemaining + // repoRows[rowIdx].EggProductionTotalWeightKg = egg.EggProductionTotalWeightKg + // repoRows[rowIdx].EggProductionTotalPieces = egg.EggProductionTotalPieces } } } @@ -1590,12 +1593,12 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes costMap := make(map[uint]HppCostAggregate, len(costRows)) for _, row := range costRows { costMap[row.ProjectFlockKandangID] = HppCostAggregate{ - FeedCost: row.FeedCost, - OvkCost: row.OvkCost, - DocCost: row.DocCost, - DocQty: row.DocQty, - BudgetCost: row.BudgetCost, - ExpenseCost: row.ExpenseCost, + // FeedCost: row.FeedCost, + // OvkCost: row.OvkCost, + DocCost: row.DocCost, + DocQty: row.DocQty, + // BudgetCost: row.BudgetCost, + // ExpenseCost: row.ExpenseCost, } } @@ -1669,19 +1672,33 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes continue } - eggPiecesFloatRemaining := row.EggProductionPiecesRemaining + var eggPiecesFloatRemaining float64 + eggRemainingWeightFloatRemaining := row.EggProductionWeightKgRemaining + var eggTotalPiecesFloat float64 + var eggWeightFloat float64 + eggHpp := 0.0 + if s.HppSvc != nil { + hppCost, err := s.HppSvc.CalculateHppCost(row.ProjectFlockKandangID, &endOfDay) + if err != nil { + return nil, nil, err + } + if hppCost != nil { + // eggRemainingWeightFloatRemaining = hppCost.Estimation.Kg - hppCost.Real.Kg + eggPiecesFloatRemaining = hppCost.Estimation.Butir - hppCost.Real.Butir + eggHpp = hppCost.Estimation.HargaKg + eggTotalPiecesFloat = hppCost.Estimation.Butir + eggWeightFloat = hppCost.Estimation.Kg + } + } if math.IsNaN(eggPiecesFloatRemaining) || math.IsInf(eggPiecesFloatRemaining, 0) { eggPiecesFloatRemaining = 0 } - eggTotalPiecesFloat := row.EggProductionTotalPieces if math.IsNaN(eggTotalPiecesFloat) || math.IsInf(eggTotalPiecesFloat, 0) { eggTotalPiecesFloat = 0 } - eggRemainingWeightFloatRemaining := row.EggProductionWeightKgRemaining if math.IsNaN(eggRemainingWeightFloatRemaining) || math.IsInf(eggRemainingWeightFloatRemaining, 0) { eggRemainingWeightFloatRemaining = 0 } - eggWeightFloat := row.EggProductionTotalWeightKg if math.IsNaN(eggWeightFloat) || math.IsInf(eggWeightFloat, 0) { eggWeightFloat = 0 } @@ -1705,11 +1722,6 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes rangeKey := weightRangeKey{Min: weightMin, Max: weightMax} costEntry := costMap[row.ProjectFlockKandangID] - totalCost := costEntry.FeedCost + costEntry.OvkCost + costEntry.DocCost + costEntry.BudgetCost + costEntry.ExpenseCost - eggHpp := 0.0 - if eggWeightFloat > 0 { - eggHpp = (totalCost / eggWeightFloat) / 1000 - } rowEggPieces := int64(math.Round(eggPiecesFloatRemaining)) rowEggValue := int64(eggHpp * eggRemainingWeightFloatRemaining)