mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 35d128cd52 | |||
| 824e96575a | |||
| d2f52b6901 | |||
| 8efe9b668b | |||
| 89480deeb0 | |||
| 4146342120 | |||
| b375fb964e | |||
| fe002c9602 | |||
| f1032b44d1 | |||
| 7f2401311b | |||
| 37c26d5877 | |||
| e004354420 | |||
| 2a884a8d09 |
+1
-1
@@ -9,7 +9,7 @@ main
|
|||||||
bin/
|
bin/
|
||||||
*.exe
|
*.exe
|
||||||
*.out
|
*.out
|
||||||
|
.air.toml
|
||||||
Makefile
|
Makefile
|
||||||
docker-compose.local.yml
|
docker-compose.local.yml
|
||||||
docker-compose.yaml
|
docker-compose.yaml
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE recording_eggs
|
||||||
|
DROP COLUMN IF EXISTS total_used,
|
||||||
|
DROP COLUMN IF EXISTS total_qty;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
ALTER TABLE recording_eggs
|
||||||
|
ADD COLUMN total_qty NUMERIC(15, 3) DEFAULT 0 NOT NULL,
|
||||||
|
ADD COLUMN total_used NUMERIC(15, 3) DEFAULT 0 NOT NULL;
|
||||||
|
|
||||||
|
UPDATE recording_eggs
|
||||||
|
SET total_qty = qty
|
||||||
|
WHERE total_qty = 0;
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
-- Rollback: add price back to nonstock_suppliers
|
||||||
|
ALTER TABLE nonstock_suppliers
|
||||||
|
ADD COLUMN IF NOT EXISTS price NUMERIC(15, 3) NOT NULL DEFAULT 0;
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
-- Migration: remove price from nonstock_suppliers
|
||||||
|
ALTER TABLE nonstock_suppliers
|
||||||
|
DROP COLUMN IF EXISTS price;
|
||||||
@@ -5,7 +5,6 @@ import "time"
|
|||||||
type NonstockSupplier struct {
|
type NonstockSupplier struct {
|
||||||
NonstockId uint `gorm:"not null"`
|
NonstockId uint `gorm:"not null"`
|
||||||
SupplierId uint `gorm:"not null"`
|
SupplierId uint `gorm:"not null"`
|
||||||
Price float64 `gorm:"type:numeric(15,3);not null;default:0"`
|
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
|
||||||
Nonstock Nonstock `gorm:"foreignKey:NonstockId;references:Id"`
|
Nonstock Nonstock `gorm:"foreignKey:NonstockId;references:Id"`
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ type RecordingEgg struct {
|
|||||||
RecordingId uint `gorm:"column:recording_id;not null;index"`
|
RecordingId uint `gorm:"column:recording_id;not null;index"`
|
||||||
ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"`
|
ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"`
|
||||||
Qty int `gorm:"column:qty;not null"`
|
Qty int `gorm:"column:qty;not null"`
|
||||||
|
TotalQty float64 `gorm:"column:total_qty"`
|
||||||
|
TotalUsed float64 `gorm:"column:total_used"`
|
||||||
Weight *float64 `gorm:"column:weight"`
|
Weight *float64 `gorm:"column:weight"`
|
||||||
CreatedBy uint `gorm:"column:created_by"`
|
CreatedBy uint `gorm:"column:created_by"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
const(
|
const (
|
||||||
P_DashboardGetAll = "lti.dashboard.list"
|
P_DashboardGetAll = "lti.dashboard.list"
|
||||||
)
|
)
|
||||||
|
|
||||||
// project-flock
|
// project-flock
|
||||||
const (
|
const (
|
||||||
P_ProjectFlockKandangsClosing = "lti.production.project_flock_kandangs.closing"
|
P_ProjectFlockKandangsClosing = "lti.production.project_flock_kandangs.closing"
|
||||||
@@ -238,3 +239,15 @@ const (
|
|||||||
P_UserGetAll = "lti.users.list"
|
P_UserGetAll = "lti.users.list"
|
||||||
P_UserGetOne = "lti.users.detail"
|
P_UserGetOne = "lti.users.detail"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// daily-checklist
|
||||||
|
const (
|
||||||
|
P_DailyChecklistDashboardList = "lti.daily_checklist.dashboard.list"
|
||||||
|
P_DailyChecklistCreateOne = "lti.daily_checklist.create"
|
||||||
|
P_DailyChecklistGetAll = "lti.daily_checklist.list"
|
||||||
|
P_DailyChecklistGetOne = "lti.daily_checklist.detail"
|
||||||
|
P_DailyChecklistReports = "lti.daily_checklist.reports"
|
||||||
|
P_DailyChecklistEmployee = "lti.daily_checklist.master_data.employee"
|
||||||
|
P_DailyChecklistActivity = "lti.daily_checklist.master_data.activity"
|
||||||
|
P_DailyChecklistActivityConfig = "lti.daily_checklist.master_data.configuration"
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
// New Closing Keuangan Response DTO Structure
|
||||||
|
|
||||||
|
// Base metrics - digunakan di banyak tempat
|
||||||
|
type NewFinancialMetrics struct {
|
||||||
|
RpPerBird float64 `json:"rp_per_bird"`
|
||||||
|
RpPerKg float64 `json:"rp_per_kg"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparison untuk Budgeting vs Realization
|
||||||
|
type NewComparison struct {
|
||||||
|
Budgeting NewFinancialMetrics `json:"budgeting"`
|
||||||
|
Realization NewFinancialMetrics `json:"realization"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HPP Purchase Section
|
||||||
|
type HppPurchase struct {
|
||||||
|
Pakan NewComparison `json:"pakan"`
|
||||||
|
OVK NewComparison `json:"OVK"`
|
||||||
|
DOC NewComparison `json:"DOC"`
|
||||||
|
Depresiasi NewComparison `json:"Depresiasi"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HPP Overhead Section
|
||||||
|
type HppOverhead struct {
|
||||||
|
Overhead NewComparison `json:"overhead"`
|
||||||
|
Ekspedisi NewComparison `json:"ekspedisi"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary HPP
|
||||||
|
type NewSummaryHpp struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
Budgeting NewFinancialMetrics `json:"budgeting"`
|
||||||
|
Realization NewFinancialMetrics `json:"realization"`
|
||||||
|
EggBudgeting *NewFinancialMetrics `json:"egg_budgeting,omitempty"`
|
||||||
|
EggRealization *NewFinancialMetrics `json:"egg_realization,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HPP wrapper
|
||||||
|
type NewHpp struct {
|
||||||
|
HppPurchase HppPurchase `json:"hpp_purchase"`
|
||||||
|
HppOverhead HppOverhead `json:"hpp_overhead"`
|
||||||
|
SummaryHpp NewSummaryHpp `json:"summary_hpp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purchase Cost (dengan type field, embedded NewFinancialMetrics)
|
||||||
|
type PurchaseCost struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
NewFinancialMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
// PL Summary
|
||||||
|
type PLSummary struct {
|
||||||
|
GrossProfit NewFinancialMetrics `json:"gross_profit"`
|
||||||
|
SubTotal NewFinancialMetrics `json:"sub_total"`
|
||||||
|
NetProfit NewFinancialMetrics `json:"net_profit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Profit Loss wrapper
|
||||||
|
type NewProfitLoss struct {
|
||||||
|
PenjualanTelur NewFinancialMetrics `json:"penjualan_telur"`
|
||||||
|
PurchaseCost PurchaseCost `json:"purchase_cost"`
|
||||||
|
Overhead NewFinancialMetrics `json:"overhead"`
|
||||||
|
Ekspedisi NewFinancialMetrics `json:"ekspedisi"`
|
||||||
|
Summary PLSummary `json:"summary"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main Data structure
|
||||||
|
type NewClosingKeuanganData struct {
|
||||||
|
Hpp NewHpp `json:"hpp"`
|
||||||
|
ProfitLoss NewProfitLoss `json:"profit_loss"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full Response DTO
|
||||||
|
type NewClosingKeuanganResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data NewClosingKeuanganData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// === MAPPER FUNCTIONS ===
|
||||||
|
|
||||||
|
// ToNewFinancialMetrics creates a new financial metrics
|
||||||
|
func ToNewFinancialMetrics(rpPerBird, rpPerKg, amount float64) NewFinancialMetrics {
|
||||||
|
return NewFinancialMetrics{
|
||||||
|
RpPerBird: rpPerBird,
|
||||||
|
RpPerKg: rpPerKg,
|
||||||
|
Amount: amount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToNewComparison creates a new budgeting vs realization comparison
|
||||||
|
func ToNewComparison(budgetingRpPerBird, budgetingRpPerKg, budgetingAmount, realizationRpPerBird, realizationRpPerKg, realizationAmount float64) NewComparison {
|
||||||
|
return NewComparison{
|
||||||
|
Budgeting: ToNewFinancialMetrics(budgetingRpPerBird, budgetingRpPerKg, budgetingAmount),
|
||||||
|
Realization: ToNewFinancialMetrics(realizationRpPerBird, realizationRpPerKg, realizationAmount),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToHppPurchase creates HPP purchase section
|
||||||
|
func ToHppPurchase(pakan, oVK, dOC, depresiasi NewComparison) HppPurchase {
|
||||||
|
return HppPurchase{
|
||||||
|
Pakan: pakan,
|
||||||
|
OVK: oVK,
|
||||||
|
DOC: dOC,
|
||||||
|
Depresiasi: depresiasi,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToHppOverhead creates HPP overhead section
|
||||||
|
func ToHppOverhead(overhead, ekspedisi NewComparison) HppOverhead {
|
||||||
|
return HppOverhead{
|
||||||
|
Overhead: overhead,
|
||||||
|
Ekspedisi: ekspedisi,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToNewSummaryHpp creates HPP summary
|
||||||
|
func ToNewSummaryHpp(label string, budgeting, realization NewFinancialMetrics, eggBudgeting, eggRealization *NewFinancialMetrics) NewSummaryHpp {
|
||||||
|
return NewSummaryHpp{
|
||||||
|
Label: label,
|
||||||
|
Budgeting: budgeting,
|
||||||
|
Realization: realization,
|
||||||
|
EggBudgeting: eggBudgeting,
|
||||||
|
EggRealization: eggRealization,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToNewHpp creates complete HPP section
|
||||||
|
func ToNewHpp(hppPurchase HppPurchase, hppOverhead HppOverhead, summaryHpp NewSummaryHpp) NewHpp {
|
||||||
|
return NewHpp{
|
||||||
|
HppPurchase: hppPurchase,
|
||||||
|
HppOverhead: hppOverhead,
|
||||||
|
SummaryHpp: summaryHpp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToPurchaseCost creates purchase cost item
|
||||||
|
func ToPurchaseCost(costType string, metrics NewFinancialMetrics) PurchaseCost {
|
||||||
|
return PurchaseCost{
|
||||||
|
Type: costType,
|
||||||
|
NewFinancialMetrics: metrics,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToPLSummary creates profit loss summary
|
||||||
|
func ToPLSummary(grossProfit, subTotal, netProfit NewFinancialMetrics) PLSummary {
|
||||||
|
return PLSummary{
|
||||||
|
GrossProfit: grossProfit,
|
||||||
|
SubTotal: subTotal,
|
||||||
|
NetProfit: netProfit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToNewProfitLoss creates complete profit loss section
|
||||||
|
func ToNewProfitLoss(penjualanTelur, overhead, ekspedisi NewFinancialMetrics, purchaseCost PurchaseCost, summary PLSummary) NewProfitLoss {
|
||||||
|
return NewProfitLoss{
|
||||||
|
PenjualanTelur: penjualanTelur,
|
||||||
|
PurchaseCost: purchaseCost,
|
||||||
|
Overhead: overhead,
|
||||||
|
Ekspedisi: ekspedisi,
|
||||||
|
Summary: summary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToNewClosingKeuanganData creates complete closing keuangan data
|
||||||
|
func ToNewClosingKeuanganData(hpp NewHpp, profitLoss NewProfitLoss) NewClosingKeuanganData {
|
||||||
|
return NewClosingKeuanganData{
|
||||||
|
Hpp: hpp,
|
||||||
|
ProfitLoss: profitLoss,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSuccessNewClosingKeuanganResponse creates success response shortcut
|
||||||
|
func ToSuccessNewClosingKeuanganResponse(data NewClosingKeuanganData) NewClosingKeuanganResponse {
|
||||||
|
return NewClosingKeuanganResponse{
|
||||||
|
Code: 200,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get closing keuangan successfully",
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -25,6 +25,7 @@ type ClosingModule struct{}
|
|||||||
|
|
||||||
func (ClosingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
func (ClosingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
closingRepo := rClosing.NewClosingRepository(db)
|
closingRepo := rClosing.NewClosingRepository(db)
|
||||||
|
closingKeuanganRepo := rClosing.NewClosingKeuanganRepository(db)
|
||||||
userRepo := rUser.NewUserRepository(db)
|
userRepo := rUser.NewUserRepository(db)
|
||||||
projectFlockRepo := rProjectFlock.NewProjectflockRepository(db)
|
projectFlockRepo := rProjectFlock.NewProjectflockRepository(db)
|
||||||
projectFlockKandangRepo := rProjectFlock.NewProjectFlockKandangRepository(db)
|
projectFlockKandangRepo := rProjectFlock.NewProjectFlockKandangRepository(db)
|
||||||
@@ -40,7 +41,7 @@ func (ClosingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
|||||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||||
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
||||||
|
|
||||||
closingService := sClosing.NewClosingService(closingRepo, projectFlockRepo, projectFlockKandangRepo, marketingRepo, marketingDeliveryProductRepo, approvalService, expenseRealizationRepo, projectBudgetRepo, chickinRepo, purchaseRepo, recordingRepo, standardGrowthDetailRepo, productionStandardDetailRepo, validate)
|
closingService := sClosing.NewClosingService(closingRepo, closingKeuanganRepo, projectFlockRepo, projectFlockKandangRepo, marketingRepo, marketingDeliveryProductRepo, approvalService, expenseRealizationRepo, projectBudgetRepo, chickinRepo, purchaseRepo, recordingRepo, standardGrowthDetailRepo, productionStandardDetailRepo, validate)
|
||||||
sapronakService := sClosing.NewSapronakService(closingRepo, projectFlockKandangRepo, validate)
|
sapronakService := sClosing.NewSapronakService(closingRepo, projectFlockKandangRepo, validate)
|
||||||
userService := sUser.NewUserService(userRepo, validate)
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,7 +23,7 @@ type ClosingRepository interface {
|
|||||||
SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, float64, float64, error)
|
SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, float64, float64, error)
|
||||||
SumRecordingEggQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, error)
|
SumRecordingEggQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, error)
|
||||||
GetFcrStandardsByFcrID(ctx context.Context, fcrID uint) ([]entity.FcrStandard, error)
|
GetFcrStandardsByFcrID(ctx context.Context, fcrID uint) ([]entity.FcrStandard, error)
|
||||||
GetExpeditionHPP(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]ExpeditionHPPRow, error)
|
GetProductsWithFlagsByIDs(ctx context.Context, productIDs []uint) ([]entity.Product, error)
|
||||||
FetchSapronakIncoming(ctx context.Context, kandangID uint) ([]SapronakIncomingRow, error)
|
FetchSapronakIncoming(ctx context.Context, kandangID uint) ([]SapronakIncomingRow, error)
|
||||||
FetchSapronakIncomingDetails(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, error)
|
FetchSapronakIncomingDetails(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, error)
|
||||||
FetchSapronakUsage(ctx context.Context, pfkID uint) ([]SapronakUsageRow, error)
|
FetchSapronakUsage(ctx context.Context, pfkID uint) ([]SapronakUsageRow, error)
|
||||||
@@ -33,8 +32,6 @@ type ClosingRepository interface {
|
|||||||
FetchSapronakChickinUsageDetails(ctx context.Context, pfkID uint) (map[uint][]SapronakDetailRow, error)
|
FetchSapronakChickinUsageDetails(ctx context.Context, pfkID uint) (map[uint][]SapronakDetailRow, error)
|
||||||
FetchSapronakAdjustments(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, 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)
|
FetchSapronakTransfers(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error)
|
||||||
GetActualUsageCostByProjectFlockID(ctx context.Context, projectFlockID uint) ([]ActualUsageCostRow, error)
|
|
||||||
GetProductsWithFlagsByIDs(ctx context.Context, productIDs []uint) ([]entity.Product, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClosingRepositoryImpl struct {
|
type ClosingRepositoryImpl struct {
|
||||||
@@ -64,11 +61,6 @@ type SapronakRow struct {
|
|||||||
Notes string `gorm:"column:notes"`
|
Notes string `gorm:"column:notes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExpeditionHPPRow struct {
|
|
||||||
SupplierName string `gorm:"column:supplier_name"`
|
|
||||||
TotalAmount float64 `gorm:"column:total_amount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SapronakQueryParams struct {
|
type SapronakQueryParams struct {
|
||||||
Type string
|
Type string
|
||||||
WarehouseIDs []uint
|
WarehouseIDs []uint
|
||||||
@@ -127,219 +119,6 @@ func (r *ClosingRepositoryImpl) GetSapronak(ctx context.Context, params Sapronak
|
|||||||
return rows, totalResults, nil
|
return rows, totalResults, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) SumFeedPurchaseAndUsedByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, error) {
|
|
||||||
if len(projectFlockKandangIDs) == 0 {
|
|
||||||
return 0, 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var purchaseAgg struct {
|
|
||||||
TotalIn float64 `gorm:"column:total_in"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := r.DB().WithContext(ctx).
|
|
||||||
Table("purchase_items pi").
|
|
||||||
Joins("JOIN flags f ON f.flagable_id = pi.product_id AND f.flagable_type = 'products'").
|
|
||||||
Where("f.name = ?", "PAKAN").
|
|
||||||
Where("pi.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
|
||||||
Select("COALESCE(SUM(pi.total_qty), 0) AS total_in").
|
|
||||||
Scan(&purchaseAgg).Error
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var usageAgg struct {
|
|
||||||
TotalUsed float64 `gorm:"column:total_used"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err = r.DB().WithContext(ctx).
|
|
||||||
Table("recording_stocks rs").
|
|
||||||
Joins("JOIN product_warehouses pw ON pw.id = rs.product_warehouse_id").
|
|
||||||
Joins("JOIN products prod ON prod.id = pw.product_id").
|
|
||||||
Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products").
|
|
||||||
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
|
||||||
Where("f.name = ?", "PAKAN").
|
|
||||||
Select("COALESCE(SUM(COALESCE(rs.usage_qty, 0) + COALESCE(rs.pending_qty, 0)), 0) AS total_used").
|
|
||||||
Scan(&usageAgg).Error
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return purchaseAgg.TotalIn, usageAgg.TotalUsed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) SumProjectChickinUsageByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) {
|
|
||||||
if len(projectFlockKandangIDs) == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var total float64
|
|
||||||
if err := r.DB().WithContext(ctx).
|
|
||||||
Model(&entity.ProjectChickin{}).
|
|
||||||
Where("project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
|
||||||
Select("COALESCE(SUM(usage_qty), 0)").
|
|
||||||
Scan(&total).Error; err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return total, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) SumClaimCullingByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) {
|
|
||||||
if len(projectFlockKandangIDs) == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var agg struct {
|
|
||||||
Total float64 `gorm:"column:total_culling"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := r.DB().WithContext(ctx).
|
|
||||||
Table("recording_depletions rd").
|
|
||||||
Joins("JOIN product_warehouses pw ON pw.id = rd.product_warehouse_id").
|
|
||||||
Joins("JOIN products prod ON prod.id = pw.product_id").
|
|
||||||
Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products").
|
|
||||||
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
|
||||||
Where("f.name = ?", utils.FlagAyamCulling).
|
|
||||||
Select("COALESCE(SUM(rd.qty), 0) AS total_culling").
|
|
||||||
Scan(&agg).Error
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return agg.Total, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) SumMarketingWeightAndQtyByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, float64, error) {
|
|
||||||
if len(projectFlockKandangIDs) == 0 {
|
|
||||||
return 0, 0, 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var agg struct {
|
|
||||||
TotalWeight float64 `gorm:"column:total_weight"`
|
|
||||||
TotalQty float64 `gorm:"column:total_qty"`
|
|
||||||
TotalPrice float64 `gorm:"column:total_price"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := r.DB().WithContext(ctx).
|
|
||||||
Table("marketing_products mp").
|
|
||||||
Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id").
|
|
||||||
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
|
||||||
Select("COALESCE(SUM(mp.total_weight), 0) AS total_weight, COALESCE(SUM(mp.qty), 0) AS total_qty, COALESCE(SUM(mp.total_price), 0) AS total_price").
|
|
||||||
Scan(&agg).Error
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return agg.TotalWeight, agg.TotalQty, agg.TotalPrice, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, float64, float64, error) {
|
|
||||||
if len(projectFlockKandangIDs) == 0 || len(flagNames) == 0 {
|
|
||||||
return 0, 0, 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var agg struct {
|
|
||||||
TotalWeight float64 `gorm:"column:total_weight"`
|
|
||||||
TotalQty float64 `gorm:"column:total_qty"`
|
|
||||||
TotalPrice float64 `gorm:"column:total_price"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := r.DB().WithContext(ctx).
|
|
||||||
Table("marketing_products mp").
|
|
||||||
Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id").
|
|
||||||
Joins("JOIN products prod ON prod.id = pw.product_id").
|
|
||||||
Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products").
|
|
||||||
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
|
||||||
Where("f.name IN ?", flagNames).
|
|
||||||
Select("COALESCE(SUM(mp.total_weight), 0) AS total_weight, COALESCE(SUM(mp.qty), 0) AS total_qty, COALESCE(SUM(mp.total_price), 0) AS total_price").
|
|
||||||
Scan(&agg).Error
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return agg.TotalWeight, agg.TotalQty, agg.TotalPrice, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) SumRecordingEggQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, error) {
|
|
||||||
if len(projectFlockKandangIDs) == 0 || len(flagNames) == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var agg struct {
|
|
||||||
TotalQty float64 `gorm:"column:total_qty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := r.DB().WithContext(ctx).
|
|
||||||
Table("recording_eggs re").
|
|
||||||
Joins("JOIN product_warehouses pw ON pw.id = re.product_warehouse_id").
|
|
||||||
Joins("JOIN products prod ON prod.id = pw.product_id").
|
|
||||||
Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products").
|
|
||||||
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
|
||||||
Where("f.name IN ?", flagNames).
|
|
||||||
Select("COALESCE(SUM(re.qty), 0) AS total_qty").
|
|
||||||
Scan(&agg).Error
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return agg.TotalQty, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) GetFcrStandardsByFcrID(ctx context.Context, fcrID uint) ([]entity.FcrStandard, error) {
|
|
||||||
if fcrID == 0 {
|
|
||||||
return []entity.FcrStandard{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var standards []entity.FcrStandard
|
|
||||||
if err := r.DB().WithContext(ctx).
|
|
||||||
Where("fcr_id = ?", fcrID).
|
|
||||||
Order("weight ASC").
|
|
||||||
Find(&standards).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return standards, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) GetExpeditionHPP(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]ExpeditionHPPRow, error) {
|
|
||||||
db := r.DB().WithContext(ctx)
|
|
||||||
|
|
||||||
if projectFlockID == 0 {
|
|
||||||
return nil, fmt.Errorf("invalid project flock id")
|
|
||||||
}
|
|
||||||
|
|
||||||
query := db.
|
|
||||||
Table("expense_realizations AS er").
|
|
||||||
Joins("JOIN expense_nonstocks ens ON ens.id = er.expense_nonstock_id").
|
|
||||||
Joins("JOIN expenses e ON e.id = ens.expense_id").
|
|
||||||
Joins("JOIN project_flock_kandangs pfk ON pfk.id = ens.project_flock_kandang_id").
|
|
||||||
Joins("JOIN nonstocks n ON n.id = ens.nonstock_id").
|
|
||||||
Joins("JOIN flags f ON f.flagable_id = n.id AND f.flagable_type = ?", entity.FlagableTypeNonstock).
|
|
||||||
Joins("JOIN suppliers s ON s.id = e.supplier_id").
|
|
||||||
Where("pfk.project_flock_id = ?", projectFlockID).
|
|
||||||
Where("e.category = ?", "BOP").
|
|
||||||
Where("UPPER(f.name) = ?", strings.ToUpper(string(utils.FlagEkspedisi)))
|
|
||||||
|
|
||||||
if projectFlockKandangID != nil && *projectFlockKandangID != 0 {
|
|
||||||
query = query.Where("pfk.id = ?", *projectFlockKandangID)
|
|
||||||
}
|
|
||||||
|
|
||||||
var rows []ExpeditionHPPRow
|
|
||||||
err := query.
|
|
||||||
Select(
|
|
||||||
"e.supplier_id AS supplier_id, " +
|
|
||||||
"s.name AS supplier_name, " +
|
|
||||||
"SUM(er.qty * er.price) AS total_amount",
|
|
||||||
).
|
|
||||||
Group("e.supplier_id, s.name").
|
|
||||||
Scan(&rows).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
sapronakIncomingPurchasesSQL = `
|
sapronakIncomingPurchasesSQL = `
|
||||||
SELECT
|
SELECT
|
||||||
@@ -902,130 +681,180 @@ func (r *ClosingRepositoryImpl) FetchSapronakTransfers(ctx context.Context, kand
|
|||||||
return in, out, nil
|
return in, out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActualUsageCostRow struct {
|
// === CLOSING DATA PRODUKSI METHODS ===
|
||||||
ProductID uint `gorm:"column:product_id"`
|
|
||||||
ProductName string `gorm:"column:product_name"`
|
func (r *ClosingRepositoryImpl) SumFeedPurchaseAndUsedByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, error) {
|
||||||
FlagName string `gorm:"column:flag_name"`
|
if len(projectFlockKandangIDs) == 0 {
|
||||||
TotalQty float64 `gorm:"column:total_qty"`
|
return 0, 0, nil
|
||||||
TotalPrice float64 `gorm:"column:total_price"`
|
}
|
||||||
AveragePrice float64 `gorm:"column:average_price"`
|
|
||||||
|
var purchaseAgg struct {
|
||||||
|
TotalIn float64 `gorm:"column:total_in"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("purchase_items pi").
|
||||||
|
Joins("JOIN flags f ON f.flagable_id = pi.product_id AND f.flagable_type = 'products'").
|
||||||
|
Where("f.name = ?", "PAKAN").
|
||||||
|
Where("pi.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||||
|
Select("COALESCE(SUM(pi.total_qty), 0) AS total_in").
|
||||||
|
Scan(&purchaseAgg).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var usageAgg struct {
|
||||||
|
TotalUsed float64 `gorm:"column:total_used"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.DB().WithContext(ctx).
|
||||||
|
Table("recording_stocks rs").
|
||||||
|
Joins("JOIN product_warehouses pw ON pw.id = rs.product_warehouse_id").
|
||||||
|
Joins("JOIN products prod ON prod.id = pw.product_id").
|
||||||
|
Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products").
|
||||||
|
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||||
|
Where("f.name = ?", "PAKAN").
|
||||||
|
Select("COALESCE(SUM(COALESCE(rs.usage_qty, 0) + COALESCE(rs.pending_qty, 0)), 0) AS total_used").
|
||||||
|
Scan(&usageAgg).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return purchaseAgg.TotalIn, usageAgg.TotalUsed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) GetActualUsageCostByProjectFlockID(ctx context.Context, projectFlockID uint) ([]ActualUsageCostRow, error) {
|
func (r *ClosingRepositoryImpl) SumProjectChickinUsageByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) {
|
||||||
if projectFlockID == 0 {
|
if len(projectFlockKandangIDs) == 0 {
|
||||||
return []ActualUsageCostRow{}, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
db := r.DB().WithContext(ctx)
|
var total float64
|
||||||
|
if err := r.DB().WithContext(ctx).
|
||||||
|
Model(&entity.ProjectChickin{}).
|
||||||
|
Where("project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||||
|
Select("COALESCE(SUM(usage_qty), 0)").
|
||||||
|
Scan(&total).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
// Get all project flock kandang IDs for this project flock
|
return total, nil
|
||||||
var pfkIDs []uint
|
}
|
||||||
err := db.Table("project_flock_kandangs").
|
|
||||||
Where("project_flock_id = ?", projectFlockID).
|
func (r *ClosingRepositoryImpl) SumClaimCullingByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) {
|
||||||
Pluck("id", &pfkIDs).Error
|
if len(projectFlockKandangIDs) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var agg struct {
|
||||||
|
Total float64 `gorm:"column:total_culling"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("recording_depletions rd").
|
||||||
|
Joins("JOIN product_warehouses pw ON pw.id = rd.product_warehouse_id").
|
||||||
|
Joins("JOIN products prod ON prod.id = pw.product_id").
|
||||||
|
Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products").
|
||||||
|
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||||
|
Where("f.name = ?", utils.FlagAyamCulling).
|
||||||
|
Select("COALESCE(SUM(rd.qty), 0) AS total_culling").
|
||||||
|
Scan(&agg).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return agg.Total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ClosingRepositoryImpl) SumMarketingWeightAndQtyByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, float64, float64, error) {
|
||||||
|
if len(projectFlockKandangIDs) == 0 {
|
||||||
|
return 0, 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var agg struct {
|
||||||
|
TotalWeight float64 `gorm:"column:total_weight"`
|
||||||
|
TotalQty float64 `gorm:"column:total_qty"`
|
||||||
|
TotalPrice float64 `gorm:"column:total_price"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("marketing_products mp").
|
||||||
|
Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id").
|
||||||
|
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||||
|
Select("COALESCE(SUM(mp.total_weight), 0) AS total_weight, COALESCE(SUM(mp.qty), 0) AS total_qty, COALESCE(SUM(mp.total_price), 0) AS total_price").
|
||||||
|
Scan(&agg).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return agg.TotalWeight, agg.TotalQty, agg.TotalPrice, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ClosingRepositoryImpl) SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, float64, float64, error) {
|
||||||
|
if len(projectFlockKandangIDs) == 0 || len(flagNames) == 0 {
|
||||||
|
return 0, 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var agg struct {
|
||||||
|
TotalWeight float64 `gorm:"column:total_weight"`
|
||||||
|
TotalQty float64 `gorm:"column:total_qty"`
|
||||||
|
TotalPrice float64 `gorm:"column:total_price"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("marketing_products mp").
|
||||||
|
Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id").
|
||||||
|
Joins("JOIN products prod ON prod.id = pw.product_id").
|
||||||
|
Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products").
|
||||||
|
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||||
|
Where("f.name IN ?", flagNames).
|
||||||
|
Select("COALESCE(SUM(mp.total_weight), 0) AS total_weight, COALESCE(SUM(mp.qty), 0) AS total_qty, COALESCE(SUM(mp.total_price), 0) AS total_price").
|
||||||
|
Scan(&agg).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return agg.TotalWeight, agg.TotalQty, agg.TotalPrice, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ClosingRepositoryImpl) SumRecordingEggQtyByProjectFlockKandangIDsAndFlagNames(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string) (float64, error) {
|
||||||
|
if len(projectFlockKandangIDs) == 0 || len(flagNames) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var agg struct {
|
||||||
|
TotalQty float64 `gorm:"column:total_qty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("recording_eggs re").
|
||||||
|
Joins("JOIN product_warehouses pw ON pw.id = re.product_warehouse_id").
|
||||||
|
Joins("JOIN products prod ON prod.id = pw.product_id").
|
||||||
|
Joins("JOIN flags f ON f.flagable_id = prod.id AND f.flagable_type = ?", "products").
|
||||||
|
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||||
|
Where("f.name IN ?", flagNames).
|
||||||
|
Select("COALESCE(SUM(re.qty), 0) AS total_qty").
|
||||||
|
Scan(&agg).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return agg.TotalQty, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ClosingRepositoryImpl) GetFcrStandardsByFcrID(ctx context.Context, fcrID uint) ([]entity.FcrStandard, error) {
|
||||||
|
if fcrID == 0 {
|
||||||
|
return []entity.FcrStandard{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var standards []entity.FcrStandard
|
||||||
|
if err := r.DB().WithContext(ctx).
|
||||||
|
Where("fcr_id = ?", fcrID).
|
||||||
|
Order("weight ASC").
|
||||||
|
Find(&standards).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(pfkIDs) == 0 {
|
return standards, nil
|
||||||
return []ActualUsageCostRow{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var rows []ActualUsageCostRow
|
|
||||||
|
|
||||||
purchaseStockableKey := fifo.StockableKeyPurchaseItems.String()
|
|
||||||
transferStockableKey := fifo.StockableKeyStockTransferIn.String()
|
|
||||||
|
|
||||||
recordingQuery := db.
|
|
||||||
Table("recordings AS r").
|
|
||||||
Select(`
|
|
||||||
pw.product_id AS product_id,
|
|
||||||
p.name AS product_name,
|
|
||||||
COALESCE(f.name, tf.name) AS flag_name,
|
|
||||||
COALESCE(SUM(
|
|
||||||
CASE
|
|
||||||
WHEN sa.stockable_type = ? THEN COALESCE(sa.qty, 0)
|
|
||||||
WHEN sa.stockable_type = ? THEN COALESCE(std.usage_qty, 0)
|
|
||||||
ELSE 0
|
|
||||||
END
|
|
||||||
), 0) AS total_qty,
|
|
||||||
COALESCE(SUM(
|
|
||||||
CASE
|
|
||||||
WHEN sa.stockable_type = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
|
|
||||||
WHEN sa.stockable_type = ? THEN COALESCE(std.usage_qty, 0) * COALESCE(tpi.price, 0)
|
|
||||||
ELSE 0
|
|
||||||
END
|
|
||||||
), 0) AS total_price,
|
|
||||||
COALESCE(SUM(
|
|
||||||
CASE
|
|
||||||
WHEN sa.stockable_type = ? THEN COALESCE(sa.qty, 0)
|
|
||||||
WHEN sa.stockable_type = ? THEN COALESCE(std.usage_qty, 0)
|
|
||||||
ELSE 0
|
|
||||||
END
|
|
||||||
), 0) AS qty_divisor,
|
|
||||||
COALESCE(SUM(
|
|
||||||
CASE
|
|
||||||
WHEN sa.stockable_type = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
|
|
||||||
WHEN sa.stockable_type = ? THEN COALESCE(std.usage_qty, 0) * COALESCE(tpi.price, 0)
|
|
||||||
ELSE 0
|
|
||||||
END
|
|
||||||
), 0) / NULLIF(COALESCE(SUM(
|
|
||||||
CASE
|
|
||||||
WHEN sa.stockable_type = ? THEN COALESCE(sa.qty, 0)
|
|
||||||
WHEN sa.stockable_type = ? THEN COALESCE(std.usage_qty, 0)
|
|
||||||
ELSE 0
|
|
||||||
END
|
|
||||||
), 0), 0) AS average_price`,
|
|
||||||
purchaseStockableKey, transferStockableKey,
|
|
||||||
purchaseStockableKey, transferStockableKey,
|
|
||||||
purchaseStockableKey, transferStockableKey,
|
|
||||||
purchaseStockableKey, transferStockableKey,
|
|
||||||
purchaseStockableKey, transferStockableKey).
|
|
||||||
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 products AS p ON p.id = pw.product_id").
|
|
||||||
Joins("LEFT JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND sa.status = ?",
|
|
||||||
"recording_stocks", 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 ?", pfkIDs).
|
|
||||||
Where("r.deleted_at IS NULL").
|
|
||||||
Group("pw.product_id, p.name, COALESCE(f.name, tf.name)")
|
|
||||||
|
|
||||||
if err := recordingQuery.Scan(&rows).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
chickinQuery := db.
|
|
||||||
Table("project_chickins AS pc").
|
|
||||||
Select(`
|
|
||||||
pw.product_id AS product_id,
|
|
||||||
p.name AS product_name,
|
|
||||||
f.name AS flag_name,
|
|
||||||
COALESCE(SUM(pc.usage_qty), 0) AS total_qty,
|
|
||||||
COALESCE(SUM(pc.usage_qty * COALESCE(pi.price, 0)), 0) AS total_price,
|
|
||||||
COALESCE(AVG(COALESCE(pi.price, 0)), 0) AS average_price
|
|
||||||
`).
|
|
||||||
Joins("JOIN product_warehouses AS pw ON pw.id = pc.product_warehouse_id").
|
|
||||||
Joins("JOIN products AS p ON p.id = pw.product_id").
|
|
||||||
Joins("LEFT JOIN purchase_items AS pi ON pi.product_warehouse_id = pc.product_warehouse_id").
|
|
||||||
Joins("LEFT JOIN flags AS f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct).
|
|
||||||
Where("pc.project_flock_kandang_id IN ?", pfkIDs).
|
|
||||||
Where("pc.usage_qty > 0").
|
|
||||||
Group("pw.product_id, p.name, f.name")
|
|
||||||
|
|
||||||
var chickinRows []ActualUsageCostRow
|
|
||||||
if err := chickinQuery.Scan(&chickinRows).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows = append(rows, chickinRows...)
|
|
||||||
|
|
||||||
return rows, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) GetProductsWithFlagsByIDs(ctx context.Context, productIDs []uint) ([]entity.Product, error) {
|
func (r *ClosingRepositoryImpl) GetProductsWithFlagsByIDs(ctx context.Context, productIDs []uint) ([]entity.Product, error) {
|
||||||
|
|||||||
@@ -0,0 +1,773 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClosingKeuanganRepository handles database operations for closing keuangan
|
||||||
|
type ClosingKeuanganRepository interface {
|
||||||
|
repository.BaseRepository[interface{}]
|
||||||
|
|
||||||
|
// Egg Production
|
||||||
|
GetTotalEggProductionByProjectFlockID(ctx context.Context, projectFlockID uint) (totalQty int, totalWeightKg float64, err error)
|
||||||
|
GetTotalEggProductionByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (totalQty int, totalWeightKg float64, totalRecordings int, err error)
|
||||||
|
GetEggProductionByProjectFlockKandangIDsWithDetails(ctx context.Context, projectFlockKandangIDs []uint) ([]EggProductionDetailRow, error)
|
||||||
|
GetCumulativeEggProductionByProjectFlockID(ctx context.Context, projectFlockID uint) (totalQty int, totalWeightKg float64, err error)
|
||||||
|
|
||||||
|
// Population Data
|
||||||
|
GetTotalPopulationByProjectFlockID(ctx context.Context, projectFlockID uint) (totalPopulation float64, err error)
|
||||||
|
GetTotalPopulationByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (totalPopulation float64, err error)
|
||||||
|
GetRemainingPopulationByProjectFlockID(ctx context.Context, projectFlockID uint) (remainingPopulation float64, err error)
|
||||||
|
|
||||||
|
// Budget Data
|
||||||
|
GetTotalBudgetByProjectFlockID(ctx context.Context, projectFlockID uint) (totalBudget float64, err error)
|
||||||
|
GetTotalBudgetByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (totalBudget float64, err error)
|
||||||
|
|
||||||
|
// Realization/Expense Data
|
||||||
|
GetTotalRealizationByProjectFlockID(ctx context.Context, projectFlockID uint) (totalRealization float64, err error)
|
||||||
|
GetTotalRealizationByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (totalRealization float64, err error)
|
||||||
|
GetActualUsageCostByProjectFlockID(ctx context.Context, projectFlockID uint) ([]ActualUsageCostRow, error)
|
||||||
|
|
||||||
|
// Expedition
|
||||||
|
GetExpeditionHPP(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]ExpeditionHPPRow, error)
|
||||||
|
|
||||||
|
// Products
|
||||||
|
GetProductsWithFlagsByIDs(ctx context.Context, productIDs []uint) ([]entity.Product, error)
|
||||||
|
|
||||||
|
// All Product Usage
|
||||||
|
GetAllProductUsageByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]ProductUsageRow, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClosingKeuanganRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[interface{}]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClosingKeuanganRepository(db *gorm.DB) ClosingKeuanganRepository {
|
||||||
|
return &ClosingKeuanganRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[interface{}](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result Rows
|
||||||
|
|
||||||
|
type EggProductionDetailRow struct {
|
||||||
|
ProjectFlockKandangID uint `gorm:"column:project_flock_kandang_id"`
|
||||||
|
KandangName string `gorm:"column:kandang_name"`
|
||||||
|
TotalQty int `gorm:"column:total_qty"`
|
||||||
|
TotalWeightKg float64 `gorm:"column:total_weight_kg"`
|
||||||
|
TotalRecordings int `gorm:"column:total_recordings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpeditionHPPRow struct {
|
||||||
|
SupplierName string `gorm:"column:supplier_name"`
|
||||||
|
TotalAmount float64 `gorm:"column:total_amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActualUsageCostRow struct {
|
||||||
|
ProductID uint `gorm:"column:product_id"`
|
||||||
|
ProductName string `gorm:"column:product_name"`
|
||||||
|
FlagName string `gorm:"column:flag_name"`
|
||||||
|
TotalQty float64 `gorm:"column:total_qty"`
|
||||||
|
TotalPrice float64 `gorm:"column:total_price"`
|
||||||
|
AveragePrice float64 `gorm:"column:average_price"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductUsageRow struct {
|
||||||
|
ProductID uint `gorm:"column:product_id"`
|
||||||
|
ProductName string `gorm:"column:product_name"`
|
||||||
|
FlagNames string `gorm:"column:flag_names"`
|
||||||
|
TotalQty float64 `gorm:"column:total_qty"`
|
||||||
|
Price float64 `gorm:"column:price"`
|
||||||
|
TotalPengeluaran float64 `gorm:"column:total_pengeluaran"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// === EGG PRODUCTION QUERIES ===
|
||||||
|
|
||||||
|
// GetTotalEggProductionByProjectFlockID gets total egg production for all kandangs in a project flock
|
||||||
|
func (r *ClosingKeuanganRepositoryImpl) GetTotalEggProductionByProjectFlockID(ctx context.Context, projectFlockID uint) (int, float64, error) {
|
||||||
|
if projectFlockID == 0 {
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result struct {
|
||||||
|
TotalQty float64
|
||||||
|
TotalWeightKg float64
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("project_flocks pf").
|
||||||
|
Select(`
|
||||||
|
COALESCE(SUM(re.qty), 0) AS total_qty,
|
||||||
|
COALESCE(SUM(re.qty * COALESCE(re.weight, 0)) / 1000, 0) AS total_weight_kg
|
||||||
|
`).
|
||||||
|
Joins("JOIN project_flock_kandangs pfk ON pfk.project_flock_id = pf.id").
|
||||||
|
Joins("LEFT JOIN recordings r ON r.project_flock_kandangs_id = pfk.id").
|
||||||
|
Joins("LEFT JOIN recording_eggs re ON re.recording_id = r.id").
|
||||||
|
Where("pf.id = ?", projectFlockID).
|
||||||
|
Where("pf.deleted_at IS NULL").
|
||||||
|
Where("r.deleted_at IS NULL").
|
||||||
|
Scan(&result).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(result.TotalQty), result.TotalWeightKg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTotalEggProductionByProjectFlockKandangID gets total egg production for a specific kandang
|
||||||
|
func (r *ClosingKeuanganRepositoryImpl) GetTotalEggProductionByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (int, float64, int, error) {
|
||||||
|
if projectFlockKandangID == 0 {
|
||||||
|
return 0, 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result struct {
|
||||||
|
TotalQty float64
|
||||||
|
TotalWeightKg float64
|
||||||
|
TotalRecordings int
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("project_flock_kandangs pfk").
|
||||||
|
Select(`
|
||||||
|
COALESCE(SUM(re.qty), 0) AS total_qty,
|
||||||
|
COALESCE(SUM(re.qty * COALESCE(re.weight, 0)) / 1000, 0) AS total_weight_kg,
|
||||||
|
COUNT(DISTINCT r.id) AS total_recordings
|
||||||
|
`).
|
||||||
|
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id").
|
||||||
|
Joins("LEFT JOIN recordings r ON r.project_flock_kandangs_id = pfk.id").
|
||||||
|
Joins("LEFT JOIN recording_eggs re ON re.recording_id = r.id").
|
||||||
|
Where("pfk.id = ?", projectFlockKandangID).
|
||||||
|
Where("pf.deleted_at IS NULL").
|
||||||
|
Where("r.deleted_at IS NULL").
|
||||||
|
Scan(&result).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(result.TotalQty), result.TotalWeightKg, result.TotalRecordings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEggProductionByProjectFlockKandangIDsWithDetails gets egg production details for multiple kandangs
|
||||||
|
func (r *ClosingKeuanganRepositoryImpl) GetEggProductionByProjectFlockKandangIDsWithDetails(ctx context.Context, projectFlockKandangIDs []uint) ([]EggProductionDetailRow, error) {
|
||||||
|
if len(projectFlockKandangIDs) == 0 {
|
||||||
|
return []EggProductionDetailRow{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var results []EggProductionDetailRow
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("project_flock_kandangs pfk").
|
||||||
|
Select(`
|
||||||
|
pfk.id AS project_flock_kandang_id,
|
||||||
|
k.name AS kandang_name,
|
||||||
|
COALESCE(SUM(re.qty), 0) AS total_qty,
|
||||||
|
COALESCE(SUM(re.qty * COALESCE(re.weight, 0)) / 1000, 0) AS total_weight_kg,
|
||||||
|
COUNT(DISTINCT r.id) AS total_recordings
|
||||||
|
`).
|
||||||
|
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id").
|
||||||
|
Joins("JOIN kandangs k ON k.id = pfk.kandang_id").
|
||||||
|
Joins("LEFT JOIN recordings r ON r.project_flock_kandangs_id = pfk.id").
|
||||||
|
Joins("LEFT JOIN recording_eggs re ON re.recording_id = r.id").
|
||||||
|
Where("pfk.id IN ?", projectFlockKandangIDs).
|
||||||
|
Where("pf.deleted_at IS NULL").
|
||||||
|
Where("r.deleted_at IS NULL").
|
||||||
|
Group("pfk.id, k.name").
|
||||||
|
Scan(&results).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCumulativeEggProductionByProjectFlockID gets cumulative egg production for project flock
|
||||||
|
func (r *ClosingKeuanganRepositoryImpl) GetCumulativeEggProductionByProjectFlockID(ctx context.Context, projectFlockID uint) (int, float64, error) {
|
||||||
|
if projectFlockID == 0 {
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result struct {
|
||||||
|
TotalQty float64
|
||||||
|
TotalWeightKg float64
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("project_flocks pf").
|
||||||
|
Select(`
|
||||||
|
COALESCE(SUM(re.qty), 0) AS total_qty,
|
||||||
|
COALESCE(SUM(re.qty * COALESCE(re.weight, 0)) / 1000, 0) AS total_weight_kg
|
||||||
|
`).
|
||||||
|
Joins("JOIN project_flock_kandangs pfk ON pfk.project_flock_id = pf.id").
|
||||||
|
Joins("JOIN recordings r ON r.project_flock_kandangs_id = pfk.id").
|
||||||
|
Joins("JOIN recording_eggs re ON re.recording_id = r.id").
|
||||||
|
Where("pf.id = ?", projectFlockID).
|
||||||
|
Where("pf.deleted_at IS NULL").
|
||||||
|
Where("r.deleted_at IS NULL").
|
||||||
|
Where("r.record_datetime <= (SELECT MAX(record_datetime) FROM recordings WHERE project_flock_kandangs_id IN (SELECT id FROM project_flock_kandangs WHERE project_flock_id = ?))", projectFlockID).
|
||||||
|
Scan(&result).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(result.TotalQty), result.TotalWeightKg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// === POPULATION QUERIES ===
|
||||||
|
|
||||||
|
// GetTotalPopulationByProjectFlockID gets total initial population for project flock
|
||||||
|
func (r *ClosingKeuanganRepositoryImpl) GetTotalPopulationByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error) {
|
||||||
|
if projectFlockID == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result float64
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("project_chickins").
|
||||||
|
Select("COALESCE(SUM(qty), 0)").
|
||||||
|
Where("project_flock_id = ?", projectFlockID).
|
||||||
|
Scan(&result).Error
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTotalPopulationByProjectFlockKandangIDs gets total population for multiple kandangs
|
||||||
|
func (r *ClosingKeuanganRepositoryImpl) GetTotalPopulationByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) {
|
||||||
|
if len(projectFlockKandangIDs) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result float64
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("project_chickins").
|
||||||
|
Select("COALESCE(SUM(qty), 0)").
|
||||||
|
Where("project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||||
|
Scan(&result).Error
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRemainingPopulationByProjectFlockID gets remaining population based on depletion
|
||||||
|
func (r *ClosingKeuanganRepositoryImpl) GetRemainingPopulationByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error) {
|
||||||
|
if projectFlockID == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result struct {
|
||||||
|
TotalChickin float64
|
||||||
|
TotalDepletion float64
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("project_flocks pf").
|
||||||
|
Select(`
|
||||||
|
COALESCE((SELECT SUM(qty) FROM project_chickins WHERE project_flock_id = pf.id), 0) AS total_chickin,
|
||||||
|
COALESCE((SELECT SUM(COALESCE(rd.qty, 0))
|
||||||
|
FROM recordings r
|
||||||
|
JOIN recording_depletions rd ON rd.recording_id = r.id
|
||||||
|
JOIN project_flock_kandangs pfk ON pfk.id = r.project_flock_kandangs_id
|
||||||
|
WHERE pfk.project_flock_id = pf.id), 0) AS total_depletion
|
||||||
|
`).
|
||||||
|
Where("pf.id = ?", projectFlockID).
|
||||||
|
Scan(&result).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.TotalChickin - result.TotalDepletion, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// === BUDGET QUERIES ===
|
||||||
|
|
||||||
|
// GetTotalBudgetByProjectFlockID gets total budget for project flock
|
||||||
|
func (r *ClosingKeuanganRepositoryImpl) GetTotalBudgetByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error) {
|
||||||
|
if projectFlockID == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result float64
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("project_budgets pb").
|
||||||
|
Select("COALESCE(SUM(pb.amount), 0)").
|
||||||
|
Joins("JOIN project_flock_kandangs pfk ON pfk.id = pb.project_flock_kandang_id").
|
||||||
|
Where("pfk.project_flock_id = ?", projectFlockID).
|
||||||
|
Where("pb.deleted_at IS NULL").
|
||||||
|
Scan(&result).Error
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTotalBudgetByProjectFlockKandangIDs gets total budget for multiple kandangs
|
||||||
|
func (r *ClosingKeuanganRepositoryImpl) GetTotalBudgetByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) {
|
||||||
|
if len(projectFlockKandangIDs) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result float64
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("project_budgets pb").
|
||||||
|
Select("COALESCE(SUM(pb.amount), 0)").
|
||||||
|
Where("pb.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||||
|
Where("pb.deleted_at IS NULL").
|
||||||
|
Scan(&result).Error
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// === REALIZATION/EXPENSE QUERIES ===
|
||||||
|
|
||||||
|
// GetTotalRealizationByProjectFlockID gets total expense realization for project flock
|
||||||
|
func (r *ClosingKeuanganRepositoryImpl) GetTotalRealizationByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error) {
|
||||||
|
if projectFlockID == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result float64
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("expense_realizations er").
|
||||||
|
Select("COALESCE(SUM(er.amount), 0)").
|
||||||
|
Joins("JOIN project_flock_kandangs pfk ON pfk.id = er.project_flock_kandang_id").
|
||||||
|
Where("pfk.project_flock_id = ?", projectFlockID).
|
||||||
|
Where("er.deleted_at IS NULL").
|
||||||
|
Scan(&result).Error
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTotalRealizationByProjectFlockKandangIDs gets total realization for multiple kandangs
|
||||||
|
func (r *ClosingKeuanganRepositoryImpl) GetTotalRealizationByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) {
|
||||||
|
if len(projectFlockKandangIDs) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result float64
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("expense_realizations er").
|
||||||
|
Select("COALESCE(SUM(er.amount), 0)").
|
||||||
|
Where("er.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||||
|
Where("er.deleted_at IS NULL").
|
||||||
|
Scan(&result).Error
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActualUsageCostByProjectFlockID gets actual usage cost by project flock
|
||||||
|
func (r *ClosingKeuanganRepositoryImpl) GetActualUsageCostByProjectFlockID(ctx context.Context, projectFlockID uint) ([]ActualUsageCostRow, error) {
|
||||||
|
if projectFlockID == 0 {
|
||||||
|
return []ActualUsageCostRow{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
db := r.DB().WithContext(ctx)
|
||||||
|
|
||||||
|
// Get all project flock kandang IDs for this project flock
|
||||||
|
var pfkIDs []uint
|
||||||
|
err := db.Table("project_flock_kandangs").
|
||||||
|
Where("project_flock_id = ?", projectFlockID).
|
||||||
|
Pluck("id", &pfkIDs).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pfkIDs) == 0 {
|
||||||
|
return []ActualUsageCostRow{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows []ActualUsageCostRow
|
||||||
|
|
||||||
|
purchaseStockableKey := fifo.StockableKeyPurchaseItems.String()
|
||||||
|
transferStockableKey := fifo.StockableKeyStockTransferIn.String()
|
||||||
|
|
||||||
|
/*
|
||||||
|
RAW SQL FOR RECORDING QUERY (untuk pengecekan database):
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
pw.product_id,
|
||||||
|
p.name AS product_name,
|
||||||
|
COALESCE(f.name, tf.name) AS flag_name,
|
||||||
|
COALESCE(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN sa.stockable_type = '<purchase_stockable_key>' THEN COALESCE(sa.qty, 0)
|
||||||
|
WHEN sa.stockable_type = '<transfer_stockable_key>' THEN COALESCE(std.usage_qty, 0)
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) AS total_qty,
|
||||||
|
COALESCE(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN sa.stockable_type = '<purchase_stockable_key>' THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
|
||||||
|
WHEN sa.stockable_type = '<transfer_stockable_key>' THEN COALESCE(std.usage_qty, 0) * COALESCE(tpi.price, 0)
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) AS total_price,
|
||||||
|
COALESCE(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN sa.stockable_type = '<purchase_stockable_key>' THEN COALESCE(sa.qty, 0)
|
||||||
|
WHEN sa.stockable_type = '<transfer_stockable_key>' THEN COALESCE(std.usage_qty, 0)
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) /
|
||||||
|
NULLIF(COALESCE(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN sa.stockable_type = '<purchase_stockable_key>' THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
|
||||||
|
WHEN sa.stockable_type = '<transfer_stockable_key>' THEN COALESCE(std.usage_qty, 0) * COALESCE(tpi.price, 0)
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0), 0) AS average_price
|
||||||
|
FROM recordings r
|
||||||
|
JOIN recording_stocks rs ON rs.recording_id = r.id
|
||||||
|
JOIN product_warehouses pw ON pw.id = rs.product_warehouse_id
|
||||||
|
JOIN products p ON p.id = pw.product_id
|
||||||
|
LEFT JOIN stock_allocations sa ON sa.usable_type = 'recording_stocks' AND sa.usable_id = rs.id AND sa.status = '<active_status>'
|
||||||
|
LEFT JOIN purchase_items pi ON pi.id = sa.stockable_id AND sa.stockable_type = '<purchase_stockable_key>'
|
||||||
|
LEFT JOIN stock_transfer_details std ON std.id = sa.stockable_id AND sa.stockable_type = '<transfer_stockable_key>'
|
||||||
|
LEFT JOIN stock_transfers st ON st.id = std.stock_transfer_id
|
||||||
|
LEFT JOIN purchase_items tpi ON tpi.product_id = std.product_id AND tpi.warehouse_id = st.from_warehouse_id
|
||||||
|
LEFT JOIN flags f ON f.flagable_id = pi.product_id AND f.flagable_type = 'products'
|
||||||
|
LEFT JOIN flags tf ON tf.flagable_id = std.product_id AND tf.flagable_type = 'products'
|
||||||
|
WHERE r.project_flock_kandangs_id IN (<pfk_ids>)
|
||||||
|
AND r.deleted_at IS NULL
|
||||||
|
GROUP BY pw.product_id, p.name, COALESCE(f.name, tf.name)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Recording stock query (pakan, OVK, dll) dengan FIFO logic
|
||||||
|
recordingQuery := db.
|
||||||
|
Table("recordings AS r").
|
||||||
|
Select(`
|
||||||
|
pw.product_id,
|
||||||
|
p.name AS product_name,
|
||||||
|
COALESCE(f.name, tf.name) AS flag_name,
|
||||||
|
COALESCE(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN sa.stockable_type = ? THEN COALESCE(sa.qty, 0)
|
||||||
|
WHEN sa.stockable_type = ? THEN COALESCE(std.usage_qty, 0)
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) AS total_qty,
|
||||||
|
COALESCE(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN sa.stockable_type = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
|
||||||
|
WHEN sa.stockable_type = ? THEN COALESCE(std.usage_qty, 0) * COALESCE(tpi.price, 0)
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) AS total_price,
|
||||||
|
COALESCE(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN sa.stockable_type = ? THEN COALESCE(sa.qty, 0)
|
||||||
|
WHEN sa.stockable_type = ? THEN COALESCE(std.usage_qty, 0)
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) AS qty_divisor,
|
||||||
|
COALESCE(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN sa.stockable_type = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
|
||||||
|
WHEN sa.stockable_type = ? THEN COALESCE(std.usage_qty, 0) * COALESCE(tpi.price, 0)
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) / NULLIF(COALESCE(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN sa.stockable_type = ? THEN COALESCE(sa.qty, 0)
|
||||||
|
WHEN sa.stockable_type = ? THEN COALESCE(std.usage_qty, 0)
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0), 0) AS average_price`,
|
||||||
|
purchaseStockableKey, transferStockableKey,
|
||||||
|
purchaseStockableKey, transferStockableKey,
|
||||||
|
purchaseStockableKey, transferStockableKey,
|
||||||
|
purchaseStockableKey, transferStockableKey,
|
||||||
|
purchaseStockableKey, transferStockableKey).
|
||||||
|
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 products AS p ON p.id = pw.product_id").
|
||||||
|
Joins("LEFT JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND sa.status = ?",
|
||||||
|
"recording_stocks", 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 ?", pfkIDs).
|
||||||
|
Where("r.deleted_at IS NULL").
|
||||||
|
Group("pw.product_id, p.name, COALESCE(f.name, tf.name)")
|
||||||
|
|
||||||
|
if err := recordingQuery.Scan(&rows).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
RAW SQL FOR CHICKIN QUERY (untuk pengecekan database):
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
pw.product_id,
|
||||||
|
p.name AS product_name,
|
||||||
|
f.name AS flag_name,
|
||||||
|
COALESCE(SUM(pc.usage_qty), 0) AS total_qty,
|
||||||
|
COALESCE(SUM(pc.usage_qty * COALESCE(pi.price, 0)), 0) AS total_price,
|
||||||
|
COALESCE(AVG(COALESCE(pi.price, 0)), 0) AS average_price
|
||||||
|
FROM project_chickins pc
|
||||||
|
JOIN product_warehouses pw ON pw.id = pc.product_warehouse_id
|
||||||
|
JOIN products p ON p.id = pw.product_id
|
||||||
|
LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pc.product_warehouse_id
|
||||||
|
LEFT JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = 'products'
|
||||||
|
WHERE pc.project_flock_kandang_id IN (<pfk_ids>)
|
||||||
|
AND pc.usage_qty > 0
|
||||||
|
GROUP BY pw.product_id, p.name, f.name
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Chickin query (DOC, pullet) dengan FIFO sederhana
|
||||||
|
chickinQuery := db.
|
||||||
|
Table("project_chickins AS pc").
|
||||||
|
Select(`
|
||||||
|
pw.product_id,
|
||||||
|
p.name AS product_name,
|
||||||
|
f.name AS flag_name,
|
||||||
|
COALESCE(SUM(pc.usage_qty), 0) AS total_qty,
|
||||||
|
COALESCE(SUM(pc.usage_qty * COALESCE(pi.price, 0)), 0) AS total_price,
|
||||||
|
COALESCE(AVG(COALESCE(pi.price, 0)), 0) AS average_price
|
||||||
|
`).
|
||||||
|
Joins("JOIN product_warehouses AS pw ON pw.id = pc.product_warehouse_id").
|
||||||
|
Joins("JOIN products AS p ON p.id = pw.product_id").
|
||||||
|
Joins("LEFT JOIN purchase_items AS pi ON pi.product_warehouse_id = pc.product_warehouse_id").
|
||||||
|
Joins("LEFT JOIN flags AS f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct).
|
||||||
|
Where("pc.project_flock_kandang_id IN ?", pfkIDs).
|
||||||
|
Where("pc.usage_qty > 0").
|
||||||
|
Group("pw.product_id, p.name, f.name")
|
||||||
|
|
||||||
|
var chickinRows []ActualUsageCostRow
|
||||||
|
if err := chickinQuery.Scan(&chickinRows).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows = append(rows, chickinRows...)
|
||||||
|
|
||||||
|
return rows, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// === EXPEDITION ===
|
||||||
|
|
||||||
|
// GetExpeditionHPP gets expedition HPP
|
||||||
|
func (r *ClosingKeuanganRepositoryImpl) GetExpeditionHPP(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]ExpeditionHPPRow, error) {
|
||||||
|
db := r.DB().WithContext(ctx)
|
||||||
|
|
||||||
|
if projectFlockID == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid project flock id")
|
||||||
|
}
|
||||||
|
|
||||||
|
var results []ExpeditionHPPRow
|
||||||
|
|
||||||
|
query := db.
|
||||||
|
Table("expense_realizations er").
|
||||||
|
Select(`
|
||||||
|
s.name AS supplier_name,
|
||||||
|
COALESCE(SUM(er.amount), 0) AS total_amount
|
||||||
|
`).
|
||||||
|
Joins("JOIN suppliers s ON s.id = er.supplier_id").
|
||||||
|
Where("er.category = 'EKSPEDISI'").
|
||||||
|
Where("er.deleted_at IS NULL")
|
||||||
|
|
||||||
|
if projectFlockKandangID != nil {
|
||||||
|
query = query.Where("er.project_flock_kandang_id = ?", *projectFlockKandangID)
|
||||||
|
} else {
|
||||||
|
query = query.Joins("JOIN project_flock_kandangs pfk ON pfk.id = er.project_flock_kandang_id").
|
||||||
|
Where("pfk.project_flock_id = ?", projectFlockID)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.
|
||||||
|
Group("s.name").
|
||||||
|
Scan(&results).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// === PRODUCTS ===
|
||||||
|
|
||||||
|
// GetProductsWithFlagsByIDs gets products with their flags
|
||||||
|
func (r *ClosingKeuanganRepositoryImpl) GetProductsWithFlagsByIDs(ctx context.Context, productIDs []uint) ([]entity.Product, error) {
|
||||||
|
if len(productIDs) == 0 {
|
||||||
|
return []entity.Product{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var products []entity.Product
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Where("id IN ?", productIDs).
|
||||||
|
Preload("Flags", func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Order("flagable_type, name")
|
||||||
|
}).
|
||||||
|
Find(&products).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return products, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllProductUsageByProjectFlockKandangID gets all product usage for a project flock kandang
|
||||||
|
// Combines data from all usable types: recordings, chickins, marketing, transfers, adjustments
|
||||||
|
func (r *ClosingKeuanganRepositoryImpl) GetAllProductUsageByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]ProductUsageRow, error) {
|
||||||
|
if projectFlockKandangID == 0 {
|
||||||
|
return []ProductUsageRow{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var results []ProductUsageRow
|
||||||
|
|
||||||
|
rawQuery := `
|
||||||
|
SELECT
|
||||||
|
q.product_id,
|
||||||
|
q.product_name,
|
||||||
|
f.flag_names,
|
||||||
|
q.total_qty,
|
||||||
|
q.price,
|
||||||
|
q.total_qty * q.price AS total_pengeluaran
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
product_id,
|
||||||
|
product_name,
|
||||||
|
SUM(total_qty) AS total_qty,
|
||||||
|
AVG(price) AS price
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
pw.product_id,
|
||||||
|
p.name AS product_name,
|
||||||
|
COALESCE(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN sa.stockable_type = 'PURCHASE_ITEMS' THEN COALESCE(sa.qty, 0)
|
||||||
|
WHEN sa.stockable_type = 'STOCK_TRANSFER_IN' THEN COALESCE(std.usage_qty, 0)
|
||||||
|
WHEN sa.stockable_type = 'TRANSFERTOLAYING_IN' THEN COALESCE(ltt.total_used, 0)
|
||||||
|
WHEN sa.stockable_type = 'ADJUSTMENT_IN' THEN COALESCE(adjs.total_used, 0)
|
||||||
|
WHEN sa.stockable_type = 'PROJECT_FLOCK_POPULATION' THEN COALESCE(pfp.total_used_qty, 0)
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
), 0) AS total_qty,
|
||||||
|
COALESCE(AVG(CASE WHEN sa.stockable_type = 'PURCHASE_ITEMS' THEN COALESCE(pi.price, 0) END), 0) AS price
|
||||||
|
FROM recordings r
|
||||||
|
JOIN recording_stocks rs ON rs.recording_id = r.id
|
||||||
|
JOIN product_warehouses pw ON pw.id = rs.product_warehouse_id
|
||||||
|
JOIN products p ON p.id = pw.product_id
|
||||||
|
LEFT JOIN stock_allocations sa ON sa.usable_type = 'RECORDING_STOCK' AND sa.usable_id = rs.id AND sa.status = 'ACTIVE'
|
||||||
|
LEFT JOIN purchase_items pi ON pi.id = sa.stockable_id AND sa.stockable_type = 'PURCHASE_ITEMS'
|
||||||
|
LEFT JOIN stock_transfer_details std ON std.id = sa.stockable_id AND sa.stockable_type = 'STOCK_TRANSFER_IN'
|
||||||
|
LEFT JOIN laying_transfer_targets ltt ON ltt.id = sa.stockable_id AND sa.stockable_type = 'TRANSFERTOLAYING_IN'
|
||||||
|
LEFT JOIN adjustment_stocks adjs ON adjs.id = sa.stockable_id AND sa.stockable_type = 'ADJUSTMENT_IN'
|
||||||
|
LEFT JOIN project_flock_populations pfp ON pfp.id = sa.stockable_id AND sa.stockable_type = 'PROJECT_FLOCK_POPULATION'
|
||||||
|
WHERE r.project_flock_kandangs_id = ?
|
||||||
|
AND r.deleted_at IS NULL
|
||||||
|
GROUP BY pw.product_id, p.name
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
pw.product_id,
|
||||||
|
p.name AS product_name,
|
||||||
|
COALESCE(SUM(pc.usage_qty), 0) AS total_qty,
|
||||||
|
COALESCE(AVG(COALESCE(pi.price, 0)), 0) AS price
|
||||||
|
FROM project_chickins pc
|
||||||
|
JOIN product_warehouses pw ON pw.id = pc.product_warehouse_id
|
||||||
|
JOIN products p ON p.id = pw.product_id
|
||||||
|
LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id
|
||||||
|
WHERE pc.project_flock_kandang_id = ?
|
||||||
|
AND pc.usage_qty > 0
|
||||||
|
GROUP BY pw.product_id, p.name
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
pw.product_id,
|
||||||
|
p.name AS product_name,
|
||||||
|
COALESCE(SUM(mdp.usage_qty), 0) AS total_qty,
|
||||||
|
COALESCE(AVG(COALESCE(pi.price, 0)), 0) AS price
|
||||||
|
FROM marketing_delivery_products mdp
|
||||||
|
JOIN marketing_products mp ON mp.id = mdp.marketing_product_id
|
||||||
|
JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id
|
||||||
|
JOIN products p ON p.id = pw.product_id
|
||||||
|
LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id
|
||||||
|
WHERE pw.project_flock_kandang_id = ?
|
||||||
|
GROUP BY pw.product_id, p.name
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
pw.product_id,
|
||||||
|
p.name AS product_name,
|
||||||
|
COALESCE(SUM(lts.usage_qty), 0) AS total_qty,
|
||||||
|
COALESCE(AVG(COALESCE(pi.price, 0)), 0) AS price
|
||||||
|
FROM laying_transfer_sources lts
|
||||||
|
JOIN laying_transfers lt ON lt.id = lts.laying_transfer_id
|
||||||
|
JOIN product_warehouses pw ON pw.id = lts.product_warehouse_id
|
||||||
|
JOIN products p ON p.id = pw.product_id
|
||||||
|
LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id
|
||||||
|
WHERE pw.project_flock_kandang_id = ?
|
||||||
|
GROUP BY pw.product_id, p.name
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
pw.product_id,
|
||||||
|
p.name AS product_name,
|
||||||
|
COALESCE(SUM(std.usage_qty), 0) AS total_qty,
|
||||||
|
COALESCE(AVG(COALESCE(pi.price, 0)), 0) AS price
|
||||||
|
FROM stock_transfer_details std
|
||||||
|
JOIN product_warehouses pw ON pw.id = std.source_product_warehouse_id
|
||||||
|
JOIN products p ON p.id = std.product_id
|
||||||
|
LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id
|
||||||
|
WHERE pw.project_flock_kandang_id = ?
|
||||||
|
GROUP BY pw.product_id, p.name
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
pw.product_id,
|
||||||
|
p.name AS product_name,
|
||||||
|
COALESCE(SUM(ads.usage_qty), 0) AS total_qty,
|
||||||
|
COALESCE(AVG(COALESCE(pi.price, 0)), 0) AS price
|
||||||
|
FROM adjustment_stocks ads
|
||||||
|
JOIN product_warehouses pw ON pw.id = ads.product_warehouse_id
|
||||||
|
JOIN products p ON p.id = pw.product_id
|
||||||
|
LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id
|
||||||
|
WHERE pw.project_flock_kandang_id = ?
|
||||||
|
AND ads.usage_qty > 0
|
||||||
|
GROUP BY pw.product_id, p.name
|
||||||
|
) x
|
||||||
|
GROUP BY product_id, product_name
|
||||||
|
) q
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
p.id AS product_id,
|
||||||
|
STRING_AGG(DISTINCT f.name, ', ') AS flag_names
|
||||||
|
FROM products p
|
||||||
|
LEFT JOIN flags f ON f.flagable_type = 'products' AND f.flagable_id = p.id
|
||||||
|
GROUP BY p.id
|
||||||
|
) f ON f.product_id = q.product_id
|
||||||
|
ORDER BY q.product_name
|
||||||
|
`
|
||||||
|
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Raw(rawQuery, projectFlockKandangID, projectFlockKandangID, projectFlockKandangID, projectFlockKandangID, projectFlockKandangID, projectFlockKandangID).
|
||||||
|
Scan(&results).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get all product usage: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
@@ -48,6 +48,7 @@ type closingService struct {
|
|||||||
Log *logrus.Logger
|
Log *logrus.Logger
|
||||||
Validate *validator.Validate
|
Validate *validator.Validate
|
||||||
Repository repository.ClosingRepository
|
Repository repository.ClosingRepository
|
||||||
|
ClosingKeuanganRepo repository.ClosingKeuanganRepository
|
||||||
ProjectFlockRepo projectflockRepository.ProjectflockRepository
|
ProjectFlockRepo projectflockRepository.ProjectflockRepository
|
||||||
ProjectFlockKandangRepo projectflockRepository.ProjectFlockKandangRepository
|
ProjectFlockKandangRepo projectflockRepository.ProjectFlockKandangRepository
|
||||||
MarketingRepo marketingRepository.MarketingRepository
|
MarketingRepo marketingRepository.MarketingRepository
|
||||||
@@ -62,11 +63,12 @@ type closingService struct {
|
|||||||
ProductionStandardDetailRepo productionStandardRepository.ProductionStandardDetailRepository
|
ProductionStandardDetailRepo productionStandardRepository.ProductionStandardDetailRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClosingService(repo repository.ClosingRepository, projectFlockRepo projectflockRepository.ProjectflockRepository, projectFlockKandangRepo projectflockRepository.ProjectFlockKandangRepository, marketingRepo marketingRepository.MarketingRepository, marketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository, approvalSvc commonSvc.ApprovalService, expenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository, projectBudgetRepo projectflockRepository.ProjectBudgetRepository, chickinRepo chickinRepository.ProjectChickinRepository, purchaseRepo purchaseRepository.PurchaseRepository, recordingRepo recordingRepository.RecordingRepository, standardGrowthDetailRepo productionStandardRepository.StandardGrowthDetailRepository, productionStandardDetailRepo productionStandardRepository.ProductionStandardDetailRepository, validate *validator.Validate) ClosingService {
|
func NewClosingService(repo repository.ClosingRepository, closingKeuanganRepo repository.ClosingKeuanganRepository, projectFlockRepo projectflockRepository.ProjectflockRepository, projectFlockKandangRepo projectflockRepository.ProjectFlockKandangRepository, marketingRepo marketingRepository.MarketingRepository, marketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository, approvalSvc commonSvc.ApprovalService, expenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository, projectBudgetRepo projectflockRepository.ProjectBudgetRepository, chickinRepo chickinRepository.ProjectChickinRepository, purchaseRepo purchaseRepository.PurchaseRepository, recordingRepo recordingRepository.RecordingRepository, standardGrowthDetailRepo productionStandardRepository.StandardGrowthDetailRepository, productionStandardDetailRepo productionStandardRepository.ProductionStandardDetailRepository, validate *validator.Validate) ClosingService {
|
||||||
return &closingService{
|
return &closingService{
|
||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
Validate: validate,
|
Validate: validate,
|
||||||
Repository: repo,
|
Repository: repo,
|
||||||
|
ClosingKeuanganRepo: closingKeuanganRepo,
|
||||||
ProjectFlockRepo: projectFlockRepo,
|
ProjectFlockRepo: projectFlockRepo,
|
||||||
ProjectFlockKandangRepo: projectFlockKandangRepo,
|
ProjectFlockKandangRepo: projectFlockKandangRepo,
|
||||||
MarketingRepo: marketingRepo,
|
MarketingRepo: marketingRepo,
|
||||||
@@ -578,6 +580,7 @@ func (s closingService) GetOverhead(c *fiber.Ctx, projectFlockID uint, projectFl
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (*dto.ReportResponse, error) {
|
func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (*dto.ReportResponse, error) {
|
||||||
|
s.Log.Infof("🔵 [CLOSING KEUANGAN] Starting fetch for ProjectFlockID: %d", projectFlockID)
|
||||||
|
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
commonSvc.RelationCheck{Name: "Project Flock", ID: &projectFlockID, Exists: s.ProjectFlockRepo.IdExists},
|
commonSvc.RelationCheck{Name: "Project Flock", ID: &projectFlockID, Exists: s.ProjectFlockRepo.IdExists},
|
||||||
@@ -589,23 +592,35 @@ func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (*
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
|
||||||
}
|
}
|
||||||
|
s.Log.Infof("✅ [CLOSING KEUANGAN] ProjectFlock fetched: ID=%d, Category=%s, FlockName=%s",
|
||||||
|
projectFlock.Id, projectFlock.Category, projectFlock.FlockName)
|
||||||
|
|
||||||
|
// Validasi: Closing Keuangan hanya untuk LAYING, bukan GROWING
|
||||||
|
if projectFlock.Category == string(utils.ProjectFlockCategoryGrowing) {
|
||||||
|
s.Log.Warnf("⚠️ [CLOSING KEUANGAN] ProjectFlock ID %d is GROWING category, closing keuangan not available", projectFlockID)
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Closing keuangan only available for LAYING category")
|
||||||
|
}
|
||||||
|
|
||||||
budgets, err := s.ProjectBudgetRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
budgets, err := s.ProjectBudgetRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch budgets")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch budgets")
|
||||||
}
|
}
|
||||||
|
s.Log.Infof("💰 [CLOSING KEUANGAN] Budgets fetched: %d records", len(budgets))
|
||||||
|
|
||||||
actualUsageRows, err := s.Repository.GetActualUsageCostByProjectFlockID(c.Context(), projectFlockID)
|
actualUsageRows, err := s.ClosingKeuanganRepo.GetActualUsageCostByProjectFlockID(c.Context(), projectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch actual usage cost")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch actual usage cost")
|
||||||
}
|
}
|
||||||
|
s.Log.Infof("📊 [CLOSING KEUANGAN] Actual Usage Costs fetched: %d records", len(actualUsageRows))
|
||||||
|
|
||||||
purchaseItems := s.convertActualUsageToPurchaseItems(c.Context(), actualUsageRows)
|
purchaseItems := s.convertActualUsageToPurchaseItems(c.Context(), actualUsageRows)
|
||||||
|
s.Log.Infof("🛒 [CLOSING KEUANGAN] Converted to Purchase Items: %d items", len(purchaseItems))
|
||||||
|
|
||||||
realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch realizations")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch realizations")
|
||||||
}
|
}
|
||||||
|
s.Log.Infof("💸 [CLOSING KEUANGAN] Expense Realizations fetched: %d records", len(realizations))
|
||||||
|
|
||||||
deliveryProducts, err := s.MarketingDeliveryProductRepo.GetDeliveryProductsByProjectFlockID(c.Context(), projectFlockID, func(db *gorm.DB) *gorm.DB {
|
deliveryProducts, err := s.MarketingDeliveryProductRepo.GetDeliveryProductsByProjectFlockID(c.Context(), projectFlockID, func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Preload("MarketingProduct").
|
return db.Preload("MarketingProduct").
|
||||||
@@ -615,26 +630,31 @@ func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (*
|
|||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch delivery products")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch delivery products")
|
||||||
}
|
}
|
||||||
|
s.Log.Infof("🚚 [CLOSING KEUANGAN] Marketing Delivery Products fetched: %d records", len(deliveryProducts))
|
||||||
|
|
||||||
chickins, err := s.ChickinRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
chickins, err := s.ChickinRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch chickins")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch chickins")
|
||||||
}
|
}
|
||||||
|
s.Log.Infof("🐣 [CLOSING KEUANGAN] Chickins fetched: %d records", len(chickins))
|
||||||
|
|
||||||
totalWeightProduced, _, err := s.RecordingRepo.GetProductionWeightAndQtyByProjectFlockID(c.Context(), projectFlockID)
|
totalWeightProduced, _, err := s.RecordingRepo.GetProductionWeightAndQtyByProjectFlockID(c.Context(), projectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Warnf("GetProductionWeightAndQtyByProjectFlockID error: %v", err)
|
s.Log.Warnf("GetProductionWeightAndQtyByProjectFlockID error: %v", err)
|
||||||
}
|
}
|
||||||
|
s.Log.Infof("⚖️ [CLOSING KEUANGAN] Total Weight Produced: %.2f kg", totalWeightProduced)
|
||||||
|
|
||||||
totalEggWeightKg, err := s.RecordingRepo.GetTotalEggProductionWeightByProjectFlockID(c.Context(), projectFlockID)
|
totalEggWeightKg, err := s.RecordingRepo.GetTotalEggProductionWeightByProjectFlockID(c.Context(), projectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Warnf("GetTotalEggProductionWeightByProjectFlockID error: %v", err)
|
s.Log.Warnf("GetTotalEggProductionWeightByProjectFlockID error: %v", err)
|
||||||
}
|
}
|
||||||
|
s.Log.Infof("🥚 [CLOSING KEUANGAN] Total Egg Weight: %.2f kg", totalEggWeightKg)
|
||||||
|
|
||||||
totalDepletion, err := s.RecordingRepo.GetTotalDepletionByProjectFlockID(c.Context(), projectFlockID)
|
totalDepletion, err := s.RecordingRepo.GetTotalDepletionByProjectFlockID(c.Context(), projectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Warnf("GetTotalDepletionByProjectFlockID error: %v", err)
|
s.Log.Warnf("GetTotalDepletionByProjectFlockID error: %v", err)
|
||||||
}
|
}
|
||||||
|
s.Log.Infof("📉 [CLOSING KEUANGAN] Total Depletion: %.2f", totalDepletion)
|
||||||
|
|
||||||
input := dto.ClosingKeuanganInput{
|
input := dto.ClosingKeuanganInput{
|
||||||
ProjectFlockCategory: projectFlock.Category,
|
ProjectFlockCategory: projectFlock.Category,
|
||||||
@@ -650,6 +670,7 @@ func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (*
|
|||||||
|
|
||||||
report := dto.ToClosingKeuanganReport(input)
|
report := dto.ToClosingKeuanganReport(input)
|
||||||
|
|
||||||
|
s.Log.Infof("✅ [CLOSING KEUANGAN] Report generated successfully for ProjectFlockID: %d", projectFlockID)
|
||||||
return &report, nil
|
return &report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,7 +679,7 @@ func (s closingService) GetExpeditionHPP(c *fiber.Ctx, projectFlockID uint, proj
|
|||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id")
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := s.Repository.GetExpeditionHPP(c.Context(), projectFlockID, projectFlockKandangID)
|
rows, err := s.ClosingKeuanganRepo.GetExpeditionHPP(c.Context(), projectFlockID, projectFlockKandangID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to get expedition HPP for project flock %d: %+v", projectFlockID, err)
|
s.Log.Errorf("Failed to get expedition HPP for project flock %d: %+v", projectFlockID, err)
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch expedition HPP")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch expedition HPP")
|
||||||
@@ -1115,7 +1136,7 @@ func (s closingService) convertActualUsageToPurchaseItems(ctx context.Context, a
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch products with flags from repository
|
// Fetch products with flags from repository
|
||||||
products, err := s.Repository.GetProductsWithFlagsByIDs(ctx, productIDs)
|
products, err := s.ClosingKeuanganRepo.GetProductsWithFlagsByIDs(ctx, productIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Warnf("Failed to fetch products for actual usage: %v", err)
|
s.Log.Warnf("Failed to fetch products for actual usage: %v", err)
|
||||||
products = []entity.Product{}
|
products = []entity.Product{}
|
||||||
|
|||||||
@@ -15,49 +15,49 @@ func DailyChecklistRoutes(v1 fiber.Router, u user.UserService, s dailyChecklist.
|
|||||||
route := v1.Group("/daily-checklists")
|
route := v1.Group("/daily-checklists")
|
||||||
route.Use(m.Auth(u))
|
route.Use(m.Auth(u))
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", m.RequirePermissions(m.P_DailyChecklistGetAll), ctrl.GetAll)
|
||||||
route.Get("/report", ctrl.GetReport)
|
route.Get("/report", m.RequirePermissions(m.P_DailyChecklistReports), ctrl.GetReport)
|
||||||
|
|
||||||
route.Get("/summary", ctrl.GetSummary)
|
route.Get("/summary", m.RequirePermissions(m.P_DailyChecklistDashboardList), ctrl.GetSummary)
|
||||||
|
|
||||||
route.Get("/report", ctrl.GetReport)
|
// route.Get("/report", ctrl.GetReport)
|
||||||
|
|
||||||
// upsert daily checklist
|
// upsert daily checklist
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", m.RequirePermissions(m.P_DailyChecklistCreateOne), ctrl.CreateOne)
|
||||||
|
|
||||||
// get detail data daily checklist by id
|
// get detail data daily checklist by id
|
||||||
route.Get("/relation/:idDailyChecklist", ctrl.GetOne)
|
route.Get("/relation/:idDailyChecklist", m.RequirePermissions(m.P_DailyChecklistGetOne), ctrl.GetOne)
|
||||||
|
|
||||||
// get phases by daily checklist id
|
// get phases by daily checklist id
|
||||||
route.Get("/phase/:idDailyChecklist", ctrl.GetPhaseByIdChecklist)
|
route.Get("/phase/:idDailyChecklist", m.RequirePermissions(m.P_DailyChecklistCreateOne), ctrl.GetPhaseByIdChecklist)
|
||||||
|
|
||||||
// create task
|
// create task
|
||||||
/*
|
/*
|
||||||
ketika add phase
|
ketika add phase
|
||||||
*/
|
*/
|
||||||
route.Post("/phase/:idDailyChecklist", ctrl.CreateDailyChecklistPhase)
|
route.Post("/phase/:idDailyChecklist", m.RequirePermissions(m.P_DailyChecklistCreateOne), ctrl.CreateDailyChecklistPhase)
|
||||||
|
|
||||||
// create assigment
|
// create assigment
|
||||||
/*
|
/*
|
||||||
ketika add ABK
|
ketika add ABK
|
||||||
*/
|
*/
|
||||||
route.Post("/assignment/:idDailyChecklist", ctrl.CreateAssignment)
|
route.Post("/assignment/:idDailyChecklist", m.RequirePermissions(m.P_DailyChecklistCreateOne), ctrl.CreateAssignment)
|
||||||
|
|
||||||
// remove assignment
|
// remove assignment
|
||||||
/*
|
/*
|
||||||
ketika remove ABK
|
ketika remove ABK
|
||||||
*/
|
*/
|
||||||
route.Delete("/:idDailyChecklist/assignments/:idEmployee", ctrl.RemoveAssignment)
|
route.Delete("/:idDailyChecklist/assignments/:idEmployee", m.RequirePermissions(m.P_DailyChecklistCreateOne), ctrl.RemoveAssignment)
|
||||||
|
|
||||||
//get all tasks
|
//get all tasks
|
||||||
route.Get("/tasks", ctrl.GetAllTasks)
|
route.Get("/tasks", m.RequirePermissions(m.P_DailyChecklistCreateOne), ctrl.GetAllTasks)
|
||||||
|
|
||||||
// update assignment
|
// update assignment
|
||||||
/*
|
/*
|
||||||
ketika check dan uncheck tugas oleh ABK
|
ketika check dan uncheck tugas oleh ABK
|
||||||
*/
|
*/
|
||||||
route.Post("/assignment", ctrl.UpdateAssignment)
|
route.Post("/assignment", m.RequirePermissions(m.P_DailyChecklistCreateOne), ctrl.UpdateAssignment)
|
||||||
|
|
||||||
route.Patch("/:idDailyChecklist", ctrl.UpdateOne)
|
route.Patch("/:idDailyChecklist", m.RequirePermissions(m.P_DailyChecklistCreateOne), ctrl.UpdateOne)
|
||||||
route.Delete("/:idDailyChecklist", ctrl.DeleteOne)
|
route.Delete("/:idDailyChecklist", m.RequirePermissions(m.P_DailyChecklistCreateOne), ctrl.DeleteOne)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ func ConfigChecklistRoutes(v1 fiber.Router, u user.UserService, s configChecklis
|
|||||||
route := v1.Group("/config-checklists")
|
route := v1.Group("/config-checklists")
|
||||||
route.Use(m.Auth(u))
|
route.Use(m.Auth(u))
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", m.RequirePermissions(m.P_DailyChecklistActivityConfig), ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", m.RequirePermissions(m.P_DailyChecklistActivityConfig), ctrl.CreateOne)
|
||||||
route.Get("/:id", ctrl.GetOne)
|
route.Get("/:id", m.RequirePermissions(m.P_DailyChecklistActivityConfig), ctrl.GetOne)
|
||||||
route.Patch("/:id", ctrl.UpdateOne)
|
route.Patch("/:id", m.RequirePermissions(m.P_DailyChecklistActivityConfig), ctrl.UpdateOne)
|
||||||
route.Delete("/:id", ctrl.DeleteOne)
|
route.Delete("/:id", m.RequirePermissions(m.P_DailyChecklistActivityConfig), ctrl.DeleteOne)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ func EmployeesRoutes(v1 fiber.Router, u user.UserService, s employees.EmployeesS
|
|||||||
route := v1.Group("/employees")
|
route := v1.Group("/employees")
|
||||||
route.Use(m.Auth(u))
|
route.Use(m.Auth(u))
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", m.RequirePermissions(m.P_DailyChecklistEmployee), ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", m.RequirePermissions(m.P_DailyChecklistEmployee), ctrl.CreateOne)
|
||||||
route.Get("/:id", ctrl.GetOne)
|
route.Get("/:id", m.RequirePermissions(m.P_DailyChecklistEmployee), ctrl.GetOne)
|
||||||
route.Patch("/:id", ctrl.UpdateOne)
|
route.Patch("/:id", m.RequirePermissions(m.P_DailyChecklistEmployee), ctrl.UpdateOne)
|
||||||
route.Delete("/:id", ctrl.DeleteOne)
|
route.Delete("/:id", m.RequirePermissions(m.P_DailyChecklistEmployee), ctrl.DeleteOne)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ type NonstockSupplierDTO struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Alias string `json:"alias"`
|
Alias string `json:"alias"`
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
Price float64 `json:"price"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
@@ -121,7 +120,6 @@ func toNonstockSupplierDTOs(relations []entity.NonstockSupplier) []NonstockSuppl
|
|||||||
Name: relation.Supplier.Name,
|
Name: relation.Supplier.Name,
|
||||||
Alias: relation.Supplier.Alias,
|
Alias: relation.Supplier.Alias,
|
||||||
Category: relation.Supplier.Category,
|
Category: relation.Supplier.Category,
|
||||||
Price: relation.Price,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,30 +61,20 @@ func (r *NonstockRepositoryImpl) SyncSuppliersDiff(ctx context.Context, tx *gorm
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
existingMap := make(map[uint]entity.NonstockSupplier, len(existing))
|
existingMap := make(map[uint]struct{}, len(existing))
|
||||||
for _, rel := range existing {
|
for _, rel := range existing {
|
||||||
existingMap[rel.SupplierId] = rel
|
existingMap[rel.SupplierId] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
incomingMap := make(map[uint]struct{}, len(suppliers))
|
incomingMap := make(map[uint]struct{}, len(suppliers))
|
||||||
for _, rel := range suppliers {
|
for _, rel := range suppliers {
|
||||||
incomingMap[rel.SupplierId] = struct{}{}
|
incomingMap[rel.SupplierId] = struct{}{}
|
||||||
if existingRel, exists := existingMap[rel.SupplierId]; exists {
|
if _, exists := existingMap[rel.SupplierId]; exists {
|
||||||
if existingRel.Price != rel.Price {
|
|
||||||
if err := db.WithContext(ctx).
|
|
||||||
Model(&entity.NonstockSupplier{}).
|
|
||||||
Where("nonstock_id = ? AND supplier_id = ?", nonstockID, rel.SupplierId).
|
|
||||||
Update("price", rel.Price).
|
|
||||||
Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
record := entity.NonstockSupplier{
|
record := entity.NonstockSupplier{
|
||||||
NonstockId: nonstockID,
|
NonstockId: nonstockID,
|
||||||
SupplierId: rel.SupplierId,
|
SupplierId: rel.SupplierId,
|
||||||
Price: rel.Price,
|
|
||||||
}
|
}
|
||||||
if err := db.WithContext(ctx).Create(&record).Error; err != nil {
|
if err := db.WithContext(ctx).Create(&record).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -115,19 +115,18 @@ func (s *nonstockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti
|
|||||||
supplierLinks []entity.NonstockSupplier
|
supplierLinks []entity.NonstockSupplier
|
||||||
supplierIDs []uint
|
supplierIDs []uint
|
||||||
)
|
)
|
||||||
if len(req.Suppliers) > 0 {
|
if len(req.SupplierIDs) > 0 {
|
||||||
seen := make(map[uint]struct{}, len(req.Suppliers))
|
seen := make(map[uint]struct{}, len(req.SupplierIDs))
|
||||||
supplierLinks = make([]entity.NonstockSupplier, 0, len(req.Suppliers))
|
supplierLinks = make([]entity.NonstockSupplier, 0, len(req.SupplierIDs))
|
||||||
supplierIDs = make([]uint, 0, len(req.Suppliers))
|
supplierIDs = make([]uint, 0, len(req.SupplierIDs))
|
||||||
for _, supplier := range req.Suppliers {
|
for _, supplierID := range req.SupplierIDs {
|
||||||
if _, exists := seen[supplier.SupplierID]; exists {
|
if _, exists := seen[supplierID]; exists {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Duplicate supplier_id %d", supplier.SupplierID))
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Duplicate supplier_id %d", supplierID))
|
||||||
}
|
}
|
||||||
seen[supplier.SupplierID] = struct{}{}
|
seen[supplierID] = struct{}{}
|
||||||
supplierIDs = append(supplierIDs, supplier.SupplierID)
|
supplierIDs = append(supplierIDs, supplierID)
|
||||||
supplierLinks = append(supplierLinks, entity.NonstockSupplier{
|
supplierLinks = append(supplierLinks, entity.NonstockSupplier{
|
||||||
SupplierId: supplier.SupplierID,
|
SupplierId: supplierID,
|
||||||
Price: supplier.Price,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
supplierList, supplierErr := s.Repository.GetSuppliersByIDs(ctx, supplierIDs)
|
supplierList, supplierErr := s.Repository.GetSuppliersByIDs(ctx, supplierIDs)
|
||||||
@@ -212,21 +211,20 @@ func (s nonstockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint
|
|||||||
|
|
||||||
var supplierLinks []entity.NonstockSupplier
|
var supplierLinks []entity.NonstockSupplier
|
||||||
var supplierUpdate bool
|
var supplierUpdate bool
|
||||||
if req.Suppliers != nil {
|
if req.SupplierIDs != nil {
|
||||||
supplierUpdate = true
|
supplierUpdate = true
|
||||||
if len(*req.Suppliers) > 0 {
|
if len(*req.SupplierIDs) > 0 {
|
||||||
seen := make(map[uint]struct{}, len(*req.Suppliers))
|
seen := make(map[uint]struct{}, len(*req.SupplierIDs))
|
||||||
supplierLinks = make([]entity.NonstockSupplier, 0, len(*req.Suppliers))
|
supplierLinks = make([]entity.NonstockSupplier, 0, len(*req.SupplierIDs))
|
||||||
supplierIDs := make([]uint, 0, len(*req.Suppliers))
|
supplierIDs := make([]uint, 0, len(*req.SupplierIDs))
|
||||||
for _, supplier := range *req.Suppliers {
|
for _, supplierID := range *req.SupplierIDs {
|
||||||
if _, exists := seen[supplier.SupplierID]; exists {
|
if _, exists := seen[supplierID]; exists {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Duplicate supplier_id %d", supplier.SupplierID))
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Duplicate supplier_id %d", supplierID))
|
||||||
}
|
}
|
||||||
seen[supplier.SupplierID] = struct{}{}
|
seen[supplierID] = struct{}{}
|
||||||
supplierIDs = append(supplierIDs, supplier.SupplierID)
|
supplierIDs = append(supplierIDs, supplierID)
|
||||||
supplierLinks = append(supplierLinks, entity.NonstockSupplier{
|
supplierLinks = append(supplierLinks, entity.NonstockSupplier{
|
||||||
SupplierId: supplier.SupplierID,
|
SupplierId: supplierID,
|
||||||
Price: supplier.Price,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type SupplierPrice struct {
|
|
||||||
SupplierID uint `json:"supplier_id" validate:"required,gt=0"`
|
|
||||||
Price float64 `json:"price" validate:"required,gte=0"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
||||||
UomID uint `json:"uom_id" validate:"required,gt=0"`
|
UomID uint `json:"uom_id" validate:"required,gt=0"`
|
||||||
Suppliers []SupplierPrice `json:"suppliers,omitempty" validate:"omitempty,dive"`
|
SupplierIDs []uint `json:"supplier_ids,omitempty" validate:"omitempty,dive,gt=0"`
|
||||||
Flags []string `json:"flags" validate:"dive,max=50"`
|
Flags []string `json:"flags" validate:"dive,max=50"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty,min=3,max=50"`
|
Name *string `json:"name,omitempty" validate:"omitempty,min=3,max=50"`
|
||||||
UomID *uint `json:"uom_id,omitempty" validate:"omitempty,gt=0"`
|
UomID *uint `json:"uom_id,omitempty" validate:"omitempty,gt=0"`
|
||||||
Suppliers *[]SupplierPrice `json:"suppliers,omitempty" validate:"omitempty,dive"`
|
SupplierIDs *[]uint `json:"supplier_ids,omitempty" validate:"omitempty,dive,gt=0"`
|
||||||
Flags *[]string `json:"flags,omitempty" validate:"omitempty,dive,max=50"`
|
Flags *[]string `json:"flags,omitempty" validate:"omitempty,dive,max=50"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ func PhaseActivityRoutes(v1 fiber.Router, u user.UserService, s phaseActivity.Ph
|
|||||||
route := v1.Group("/phase-activities")
|
route := v1.Group("/phase-activities")
|
||||||
route.Use(m.Auth(u))
|
route.Use(m.Auth(u))
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", m.RequirePermissions(m.P_DailyChecklistActivity), ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", m.RequirePermissions(m.P_DailyChecklistActivity), ctrl.CreateOne)
|
||||||
route.Get("/:id", ctrl.GetOne)
|
route.Get("/:id", m.RequirePermissions(m.P_DailyChecklistActivity), ctrl.GetOne)
|
||||||
route.Patch("/:id", ctrl.UpdateOne)
|
route.Patch("/:id", m.RequirePermissions(m.P_DailyChecklistActivity), ctrl.UpdateOne)
|
||||||
route.Delete("/:id", ctrl.DeleteOne)
|
route.Delete("/:id", m.RequirePermissions(m.P_DailyChecklistActivity), ctrl.DeleteOne)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ func PhasesRoutes(v1 fiber.Router, u user.UserService, s phases.PhasesService) {
|
|||||||
route := v1.Group("/phases")
|
route := v1.Group("/phases")
|
||||||
route.Use(m.Auth(u))
|
route.Use(m.Auth(u))
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", m.RequirePermissions(m.P_DailyChecklistActivity), ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", m.RequirePermissions(m.P_DailyChecklistActivity), ctrl.CreateOne)
|
||||||
route.Get("/:id", ctrl.GetOne)
|
route.Get("/:id", m.RequirePermissions(m.P_DailyChecklistActivity), ctrl.GetOne)
|
||||||
route.Patch("/:id", ctrl.UpdateOne)
|
route.Patch("/:id", m.RequirePermissions(m.P_DailyChecklistActivity), ctrl.UpdateOne)
|
||||||
route.Delete("/:id", ctrl.DeleteOne)
|
route.Delete("/:id", m.RequirePermissions(m.P_DailyChecklistActivity), ctrl.DeleteOne)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
type SupplierNonstockDTO struct {
|
type SupplierNonstockDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Price float64 `json:"price"`
|
|
||||||
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
||||||
Flags []string `json:"flags"`
|
Flags []string `json:"flags"`
|
||||||
}
|
}
|
||||||
@@ -43,7 +42,6 @@ func toSupplierNonstockDTOs(relations []entity.NonstockSupplier) []SupplierNonst
|
|||||||
result = append(result, SupplierNonstockDTO{
|
result = append(result, SupplierNonstockDTO{
|
||||||
Id: Nonstock.Id,
|
Id: Nonstock.Id,
|
||||||
Name: Nonstock.Name,
|
Name: Nonstock.Name,
|
||||||
Price: relation.Price,
|
|
||||||
Uom: uomRef,
|
Uom: uomRef,
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/dto"
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/dto"
|
||||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services"
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
|
||||||
@@ -278,14 +279,22 @@ func (u *ProjectflockController) LookupProjectFlockKandang(c *fiber.Ctx) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
_ = availableStock
|
||||||
|
|
||||||
dtoResult := dto.ToProjectFlockKandangDTO(*result)
|
dtoResult := dto.ToProjectFlockKandangDTO(*result)
|
||||||
dtoResult.AvailableQuantity = float64(availableStock)
|
if population, err := u.ProjectflockService.GetProjectFlockKandangPopulation(c, result.Id); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
dtoResult.AvailableQuantity = population
|
||||||
|
}
|
||||||
|
if warehouse, werr := u.ProjectflockService.GetWarehouseByKandangID(c, result.KandangId); werr != nil {
|
||||||
|
return werr
|
||||||
|
} else if warehouse != nil {
|
||||||
|
mapped := warehouseDTO.ToWarehouseRelationDTO(*warehouse)
|
||||||
|
dtoResult.Warehouse = &mapped
|
||||||
|
}
|
||||||
if withPopulation {
|
if withPopulation {
|
||||||
population, err := u.ProjectflockService.GetProjectFlockKandangPopulation(c, result.Id)
|
population := dtoResult.AvailableQuantity
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dtoResult.Population = &population
|
dtoResult.Population = &population
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
||||||
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||||
productionStandardDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/dto"
|
productionStandardDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/dto"
|
||||||
|
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
||||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,24 +18,26 @@ type KandangWithPivotDTO struct {
|
|||||||
|
|
||||||
type ProjectFlockWithPivotDTO struct {
|
type ProjectFlockWithPivotDTO struct {
|
||||||
ProjectFlockRelationDTO
|
ProjectFlockRelationDTO
|
||||||
Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
|
Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
Fcr *fcrDTO.FcrRelationDTO `json:"fcr,omitempty"`
|
Fcr *fcrDTO.FcrRelationDTO `json:"fcr,omitempty"`
|
||||||
ProductionStandard *productionStandardDTO.ProductionStandardRelationDTO `json:"production_standard,omitempty"`
|
ProductionStandard *productionStandardDTO.ProductionStandardRelationDTO `json:"production_standard,omitempty"`
|
||||||
Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
|
ProductionStandardId uint `json:"production_standard_id"`
|
||||||
Kandangs []KandangWithPivotDTO `json:"kandangs,omitempty"`
|
Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
|
||||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
Kandangs []KandangWithPivotDTO `json:"kandangs,omitempty"`
|
||||||
|
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectFlockKandangDTO struct {
|
type ProjectFlockKandangDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
||||||
ProjectFlockId uint `json:"project_flock_id"`
|
ProjectFlockId uint `json:"project_flock_id"`
|
||||||
KandangId uint `json:"kandang_id"`
|
KandangId uint `json:"kandang_id"`
|
||||||
Kandang *kandangDTO.KandangRelationDTO `json:"kandang,omitempty"`
|
Kandang *kandangDTO.KandangRelationDTO `json:"kandang,omitempty"`
|
||||||
ProjectFlock *ProjectFlockWithPivotDTO `json:"project_flock,omitempty"`
|
Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"`
|
||||||
AvailableQuantity float64 `json:"available_quantity"`
|
ProjectFlock *ProjectFlockWithPivotDTO `json:"project_flock,omitempty"`
|
||||||
Population *float64 `json:"population,omitempty"`
|
AvailableQuantity float64 `json:"available_quantity"`
|
||||||
|
Population *float64 `json:"population,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDTO {
|
func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDTO {
|
||||||
@@ -53,7 +56,8 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
|
|||||||
Period: e.Period,
|
Period: e.Period,
|
||||||
FlockName: e.ProjectFlock.FlockName,
|
FlockName: e.ProjectFlock.FlockName,
|
||||||
},
|
},
|
||||||
Category: e.ProjectFlock.Category,
|
Category: e.ProjectFlock.Category,
|
||||||
|
ProductionStandardId: e.ProjectFlock.ProductionStandardId,
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.ProjectFlock.Area.Id != 0 {
|
if e.ProjectFlock.Area.Id != 0 {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ type ProjectflockService interface {
|
|||||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlock, *flockDTO.FlockRelationDTO, error)
|
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlock, *flockDTO.FlockRelationDTO, error)
|
||||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectFlock, error)
|
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectFlock, error)
|
||||||
GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID uint) (float64, error)
|
GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID uint) (float64, error)
|
||||||
|
GetWarehouseByKandangID(ctx *fiber.Ctx, kandangID uint) (*entity.Warehouse, error)
|
||||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||||
GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, float64, error)
|
GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, float64, error)
|
||||||
GetProjectFlockKandangPopulation(ctx *fiber.Ctx, projectFlockKandangID uint) (float64, error)
|
GetProjectFlockKandangPopulation(ctx *fiber.Ctx, projectFlockKandangID uint) (float64, error)
|
||||||
@@ -518,6 +519,31 @@ func (s projectflockService) GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID u
|
|||||||
return total, nil
|
return total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s projectflockService) GetWarehouseByKandangID(ctx *fiber.Ctx, kandangID uint) (*entity.Warehouse, error) {
|
||||||
|
if kandangID == 0 || s.WarehouseRepo == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var warehouse entity.Warehouse
|
||||||
|
err := s.WarehouseRepo.DB().WithContext(ctx.Context()).
|
||||||
|
Preload("Area").
|
||||||
|
Preload("Location").
|
||||||
|
Preload("Kandang").
|
||||||
|
Where("kandang_id = ?", kandangID).
|
||||||
|
Where("deleted_at IS NULL").
|
||||||
|
Order("id DESC").
|
||||||
|
First(&warehouse).Error
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to fetch warehouse for kandang %d: %+v", kandangID, err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch warehouse")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &warehouse, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s projectflockService) GetProjectPeriods(c *fiber.Ctx, projectIDs []uint) (map[uint]int, error) {
|
func (s projectflockService) GetProjectPeriods(c *fiber.Ctx, projectIDs []uint) (map[uint]int, error) {
|
||||||
if len(projectIDs) == 0 {
|
if len(projectIDs) == 0 {
|
||||||
return map[uint]int{}, nil
|
return map[uint]int{}, nil
|
||||||
|
|||||||
@@ -82,11 +82,11 @@ type RecordingListDTO struct {
|
|||||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Warehouse *RecordingWarehouseDTO `json:"warehouse,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecordingDetailDTO struct {
|
type RecordingDetailDTO struct {
|
||||||
RecordingListDTO
|
RecordingListDTO
|
||||||
Warehouse *RecordingWarehouseDTO `json:"warehouse,omitempty"`
|
|
||||||
ProductCategory string `json:"product_category"`
|
ProductCategory string `json:"product_category"`
|
||||||
Depletions []RecordingDepletionDTO `json:"depletions"`
|
Depletions []RecordingDepletionDTO `json:"depletions"`
|
||||||
Stocks []RecordingStockDTO `json:"stocks"`
|
Stocks []RecordingStockDTO `json:"stocks"`
|
||||||
@@ -133,7 +133,6 @@ func ToRecordingDetailDTO(e entity.Recording) RecordingDetailDTO {
|
|||||||
|
|
||||||
return RecordingDetailDTO{
|
return RecordingDetailDTO{
|
||||||
RecordingListDTO: listDTO,
|
RecordingListDTO: listDTO,
|
||||||
Warehouse: recordingWarehouseDTO(e),
|
|
||||||
ProductCategory: recordingProductCategory(e),
|
ProductCategory: recordingProductCategory(e),
|
||||||
Depletions: ToRecordingDepletionDTOs(e.Depletions),
|
Depletions: ToRecordingDepletionDTOs(e.Depletions),
|
||||||
Stocks: ToRecordingStockDTOs(e.Stocks),
|
Stocks: ToRecordingStockDTOs(e.Stocks),
|
||||||
@@ -203,6 +202,7 @@ func toRecordingListDTO(e entity.Recording) RecordingListDTO {
|
|||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
UpdatedAt: e.UpdatedAt,
|
UpdatedAt: e.UpdatedAt,
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
|
Warehouse: recordingWarehouseDTO(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,22 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
)
|
)
|
||||||
|
|
||||||
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
|
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
|
||||||
|
if err := fifoService.RegisterStockable(fifo.StockableConfig{
|
||||||
|
Key: fifo.StockableKeyRecordingEgg,
|
||||||
|
Table: "recording_eggs",
|
||||||
|
Columns: fifo.StockableColumns{
|
||||||
|
ID: "id",
|
||||||
|
ProductWarehouseID: "product_warehouse_id",
|
||||||
|
TotalQuantity: "total_qty",
|
||||||
|
TotalUsedQuantity: "total_used",
|
||||||
|
CreatedAt: "created_at",
|
||||||
|
},
|
||||||
|
OrderBy: []string{"created_at ASC", "id ASC"},
|
||||||
|
}); err != nil {
|
||||||
|
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
|
||||||
|
panic(fmt.Sprintf("failed to register recording egg stockable workflow: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := fifoService.RegisterUsable(fifo.UsableConfig{
|
if err := fifoService.RegisterUsable(fifo.UsableConfig{
|
||||||
Key: fifo.UsableKeyRecordingStock,
|
Key: fifo.UsableKeyRecordingStock,
|
||||||
Table: "recording_stocks",
|
Table: "recording_stocks",
|
||||||
|
|||||||
@@ -290,8 +290,19 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
s.Log.Errorf("Failed to persist eggs: %+v", err)
|
s.Log.Errorf("Failed to persist eggs: %+v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if s.FifoSvc != nil {
|
||||||
|
if err := s.replenishRecordingEggs(ctx, tx, mappedEggs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, mappedDepletions, nil, mappedEggs)); err != nil {
|
var warehouseDeltas map[uint]float64
|
||||||
|
if s.FifoSvc != nil {
|
||||||
|
warehouseDeltas = buildWarehouseDeltas(nil, mappedDepletions, nil, nil)
|
||||||
|
} else {
|
||||||
|
warehouseDeltas = buildWarehouseDeltas(nil, mappedDepletions, nil, mappedEggs)
|
||||||
|
}
|
||||||
|
if err := s.adjustProductWarehouseQuantities(ctx, tx, warehouseDeltas); err != nil {
|
||||||
s.Log.Errorf("Failed to adjust product warehouses: %+v", err)
|
s.Log.Errorf("Failed to adjust product warehouses: %+v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -438,6 +449,16 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hasEggChanges {
|
if hasEggChanges {
|
||||||
|
if s.FifoSvc != nil {
|
||||||
|
if err := ensureRecordingEggsUnused(existingEggs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, nil, existingEggs, nil)); err != nil {
|
||||||
|
s.Log.Errorf("Failed to adjust product warehouses for eggs: %+v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.Repository.DeleteEggs(tx, recordingEntity.Id); err != nil {
|
if err := s.Repository.DeleteEggs(tx, recordingEntity.Id); err != nil {
|
||||||
s.Log.Errorf("Failed to clear eggs: %+v", err)
|
s.Log.Errorf("Failed to clear eggs: %+v", err)
|
||||||
return err
|
return err
|
||||||
@@ -449,9 +470,15 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, nil, existingEggs, mappedEggs)); err != nil {
|
if s.FifoSvc != nil {
|
||||||
s.Log.Errorf("Failed to adjust product warehouses for eggs: %+v", err)
|
if err := s.replenishRecordingEggs(ctx, tx, mappedEggs); err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, nil, existingEggs, mappedEggs)); err != nil {
|
||||||
|
s.Log.Errorf("Failed to adjust product warehouses for eggs: %+v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,6 +653,11 @@ func (s recordingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
|||||||
s.Log.Errorf("Failed to list eggs before delete: %+v", err)
|
s.Log.Errorf("Failed to list eggs before delete: %+v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if s.FifoSvc != nil {
|
||||||
|
if err := ensureRecordingEggsUnused(oldEggs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
oldStocks, err := s.Repository.ListStocks(tx, id)
|
oldStocks, err := s.Repository.ListStocks(tx, id)
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -802,6 +834,32 @@ func (s *recordingService) adjustProductWarehouseQuantities(ctx context.Context,
|
|||||||
return s.ProductWarehouseRepo.AdjustQuantities(ctx, deltas, func(*gorm.DB) *gorm.DB { return tx })
|
return s.ProductWarehouseRepo.AdjustQuantities(ctx, deltas, func(*gorm.DB) *gorm.DB { return tx })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *recordingService) replenishRecordingEggs(ctx context.Context, tx *gorm.DB, eggs []entity.RecordingEgg) error {
|
||||||
|
if len(eggs) == 0 || s.FifoSvc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, egg := range eggs {
|
||||||
|
if egg.Id == 0 || egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
note := fmt.Sprintf("Recording egg #%d", egg.Id)
|
||||||
|
if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
||||||
|
StockableKey: fifo.StockableKeyRecordingEgg,
|
||||||
|
StockableID: egg.Id,
|
||||||
|
ProductWarehouseID: egg.ProductWarehouseId,
|
||||||
|
Quantity: float64(egg.Qty),
|
||||||
|
Note: ¬e,
|
||||||
|
Tx: tx,
|
||||||
|
}); err != nil {
|
||||||
|
s.Log.Errorf("Failed to replenish FIFO stock for recording egg %d: %+v", egg.Id, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type desiredStock struct {
|
type desiredStock struct {
|
||||||
Usage float64
|
Usage float64
|
||||||
Pending float64
|
Pending float64
|
||||||
@@ -922,6 +980,14 @@ type eggTotals struct {
|
|||||||
Weight float64
|
Weight float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ensureRecordingEggsUnused(eggs []entity.RecordingEgg) error {
|
||||||
|
for _, egg := range eggs {
|
||||||
|
if egg.TotalUsed > 0 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Recording egg sudah digunakan sehingga tidak dapat diubah")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func stocksMatch(existing []entity.RecordingStock, incoming []validation.Stock) bool {
|
func stocksMatch(existing []entity.RecordingStock, incoming []validation.Stock) bool {
|
||||||
hasPending := false
|
hasPending := false
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
||||||
marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||||||
|
productionStandardRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories"
|
||||||
chickinRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
|
chickinRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
|
||||||
recordingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
recordingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
||||||
purchaseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
|
purchaseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
|
||||||
@@ -34,10 +35,26 @@ func (RepportModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
|||||||
debtSupplierRepository := repportRepo.NewDebtSupplierRepository(db)
|
debtSupplierRepository := repportRepo.NewDebtSupplierRepository(db)
|
||||||
hppPerKandangRepository := repportRepo.NewHppPerKandangRepository(db)
|
hppPerKandangRepository := repportRepo.NewHppPerKandangRepository(db)
|
||||||
productionResultRepository := repportRepo.NewProductionResultRepository(db)
|
productionResultRepository := repportRepo.NewProductionResultRepository(db)
|
||||||
|
standardGrowthDetailRepository := productionStandardRepo.NewStandardGrowthDetailRepository(db)
|
||||||
|
productionStandardDetailRepository := productionStandardRepo.NewProductionStandardDetailRepository(db)
|
||||||
userRepository := rUser.NewUserRepository(db)
|
userRepository := rUser.NewUserRepository(db)
|
||||||
|
|
||||||
approvalSvc := approvalService.NewApprovalService(approvalRepository)
|
approvalSvc := approvalService.NewApprovalService(approvalRepository)
|
||||||
repportService := sRepport.NewRepportService(validate, expenseRealizationRepository, marketingDeliveryProductRepository, purchaseRepository, chickinRepository, recordingRepository, approvalSvc, purchaseSupplierRepository, debtSupplierRepository, hppPerKandangRepository, productionResultRepository)
|
repportService := sRepport.NewRepportService(
|
||||||
|
validate,
|
||||||
|
expenseRealizationRepository,
|
||||||
|
marketingDeliveryProductRepository,
|
||||||
|
purchaseRepository,
|
||||||
|
chickinRepository,
|
||||||
|
recordingRepository,
|
||||||
|
approvalSvc,
|
||||||
|
purchaseSupplierRepository,
|
||||||
|
debtSupplierRepository,
|
||||||
|
hppPerKandangRepository,
|
||||||
|
productionResultRepository,
|
||||||
|
standardGrowthDetailRepository,
|
||||||
|
productionStandardDetailRepository,
|
||||||
|
)
|
||||||
userService := sUser.NewUserService(userRepository, validate)
|
userService := sUser.NewUserService(userRepository, validate)
|
||||||
|
|
||||||
RepportRoutes(router, userService, repportService)
|
RepportRoutes(router, userService, repportService)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
type ProductionResultRepository interface {
|
type ProductionResultRepository interface {
|
||||||
GetRecordingsByProjectFlockKandang(ctx context.Context, projectFlockKandangID uint, offset, limit int) ([]entity.Recording, int64, error)
|
GetRecordingsByProjectFlockKandang(ctx context.Context, projectFlockKandangID uint, offset, limit int) ([]entity.Recording, int64, error)
|
||||||
|
GetProductionStandardIDByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (uint, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type productionResultRepositoryImpl struct {
|
type productionResultRepositoryImpl struct {
|
||||||
@@ -76,3 +77,25 @@ func (r *productionResultRepositoryImpl) GetRecordingsByProjectFlockKandang(
|
|||||||
|
|
||||||
return recordings, total, nil
|
return recordings, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *productionResultRepositoryImpl) GetProductionStandardIDByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (uint, error) {
|
||||||
|
if projectFlockKandangID == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var row struct {
|
||||||
|
ProductionStandardID uint `gorm:"column:production_standard_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Table("project_flock_kandangs pfk").
|
||||||
|
Select("pf.production_standard_id").
|
||||||
|
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id").
|
||||||
|
Where("pfk.id = ?", projectFlockKandangID).
|
||||||
|
Take(&row).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return row.ProductionStandardID, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -21,6 +22,7 @@ import (
|
|||||||
areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
||||||
supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto"
|
supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto"
|
||||||
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
||||||
|
productionStandardRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories"
|
||||||
chickinRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
|
chickinRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
|
||||||
recordingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
recordingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
||||||
purchaseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
|
purchaseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
|
||||||
@@ -55,6 +57,8 @@ type repportService struct {
|
|||||||
DebtSupplierRepo repportRepo.DebtSupplierRepository
|
DebtSupplierRepo repportRepo.DebtSupplierRepository
|
||||||
HppPerKandangRepo repportRepo.HppPerKandangRepository
|
HppPerKandangRepo repportRepo.HppPerKandangRepository
|
||||||
ProductionResultRepo repportRepo.ProductionResultRepository
|
ProductionResultRepo repportRepo.ProductionResultRepository
|
||||||
|
StandardGrowthDetailRepo productionStandardRepository.StandardGrowthDetailRepository
|
||||||
|
ProductionStandardDetailRepo productionStandardRepository.ProductionStandardDetailRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
type HppCostAggregate struct {
|
type HppCostAggregate struct {
|
||||||
@@ -78,6 +82,8 @@ func NewRepportService(
|
|||||||
debtSupplierRepo repportRepo.DebtSupplierRepository,
|
debtSupplierRepo repportRepo.DebtSupplierRepository,
|
||||||
hppPerKandangRepo repportRepo.HppPerKandangRepository,
|
hppPerKandangRepo repportRepo.HppPerKandangRepository,
|
||||||
productionResultRepo repportRepo.ProductionResultRepository,
|
productionResultRepo repportRepo.ProductionResultRepository,
|
||||||
|
standardGrowthDetailRepo productionStandardRepository.StandardGrowthDetailRepository,
|
||||||
|
productionStandardDetailRepo productionStandardRepository.ProductionStandardDetailRepository,
|
||||||
) RepportService {
|
) RepportService {
|
||||||
return &repportService{
|
return &repportService{
|
||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
@@ -92,6 +98,8 @@ func NewRepportService(
|
|||||||
DebtSupplierRepo: debtSupplierRepo,
|
DebtSupplierRepo: debtSupplierRepo,
|
||||||
HppPerKandangRepo: hppPerKandangRepo,
|
HppPerKandangRepo: hppPerKandangRepo,
|
||||||
ProductionResultRepo: productionResultRepo,
|
ProductionResultRepo: productionResultRepo,
|
||||||
|
StandardGrowthDetailRepo: standardGrowthDetailRepo,
|
||||||
|
ProductionStandardDetailRepo: productionStandardDetailRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,6 +293,21 @@ func (s *repportService) GetProductionResult(ctx *fiber.Ctx, params *validation.
|
|||||||
|
|
||||||
weeklyResults := summarizeProductionResults(dailyResults, recordsPerWeek)
|
weeklyResults := summarizeProductionResults(dailyResults, recordsPerWeek)
|
||||||
|
|
||||||
|
var productionStandardID uint
|
||||||
|
if s.ProductionResultRepo != nil {
|
||||||
|
standardID, err := s.ProductionResultRepo.GetProductionStandardIDByProjectFlockKandangID(ctx.Context(), params.ProjectFlockKandangID)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
productionStandardID = standardID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
standardDetailCache := make(map[int]*entity.ProductionStandardDetail)
|
||||||
|
growthDetailCache := make(map[int]*entity.StandardGrowthDetail)
|
||||||
|
|
||||||
var cumulativeButir int64
|
var cumulativeButir int64
|
||||||
var cumulativeKg float64
|
var cumulativeKg float64
|
||||||
for i := range weeklyResults {
|
for i := range weeklyResults {
|
||||||
@@ -300,6 +323,66 @@ func (s *repportService) GetProductionResult(ctx *fiber.Ctx, params *validation.
|
|||||||
|
|
||||||
cumulativeKg += weeklyResults[i].KgJumlah
|
cumulativeKg += weeklyResults[i].KgJumlah
|
||||||
weeklyResults[i].TotalKg = cumulativeKg
|
weeklyResults[i].TotalKg = cumulativeKg
|
||||||
|
|
||||||
|
if productionStandardID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
week := int(weeklyResults[i].Woa)
|
||||||
|
if s.ProductionStandardDetailRepo != nil {
|
||||||
|
detail, ok := standardDetailCache[week]
|
||||||
|
if !ok {
|
||||||
|
fetched, fetchErr := s.ProductionStandardDetailRepo.GetByStandardIDAndWeek(ctx.Context(), productionStandardID, week)
|
||||||
|
if fetchErr != nil {
|
||||||
|
if !errors.Is(fetchErr, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, 0, fetchErr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
detail = fetched
|
||||||
|
}
|
||||||
|
standardDetailCache[week] = detail
|
||||||
|
}
|
||||||
|
|
||||||
|
if detail != nil {
|
||||||
|
if detail.TargetHenDayProduction != nil {
|
||||||
|
weeklyResults[i].HdStd = *detail.TargetHenDayProduction
|
||||||
|
}
|
||||||
|
if detail.TargetHenHouseProduction != nil {
|
||||||
|
weeklyResults[i].HhStd = *detail.TargetHenHouseProduction
|
||||||
|
}
|
||||||
|
if detail.TargetEggWeight != nil {
|
||||||
|
weeklyResults[i].EwStd = *detail.TargetEggWeight
|
||||||
|
}
|
||||||
|
if detail.TargetEggMass != nil {
|
||||||
|
weeklyResults[i].EmStd = *detail.TargetEggMass
|
||||||
|
}
|
||||||
|
if detail.StandardFCR != nil {
|
||||||
|
weeklyResults[i].FcrStd = *detail.StandardFCR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.StandardGrowthDetailRepo != nil {
|
||||||
|
detail, ok := growthDetailCache[week]
|
||||||
|
if !ok {
|
||||||
|
fetched, fetchErr := s.StandardGrowthDetailRepo.GetByStandardIDAndWeek(ctx.Context(), productionStandardID, week)
|
||||||
|
if fetchErr != nil {
|
||||||
|
if !errors.Is(fetchErr, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, 0, fetchErr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
detail = fetched
|
||||||
|
}
|
||||||
|
growthDetailCache[week] = detail
|
||||||
|
}
|
||||||
|
|
||||||
|
if detail != nil && detail.FeedIntake != nil {
|
||||||
|
weeklyResults[i].FiStd = *detail.FeedIntake
|
||||||
|
}
|
||||||
|
if detail != nil && detail.TargetMeanBw != nil {
|
||||||
|
weeklyResults[i].StdBw = *detail.TargetMeanBw
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
totalWeeks := int64(math.Ceil(float64(totalRecordings) / float64(recordsPerWeek)))
|
totalWeeks := int64(math.Ceil(float64(totalRecordings) / float64(recordsPerWeek)))
|
||||||
@@ -314,17 +397,17 @@ func mapRecordingToProductionResultDTO(record entity.Recording) dto.ProductionRe
|
|||||||
StdUniformity: "90% up",
|
StdUniformity: "90% up",
|
||||||
DepKum: valueOrZero(record.CumDepletionRate),
|
DepKum: valueOrZero(record.CumDepletionRate),
|
||||||
DepStd: valueOrZero(record.TotalDepletionQty),
|
DepStd: valueOrZero(record.TotalDepletionQty),
|
||||||
|
Hd: valueOrZero(record.HenDay),
|
||||||
|
Fi: valueOrZero(record.FeedIntake),
|
||||||
Fcr: valueOrZero(record.FcrValue),
|
Fcr: valueOrZero(record.FcrValue),
|
||||||
Hh: valueOrZero(record.TotalChickQty),
|
Hh: valueOrZero(record.HenHouse),
|
||||||
|
Em: valueOrZero(record.EggMass),
|
||||||
|
Ew: valueOrZero(record.EggWeight),
|
||||||
}
|
}
|
||||||
|
|
||||||
if record.Day != nil {
|
if record.Day != nil {
|
||||||
result.Woa = float64(*record.Day)
|
result.Woa = float64(*record.Day)
|
||||||
}
|
}
|
||||||
if record.CumIntake != nil {
|
|
||||||
result.Fi = float64(*record.CumIntake)
|
|
||||||
}
|
|
||||||
|
|
||||||
// avgWeight := calculateAverageBodyWeight(record.BodyWeights)
|
// avgWeight := calculateAverageBodyWeight(record.BodyWeights)
|
||||||
avgWeight := 1.0
|
avgWeight := 1.0
|
||||||
if avgWeight > 0 {
|
if avgWeight > 0 {
|
||||||
@@ -351,8 +434,6 @@ func mapRecordingToProductionResultDTO(record entity.Recording) dto.ProductionRe
|
|||||||
result.PersenPutih = roundFloat((float64(result.ButiranPutih)/total)*100, 2)
|
result.PersenPutih = roundFloat((float64(result.ButiranPutih)/total)*100, 2)
|
||||||
result.PersenRetak = roundFloat((float64(result.ButiranRetak)/total)*100, 2)
|
result.PersenRetak = roundFloat((float64(result.ButiranRetak)/total)*100, 2)
|
||||||
result.PersenPecah = roundFloat((float64(result.ButiranPecah)/total)*100, 2)
|
result.PersenPecah = roundFloat((float64(result.ButiranPecah)/total)*100, 2)
|
||||||
result.Ew = (eggSummary.TotalKg * 1000) / total
|
|
||||||
result.Em = eggSummary.TotalKg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -464,13 +545,13 @@ func summarizeProductionResults(daily []dto.ProductionResultDTO, groupSize int)
|
|||||||
if end > len(daily) {
|
if end > len(daily) {
|
||||||
end = len(daily)
|
end = len(daily)
|
||||||
}
|
}
|
||||||
result = append(result, aggregateProductionResultGroup(daily[i:end]))
|
result = append(result, aggregateProductionResultGroup(daily[i:end], groupSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func aggregateProductionResultGroup(group []dto.ProductionResultDTO) dto.ProductionResultDTO {
|
func aggregateProductionResultGroup(group []dto.ProductionResultDTO, groupSize int) dto.ProductionResultDTO {
|
||||||
count := len(group)
|
count := len(group)
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
return dto.ProductionResultDTO{}
|
return dto.ProductionResultDTO{}
|
||||||
@@ -542,6 +623,10 @@ func aggregateProductionResultGroup(group []dto.ProductionResultDTO) dto.Product
|
|||||||
if divider == 0 {
|
if divider == 0 {
|
||||||
divider = 1
|
divider = 1
|
||||||
}
|
}
|
||||||
|
weeklyDivider := float64(groupSize)
|
||||||
|
if weeklyDivider == 0 {
|
||||||
|
weeklyDivider = divider
|
||||||
|
}
|
||||||
|
|
||||||
agg.Bw = sumBw / divider
|
agg.Bw = sumBw / divider
|
||||||
agg.StdBw = sumStdBw / divider
|
agg.StdBw = sumStdBw / divider
|
||||||
@@ -570,17 +655,17 @@ func aggregateProductionResultGroup(group []dto.ProductionResultDTO) dto.Product
|
|||||||
agg.PersenPecah = roundFloat(sumPersenPecah/percentDivider, 2)
|
agg.PersenPecah = roundFloat(sumPersenPecah/percentDivider, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
agg.Hd = sumHd / divider
|
agg.Hd = roundFloat(sumHd/weeklyDivider, 2)
|
||||||
agg.HdStd = sumHdStd / divider
|
agg.HdStd = sumHdStd / divider
|
||||||
agg.Fi = sumFi / divider
|
agg.Fi = roundFloat(sumFi/weeklyDivider, 2)
|
||||||
agg.FiStd = sumFiStd / divider
|
agg.FiStd = sumFiStd / divider
|
||||||
agg.Em = sumEm / divider
|
agg.Em = group[count-1].Em
|
||||||
agg.EmStd = sumEmStd / divider
|
agg.EmStd = sumEmStd / divider
|
||||||
agg.Ew = sumEw / divider
|
agg.Ew = group[count-1].Ew
|
||||||
agg.EwStd = sumEwStd / divider
|
agg.EwStd = sumEwStd / divider
|
||||||
agg.Fcr = sumFcr / divider
|
agg.Fcr = roundFloat(sumFcr/weeklyDivider, 2)
|
||||||
agg.FcrStd = sumFcrStd / divider
|
agg.FcrStd = sumFcrStd / divider
|
||||||
agg.Hh = sumHh / divider
|
agg.Hh = roundFloat(sumHh/weeklyDivider, 2)
|
||||||
agg.HhStd = sumHhStd / divider
|
agg.HhStd = sumHhStd / divider
|
||||||
|
|
||||||
return agg
|
return agg
|
||||||
|
|||||||
@@ -15,4 +15,5 @@ const (
|
|||||||
StockableKeyAdjustmentIn StockableKey = "ADJUSTMENT_IN"
|
StockableKeyAdjustmentIn StockableKey = "ADJUSTMENT_IN"
|
||||||
StockableKeyPurchaseItems StockableKey = "PURCHASE_ITEMS"
|
StockableKeyPurchaseItems StockableKey = "PURCHASE_ITEMS"
|
||||||
StockableKeyProjectFlockPopulation StockableKey = "PROJECT_FLOCK_POPULATION"
|
StockableKeyProjectFlockPopulation StockableKey = "PROJECT_FLOCK_POPULATION"
|
||||||
|
StockableKeyRecordingEgg StockableKey = "RECORDING_EGG"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user