From 95965cb26a68f71d34ae8d54b212defcc2767e28 Mon Sep 17 00:00:00 2001 From: giovanni Date: Tue, 20 Jan 2026 18:21:49 +0700 Subject: [PATCH 01/20] add common service and repo for calculate hpp --- .../repository/common.hpp.repository.go | 150 ++++++++++++++++++ internal/common/service/common.hpp.service.go | 89 +++++++++++ 2 files changed, 239 insertions(+) create mode 100644 internal/common/repository/common.hpp.repository.go create mode 100644 internal/common/service/common.hpp.service.go diff --git a/internal/common/repository/common.hpp.repository.go b/internal/common/repository/common.hpp.repository.go new file mode 100644 index 00000000..f9bbaa3c --- /dev/null +++ b/internal/common/repository/common.hpp.repository.go @@ -0,0 +1,150 @@ +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 { + GetDocCost(ctx context.Context, projectFlockKandangId uint) (float64, error) + GetBudgetCost(ctx context.Context, projectFlockKandangId uint) (float64, error) + GetExpedisionCost(ctx context.Context, projectFlockKandangId uint) (float64, error) + GetFeedCost(ctx context.Context, projectFlockKandangId uint, date *time.Time) (float64, error) + GetOvkCost(ctx context.Context, projectFlockKandangId uint, date *time.Time) (float64, error) +} + +type HppRepositoryImpl struct { + db *gorm.DB +} + +func NewHppCostRepository(db *gorm.DB) HppCostRepository { + return &HppRepositoryImpl{db: db} +} + +func (r *HppRepositoryImpl) GetDocCost(ctx context.Context, projectFlockKandangId 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 = ?", projectFlockKandangId). + Scan(&total).Error + if err != nil { + return 0, err + } + + return total, nil +} + +func (r *HppRepositoryImpl) GetBudgetCost(ctx context.Context, projectFlockKandangId uint) (float64, error) { + 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") + + var total float64 + err := r.db.WithContext(ctx). + Table("project_flock_kandangs AS pfk"). + Select(` + COALESCE(SUM((pb.qty * pb.price) * COALESCE(k_usage.kandang_usage_qty, 0) / NULLIF(p_usage.project_usage_qty, 0)), 0)`). + 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 = ?", projectFlockKandangId). + Scan(&total).Error + if err != nil { + return 0, err + } + + return total, nil +} + +func (r *HppRepositoryImpl) GetExpedisionCost(ctx context.Context, projectFlockKandangId 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"). + Where("en.project_flock_kandang_id = ?", projectFlockKandangId). + Scan(&total).Error + if err != nil { + return 0, err + } + + return total, nil +} + +func (r *HppRepositoryImpl) GetFeedCost(ctx context.Context, projectFlockKandangId 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 = ?", projectFlockKandangId). + 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) GetOvkCost(ctx context.Context, projectFlockKandangId 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 = ?", projectFlockKandangId). + Where("r.record_datetime <= ?", *date). + Where("f.name IN ?", flags). + Scan(&total).Error + if err != nil { + return 0, err + } + + return total, 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..5ef9091e --- /dev/null +++ b/internal/common/service/common.hpp.service.go @@ -0,0 +1,89 @@ +package service + +import ( + "context" + "time" + + commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" +) + +type HppService interface { + CalculateHppCost(projectFlockKandangId uint, date *time.Time) (*HppCostResponse, error) + GetTotalDepresiasi(projectFlockKandangId uint, date *time.Time) (float64, 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 + } + + // _ = projectFlockKandangId + _ = date + + return &HppCostResponse{ + Estimation: HppCostDetail{}, + Real: HppCostDetail{}, + }, nil +} + +func (s *hppService) GetTotalDepresiasi(projectFlockKandangId uint, date *time.Time) (float64, error) { + if date == nil { + now := time.Now() + date = &now + } + + if s.hppRepo == nil { + return 0, nil + } + + docCost, err := s.hppRepo.GetDocCost(context.Background(), projectFlockKandangId) + if err != nil { + return 0, err + } + + budgetCost, err := s.hppRepo.GetBudgetCost(context.Background(), projectFlockKandangId) + if err != nil { + return 0, err + } + + expedisionCost, err := s.hppRepo.GetExpedisionCost(context.Background(), projectFlockKandangId) + if err != nil { + return 0, err + } + + feedCost, err := s.hppRepo.GetFeedCost(context.Background(), projectFlockKandangId, date) + if err != nil { + return 0, err + } + + ovkCost, err := s.hppRepo.GetOvkCost(context.Background(), projectFlockKandangId, date) + if err != nil { + return 0, err + } + + _ = date + + return docCost + budgetCost + expedisionCost + feedCost + ovkCost, nil +} From d96a12776a61c4e9bae90dc00480293dae872a67 Mon Sep 17 00:00:00 2001 From: giovanni Date: Wed, 21 Jan 2026 13:38:59 +0700 Subject: [PATCH 02/20] next commit --- .../repository/common.hpp.repository.go | 133 +++++++++++++----- internal/common/service/common.hpp.service.go | 69 +++++++-- 2 files changed, 156 insertions(+), 46 deletions(-) diff --git a/internal/common/repository/common.hpp.repository.go b/internal/common/repository/common.hpp.repository.go index f9bbaa3c..74ed5261 100644 --- a/internal/common/repository/common.hpp.repository.go +++ b/internal/common/repository/common.hpp.repository.go @@ -11,11 +11,15 @@ import ( ) type HppCostRepository interface { - GetDocCost(ctx context.Context, projectFlockKandangId uint) (float64, error) - GetBudgetCost(ctx context.Context, projectFlockKandangId uint) (float64, error) - GetExpedisionCost(ctx context.Context, projectFlockKandangId uint) (float64, error) - GetFeedCost(ctx context.Context, projectFlockKandangId uint, date *time.Time) (float64, error) - GetOvkCost(ctx context.Context, projectFlockKandangId uint, date *time.Time) (float64, error) + GetProjectFlockKandangIDs(ctx context.Context, projectFlockId uint) ([]uint, error) + GetDocCost(ctx context.Context, kandangIDs []uint) (float64, error) + GetBudgetCostByProjectFlockId(ctx context.Context, projectFlockId uint) (float64, error) + GetExpedisionCost(ctx context.Context, kandangIDs []uint) (float64, error) + GetFeedCost(ctx context.Context, kandangIDs []uint, date *time.Time) (float64, error) + GetOvkCost(ctx context.Context, kandangIDs []uint, date *time.Time) (float64, error) + GetTotalPopulation(ctx context.Context, kandangIDs []uint) (float64, error) + GetPulletCost(ctx context.Context, projectFlockKandangId uint) (float64, error) + GetTransferSourceSummary(ctx context.Context, projectFlockKandangId uint) (uint, float64, error) } type HppRepositoryImpl struct { @@ -26,14 +30,28 @@ func NewHppCostRepository(db *gorm.DB) HppCostRepository { return &HppRepositoryImpl{db: db} } -func (r *HppRepositoryImpl) GetDocCost(ctx context.Context, projectFlockKandangId uint) (float64, error) { +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, kandangIDs []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 = ?", projectFlockKandangId). + Where("pc.project_flock_kandang_id IN (?)", kandangIDs). Scan(&total).Error if err != nil { return 0, err @@ -42,31 +60,12 @@ func (r *HppRepositoryImpl) GetDocCost(ctx context.Context, projectFlockKandangI return total, nil } -func (r *HppRepositoryImpl) GetBudgetCost(ctx context.Context, projectFlockKandangId uint) (float64, error) { - 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") - +func (r *HppRepositoryImpl) GetBudgetCostByProjectFlockId(ctx context.Context, projectFlockId uint) (float64, error) { var total float64 err := r.db.WithContext(ctx). - Table("project_flock_kandangs AS pfk"). - Select(` - COALESCE(SUM((pb.qty * pb.price) * COALESCE(k_usage.kandang_usage_qty, 0) / NULLIF(p_usage.project_usage_qty, 0)), 0)`). - 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 = ?", projectFlockKandangId). + Table("project_chickin_details AS pcd"). + Select("COALESCE(SUM(pcd.qty * pcd.price), 0)"). + Where("pcd.project_flock_id = ?", projectFlockId). Scan(&total).Error if err != nil { return 0, err @@ -75,13 +74,15 @@ func (r *HppRepositoryImpl) GetBudgetCost(ctx context.Context, projectFlockKanda return total, nil } -func (r *HppRepositoryImpl) GetExpedisionCost(ctx context.Context, projectFlockKandangId uint) (float64, error) { +func (r *HppRepositoryImpl) GetExpedisionCost(ctx context.Context, kandangIDs []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"). - Where("en.project_flock_kandang_id = ?", projectFlockKandangId). + 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 (?)", kandangIDs). + Where("f.name = ?", utils.FlagEkspedisi). Scan(&total).Error if err != nil { return 0, err @@ -90,7 +91,7 @@ func (r *HppRepositoryImpl) GetExpedisionCost(ctx context.Context, projectFlockK return total, nil } -func (r *HppRepositoryImpl) GetFeedCost(ctx context.Context, projectFlockKandangId uint, date *time.Time) (float64, error) { +func (r *HppRepositoryImpl) GetFeedCost(ctx context.Context, kandangIDs []uint, date *time.Time) (float64, error) { if date == nil { now := time.Now() date = &now @@ -105,7 +106,7 @@ func (r *HppRepositoryImpl) GetFeedCost(ctx context.Context, projectFlockKandang 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 = ?", projectFlockKandangId). + Where("r.project_flock_kandangs_id IN (?)", kandangIDs). Where("r.record_datetime <= ?", *date). Where("f.name = ?", utils.FlagPakan). Scan(&total).Error @@ -116,7 +117,7 @@ func (r *HppRepositoryImpl) GetFeedCost(ctx context.Context, projectFlockKandang return total, nil } -func (r *HppRepositoryImpl) GetOvkCost(ctx context.Context, projectFlockKandangId uint, date *time.Time) (float64, error) { +func (r *HppRepositoryImpl) GetOvkCost(ctx context.Context, kandangIDs []uint, date *time.Time) (float64, error) { if date == nil { now := time.Now() date = &now @@ -138,7 +139,7 @@ func (r *HppRepositoryImpl) GetOvkCost(ctx context.Context, projectFlockKandangI 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 = ?", projectFlockKandangId). + Where("r.project_flock_kandangs_id IN (?)", kandangIDs). Where("r.record_datetime <= ?", *date). Where("f.name IN ?", flags). Scan(&total).Error @@ -148,3 +149,63 @@ func (r *HppRepositoryImpl) GetOvkCost(ctx context.Context, projectFlockKandangI return total, nil } + +func (r *HppRepositoryImpl) GetTotalPopulation(ctx context.Context, kandangIDs []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 (?)", kandangIDs). + 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) 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). + 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 index 5ef9091e..8a78aded 100644 --- a/internal/common/service/common.hpp.service.go +++ b/internal/common/service/common.hpp.service.go @@ -9,7 +9,8 @@ import ( type HppService interface { CalculateHppCost(projectFlockKandangId uint, date *time.Time) (*HppCostResponse, error) - GetTotalDepresiasi(projectFlockKandangId uint, date *time.Time) (float64, error) + GetTotalDepresiasiFlockGrowing(sourceProjectFlockID uint, date *time.Time) (float64, error) + GetTotalProductionCost(projectFlockKandangId uint, date *time.Time, totalDepresiasiGrowing float64) (float64, error) } type HppCostResponse struct { @@ -39,8 +40,36 @@ func (s *hppService) CalculateHppCost(projectFlockKandangId uint, date *time.Tim date = &now } - // _ = projectFlockKandangId - _ = date + var sourceProjectFlockID uint + var transferTotalQty float64 + var err error + sourceProjectFlockID, transferTotalQty, err = s.hppRepo.GetTransferSourceSummary(context.Background(), projectFlockKandangId) + if err != nil { + return nil, err + } + + kandangIDsGrowing, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), sourceProjectFlockID) + + if err != nil { + return nil, err + } + + totalPopulationFlockGrowing, err := s.hppRepo.GetTotalPopulation(context.Background(), kandangIDsGrowing) + if err != nil { + return nil, err + } + + totalDepresiasiFlockGrowing, err := s.GetTotalDepresiasiFlockGrowing(sourceProjectFlockID, date) + if err != nil { + return nil, err + } + + depresiasiTransfer := (totalDepresiasiFlockGrowing * transferTotalQty) / totalPopulationFlockGrowing + + _, err = s.GetTotalProductionCost(projectFlockKandangId, date, depresiasiTransfer) + if err != nil { + return nil, err + } return &HppCostResponse{ Estimation: HppCostDetail{}, @@ -48,7 +77,7 @@ func (s *hppService) CalculateHppCost(projectFlockKandangId uint, date *time.Tim }, nil } -func (s *hppService) GetTotalDepresiasi(projectFlockKandangId uint, date *time.Time) (float64, error) { +func (s *hppService) GetTotalDepresiasiFlockGrowing(sourceProjectFlockID uint, date *time.Time) (float64, error) { if date == nil { now := time.Now() date = &now @@ -58,32 +87,52 @@ func (s *hppService) GetTotalDepresiasi(projectFlockKandangId uint, date *time.T return 0, nil } - docCost, err := s.hppRepo.GetDocCost(context.Background(), projectFlockKandangId) + kandangIDs, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), sourceProjectFlockID) if err != nil { return 0, err } - budgetCost, err := s.hppRepo.GetBudgetCost(context.Background(), projectFlockKandangId) + docCost, err := s.hppRepo.GetDocCost(context.Background(), kandangIDs) if err != nil { return 0, err } - expedisionCost, err := s.hppRepo.GetExpedisionCost(context.Background(), projectFlockKandangId) + budgetCost, err := s.hppRepo.GetBudgetCostByProjectFlockId(context.Background(), sourceProjectFlockID) if err != nil { return 0, err } - feedCost, err := s.hppRepo.GetFeedCost(context.Background(), projectFlockKandangId, date) + expedisionCost, err := s.hppRepo.GetExpedisionCost(context.Background(), kandangIDs) if err != nil { return 0, err } - ovkCost, err := s.hppRepo.GetOvkCost(context.Background(), projectFlockKandangId, date) + feedCost, err := s.hppRepo.GetFeedCost(context.Background(), kandangIDs, date) if err != nil { return 0, err } + ovkCost, err := s.hppRepo.GetOvkCost(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 + + } + _ = date - return docCost + budgetCost + expedisionCost + feedCost + ovkCost, nil + return depresiasiTransfer + costPullet, nil } From f2a46843c88501b6c30a2685114432b30ebca1e6 Mon Sep 17 00:00:00 2001 From: giovanni Date: Thu, 22 Jan 2026 12:53:02 +0700 Subject: [PATCH 03/20] continue common service hpp --- .../repository/common.hpp.repository.go | 97 +++++++++++-- internal/common/service/common.hpp.service.go | 132 ++++++++++++++---- 2 files changed, 187 insertions(+), 42 deletions(-) diff --git a/internal/common/repository/common.hpp.repository.go b/internal/common/repository/common.hpp.repository.go index 74ed5261..6c50708a 100644 --- a/internal/common/repository/common.hpp.repository.go +++ b/internal/common/repository/common.hpp.repository.go @@ -12,13 +12,16 @@ import ( type HppCostRepository interface { GetProjectFlockKandangIDs(ctx context.Context, projectFlockId uint) ([]uint, error) - GetDocCost(ctx context.Context, kandangIDs []uint) (float64, error) + GetDocCost(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) GetBudgetCostByProjectFlockId(ctx context.Context, projectFlockId uint) (float64, error) - GetExpedisionCost(ctx context.Context, kandangIDs []uint) (float64, error) - GetFeedCost(ctx context.Context, kandangIDs []uint, date *time.Time) (float64, error) - GetOvkCost(ctx context.Context, kandangIDs []uint, date *time.Time) (float64, error) - GetTotalPopulation(ctx context.Context, kandangIDs []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) } @@ -44,14 +47,14 @@ func (r *HppRepositoryImpl) GetProjectFlockKandangIDs(ctx context.Context, proje return ids, nil } -func (r *HppRepositoryImpl) GetDocCost(ctx context.Context, kandangIDs []uint) (float64, error) { +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 (?)", kandangIDs). + Where("pc.project_flock_kandang_id IN (?)", projectFlockKandangIDs). Scan(&total).Error if err != nil { return 0, err @@ -74,14 +77,14 @@ func (r *HppRepositoryImpl) GetBudgetCostByProjectFlockId(ctx context.Context, p return total, nil } -func (r *HppRepositoryImpl) GetExpedisionCost(ctx context.Context, kandangIDs []uint) (float64, error) { +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 (?)", kandangIDs). + Where("en.project_flock_kandang_id IN (?)", projectFlockKandangIDs). Where("f.name = ?", utils.FlagEkspedisi). Scan(&total).Error if err != nil { @@ -91,7 +94,7 @@ func (r *HppRepositoryImpl) GetExpedisionCost(ctx context.Context, kandangIDs [] return total, nil } -func (r *HppRepositoryImpl) GetFeedCost(ctx context.Context, kandangIDs []uint, date *time.Time) (float64, error) { +func (r *HppRepositoryImpl) GetFeedUsageCost(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, error) { if date == nil { now := time.Now() date = &now @@ -106,7 +109,7 @@ func (r *HppRepositoryImpl) GetFeedCost(ctx context.Context, kandangIDs []uint, 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 (?)", kandangIDs). + Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs). Where("r.record_datetime <= ?", *date). Where("f.name = ?", utils.FlagPakan). Scan(&total).Error @@ -117,7 +120,7 @@ func (r *HppRepositoryImpl) GetFeedCost(ctx context.Context, kandangIDs []uint, return total, nil } -func (r *HppRepositoryImpl) GetOvkCost(ctx context.Context, kandangIDs []uint, date *time.Time) (float64, error) { +func (r *HppRepositoryImpl) GetOvkUsageCost(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, error) { if date == nil { now := time.Now() date = &now @@ -139,7 +142,7 @@ func (r *HppRepositoryImpl) GetOvkCost(ctx context.Context, kandangIDs []uint, d 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 (?)", kandangIDs). + Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs). Where("r.record_datetime <= ?", *date). Where("f.name IN ?", flags). Scan(&total).Error @@ -150,12 +153,12 @@ func (r *HppRepositoryImpl) GetOvkCost(ctx context.Context, kandangIDs []uint, d return total, nil } -func (r *HppRepositoryImpl) GetTotalPopulation(ctx context.Context, kandangIDs []uint) (float64, error) { +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 (?)", kandangIDs). + Where("pc.project_flock_kandang_id IN (?)", projectFlockKandangIDs). Scan(&total).Error if err != nil { return 0, err @@ -192,6 +195,70 @@ func (r *HppRepositoryImpl) GetPulletCost(ctx context.Context, projectFlockKanda 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) / 1000 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 diff --git a/internal/common/service/common.hpp.service.go b/internal/common/service/common.hpp.service.go index 8a78aded..8210fc17 100644 --- a/internal/common/service/common.hpp.service.go +++ b/internal/common/service/common.hpp.service.go @@ -11,6 +11,8 @@ 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) } type HppCostResponse struct { @@ -40,36 +42,17 @@ func (s *hppService) CalculateHppCost(projectFlockKandangId uint, date *time.Tim date = &now } - var sourceProjectFlockID uint - var transferTotalQty float64 - var err error - sourceProjectFlockID, transferTotalQty, err = s.hppRepo.GetTransferSourceSummary(context.Background(), projectFlockKandangId) + depresiasiTransfer, err := s.GetDepresiasiTransfer(projectFlockKandangId, date) if err != nil { return nil, err } - kandangIDsGrowing, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), sourceProjectFlockID) - + totalProductionCost, err := s.GetTotalProductionCost(projectFlockKandangId, date, depresiasiTransfer) if err != nil { return nil, err } - totalPopulationFlockGrowing, err := s.hppRepo.GetTotalPopulation(context.Background(), kandangIDsGrowing) - if err != nil { - return nil, err - } - - totalDepresiasiFlockGrowing, err := s.GetTotalDepresiasiFlockGrowing(sourceProjectFlockID, date) - if err != nil { - return nil, err - } - - depresiasiTransfer := (totalDepresiasiFlockGrowing * transferTotalQty) / totalPopulationFlockGrowing - - _, err = s.GetTotalProductionCost(projectFlockKandangId, date, depresiasiTransfer) - if err != nil { - return nil, err - } + _ = totalProductionCost return &HppCostResponse{ Estimation: HppCostDetail{}, @@ -107,12 +90,12 @@ func (s *hppService) GetTotalDepresiasiFlockGrowing(sourceProjectFlockID uint, d return 0, err } - feedCost, err := s.hppRepo.GetFeedCost(context.Background(), kandangIDs, date) + feedCost, err := s.hppRepo.GetFeedUsageCost(context.Background(), kandangIDs, date) if err != nil { return 0, err } - ovkCost, err := s.hppRepo.GetOvkCost(context.Background(), kandangIDs, date) + ovkCost, err := s.hppRepo.GetOvkUsageCost(context.Background(), kandangIDs, date) if err != nil { return 0, err } @@ -129,10 +112,105 @@ func (s *hppService) GetTotalProductionCost(projectFlockKandangId uint, date *ti costPullet, err := s.hppRepo.GetPulletCost(context.Background(), projectFlockKandangId) if err != nil { return 0, err - } - _ = date + costFeed, err := s.hppRepo.GetFeedUsageCost(context.Background(), []uint{projectFlockKandangId}, date) + if err != nil { + return 0, err + } - return depresiasiTransfer + costPullet, nil + 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 } From 58b29501c039c7a9483a5cba995f3c9946168224 Mon Sep 17 00:00:00 2001 From: giovanni Date: Thu, 22 Jan 2026 13:54:32 +0700 Subject: [PATCH 04/20] finishing common service calculate hpp --- internal/common/service/common.hpp.service.go | 57 +++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/internal/common/service/common.hpp.service.go b/internal/common/service/common.hpp.service.go index 8210fc17..1b94e791 100644 --- a/internal/common/service/common.hpp.service.go +++ b/internal/common/service/common.hpp.service.go @@ -13,6 +13,7 @@ type HppService interface { 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 { @@ -52,12 +53,8 @@ func (s *hppService) CalculateHppCost(projectFlockKandangId uint, date *time.Tim return nil, err } - _ = totalProductionCost + return s.GetHppEstimationDanRealisasi(totalProductionCost, projectFlockKandangId, date) - return &HppCostResponse{ - Estimation: HppCostDetail{}, - Real: HppCostDetail{}, - }, nil } func (s *hppService) GetTotalDepresiasiFlockGrowing(sourceProjectFlockID uint, date *time.Time) (float64, error) { @@ -214,3 +211,53 @@ func (s *hppService) GetDepresiasiTransfer(projectFlockKandangId uint, date *tim 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 = totalProductionCost / estimWeightKg + } + if estimPieces > 0 { + estimation.HargaButir = totalProductionCost / estimPieces + } + + real := HppCostDetail{ + Total: totalProductionCost, + Kg: realWeightKg, + Butir: realPieces, + } + if realWeightKg > 0 { + real.HargaKg = totalProductionCost / realWeightKg + } + if realPieces > 0 { + real.HargaButir = totalProductionCost / realPieces + } + + return &HppCostResponse{ + Estimation: estimation, + Real: real, + }, nil +} From 0d585a99a62928df2807f44b2382916d62c99bc7 Mon Sep 17 00:00:00 2001 From: giovanni Date: Thu, 22 Jan 2026 17:37:28 +0700 Subject: [PATCH 05/20] adjust api hpp per kandang and implement common service hpp --- .../repository/common.hpp.repository.go | 7 +- internal/modules/repports/module.go | 4 +- .../hpp_per_kandang.repository.go | 177 +----------------- .../repports/services/repport.service.go | 66 ++++--- 4 files changed, 47 insertions(+), 207 deletions(-) diff --git a/internal/common/repository/common.hpp.repository.go b/internal/common/repository/common.hpp.repository.go index 6c50708a..fd51e329 100644 --- a/internal/common/repository/common.hpp.repository.go +++ b/internal/common/repository/common.hpp.repository.go @@ -66,9 +66,9 @@ func (r *HppRepositoryImpl) GetDocCost(ctx context.Context, projectFlockKandangI func (r *HppRepositoryImpl) GetBudgetCostByProjectFlockId(ctx context.Context, projectFlockId uint) (float64, error) { var total float64 err := r.db.WithContext(ctx). - Table("project_chickin_details AS pcd"). - Select("COALESCE(SUM(pcd.qty * pcd.price), 0)"). - Where("pcd.project_flock_id = ?", projectFlockId). + 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 @@ -269,6 +269,7 @@ func (r *HppRepositoryImpl) GetTransferSourceSummary(ctx context.Context, projec 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 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..a0d96863 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" ) @@ -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,127 +207,7 @@ 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) { diff --git a/internal/modules/repports/services/repport.service.go b/internal/modules/repports/services/repport.service.go index 03b1b370..0073b8ba 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, @@ -1512,29 +1515,29 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes return nil, nil, err } - eggMap, err := s.HppPerKandangRepo.GetEggProductionByProjectFlockKandangIDs(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 - } - } + // eggMap, err := s.HppPerKandangRepo.GetEggProductionByProjectFlockKandangIDs(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 + // } + // } } 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, } } @@ -1608,19 +1611,33 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes continue } - eggPiecesFloatRemaining := row.EggProductionPiecesRemaining + var eggPiecesFloatRemaining float64 + var eggRemainingWeightFloatRemaining float64 + 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 } @@ -1644,11 +1661,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) From 8c58cc41036760a4a3084a0a67d56fa625f726e2 Mon Sep 17 00:00:00 2001 From: ragilap Date: Thu, 22 Jan 2026 17:49:46 +0700 Subject: [PATCH 06/20] [FIX/BE-US] fix uniformity relation chickin date --- .../production/uniformities/services/uniformity.service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/modules/production/uniformities/services/uniformity.service.go b/internal/modules/production/uniformities/services/uniformity.service.go index 41611ac3..79e4d3e7 100644 --- a/internal/modules/production/uniformities/services/uniformity.service.go +++ b/internal/modules/production/uniformities/services/uniformity.service.go @@ -375,7 +375,7 @@ func (s *uniformityService) CreateOne(c *fiber.Ctx, req *validation.Create, file var latestWeek int if err := s.Repository.DB().WithContext(c.Context()). Model(&entity.ProjectFlockKandangUniformity{}). - Where("project_flock_kandang_id = ? AND deleted_at IS NULL", req.ProjectFlockKandangId). + Where("project_flock_kandang_id = ?", req.ProjectFlockKandangId). Select("COALESCE(MAX(week), 0)"). Scan(&latestWeek).Error; err != nil { return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate uniformity week sequence") From 9928b4c97065a2aeff70eb5c80d574f5f5bdeb8a Mon Sep 17 00:00:00 2001 From: ragilap Date: Thu, 22 Jan 2026 17:50:04 +0700 Subject: [PATCH 07/20] [FIX/BE-US] fix uniformity relation chickin date --- .../controllers/projectflock.controller.go | 5 +++ .../dto/projectflock_kandang.dto.go | 3 ++ .../services/projectflock.service.go | 31 +++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/internal/modules/production/project_flocks/controllers/projectflock.controller.go b/internal/modules/production/project_flocks/controllers/projectflock.controller.go index e82d3af5..8c5a9298 100644 --- a/internal/modules/production/project_flocks/controllers/projectflock.controller.go +++ b/internal/modules/production/project_flocks/controllers/projectflock.controller.go @@ -287,6 +287,11 @@ func (u *ProjectflockController) LookupProjectFlockKandang(c *fiber.Ctx) error { } else { dtoResult.AvailableQuantity = population } + if chickinDate, err := u.ProjectflockService.GetProjectFlockKandangChickinDate(c, result.Id); err != nil { + return err + } else if chickinDate != nil { + dtoResult.ChickInDate = chickinDate + } if warehouse, werr := u.ProjectflockService.GetWarehouseByKandangID(c, result.KandangId); werr != nil { return werr } else if warehouse != nil { diff --git a/internal/modules/production/project_flocks/dto/projectflock_kandang.dto.go b/internal/modules/production/project_flocks/dto/projectflock_kandang.dto.go index c18f3f65..39abfe62 100644 --- a/internal/modules/production/project_flocks/dto/projectflock_kandang.dto.go +++ b/internal/modules/production/project_flocks/dto/projectflock_kandang.dto.go @@ -1,6 +1,8 @@ package dto import ( + "time" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto" fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto" @@ -38,6 +40,7 @@ type ProjectFlockKandangDTO struct { ProjectFlock *ProjectFlockWithPivotDTO `json:"project_flock,omitempty"` AvailableQuantity float64 `json:"available_quantity"` Population *float64 `json:"population,omitempty"` + ChickInDate *time.Time `json:"chick_in_date,omitempty"` } func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDTO { diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index 05e21894..21925a24 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -6,6 +6,7 @@ import ( "fmt" "strconv" "strings" + "time" commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" @@ -42,6 +43,7 @@ type ProjectflockService interface { DeleteOne(ctx *fiber.Ctx, id uint) error GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, float64, error) GetProjectFlockKandangPopulation(ctx *fiber.Ctx, projectFlockKandangID uint) (float64, error) + GetProjectFlockKandangChickinDate(ctx *fiber.Ctx, projectFlockKandangID uint) (*time.Time, error) GetPeriodSummary(ctx *fiber.Ctx, locationID uint) ([]KandangPeriodSummary, error) GetProjectPeriods(ctx *fiber.Ctx, projectIDs []uint) (map[uint]int, error) Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error) @@ -459,6 +461,35 @@ func (s projectflockService) GetProjectFlockKandangPopulation(ctx *fiber.Ctx, pr return total, nil } +func (s projectflockService) GetProjectFlockKandangChickinDate(ctx *fiber.Ctx, projectFlockKandangID uint) (*time.Time, error) { + if s.PopulationRepo == nil { + return nil, fiber.NewError(fiber.StatusInternalServerError, "Project flock population repository is not configured") + } + if projectFlockKandangID == 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is required") + } + + populations, err := s.PopulationRepo.GetByProjectFlockKandangID(ctx.Context(), projectFlockKandangID) + if err != nil { + s.Log.Errorf("Failed to fetch populations for project flock kandang %d: %+v", projectFlockKandangID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandang chick in date") + } + + var earliest *time.Time + for _, pop := range populations { + if pop.ProjectChickin == nil || pop.ProjectChickin.ChickInDate.IsZero() { + continue + } + chickinDate := pop.ProjectChickin.ChickInDate + if earliest == nil || chickinDate.Before(*earliest) { + copy := chickinDate + earliest = © + } + } + + return earliest, nil +} + func (s projectflockService) GetProjectFlockKandangByParams(ctx *fiber.Ctx, idStr string, projectFlockIdStr string, kandangIdStr string) (*entity.ProjectFlockKandang, float64, error) { idStr = strings.TrimSpace(idStr) projectFlockIdStr = strings.TrimSpace(projectFlockIdStr) From fb565ef7285fa99346dec2a70ad0250156b38e1e Mon Sep 17 00:00:00 2001 From: giovanni Date: Fri, 23 Jan 2026 10:01:48 +0700 Subject: [PATCH 08/20] fix hpp harian kandang --- .../repository/common.hpp.repository.go | 2 +- .../hpp_per_kandang.repository.go | 27 ++++++++---------- .../repports/services/repport.service.go | 28 +++++++++---------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/internal/common/repository/common.hpp.repository.go b/internal/common/repository/common.hpp.repository.go index fd51e329..37094c16 100644 --- a/internal/common/repository/common.hpp.repository.go +++ b/internal/common/repository/common.hpp.repository.go @@ -207,7 +207,7 @@ func (r *HppRepositoryImpl) GetEggProduksiPiecesAndWeightKgByProjectFlockKandang } err := r.db.WithContext(ctx). Table("recordings AS r"). - Select("COALESCE(SUM(re.qty), 0) AS total_pieces, COALESCE(SUM(re.weight), 0) / 1000 AS total_weight_kg"). + 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). diff --git a/internal/modules/repports/repositories/hpp_per_kandang.repository.go b/internal/modules/repports/repositories/hpp_per_kandang.repository.go index a0d96863..03d56fc6 100644 --- a/internal/modules/repports/repositories/hpp_per_kandang.repository.go +++ b/internal/modules/repports/repositories/hpp_per_kandang.repository.go @@ -23,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 { @@ -49,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 { @@ -210,7 +210,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context, 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 } @@ -231,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) @@ -241,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). @@ -262,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 0073b8ba..ba0e2098 100644 --- a/internal/modules/repports/services/repport.service.go +++ b/internal/modules/repports/services/repport.service.go @@ -1515,18 +1515,18 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes return nil, nil, err } - // eggMap, err := s.HppPerKandangRepo.GetEggProductionByProjectFlockKandangIDs(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 - // } - // } + 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 + } + } } costMap := make(map[uint]HppCostAggregate, len(costRows)) @@ -1612,7 +1612,7 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes } var eggPiecesFloatRemaining float64 - var eggRemainingWeightFloatRemaining float64 + eggRemainingWeightFloatRemaining := row.EggProductionWeightKgRemaining var eggTotalPiecesFloat float64 var eggWeightFloat float64 eggHpp := 0.0 @@ -1622,7 +1622,7 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes return nil, nil, err } if hppCost != nil { - eggRemainingWeightFloatRemaining = hppCost.Estimation.Kg - hppCost.Real.Kg + // eggRemainingWeightFloatRemaining = hppCost.Estimation.Kg - hppCost.Real.Kg eggPiecesFloatRemaining = hppCost.Estimation.Butir - hppCost.Real.Butir eggHpp = hppCost.Estimation.HargaKg eggTotalPiecesFloat = hppCost.Estimation.Butir From d54911f8b40b2d8d5d1b1a10380ecc94e5c98410 Mon Sep 17 00:00:00 2001 From: giovanni Date: Fri, 23 Jan 2026 10:29:48 +0700 Subject: [PATCH 09/20] adjust value hpp --- internal/common/service/common.hpp.service.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/internal/common/service/common.hpp.service.go b/internal/common/service/common.hpp.service.go index 1b94e791..44f2dd5f 100644 --- a/internal/common/service/common.hpp.service.go +++ b/internal/common/service/common.hpp.service.go @@ -2,6 +2,7 @@ package service import ( "context" + "math" "time" commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" @@ -238,10 +239,10 @@ func (s *hppService) GetHppEstimationDanRealisasi(totalProductionCost float64, p Butir: estimPieces, } if estimWeightKg > 0 { - estimation.HargaKg = totalProductionCost / estimWeightKg + estimation.HargaKg = roundToTwoDecimals(totalProductionCost / estimWeightKg) } if estimPieces > 0 { - estimation.HargaButir = totalProductionCost / estimPieces + estimation.HargaButir = roundToTwoDecimals(totalProductionCost / estimPieces) } real := HppCostDetail{ @@ -250,10 +251,10 @@ func (s *hppService) GetHppEstimationDanRealisasi(totalProductionCost float64, p Butir: realPieces, } if realWeightKg > 0 { - real.HargaKg = totalProductionCost / realWeightKg + real.HargaKg = roundToTwoDecimals(totalProductionCost / realWeightKg) } if realPieces > 0 { - real.HargaButir = totalProductionCost / realPieces + real.HargaButir = roundToTwoDecimals(totalProductionCost / realPieces) } return &HppCostResponse{ @@ -261,3 +262,7 @@ func (s *hppService) GetHppEstimationDanRealisasi(totalProductionCost float64, p Real: real, }, nil } + +func roundToTwoDecimals(value float64) float64 { + return math.Round(value*100) / 100 +} From 6b4eb758e4cf6726471e46095fefbc1efe65db84 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Fri, 23 Jan 2026 10:32:40 +0700 Subject: [PATCH 10/20] FIX[BE]: fixing umur on closing penjualan for penjualan OVK and PAKAN --- .../modules/closings/dto/closingMarketing.dto.go | 15 +++++++++++++-- .../marketing/services/salesorder.service.go | 3 --- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/internal/modules/closings/dto/closingMarketing.dto.go b/internal/modules/closings/dto/closingMarketing.dto.go index d725b430..72523b69 100644 --- a/internal/modules/closings/dto/closingMarketing.dto.go +++ b/internal/modules/closings/dto/closingMarketing.dto.go @@ -44,7 +44,12 @@ type PenjualanRealisasiResponseDTO struct { func ToSalesDTO(e entity.MarketingDeliveryProduct) SalesDTO { - ageInDay, ageInWeeks := calculateAgeFromChickin(e.MarketingProduct.ProductWarehouse.ProjectFlockKandang, e.DeliveryDate) + productFlags := make([]string, len(e.MarketingProduct.ProductWarehouse.Product.Flags)) + for i, f := range e.MarketingProduct.ProductWarehouse.Product.Flags { + productFlags[i] = f.Name + } + + ageInDay, ageInWeeks := calculateAgeFromChickin(e.MarketingProduct.ProductWarehouse.ProjectFlockKandang, e.DeliveryDate, productFlags) var product *productDTO.ProductRelationDTO if e.MarketingProduct.ProductWarehouse.Product.Id != 0 { @@ -126,11 +131,17 @@ func ToPenjualanRealisasiResponseDTO(e []entity.MarketingDeliveryProduct) Penjua } } -func calculateAgeFromChickin(projectFlockKandang *entity.ProjectFlockKandang, deliveryDate *time.Time) (int, int) { +func calculateAgeFromChickin(projectFlockKandang *entity.ProjectFlockKandang, deliveryDate *time.Time, productFlags []string) (int, int) { if projectFlockKandang == nil || deliveryDate == nil || len(projectFlockKandang.Chickins) == 0 { return 0, 0 } + for _, flag := range productFlags { + if flag == "OVK" || flag == "PAKAN" { + return 0, 0 // + } + } + earliestChickinDate := projectFlockKandang.Chickins[0].ChickInDate for _, chickin := range projectFlockKandang.Chickins { if chickin.ChickInDate.Before(earliestChickinDate) { diff --git a/internal/modules/marketing/services/salesorder.service.go b/internal/modules/marketing/services/salesorder.service.go index e2cfcabb..dbf99219 100644 --- a/internal/modules/marketing/services/salesorder.service.go +++ b/internal/modules/marketing/services/salesorder.service.go @@ -311,14 +311,11 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u } } - // Hitung total_weight dan total_price berdasarkan flag totalWeight := rp.Qty * rp.AvgWeight var totalPrice float64 if isPakanOrOVK { - // PAKAN atau OVK: qty × unit_price totalPrice = rp.Qty * rp.UnitPrice } else { - // Produk lain: total_weight × unit_price totalPrice = totalWeight * rp.UnitPrice } From f1787d3375aab16e786c85976dfb909f54fe09d5 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Fri, 23 Jan 2026 11:07:11 +0700 Subject: [PATCH 11/20] FIX[BE]: Fix wrong calculation avg sales on report penjualan harian --- internal/modules/repports/dto/repportMarketing.dto.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/internal/modules/repports/dto/repportMarketing.dto.go b/internal/modules/repports/dto/repportMarketing.dto.go index 336b6576..b12fdfeb 100644 --- a/internal/modules/repports/dto/repportMarketing.dto.go +++ b/internal/modules/repports/dto/repportMarketing.dto.go @@ -190,16 +190,13 @@ func ToSummaryFromDTOItems(items []RepportMarketingItemDTO) *Summary { totalWeightKg += item.TotalWeightKg totalSalesAmount += int64(item.SalesAmount) totalHppAmount += int64(item.HppAmount) - avgSalesPrice += item.SalesPricePerKg } totalHppPricePerKg := float64(0) + if totalWeightKg > 0 { totalHppPricePerKg = float64(totalHppAmount) / totalWeightKg - } - - if len(items) > 0 { - avgSalesPrice = avgSalesPrice / float64(len(items)) + avgSalesPrice = float64(totalSalesAmount) / totalWeightKg } if totalQty > 0 { From 8dc88b97a49319e599c8d794ac8bb3cc8ba2b00e Mon Sep 17 00:00:00 2001 From: ragilap Date: Fri, 23 Jan 2026 11:43:50 +0700 Subject: [PATCH 12/20] [FIX/BE-US] fix closing counting sapronak --- .../closings/dto/closingSapronak.dto.go | 2 +- .../repositories/closing.repository.go | 119 +++++++++++++++++- .../closings/services/sapronak.service.go | 10 +- 3 files changed, 124 insertions(+), 7 deletions(-) diff --git a/internal/modules/closings/dto/closingSapronak.dto.go b/internal/modules/closings/dto/closingSapronak.dto.go index ad66d5a7..81fe7ebd 100644 --- a/internal/modules/closings/dto/closingSapronak.dto.go +++ b/internal/modules/closings/dto/closingSapronak.dto.go @@ -196,7 +196,7 @@ func ToSapronakProjectAggregatedFromReport(report *SapronakReportDTO, flag strin } for idx, item := range group.Items { - productKey := strings.ToUpper(flagKey + "|" + item.ProductName) + productKey := strings.ToUpper(flagKey + "|" + item.ProductName + "|" + item.NoReferensi + "|" + formatDate(item.Tanggal)) baseRow := SapronakCategoryRowDTO{ ID: idx + 1, Date: formatDate(item.Tanggal), diff --git a/internal/modules/closings/repositories/closing.repository.go b/internal/modules/closings/repositories/closing.repository.go index 120c3e5c..daff5d35 100644 --- a/internal/modules/closings/repositories/closing.repository.go +++ b/internal/modules/closings/repositories/closing.repository.go @@ -32,9 +32,10 @@ type ClosingRepository interface { FetchSapronakUsageDetails(ctx context.Context, pfkID uint) (map[uint][]SapronakDetailRow, error) FetchSapronakChickinUsage(ctx context.Context, pfkID uint) ([]SapronakUsageRow, error) FetchSapronakChickinUsageDetails(ctx context.Context, pfkID uint) (map[uint][]SapronakDetailRow, error) + FetchSapronakUsageAllocatedDetails(ctx context.Context, projectFlockKandangID uint) (map[uint][]SapronakDetailRow, error) FetchSapronakAdjustments(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) FetchSapronakTransfers(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) - FetchSapronakSales(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, error) + FetchSapronakSales(ctx context.Context, projectFlockKandangID uint) (map[uint][]SapronakDetailRow, error) GetProductsWithFlagsByIDs(ctx context.Context, productIDs []uint) ([]entity.Product, error) } @@ -872,6 +873,74 @@ func (r *ClosingRepositoryImpl) FetchSapronakChickinUsageDetails(ctx context.Con ) } +func (r *ClosingRepositoryImpl) FetchSapronakUsageAllocatedDetails(ctx context.Context, projectFlockKandangID uint) (map[uint][]SapronakDetailRow, error) { + if projectFlockKandangID == 0 { + return map[uint][]SapronakDetailRow{}, nil + } + + query := r.withCtx(ctx). + Table("stock_allocations AS sa"). + Select(` + pw.product_id AS product_id, + p.name AS product_name, + f.name AS flag, + COALESCE( + pi.received_date, + st.transfer_date, + lt.transfer_date, + sl.created_at, + pc.chick_in_date, + r.record_datetime + ) AS date, + COALESCE( + po.po_number, + st.movement_number, + lt.transfer_number, + CONCAT('ADJ-', ast.id), + CONCAT('CHICKIN-', pc.id), + CAST(r.id AS TEXT), + '' + ) AS reference, + 0 AS qty_in, + COALESCE(SUM(sa.qty), 0) AS qty_out, + COALESCE(pi.price, p.product_price, 0) AS price + `). + Joins("JOIN product_warehouses pw ON pw.id = sa.product_warehouse_id"). + 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). + Joins("LEFT JOIN recording_stocks rs ON rs.id = sa.usable_id AND sa.usable_type = ?", fifo.UsableKeyRecordingStock.String()). + Joins("LEFT JOIN recordings r ON r.id = rs.recording_id"). + Joins("LEFT JOIN project_chickins pc_used ON pc_used.id = sa.usable_id AND sa.usable_type = ?", fifo.UsableKeyProjectChickin.String()). + Joins("LEFT JOIN purchase_items pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyPurchaseItems.String()). + Joins("LEFT JOIN purchases po ON po.id = pi.purchase_id"). + Joins("LEFT JOIN stock_transfer_details std ON std.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyStockTransferIn.String()). + Joins("LEFT JOIN stock_transfers st ON st.id = std.stock_transfer_id"). + Joins("LEFT JOIN laying_transfer_targets ltt ON ltt.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyTransferToLayingIn.String()). + Joins("LEFT JOIN laying_transfers lt ON lt.id = ltt.laying_transfer_id"). + Joins("LEFT JOIN adjustment_stocks ast ON ast.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyAdjustmentIn.String()). + Joins("LEFT JOIN stock_logs sl ON sl.id = ast.stock_log_id"). + Joins("LEFT JOIN project_flock_populations pfp ON pfp.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyProjectFlockPopulation.String()). + Joins("LEFT JOIN project_chickins pc ON pc.id = pfp.project_chickin_id"). + Where("sa.status = ?", entity.StockAllocationStatusActive). + Where("f.name IN ?", sapronakFlagsAll). + Where(` + (sa.usable_type = ? AND r.project_flock_kandangs_id = ?) + OR + (sa.usable_type = ? AND pc_used.project_flock_kandang_id = ?) + `, + fifo.UsableKeyRecordingStock.String(), projectFlockKandangID, + fifo.UsableKeyProjectChickin.String(), projectFlockKandangID, + ). + Group(` + pw.product_id, p.name, f.name, + pi.received_date, st.transfer_date, lt.transfer_date, sl.created_at, pc.chick_in_date, r.record_datetime, + po.po_number, st.movement_number, lt.transfer_number, ast.id, pc.id, r.id, + pi.price, p.product_price + `) + + return scanAndGroupDetails(query) +} + func (r *ClosingRepositoryImpl) incomingPurchaseBase(ctx context.Context, kandangID uint) *gorm.DB { return r.withCtx(ctx). Table("purchase_items AS pi"). @@ -1131,7 +1200,7 @@ func (r *ClosingRepositoryImpl) FetchSapronakTransfers(ctx context.Context, kand return incoming, outgoing, nil } -func (r *ClosingRepositoryImpl) FetchSapronakSales(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, error) { +func (r *ClosingRepositoryImpl) FetchSapronakSales(ctx context.Context, projectFlockKandangID uint) (map[uint][]SapronakDetailRow, error) { query := r.withCtx(ctx). Table("stock_allocations AS sa"). Select(` @@ -1148,15 +1217,55 @@ func (r *ClosingRepositoryImpl) FetchSapronakSales(ctx context.Context, kandangI Joins("JOIN marketing_products mp ON mp.id = mdp.marketing_product_id"). Joins("JOIN marketings m ON m.id = mp.marketing_id"). Joins("JOIN product_warehouses pw ON pw.id = sa.product_warehouse_id"). - Joins("JOIN warehouses w ON w.id = pw.warehouse_id"). 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("sa.status = ?", entity.StockAllocationStatusActive). - Where("w.kandang_id = ?", kandangID). + Where("pw.project_flock_kandang_id = ?", projectFlockKandangID). Where("f.name IN ?", sapronakFlagsAll). Group("mdp.id, pw.product_id, p.name, f.name, mdp.delivery_date, mdp.created_at, m.so_number, mdp.unit_price, mp.unit_price") - return scanAndGroupDetails(query) + sales, err := scanAndGroupDetails(query) + if err != nil { + return nil, err + } + + nonFifoQuery := r.withCtx(ctx). + Table("marketing_delivery_products AS mdp"). + Select(` + pw.product_id AS product_id, + p.name AS product_name, + f.name AS flag, + COALESCE(mdp.delivery_date, mdp.created_at) AS date, + COALESCE(m.so_number, '') AS reference, + 0 AS qty_in, + COALESCE(mdp.usage_qty, 0) AS qty_out, + COALESCE(mdp.unit_price, mp.unit_price, 0) AS price + `). + Joins("JOIN marketing_products mp ON mp.id = mdp.marketing_product_id"). + Joins("JOIN marketings m ON m.id = mp.marketing_id"). + Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id"). + 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). + Joins("LEFT JOIN stock_allocations sa ON sa.usable_id = mdp.id AND sa.usable_type = ? AND sa.status = ?", + fifo.UsableKeyMarketingDelivery.String(), + entity.StockAllocationStatusActive, + ). + Where("mdp.usage_qty > 0"). + Where("sa.id IS NULL"). + Where("pw.project_flock_kandang_id = ?", projectFlockKandangID). + Where("f.name IN ?", sapronakFlagsAll). + Group("mdp.id, pw.product_id, p.name, f.name, mdp.delivery_date, mdp.created_at, m.so_number, mdp.unit_price, mp.unit_price") + + nonFifoSales, err := scanAndGroupDetails(nonFifoQuery) + if err != nil { + return nil, err + } + + for pid, rows := range nonFifoSales { + sales[pid] = append(sales[pid], rows...) + } + + return sales, nil } func (r *ClosingRepositoryImpl) GetProductsWithFlagsByIDs(ctx context.Context, productIDs []uint) ([]entity.Product, error) { diff --git a/internal/modules/closings/services/sapronak.service.go b/internal/modules/closings/services/sapronak.service.go index 930c1bc5..9501cfbc 100644 --- a/internal/modules/closings/services/sapronak.service.go +++ b/internal/modules/closings/services/sapronak.service.go @@ -347,6 +347,14 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj if err != nil { return nil, nil, 0, 0, err } + usageAllocatedDetails, err := s.Repository.FetchSapronakUsageAllocatedDetails(ctx, pfk.Id) + if err != nil { + return nil, nil, 0, 0, err + } + if len(usageAllocatedDetails) > 0 { + usageDetailsRows = usageAllocatedDetails + chickinUsageDetailsRows = map[uint][]repository.SapronakDetailRow{} + } adjIncomingRows, adjOutgoingRows, err := s.Repository.FetchSapronakAdjustments(ctx, pfk.KandangId) if err != nil { return nil, nil, 0, 0, err @@ -355,7 +363,7 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj if err != nil { return nil, nil, 0, 0, err } - salesOutRows, err := s.Repository.FetchSapronakSales(ctx, pfk.KandangId) + salesOutRows, err := s.Repository.FetchSapronakSales(ctx, pfk.Id) if err != nil { return nil, nil, 0, 0, err } From f82ac01e7c54d46e4c6b3b45a6fbe1bff57a53ce Mon Sep 17 00:00:00 2001 From: ragilap Date: Fri, 23 Jan 2026 12:02:26 +0700 Subject: [PATCH 13/20] [FIX/BE-US] fix recording date --- .../recordings/controllers/recording.controller.go | 13 ++++++++++++- .../recordings/services/recording.service.go | 11 +++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/internal/modules/production/recordings/controllers/recording.controller.go b/internal/modules/production/recordings/controllers/recording.controller.go index 7edb7b9a..51e3100d 100644 --- a/internal/modules/production/recordings/controllers/recording.controller.go +++ b/internal/modules/production/recordings/controllers/recording.controller.go @@ -3,6 +3,8 @@ package controller import ( "math" "strconv" + "strings" + "time" "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/dto" service "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services" @@ -82,7 +84,16 @@ func (u *RecordingController) GetNextDay(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is required") } - nextDay, err := u.RecordingService.GetNextDay(c, uint(projectFlockID)) + recordTime := time.Now().UTC() + if recordDate := strings.TrimSpace(c.Query("record_date")); recordDate != "" { + parsed, err := time.Parse("2006-01-02", recordDate) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "record_date must be in YYYY-MM-DD format") + } + recordTime = parsed.UTC() + } + + nextDay, err := u.RecordingService.GetNextDay(c, uint(projectFlockID), recordTime) if err != nil { return err } diff --git a/internal/modules/production/recordings/services/recording.service.go b/internal/modules/production/recordings/services/recording.service.go index b408995f..d490185d 100644 --- a/internal/modules/production/recordings/services/recording.service.go +++ b/internal/modules/production/recordings/services/recording.service.go @@ -13,8 +13,8 @@ import ( sProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/services" rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories" - rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations" + rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals" "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo" @@ -32,7 +32,7 @@ import ( type RecordingService interface { GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Recording, int64, error) GetOne(ctx *fiber.Ctx, id uint) (*entity.Recording, error) - GetNextDay(ctx *fiber.Ctx, projectFlockKandangId uint) (int, error) + GetNextDay(ctx *fiber.Ctx, projectFlockKandangId uint, recordTime time.Time) (int, error) CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Recording, error) UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Recording, error) DeleteOne(ctx *fiber.Ctx, id uint) error @@ -160,12 +160,15 @@ func (s recordingService) GetOne(c *fiber.Ctx, id uint) (*entity.Recording, erro return recording, nil } -func (s recordingService) GetNextDay(c *fiber.Ctx, projectFlockKandangId uint) (int, error) { +func (s recordingService) GetNextDay(c *fiber.Ctx, projectFlockKandangId uint, recordTime time.Time) (int, error) { if projectFlockKandangId == 0 { return 0, fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is required") } - day, err := s.computeRecordingDay(c.Context(), projectFlockKandangId, time.Now().UTC()) + if recordTime.IsZero() { + recordTime = time.Now().UTC() + } + day, err := s.computeRecordingDay(c.Context(), projectFlockKandangId, recordTime) if err != nil { s.Log.Errorf("Failed to compute recording day for project_flock_kandang_id=%d: %+v", projectFlockKandangId, err) return 0, err From f060da1cd3b643cd001e742f242ef46d69d1585f Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Fri, 23 Jan 2026 12:35:34 +0700 Subject: [PATCH 14/20] FIX[BE]: Integrate StockLogRepository into deliveryOrdersService for stock logging functionality --- internal/modules/marketing/module.go | 4 +- .../services/deliveryorder.service.go | 71 +++++++++++++++---- 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/internal/modules/marketing/module.go b/internal/modules/marketing/module.go index b93c6129..2f8ea4fb 100644 --- a/internal/modules/marketing/module.go +++ b/internal/modules/marketing/module.go @@ -16,6 +16,7 @@ import ( rCustomer "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories" rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" rProjectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" + rShared "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" "gitlab.com/mbugroup/lti-api.git/internal/utils" @@ -32,6 +33,7 @@ func (MarketingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate userRepo := rUser.NewUserRepository(db) customerRepo := rCustomer.NewCustomerRepository(db) productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db) + stockLogRepo := rShared.NewStockLogRepository(db) stockAllocationRepo := commonRepo.NewStockAllocationRepository(db) fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log) @@ -63,7 +65,7 @@ func (MarketingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db) salesOrdersService := service.NewSalesOrdersService(marketingRepo, customerRepo, productWarehouseRepo, userRepo, approvalSvc, warehouseRepo, projectFlockKandangRepo, validate) - deliveryOrdersService := service.NewDeliveryOrdersService(marketingRepo, marketingProductRepo, marketingDeliveryProductRepo, approvalSvc, fifoService, validate) + deliveryOrdersService := service.NewDeliveryOrdersService(marketingRepo, marketingProductRepo, marketingDeliveryProductRepo, stockLogRepo, approvalSvc, fifoService, validate) userService := sUser.NewUserService(userRepo, validate) RegisterRoutes(router, userService, salesOrdersService, deliveryOrdersService) diff --git a/internal/modules/marketing/services/deliveryorder.service.go b/internal/modules/marketing/services/deliveryorder.service.go index b4e3eea0..7e60662d 100644 --- a/internal/modules/marketing/services/deliveryorder.service.go +++ b/internal/modules/marketing/services/deliveryorder.service.go @@ -14,6 +14,7 @@ import ( "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto" marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations" + rShared "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo" @@ -34,6 +35,7 @@ type deliveryOrdersService struct { MarketingRepo marketingRepo.MarketingRepository MarketingProductRepo marketingRepo.MarketingProductRepository MarketingDeliveryProductRepo marketingRepo.MarketingDeliveryProductRepository + StockLogRepo rShared.StockLogRepository ApprovalSvc commonSvc.ApprovalService FifoSvc commonSvc.FifoService } @@ -42,6 +44,7 @@ func NewDeliveryOrdersService( marketingRepo marketingRepo.MarketingRepository, marketingProductRepo marketingRepo.MarketingProductRepository, marketingDeliveryProductRepo marketingRepo.MarketingDeliveryProductRepository, + stockLogRepo rShared.StockLogRepository, approvalSvc commonSvc.ApprovalService, fifoSvc commonSvc.FifoService, validate *validator.Validate, @@ -51,6 +54,7 @@ func NewDeliveryOrdersService( MarketingRepo: marketingRepo, MarketingProductRepo: marketingProductRepo, MarketingDeliveryProductRepo: marketingDeliveryProductRepo, + StockLogRepo: stockLogRepo, ApprovalSvc: approvalSvc, FifoSvc: fifoSvc, } @@ -247,7 +251,6 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery itemDeliveryDate = &parsedDate } - // Cek apakah product punya flag PAKAN atau OVK isPakanOrOVK := false if foundMarketingProduct.ProductWarehouse.Product.Id != 0 && len(foundMarketingProduct.ProductWarehouse.Product.Flags) > 0 { for _, flag := range foundMarketingProduct.ProductWarehouse.Product.Flags { @@ -258,14 +261,13 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery } } - // Hitung total_weight dan total_price berdasarkan flag totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight var totalPrice float64 if isPakanOrOVK { - // PAKAN atau OVK: qty × unit_price + totalPrice = requestedProduct.Qty * requestedProduct.UnitPrice } else { - // Produk lain: total_weight × unit_price + totalPrice = totalWeight * requestedProduct.UnitPrice } @@ -279,7 +281,7 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery if requestedProduct.Qty > 0 { - if err := s.consumeDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, requestedProduct.Qty); err != nil { + if err := s.consumeDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, requestedProduct.Qty, actorID); err != nil { return err } } @@ -327,7 +329,12 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO return nil, err } - err := s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } + + err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { marketingProductRepositoryTx := marketingRepo.NewMarketingProductRepository(dbTransaction) marketingDeliveryProductRepositoryTx := marketingRepo.NewMarketingDeliveryProductRepository(dbTransaction) @@ -390,14 +397,13 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO } } - // Hitung total_weight dan total_price berdasarkan flag totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight var totalPrice float64 if isPakanOrOVK { - // PAKAN atau OVK: qty × unit_price + totalPrice = requestedProduct.Qty * requestedProduct.UnitPrice } else { - // Produk lain: total_weight × unit_price + totalPrice = totalWeight * requestedProduct.UnitPrice } @@ -412,13 +418,13 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO if requestedProduct.Qty != oldRequestedQty { if oldRequestedQty > 0 { - if err := s.releaseDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct); err != nil { + if err := s.releaseDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, actorID); err != nil { return err } } if requestedProduct.Qty > 0 { - if err := s.consumeDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, requestedProduct.Qty); err != nil { + if err := s.consumeDeliveryStock(c.Context(), dbTransaction, deliveryProduct, foundMarketingProduct, requestedProduct.Qty, actorID); err != nil { return err } } @@ -443,7 +449,7 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO return s.getMarketingWithDeliveries(c, id) } -func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct, requestedQty float64) error { +func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct, requestedQty float64, actorID uint) error { if marketingProduct == nil || marketingProduct.ProductWarehouseId == 0 { return fiber.NewError(fiber.StatusInternalServerError, "Product warehouse not found") } @@ -463,6 +469,20 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor deliveryProductRepo := marketingRepo.NewMarketingDeliveryProductRepository(tx) + if err == nil && result.UsageQuantity > 0 { + if actorID > 0 { + decreaseLog := &entity.StockLog{ + Decrease: result.UsageQuantity, + LoggableType: string(utils.StockLogTypeMarketing), + LoggableId: deliveryProduct.Id, + ProductWarehouseId: marketingProduct.ProductWarehouseId, + CreatedBy: actorID, + Notes: "", + } + s.StockLogRepo.WithTx(tx).CreateOne(ctx, decreaseLog, nil) + } + } + if err != nil { pwRepo := productWarehouseRepo.NewProductWarehouseRepository(tx) pw, err2 := pwRepo.GetByID(ctx, marketingProduct.ProductWarehouseId, nil) @@ -483,6 +503,19 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor if err := deliveryProductRepo.UpdateFifoFields(ctx, deliveryProduct.Id, requestedQty, 0); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery product") } + + if actorID > 0 { + decreaseLog := &entity.StockLog{ + Decrease: requestedQty, + LoggableType: string(utils.StockLogTypeMarketing), + LoggableId: deliveryProduct.Id, + ProductWarehouseId: marketingProduct.ProductWarehouseId, + CreatedBy: actorID, + Notes: "", + } + s.StockLogRepo.WithTx(tx).CreateOne(ctx, decreaseLog, nil) + } + return nil } @@ -493,7 +526,7 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor return nil } -func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct) error { +func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct, actorID uint) error { if deliveryProduct == nil || deliveryProduct.Id == 0 { return nil } @@ -520,6 +553,18 @@ func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gor return err } + if actorID > 0 && currentUsage > 0 { + increaseLog := &entity.StockLog{ + Increase: currentUsage, + LoggableType: string(utils.StockLogTypeMarketing), + LoggableId: deliveryProduct.Id, + ProductWarehouseId: marketingProduct.ProductWarehouseId, + CreatedBy: actorID, + Notes: "", + } + s.StockLogRepo.WithTx(tx).CreateOne(ctx, increaseLog, nil) + } + if err := deliveryProductRepo.ResetFifoFields(ctx, deliveryProduct.Id); err != nil { return err } From e1ab5a90cbf7028e2123e370112633d0e31fb87f Mon Sep 17 00:00:00 2001 From: giovanni Date: Fri, 23 Jan 2026 14:20:11 +0700 Subject: [PATCH 15/20] fix value all standard --- internal/modules/closings/services/closing.service.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/modules/closings/services/closing.service.go b/internal/modules/closings/services/closing.service.go index 372d38fd..02942f44 100644 --- a/internal/modules/closings/services/closing.service.go +++ b/internal/modules/closings/services/closing.service.go @@ -743,6 +743,10 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to determine production week") } + if !isGrowing && currentWeek != 0 { + currentWeek = currentWeek + 17 + } + 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) From 43286cead10258d13009b9ef845597061218f297 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Fri, 23 Jan 2026 19:45:13 +0700 Subject: [PATCH 16/20] Fix[BE]: fixing transfer to non kandang warehouse --- .../transfers/services/transfer.service.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/modules/inventory/transfers/services/transfer.service.go b/internal/modules/inventory/transfers/services/transfer.service.go index 86ace0c2..eb096257 100644 --- a/internal/modules/inventory/transfers/services/transfer.service.go +++ b/internal/modules/inventory/transfers/services/transfer.service.go @@ -163,12 +163,14 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques return nil, err } - projectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), destPfkID) - if err != nil { - return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data project flock untuk gudang tujuan") - } - if projectFlockKandang.ClosedAt != nil { - return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Project flock untuk gudang tujuan sudah ditutup (closing) pada %s", projectFlockKandang.ClosedAt.Format("2006-01-02"))) + if destPfkID > 0 { + projectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), destPfkID) + if err != nil { + return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data project flock untuk gudang tujuan") + } + if projectFlockKandang.ClosedAt != nil { + return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Project flock untuk gudang tujuan sudah ditutup (closing) pada %s", projectFlockKandang.ClosedAt.Format("2006-01-02"))) + } } actorID, err := m.ActorIDFromContext(c) @@ -261,7 +263,6 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques return err } - // Set ProjectFlockKandangId hanya jika ada kandang var pfkID *uint if projectFlockKandangID > 0 { pfkID = &projectFlockKandangID @@ -480,7 +481,6 @@ func (s *transferService) getActiveProjectFlockKandangID(ctx context.Context, wa return 0, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data gudang dengan ID %d", warehouseID)) } - // Jika warehouse tidak punya kandang_id, return 0 tanpa error if warehouse.KandangId == nil || *warehouse.KandangId == 0 { return 0, nil } From 507d6c42932c69223a9634d5dc41eea5d7196570 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Fri, 23 Jan 2026 22:02:19 +0700 Subject: [PATCH 17/20] FEAT[BE]: implement HPP on penjualan harian --- .../repports/services/repport.service.go | 94 ++++--------------- 1 file changed, 17 insertions(+), 77 deletions(-) diff --git a/internal/modules/repports/services/repport.service.go b/internal/modules/repports/services/repport.service.go index 9c976138..2b6ab745 100644 --- a/internal/modules/repports/services/repport.service.go +++ b/internal/modules/repports/services/repport.service.go @@ -218,8 +218,23 @@ func (s *repportService) GetMarketing(c *fiber.Ctx, params *validation.Marketing projectFlockIDMap[projectFlockID] = true category := projectFlockKandang.ProjectFlock.Category - hppPerKg := s.calculateHppPricePerKg(c.Context(), projectFlockID, category) - hppMap[projectFlockID] = hppPerKg + if utils.ProjectFlockCategory(category) == utils.ProjectFlockCategoryLaying { + if s.HppSvc != nil { + hppCost, err := s.HppSvc.CalculateHppCost(projectFlockID, nil) + if err != nil { + hppMap[projectFlockID] = 0.0 + } else if hppCost != nil { + hppMap[projectFlockID] = hppCost.Real.HargaKg + } else { + hppMap[projectFlockID] = 0.0 + } + } else { + hppMap[projectFlockID] = 0.0 + } + } else { + + hppMap[projectFlockID] = 0.0 + } } } } @@ -228,81 +243,6 @@ func (s *repportService) GetMarketing(c *fiber.Ctx, params *validation.Marketing return items, total, nil } -func (s *repportService) calculateHppPricePerKg(ctx context.Context, projectFlockID uint, category string) float64 { - totalCost := s.getTotalProjectCost(ctx, projectFlockID) - if totalCost == 0 { - return 0 - } - - chickinQty, err := s.ChickinRepo.GetTotalChickinQtyByProjectFlockID(ctx, projectFlockID) - if err != nil { - s.Log.Warnf("HPP calculation: Failed to get chickin qty for project flock ID %d: %v", projectFlockID, err) - } - - depletion, err := s.RecordingRepo.GetTotalDepletionByProjectFlockID(ctx, projectFlockID) - if err != nil { - s.Log.Warnf("HPP calculation: Failed to get depletion for project flock ID %d: %v", projectFlockID, err) - } - - avgWeight, err := s.RecordingRepo.GetLatestAvgWeightByProjectFlockID(ctx, projectFlockID) - if err != nil { - s.Log.Warnf("HPP calculation: Failed to get avg weight for project flock ID %d: %v", projectFlockID, err) - } - - var totalWeight float64 - if utils.ProjectFlockCategory(category) == utils.ProjectFlockCategoryGrowing { - totalWeight = (chickinQty - depletion) * avgWeight - } else { - eggWeight, err := s.RecordingRepo.GetTotalEggProductionWeightByProjectFlockID(ctx, projectFlockID) - if err != nil { - s.Log.Warnf("HPP calculation: Failed to get egg weight for project flock ID %d: %v", projectFlockID, err) - } - totalWeight = (chickinQty-depletion)*avgWeight + eggWeight - } - - if totalWeight == 0 { - return 0 - } - - hppPricePerKg := totalCost / totalWeight - return hppPricePerKg -} - -func (s *repportService) getTotalProjectCost(ctx context.Context, projectFlockID uint) float64 { - if projectFlockID == 0 { - return 0 - } - - purchases, err := s.PurchaseRepo.GetItemsByProjectFlockID(ctx, projectFlockID) - if err != nil { - s.Log.Errorf("getTotalProjectCost: GetItemsByProjectFlockID error for project flock ID %d: %v", projectFlockID, err) - return 0 - } - - cost := float64(0) - purchaseCost := float64(0) - for _, p := range purchases { - purchaseCost += p.TotalPrice - } - cost += purchaseCost - - realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(ctx, projectFlockID) - if err != nil { - s.Log.Warnf("getTotalProjectCost: GetByProjectFlockID error for project flock ID %d: %v", projectFlockID, err) - } - - bopCost := float64(0) - for _, r := range realizations { - if r.ExpenseNonstock != nil && r.ExpenseNonstock.Expense != nil && - r.ExpenseNonstock.Expense.Category == string(utils.ExpenseCategoryBOP) { - bopCost += r.Price * r.Qty - } - } - cost += bopCost - - return cost -} - func (s *repportService) GetProductionResult(ctx *fiber.Ctx, params *validation.ProductionResultQuery) ([]dto.ProductionResultDTO, int64, error) { if err := s.Validate.Struct(params); err != nil { return nil, 0, err From 699a6e9289a674b2946f836ac38a66c646e315fd Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Sat, 24 Jan 2026 09:40:00 +0700 Subject: [PATCH 18/20] Fix[BE]: update error message for insufficient stock in adjustment service --- .../inventory/adjustments/services/adjustment.service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/modules/inventory/adjustments/services/adjustment.service.go b/internal/modules/inventory/adjustments/services/adjustment.service.go index c92d059b..bec0ef74 100644 --- a/internal/modules/inventory/adjustments/services/adjustment.service.go +++ b/internal/modules/inventory/adjustments/services/adjustment.service.go @@ -167,7 +167,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e newLog.Increase = afterQuantity } else { if productWarehouse.Quantity < req.Quantity { - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient stock. Current: %.2f, Requested: %.2f", productWarehouse.Quantity, req.Quantity)) + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok tidak mencukupi untuk pengurangan. Stok saat ini: %.2f, Jumlah yang akan dikurangi: %.2f", productWarehouse.Quantity, req.Quantity)) } afterQuantity -= req.Quantity newLog.Decrease = afterQuantity From 74158138c0309f3cc2e4e543f14455c26018b9bd Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Sat, 24 Jan 2026 10:38:52 +0700 Subject: [PATCH 19/20] Fix[BE]: enhance error logging and messages in transfer service --- .../transfers/services/transfer.service.go | 53 ++++++++++++------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/internal/modules/inventory/transfers/services/transfer.service.go b/internal/modules/inventory/transfers/services/transfer.service.go index eb096257..ea1041ea 100644 --- a/internal/modules/inventory/transfers/services/transfer.service.go +++ b/internal/modules/inventory/transfers/services/transfer.service.go @@ -124,7 +124,8 @@ func (s transferService) GetOne(c *fiber.Ctx, id uint) (*entity.StockTransfer, e if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Transfer dengan ID %d tidak ditemukan", id)) } - return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data transfer dengan ID %d", id)) + s.Log.Errorf("Failed to fetch transfer by ID %d: %+v", id, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data transfer") } return transferPtr, nil @@ -142,7 +143,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Produk dengan ID %d tidak ditemukan di gudang asal (ID: %d)", product.ProductID, req.SourceWarehouseID)) } - return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengecek stok produk %d di gudang asal", product.ProductID)) + s.Log.Errorf("Failed to fetch product warehouse for product_id=%d, warehouse_id=%d: %+v", product.ProductID, req.SourceWarehouseID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengecek stok produk") } if sourcePW.Quantity < product.ProductQty { return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok produk %d di gudang asal tidak mencukupi. Tersedia: %.2f, Diminta: %.2f", product.ProductID, sourcePW.Quantity, product.ProductQty)) @@ -166,7 +168,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques if destPfkID > 0 { projectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), destPfkID) if err != nil { - return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data project flock untuk gudang tujuan") + s.Log.Errorf("Failed to fetch project flock kandang by ID %d: %+v", destPfkID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data project flock") } if projectFlockKandang.ClosedAt != nil { return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Project flock untuk gudang tujuan sudah ditutup (closing) pada %s", projectFlockKandang.ClosedAt.Format("2006-01-02"))) @@ -198,7 +201,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Supplier dengan ID %d tidak ditemukan", delivery.SupplierID)) } - return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data supplier dengan ID %d", delivery.SupplierID)) + s.Log.Errorf("Failed to fetch supplier by ID %d: %+v", delivery.SupplierID, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data supplier") } if supplier.Category != string(utils.SupplierCategoryBOP) { return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Supplier '%s' (ID: %d) bukan kategori BOP. Kategori saat ini: %s", supplier.Name, delivery.SupplierID, supplier.Category)) @@ -207,7 +211,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques movementNumber, err := s.StockTransferRepo.GenerateMovementNumber(c.Context()) if err != nil { - return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat nomor movement transfer") + s.Log.Errorf("Failed to generate movement number: %+v", err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat nomor transfer") } transferDate, _ := utils.ParseDateString(req.TransferDate) @@ -247,14 +252,16 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Produk %d tidak ditemukan di gudang asal (ID: %d)", product.ProductID, req.SourceWarehouseID)) } - return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data product warehouse untuk produk %d di gudang asal", product.ProductID)) + s.Log.Errorf("Failed to fetch source product warehouse for product_id=%d, warehouse_id=%d: %+v", product.ProductID, req.SourceWarehouseID, err) + return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data stok gudang asal") } destPW, err := productWarehouseRepoTX.GetProductWarehouseByProductAndWarehouseID( c.Context(), uint(product.ProductID), uint(req.DestinationWarehouseID), ) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data product warehouse untuk produk %d di gudang tujuan", product.ProductID)) + s.Log.Errorf("Failed to fetch dest product warehouse for product_id=%d, warehouse_id=%d: %+v", product.ProductID, req.DestinationWarehouseID, err) + return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data stok gudang tujuan") } if errors.Is(err, gorm.ErrRecordNotFound) { ctx := c.Context() @@ -275,7 +282,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques ProjectFlockKandangId: pfkID, } if err := productWarehouseRepoTX.CreateOne(c.Context(), destPW, nil); err != nil { - return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal membuat product warehouse untuk produk %d di gudang tujuan", product.ProductID)) + s.Log.Errorf("Failed to create product warehouse for product_id=%d, warehouse_id=%d: %+v", product.ProductID, req.DestinationWarehouseID, err) + return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat data stok gudang tujuan") } } @@ -365,9 +373,9 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques Files: documentFiles, }) if err != nil { - s.Log.WithError(err).Errorf("Failed to upload document for delivery %d (delivery_id: %d, filename: %s)", - deliveryIdx+1, delivery.Id, file.Filename) - return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to upload document for delivery %d: %v", deliveryIdx+1, err)) + s.Log.Errorf("Failed to upload document for delivery %d (delivery_id=%d, filename=%s): %+v", + deliveryIdx+1, delivery.Id, file.Filename, err) + return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengunggah dokumen") } } } @@ -393,7 +401,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques "usage_qty": consumeResult.UsageQuantity, "pending_qty": consumeResult.PendingQuantity, }).Error; err != nil { - return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengupdate tracking usage untuk produk %d", product.ProductID)) + s.Log.Errorf("Failed to update tracking usage for detail_id=%d, product_id=%d: %+v", detail.Id, product.ProductID, err) + return fiber.NewError(fiber.StatusInternalServerError, "Gagal memperbarui data tracking") } note := fmt.Sprintf("Transfer #%s", entityTransfer.MovementNumber) @@ -406,7 +415,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques Tx: tx, }) if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal menambah stok untuk produk %d di gudang tujuan. Error: %v", product.ProductID, err)) + s.Log.Errorf("Failed to replenish stock for product_id=%d, pw_id=%d, qty=%.2f: %+v", product.ProductID, *detail.DestProductWarehouseID, product.ProductQty, err) + return fiber.NewError(fiber.StatusInternalServerError, "Gagal menambah stok gudang tujuan") } if err := tx.Model(&entity.StockTransferDetail{}). @@ -414,7 +424,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques Updates(map[string]interface{}{ "total_qty": replenishResult.AddedQuantity, }).Error; err != nil { - return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengupdate tracking total untuk produk %d", product.ProductID)) + s.Log.Errorf("Failed to update tracking total for detail_id=%d, product_id=%d: %+v", detail.Id, product.ProductID, err) + return fiber.NewError(fiber.StatusInternalServerError, "Gagal memperbarui data tracking") } } @@ -448,7 +459,10 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques }) if err != nil { - return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal memproses transfer. Error: %v", err)) + if fiberErr, ok := err.(*fiber.Error); ok { + return nil, fiberErr + } + return nil, fiber.NewError(fiber.StatusInternalServerError, "Internal server error") } result, err := s.GetOne(c, uint(entityTransfer.Id)) @@ -458,7 +472,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques if len(expensePayloads) > 0 { if err := s.notifyExpenseItemsDelivered(c, entityTransfer.Id, expensePayloads); err != nil { - return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal sinkronisasi data expense untuk transfer %s. Silakan cek manual di module expense", entityTransfer.MovementNumber)) + s.Log.Errorf("Failed to sync expense for transfer_id=%d, movement_number=%s: %+v", entityTransfer.Id, entityTransfer.MovementNumber, err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal sinkronisasi data expense. Silakan cek manual di module expense") } } @@ -478,7 +493,8 @@ func (s *transferService) getActiveProjectFlockKandangID(ctx context.Context, wa if errors.Is(err, gorm.ErrRecordNotFound) { return 0, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Gudang dengan ID %d tidak ditemukan", warehouseID)) } - return 0, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data gudang dengan ID %d", warehouseID)) + s.Log.Errorf("Failed to fetch warehouse by ID %d: %+v", warehouseID, err) + return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data gudang") } if warehouse.KandangId == nil || *warehouse.KandangId == 0 { @@ -490,7 +506,8 @@ func (s *transferService) getActiveProjectFlockKandangID(ctx context.Context, wa if errors.Is(err, gorm.ErrRecordNotFound) { return 0, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Tidak ada project flock aktif untuk kandang %d", *warehouse.KandangId)) } - return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data project flock kandang yang aktif") + s.Log.Errorf("Failed to fetch active project flock kandang for kandang_id=%d: %+v", *warehouse.KandangId, err) + return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data project flock") } return uint(projectFlockKandang.Id), nil From f69321d9cd0c0f549d281273c083975a7605c2db Mon Sep 17 00:00:00 2001 From: giovanni Date: Sat, 24 Jan 2026 11:25:11 +0700 Subject: [PATCH 20/20] fix get egg sales pieces and weight --- .../repository/common.hpp.repository.go | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/internal/common/repository/common.hpp.repository.go b/internal/common/repository/common.hpp.repository.go index 37094c16..c005e24e 100644 --- a/internal/common/repository/common.hpp.repository.go +++ b/internal/common/repository/common.hpp.repository.go @@ -219,25 +219,53 @@ func (r *HppRepositoryImpl) GetEggProduksiPiecesAndWeightKgByProjectFlockKandang return totals.TotalPieces, totals.TotalWeightKg, nil } -func (r *HppRepositoryImpl) GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, float64, error) { +func (r *HppRepositoryImpl) GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds( + ctx context.Context, + projectFlockKandangIDs []uint, + date *time.Time, +) (float64, float64, error) { + if date == nil { now := time.Now() date = &now } + type subResult struct { + UsableID uint + MdpUsageQty float64 + MdpWeight float64 + } + + subQuery := r.db.WithContext(ctx). + Table("recordings AS r"). + Select(` + DISTINCT sa.usable_id, + mdp.usage_qty AS mdp_usage_qty, + mdp.total_weight AS mdp_weight + `). + Joins("JOIN recording_eggs re ON re.recording_id = r.id"). + Joins( + "JOIN stock_allocations 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 mdp ON mdp.id = sa.usable_id"). + Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs). + Where("r.record_datetime <= ?", *date) + 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). + Table("(?) AS x", subQuery). + Select(` + COALESCE(SUM(x.mdp_usage_qty), 0) AS total_pieces, + COALESCE(SUM(x.mdp_weight), 0) AS total_weight + `). Scan(&totals).Error + if err != nil { return 0, 0, err }