mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'codex/sales-at-farm-level' into 'development'
codex/fix: show farm stock usage on closing page See merge request mbugroup/lti-api!384
This commit is contained in:
@@ -25,8 +25,8 @@ 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)
|
||||||
GetExpeditionHPP(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]ExpeditionHPPRow, error)
|
GetExpeditionHPP(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]ExpeditionHPPRow, error)
|
||||||
FetchSapronakIncoming(ctx context.Context, kandangID uint, start, end *time.Time) ([]SapronakIncomingRow, error)
|
FetchSapronakIncoming(ctx context.Context, projectFlockKandangID uint, kandangID uint, start, end *time.Time) ([]SapronakIncomingRow, error)
|
||||||
FetchSapronakIncomingDetails(ctx context.Context, kandangID uint, start, end *time.Time) (map[uint][]SapronakDetailRow, error)
|
FetchSapronakIncomingDetails(ctx context.Context, projectFlockKandangID uint, kandangID uint, start, end *time.Time) (map[uint][]SapronakDetailRow, error)
|
||||||
FetchSapronakUsage(ctx context.Context, pfkID uint, start, end *time.Time) ([]SapronakUsageRow, error)
|
FetchSapronakUsage(ctx context.Context, pfkID uint, start, end *time.Time) ([]SapronakUsageRow, error)
|
||||||
FetchSapronakUsageDetails(ctx context.Context, pfkID uint, start, end *time.Time) (map[uint][]SapronakDetailRow, error)
|
FetchSapronakUsageDetails(ctx context.Context, pfkID uint, start, end *time.Time) (map[uint][]SapronakDetailRow, error)
|
||||||
FetchSapronakChickinUsage(ctx context.Context, pfkID uint, start, end *time.Time) ([]SapronakUsageRow, error)
|
FetchSapronakChickinUsage(ctx context.Context, pfkID uint, start, end *time.Time) ([]SapronakUsageRow, error)
|
||||||
@@ -90,6 +90,23 @@ type SapronakQueryParams struct {
|
|||||||
EndDate *time.Time
|
EndDate *time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sapronakIncomingPurchaseQueryParts(params SapronakQueryParams) (string, []any) {
|
||||||
|
if len(params.ProjectFlockKandangIDs) > 0 {
|
||||||
|
return sapronakIncomingPurchasesScopedSQL(), []any{
|
||||||
|
fifo.UsableKeyRecordingStock.String(),
|
||||||
|
fifo.UsableKeyProjectChickin.String(),
|
||||||
|
fifo.StockableKeyPurchaseItems.String(),
|
||||||
|
entity.StockAllocationStatusActive,
|
||||||
|
entity.StockAllocationPurposeConsume,
|
||||||
|
params.ProjectFlockKandangIDs,
|
||||||
|
params.ProjectFlockKandangIDs,
|
||||||
|
params.WarehouseIDs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sapronakIncomingPurchasesSQL, []any{params.WarehouseIDs}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) GetSapronak(ctx context.Context, params SapronakQueryParams) ([]SapronakRow, int64, error) {
|
func (r *ClosingRepositoryImpl) GetSapronak(ctx context.Context, params SapronakQueryParams) ([]SapronakRow, int64, error) {
|
||||||
db := r.DB().WithContext(ctx)
|
db := r.DB().WithContext(ctx)
|
||||||
|
|
||||||
@@ -103,8 +120,10 @@ func (r *ClosingRepositoryImpl) GetSapronak(ctx context.Context, params Sapronak
|
|||||||
if len(params.WarehouseIDs) == 0 {
|
if len(params.WarehouseIDs) == 0 {
|
||||||
return []SapronakRow{}, 0, nil
|
return []SapronakRow{}, 0, nil
|
||||||
}
|
}
|
||||||
unionParts = append(unionParts, sapronakIncomingPurchasesSQL, sapronakIncomingTransfersSQL, sapronakIncomingAdjustmentsSQL)
|
purchasesSQL, purchaseArgs := sapronakIncomingPurchaseQueryParts(params)
|
||||||
args = append(args, params.WarehouseIDs, params.WarehouseIDs, params.WarehouseIDs)
|
unionParts = append(unionParts, purchasesSQL, sapronakIncomingTransfersSQL, sapronakIncomingAdjustmentsSQL)
|
||||||
|
args = append(args, purchaseArgs...)
|
||||||
|
args = append(args, params.WarehouseIDs, params.WarehouseIDs)
|
||||||
case validation.SapronakTypeOutgoing:
|
case validation.SapronakTypeOutgoing:
|
||||||
if len(params.WarehouseIDs) > 0 {
|
if len(params.WarehouseIDs) > 0 {
|
||||||
unionParts = append(unionParts, sapronakOutgoingTransfersSQL, sapronakOutgoingAdjustmentsSQL)
|
unionParts = append(unionParts, sapronakOutgoingTransfersSQL, sapronakOutgoingAdjustmentsSQL)
|
||||||
@@ -193,8 +212,10 @@ func (r *ClosingRepositoryImpl) GetSapronakSummary(ctx context.Context, params S
|
|||||||
if len(params.WarehouseIDs) == 0 {
|
if len(params.WarehouseIDs) == 0 {
|
||||||
return []SapronakSummaryRow{}, nil
|
return []SapronakSummaryRow{}, nil
|
||||||
}
|
}
|
||||||
unionParts = append(unionParts, sapronakIncomingPurchasesSQL, sapronakIncomingTransfersSQL, sapronakIncomingAdjustmentsSQL)
|
purchasesSQL, purchaseArgs := sapronakIncomingPurchaseQueryParts(params)
|
||||||
args = append(args, params.WarehouseIDs, params.WarehouseIDs, params.WarehouseIDs)
|
unionParts = append(unionParts, purchasesSQL, sapronakIncomingTransfersSQL, sapronakIncomingAdjustmentsSQL)
|
||||||
|
args = append(args, purchaseArgs...)
|
||||||
|
args = append(args, params.WarehouseIDs, params.WarehouseIDs)
|
||||||
case validation.SapronakTypeOutgoing:
|
case validation.SapronakTypeOutgoing:
|
||||||
if len(params.WarehouseIDs) > 0 {
|
if len(params.WarehouseIDs) > 0 {
|
||||||
unionParts = append(unionParts, sapronakOutgoingTransfersSQL, sapronakOutgoingAdjustmentsSQL)
|
unionParts = append(unionParts, sapronakOutgoingTransfersSQL, sapronakOutgoingAdjustmentsSQL)
|
||||||
@@ -855,6 +876,140 @@ func sapronakFlags(flags ...utils.FlagType) []string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sapronakLegacyFlagByProductCategoryCase(categoryCodeExpr string) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`CASE
|
||||||
|
WHEN UPPER(%s) = 'DOC' THEN '%s'
|
||||||
|
WHEN UPPER(%s) = 'PLT' THEN '%s'
|
||||||
|
WHEN UPPER(%s) IN ('RAW', 'PST', 'STR', 'FSR') THEN '%s'
|
||||||
|
WHEN UPPER(%s) IN ('OBT', 'VTM', 'KMA') THEN '%s'
|
||||||
|
ELSE NULL
|
||||||
|
END`,
|
||||||
|
categoryCodeExpr, utils.FlagDOC,
|
||||||
|
categoryCodeExpr, utils.FlagPullet,
|
||||||
|
categoryCodeExpr, utils.FlagPakan,
|
||||||
|
categoryCodeExpr, utils.FlagOVK,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sapronakIncomingPurchasesScopedSQL() string {
|
||||||
|
return `
|
||||||
|
WITH scoped_farm_allocations AS (
|
||||||
|
SELECT
|
||||||
|
sa.stockable_id AS purchase_item_id,
|
||||||
|
COALESCE(SUM(sa.qty), 0) AS allocated_qty
|
||||||
|
FROM stock_allocations sa
|
||||||
|
LEFT JOIN recording_stocks rs ON rs.id = sa.usable_id AND sa.usable_type = ?
|
||||||
|
LEFT JOIN recordings rec ON rec.id = rs.recording_id AND rec.deleted_at IS NULL
|
||||||
|
LEFT JOIN project_chickins pc ON pc.id = sa.usable_id AND sa.usable_type = ?
|
||||||
|
WHERE sa.stockable_type = ?
|
||||||
|
AND sa.status = ?
|
||||||
|
AND sa.allocation_purpose = ?
|
||||||
|
AND COALESCE(rec.project_flock_kandangs_id, pc.project_flock_kandang_id) IN ?
|
||||||
|
GROUP BY sa.stockable_id
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
CAST(pi.id AS BIGINT) AS id,
|
||||||
|
COALESCE(pi.received_date, '1970-01-01') AS sort_date,
|
||||||
|
COALESCE(TO_CHAR(pi.received_date, 'DD-Mon-YYYY'), '') AS date_text,
|
||||||
|
COALESCE(p.po_number, '') AS reference_number,
|
||||||
|
'Pembelian' AS transaction_type,
|
||||||
|
prod.name AS product_name,
|
||||||
|
COALESCE((
|
||||||
|
SELECT string_agg(
|
||||||
|
f.name,
|
||||||
|
' ' ORDER BY
|
||||||
|
CASE
|
||||||
|
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||||
|
ELSE 1
|
||||||
|
END,
|
||||||
|
f.name
|
||||||
|
)
|
||||||
|
FROM flags f
|
||||||
|
WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id
|
||||||
|
), '') AS product_category,
|
||||||
|
COALESCE((
|
||||||
|
SELECT string_agg(
|
||||||
|
f.name,
|
||||||
|
' ' ORDER BY
|
||||||
|
CASE
|
||||||
|
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||||
|
ELSE 1
|
||||||
|
END,
|
||||||
|
f.name
|
||||||
|
)
|
||||||
|
FROM flags f
|
||||||
|
WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id
|
||||||
|
), '') AS product_sub_category,
|
||||||
|
'-' AS source_warehouse,
|
||||||
|
w.name AS destination_warehouse,
|
||||||
|
'' AS destination,
|
||||||
|
pi.total_qty AS quantity,
|
||||||
|
u.id AS unit_id,
|
||||||
|
u.name AS unit,
|
||||||
|
COALESCE(p.notes, '') AS notes
|
||||||
|
FROM purchase_items pi
|
||||||
|
JOIN purchases p ON p.id = pi.purchase_id
|
||||||
|
JOIN products prod ON prod.id = pi.product_id
|
||||||
|
JOIN uoms u ON u.id = prod.uom_id
|
||||||
|
JOIN warehouses w ON w.id = pi.warehouse_id
|
||||||
|
WHERE w.kandang_id IS NOT NULL
|
||||||
|
AND (
|
||||||
|
pi.project_flock_kandang_id IN ?
|
||||||
|
OR (pi.project_flock_kandang_id IS NULL AND pi.warehouse_id IN ?)
|
||||||
|
)
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
CAST(pi.id AS BIGINT) AS id,
|
||||||
|
COALESCE(pi.received_date, '1970-01-01') AS sort_date,
|
||||||
|
COALESCE(TO_CHAR(pi.received_date, 'DD-Mon-YYYY'), '') AS date_text,
|
||||||
|
COALESCE(p.po_number, '') AS reference_number,
|
||||||
|
'Pembelian' AS transaction_type,
|
||||||
|
prod.name AS product_name,
|
||||||
|
COALESCE((
|
||||||
|
SELECT string_agg(
|
||||||
|
f.name,
|
||||||
|
' ' ORDER BY
|
||||||
|
CASE
|
||||||
|
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||||
|
ELSE 1
|
||||||
|
END,
|
||||||
|
f.name
|
||||||
|
)
|
||||||
|
FROM flags f
|
||||||
|
WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id
|
||||||
|
), '') AS product_category,
|
||||||
|
COALESCE((
|
||||||
|
SELECT string_agg(
|
||||||
|
f.name,
|
||||||
|
' ' ORDER BY
|
||||||
|
CASE
|
||||||
|
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||||
|
ELSE 1
|
||||||
|
END,
|
||||||
|
f.name
|
||||||
|
)
|
||||||
|
FROM flags f
|
||||||
|
WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id
|
||||||
|
), '') AS product_sub_category,
|
||||||
|
'-' AS source_warehouse,
|
||||||
|
w.name AS destination_warehouse,
|
||||||
|
'' AS destination,
|
||||||
|
sfa.allocated_qty AS quantity,
|
||||||
|
u.id AS unit_id,
|
||||||
|
u.name AS unit,
|
||||||
|
COALESCE(p.notes, '') AS notes
|
||||||
|
FROM purchase_items pi
|
||||||
|
JOIN purchases p ON p.id = pi.purchase_id
|
||||||
|
JOIN products prod ON prod.id = pi.product_id
|
||||||
|
JOIN uoms u ON u.id = prod.uom_id
|
||||||
|
JOIN warehouses w ON w.id = pi.warehouse_id
|
||||||
|
JOIN scoped_farm_allocations sfa ON sfa.purchase_item_id = pi.id
|
||||||
|
WHERE w.kandang_id IS NULL
|
||||||
|
AND COALESCE(sfa.allocated_qty, 0) > 0
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
sapronakFlagsAll = sapronakFlags(utils.FlagDOC, utils.FlagPakan, utils.FlagOVK, utils.FlagPullet)
|
sapronakFlagsAll = sapronakFlags(utils.FlagDOC, utils.FlagPakan, utils.FlagOVK, utils.FlagPullet)
|
||||||
sapronakFlagsUsage = sapronakFlags(utils.FlagPakan, utils.FlagOVK)
|
sapronakFlagsUsage = sapronakFlags(utils.FlagPakan, utils.FlagOVK)
|
||||||
@@ -862,18 +1017,44 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) joinSapronakProductFlag(db *gorm.DB, productAlias string) *gorm.DB {
|
func (r *ClosingRepositoryImpl) joinSapronakProductFlag(db *gorm.DB, productAlias string) *gorm.DB {
|
||||||
subquery := r.DB().
|
actualFlags := r.DB().
|
||||||
Table("flags").
|
Table("flags").
|
||||||
Select("DISTINCT ON (flagable_id) flagable_id, name").
|
Select(`
|
||||||
|
flagable_id,
|
||||||
|
MIN(CASE
|
||||||
|
WHEN UPPER(name) = 'DOC' THEN 1
|
||||||
|
WHEN UPPER(name) = 'PULLET' THEN 2
|
||||||
|
WHEN UPPER(name) = 'PAKAN' THEN 3
|
||||||
|
WHEN UPPER(name) = 'OVK' THEN 4
|
||||||
|
ELSE 5
|
||||||
|
END) AS priority
|
||||||
|
`).
|
||||||
Where("flagable_type = ?", entity.FlagableTypeProduct).
|
Where("flagable_type = ?", entity.FlagableTypeProduct).
|
||||||
Where("name IN ?", sapronakFlagsAll).
|
Where("UPPER(name) IN ?", sapronakFlagsAll).
|
||||||
Order(fmt.Sprintf(
|
Group("flagable_id")
|
||||||
"flagable_id, CASE WHEN name = '%s' THEN 1 WHEN name = '%s' THEN 2 WHEN name = '%s' THEN 3 WHEN name = '%s' THEN 4 ELSE 5 END",
|
|
||||||
|
legacyFlagExpr := sapronakLegacyFlagByProductCategoryCase("pc.code")
|
||||||
|
subquery := r.DB().
|
||||||
|
Table("products AS sapronak_products").
|
||||||
|
Select(fmt.Sprintf(`
|
||||||
|
sapronak_products.id AS flagable_id,
|
||||||
|
CASE
|
||||||
|
WHEN actual_flags.priority = 1 THEN '%s'
|
||||||
|
WHEN actual_flags.priority = 2 THEN '%s'
|
||||||
|
WHEN actual_flags.priority = 3 THEN '%s'
|
||||||
|
WHEN actual_flags.priority = 4 THEN '%s'
|
||||||
|
ELSE %s
|
||||||
|
END AS name
|
||||||
|
`,
|
||||||
utils.FlagDOC,
|
utils.FlagDOC,
|
||||||
utils.FlagPullet,
|
utils.FlagPullet,
|
||||||
utils.FlagPakan,
|
utils.FlagPakan,
|
||||||
utils.FlagOVK,
|
utils.FlagOVK,
|
||||||
))
|
legacyFlagExpr,
|
||||||
|
)).
|
||||||
|
Joins("LEFT JOIN (?) AS actual_flags ON actual_flags.flagable_id = sapronak_products.id", actualFlags).
|
||||||
|
Joins("LEFT JOIN product_categories pc ON pc.id = sapronak_products.product_category_id").
|
||||||
|
Where("actual_flags.priority IS NOT NULL OR " + legacyFlagExpr + " IS NOT NULL")
|
||||||
|
|
||||||
return db.Joins("JOIN (?) f ON f.flagable_id = "+productAlias+".id", subquery)
|
return db.Joins("JOIN (?) f ON f.flagable_id = "+productAlias+".id", subquery)
|
||||||
}
|
}
|
||||||
@@ -1132,22 +1313,111 @@ func (r *ClosingRepositoryImpl) FetchSapronakUsageAllocatedDetails(ctx context.C
|
|||||||
return scanAndGroupDetails(query)
|
return scanAndGroupDetails(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) incomingPurchaseBase(ctx context.Context, kandangID uint, start, end *time.Time) *gorm.DB {
|
func (r *ClosingRepositoryImpl) incomingPurchaseBase(ctx context.Context, projectFlockKandangID uint, kandangID uint, start, end *time.Time) *gorm.DB {
|
||||||
db := r.withCtx(ctx).
|
db := r.withCtx(ctx).
|
||||||
Table("purchase_items AS pi").
|
Table("purchase_items AS pi").
|
||||||
Joins("JOIN purchases po ON po.id = pi.purchase_id AND po.deleted_at IS NULL").
|
Joins("JOIN purchases po ON po.id = pi.purchase_id AND po.deleted_at IS NULL").
|
||||||
Joins("JOIN products p ON p.id = pi.product_id").
|
Joins("JOIN products p ON p.id = pi.product_id").
|
||||||
Joins("JOIN warehouses w ON w.id = pi.warehouse_id").
|
Joins("JOIN warehouses w ON w.id = pi.warehouse_id").
|
||||||
Where("w.kandang_id = ?", kandangID).
|
Where("f.name IN ?", sapronakFlagsAll).
|
||||||
|
Where("pi.received_date IS NOT NULL")
|
||||||
|
if projectFlockKandangID > 0 {
|
||||||
|
db = db.Where(
|
||||||
|
"w.kandang_id = ? AND (pi.project_flock_kandang_id = ? OR pi.project_flock_kandang_id IS NULL)",
|
||||||
|
kandangID,
|
||||||
|
projectFlockKandangID,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
db = db.Where("w.kandang_id = ?", kandangID)
|
||||||
|
}
|
||||||
|
db = applyDateRange(db, "pi.received_date", start, end)
|
||||||
|
return r.joinSapronakProductFlag(db, "p")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ClosingRepositoryImpl) incomingFarmPurchaseAllocationBase(ctx context.Context, projectFlockKandangID uint, start, end *time.Time) *gorm.DB {
|
||||||
|
db := r.withCtx(ctx).
|
||||||
|
Table("stock_allocations AS sa").
|
||||||
|
Joins("JOIN purchase_items pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyPurchaseItems.String()).
|
||||||
|
Joins("JOIN purchases po ON po.id = pi.purchase_id AND po.deleted_at IS NULL").
|
||||||
|
Joins("JOIN products p ON p.id = pi.product_id").
|
||||||
|
Joins("JOIN warehouses w ON w.id = pi.warehouse_id").
|
||||||
|
Joins("LEFT JOIN recording_stocks rs ON rs.id = sa.usable_id AND sa.usable_type = ?", fifo.UsableKeyRecordingStock.String()).
|
||||||
|
Joins("LEFT JOIN recordings rec ON rec.id = rs.recording_id AND rec.deleted_at IS NULL").
|
||||||
|
Joins("LEFT JOIN project_chickins pc ON pc.id = sa.usable_id AND sa.usable_type = ?", fifo.UsableKeyProjectChickin.String()).
|
||||||
|
Where("sa.status = ?", entity.StockAllocationStatusActive).
|
||||||
|
Where("sa.allocation_purpose = ?", entity.StockAllocationPurposeConsume).
|
||||||
|
Where("w.kandang_id IS NULL").
|
||||||
|
Where("COALESCE(rec.project_flock_kandangs_id, pc.project_flock_kandang_id) = ?", projectFlockKandangID).
|
||||||
Where("f.name IN ?", sapronakFlagsAll).
|
Where("f.name IN ?", sapronakFlagsAll).
|
||||||
Where("pi.received_date IS NOT NULL")
|
Where("pi.received_date IS NOT NULL")
|
||||||
db = applyDateRange(db, "pi.received_date", start, end)
|
db = applyDateRange(db, "pi.received_date", start, end)
|
||||||
return r.joinSapronakProductFlag(db, "p")
|
return r.joinSapronakProductFlag(db, "p")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) FetchSapronakIncoming(ctx context.Context, kandangID uint, start, end *time.Time) ([]SapronakIncomingRow, error) {
|
func mergeSapronakIncomingRows(primary []SapronakIncomingRow, extra []SapronakIncomingRow) []SapronakIncomingRow {
|
||||||
|
if len(extra) == 0 {
|
||||||
|
return primary
|
||||||
|
}
|
||||||
|
|
||||||
|
type key struct {
|
||||||
|
productID uint
|
||||||
|
flag string
|
||||||
|
}
|
||||||
|
|
||||||
|
merged := make(map[key]*SapronakIncomingRow, len(primary)+len(extra))
|
||||||
|
order := make([]key, 0, len(primary)+len(extra))
|
||||||
|
|
||||||
|
add := func(rows []SapronakIncomingRow) {
|
||||||
|
for _, row := range rows {
|
||||||
|
k := key{productID: row.ProductID, flag: row.Flag}
|
||||||
|
if existing, ok := merged[k]; ok {
|
||||||
|
existing.Qty += row.Qty
|
||||||
|
existing.Value += row.Value
|
||||||
|
if existing.ProductName == "" {
|
||||||
|
existing.ProductName = row.ProductName
|
||||||
|
}
|
||||||
|
if existing.DefaultPrice == 0 {
|
||||||
|
existing.DefaultPrice = row.DefaultPrice
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
copyRow := row
|
||||||
|
merged[k] = ©Row
|
||||||
|
order = append(order, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add(primary)
|
||||||
|
add(extra)
|
||||||
|
|
||||||
|
result := make([]SapronakIncomingRow, 0, len(order))
|
||||||
|
for _, k := range order {
|
||||||
|
result = append(result, *merged[k])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeSapronakDetailMaps(primary map[uint][]SapronakDetailRow, extra map[uint][]SapronakDetailRow) map[uint][]SapronakDetailRow {
|
||||||
|
if len(primary) == 0 && len(extra) == 0 {
|
||||||
|
return map[uint][]SapronakDetailRow{}
|
||||||
|
}
|
||||||
|
if len(extra) == 0 {
|
||||||
|
return primary
|
||||||
|
}
|
||||||
|
if len(primary) == 0 {
|
||||||
|
return extra
|
||||||
|
}
|
||||||
|
|
||||||
|
for productID, rows := range extra {
|
||||||
|
primary[productID] = append(primary[productID], rows...)
|
||||||
|
}
|
||||||
|
return primary
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ClosingRepositoryImpl) FetchSapronakIncoming(ctx context.Context, projectFlockKandangID uint, kandangID uint, start, end *time.Time) ([]SapronakIncomingRow, error) {
|
||||||
rows := make([]SapronakIncomingRow, 0)
|
rows := make([]SapronakIncomingRow, 0)
|
||||||
db := r.incomingPurchaseBase(ctx, kandangID, start, end).Select(`
|
db := r.incomingPurchaseBase(ctx, projectFlockKandangID, kandangID, start, end).Select(`
|
||||||
pi.product_id AS product_id,
|
pi.product_id AS product_id,
|
||||||
p.name AS product_name,
|
p.name AS product_name,
|
||||||
f.name AS flag,
|
f.name AS flag,
|
||||||
@@ -1158,22 +1428,68 @@ func (r *ClosingRepositoryImpl) FetchSapronakIncoming(ctx context.Context, kanda
|
|||||||
if err := db.Group("pi.product_id, p.name, f.name, p.product_price").Scan(&rows).Error; err != nil {
|
if err := db.Group("pi.product_id, p.name, f.name, p.product_price").Scan(&rows).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return rows, nil
|
|
||||||
|
if projectFlockKandangID == 0 {
|
||||||
|
return rows, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
farmRows := make([]SapronakIncomingRow, 0)
|
||||||
|
farmDB := r.incomingFarmPurchaseAllocationBase(ctx, projectFlockKandangID, start, end).Select(`
|
||||||
|
pi.product_id AS product_id,
|
||||||
|
p.name AS product_name,
|
||||||
|
f.name AS flag,
|
||||||
|
COALESCE(SUM(sa.qty), 0) AS qty,
|
||||||
|
COALESCE(SUM(sa.qty * pi.price), 0) AS value,
|
||||||
|
COALESCE(p.product_price, 0) AS default_price
|
||||||
|
`)
|
||||||
|
if err := farmDB.Group("pi.product_id, p.name, f.name, p.product_price").Scan(&farmRows).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergeSapronakIncomingRows(rows, farmRows), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) FetchSapronakIncomingDetails(ctx context.Context, kandangID uint, start, end *time.Time) (map[uint][]SapronakDetailRow, error) {
|
func (r *ClosingRepositoryImpl) FetchSapronakIncomingDetails(ctx context.Context, projectFlockKandangID uint, kandangID uint, start, end *time.Time) (map[uint][]SapronakDetailRow, error) {
|
||||||
return scanAndGroupDetails(
|
rows, err := scanAndGroupDetails(
|
||||||
r.incomingPurchaseBase(ctx, kandangID, start, end).Select(`
|
r.incomingPurchaseBase(ctx, projectFlockKandangID, kandangID, start, end).Select(`
|
||||||
pi.product_id AS product_id,
|
pi.product_id AS product_id,
|
||||||
p.name AS product_name,
|
p.name AS product_name,
|
||||||
f.name AS flag,
|
f.name AS flag,
|
||||||
pi.received_date AS date,
|
pi.received_date AS date,
|
||||||
COALESCE(po.po_number, '') AS reference,
|
COALESCE(po.po_number, '') AS reference,
|
||||||
COALESCE(pi.total_qty,0) AS qty_in,
|
COALESCE(pi.total_qty,0) AS qty_in,
|
||||||
|
0 AS qty_out,
|
||||||
|
COALESCE(pi.price,0) AS price
|
||||||
|
`),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if projectFlockKandangID == 0 {
|
||||||
|
return rows, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
farmRows, err := scanAndGroupDetails(
|
||||||
|
r.incomingFarmPurchaseAllocationBase(ctx, projectFlockKandangID, start, end).Select(`
|
||||||
|
pi.product_id AS product_id,
|
||||||
|
p.name AS product_name,
|
||||||
|
f.name AS flag,
|
||||||
|
pi.received_date AS date,
|
||||||
|
COALESCE(po.po_number, '') AS reference,
|
||||||
|
COALESCE(SUM(sa.qty),0) AS qty_in,
|
||||||
0 AS qty_out,
|
0 AS qty_out,
|
||||||
COALESCE(pi.price,0) AS price
|
COALESCE(pi.price,0) AS price
|
||||||
|
`).Group(`
|
||||||
|
pi.id, pi.product_id, p.name, f.name,
|
||||||
|
pi.received_date, po.po_number, pi.price
|
||||||
`),
|
`),
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergeSapronakDetailMaps(rows, farmRows), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type stockLogSapronakRow struct {
|
type stockLogSapronakRow struct {
|
||||||
|
|||||||
@@ -0,0 +1,208 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/glebarez/sqlite"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSapronakIncomingPurchaseQueryPartsUsesAttributedPurchasesWhenProjectFlockKandangIDsProvided(t *testing.T) {
|
||||||
|
sql, args := sapronakIncomingPurchaseQueryParts(SapronakQueryParams{
|
||||||
|
WarehouseIDs: []uint{46},
|
||||||
|
ProjectFlockKandangIDs: []uint{101},
|
||||||
|
})
|
||||||
|
|
||||||
|
if sql != sapronakIncomingPurchasesScopedSQL() {
|
||||||
|
t.Fatalf("expected scoped purchase SQL, got %q", sql)
|
||||||
|
}
|
||||||
|
if len(args) != 8 {
|
||||||
|
t.Fatalf("expected 8 argument groups, got %d", len(args))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchSapronakIncomingIncludesAttributedFarmPurchasesAndHistoricalWarehouseFallback(t *testing.T) {
|
||||||
|
db := setupClosingRepositoryTestDB(t)
|
||||||
|
repo := NewClosingRepository(db)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
receivedAt := time.Date(2026, 4, 1, 4, 0, 0, 0, time.UTC)
|
||||||
|
statements := []string{
|
||||||
|
`INSERT INTO warehouses (id, kandang_id) VALUES (1, NULL), (2, 59), (3, 88)`,
|
||||||
|
`INSERT INTO product_categories (id, code) VALUES (1, 'OBT'), (2, 'RAW')`,
|
||||||
|
`INSERT INTO products (id, name, product_category_id, product_price) VALUES
|
||||||
|
(10, 'MEFISTO @1 LITER', 1, 261700),
|
||||||
|
(20, 'PAKAN GROWING CRUMBLE MALINDO', 2, 15000)`,
|
||||||
|
`INSERT INTO flags (id, flagable_id, flagable_type, name) VALUES
|
||||||
|
(1, 10, 'products', 'OVK'),
|
||||||
|
(2, 10, 'products', 'OBAT')`,
|
||||||
|
`INSERT INTO purchases (id, po_number, deleted_at) VALUES (1, 'PO-LTI-0005', NULL)`,
|
||||||
|
`INSERT INTO recordings (id, project_flock_kandangs_id, deleted_at) VALUES (11, 101, NULL), (12, 999, NULL)`,
|
||||||
|
`INSERT INTO recording_stocks (id, recording_id, product_warehouse_id, usage_qty) VALUES (21, 11, 501, 150), (22, 12, 502, 10)`,
|
||||||
|
`INSERT INTO purchase_items (id, purchase_id, product_id, warehouse_id, project_flock_kandang_id, total_qty, price, received_date) VALUES
|
||||||
|
(1, 1, 10, 1, NULL, 100, 261700, '` + receivedAt.Format(time.RFC3339) + `'),
|
||||||
|
(2, 1, 20, 1, NULL, 50, 15000, '` + receivedAt.Format(time.RFC3339) + `'),
|
||||||
|
(3, 1, 20, 2, NULL, 25, 12000, '` + receivedAt.Format(time.RFC3339) + `'),
|
||||||
|
(4, 1, 10, 3, 999, 10, 261700, '` + receivedAt.Format(time.RFC3339) + `'),
|
||||||
|
(5, 1, 20, 1, NULL, 40, 15000, '` + receivedAt.Format(time.RFC3339) + `')`,
|
||||||
|
fmt.Sprintf(`INSERT INTO stock_allocations (id, product_warehouse_id, stockable_type, stockable_id, usable_type, usable_id, qty, allocation_purpose, status) VALUES
|
||||||
|
(1, 701, '%s', 1, '%s', 21, 100, 'CONSUME', 'ACTIVE'),
|
||||||
|
(2, 702, '%s', 2, '%s', 21, 50, 'CONSUME', 'ACTIVE'),
|
||||||
|
(3, 703, '%s', 5, '%s', 22, 40, 'CONSUME', 'ACTIVE')`,
|
||||||
|
fifo.StockableKeyPurchaseItems.String(),
|
||||||
|
fifo.UsableKeyRecordingStock.String(),
|
||||||
|
fifo.StockableKeyPurchaseItems.String(),
|
||||||
|
fifo.UsableKeyRecordingStock.String(),
|
||||||
|
fifo.StockableKeyPurchaseItems.String(),
|
||||||
|
fifo.UsableKeyRecordingStock.String(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
for _, stmt := range statements {
|
||||||
|
if err := db.Exec(stmt).Error; err != nil {
|
||||||
|
t.Fatalf("failed seeding schema: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := repo.FetchSapronakIncoming(ctx, 101, 59, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if len(rows) != 2 {
|
||||||
|
t.Fatalf("expected 2 sapronak rows, got %d", len(rows))
|
||||||
|
}
|
||||||
|
|
||||||
|
byProduct := make(map[uint]SapronakIncomingRow, len(rows))
|
||||||
|
for _, row := range rows {
|
||||||
|
byProduct[row.ProductID] = row
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := byProduct[10]; got.ProductID == 0 || got.Flag != "OVK" || got.Qty != 100 {
|
||||||
|
t.Fatalf("expected OVK farm purchase qty 100 for product 10, got %+v", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := byProduct[20]; got.ProductID == 0 || got.Flag != "PAKAN" || got.Qty != 75 {
|
||||||
|
t.Fatalf("expected PAKAN total qty 75 including farm allocated qty 50 and kandang receipt qty 25, got %+v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupClosingRepositoryTestDB(t *testing.T) *gorm.DB {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=private"), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed opening sqlite db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
statements := []string{
|
||||||
|
`CREATE TABLE warehouses (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
kandang_id INTEGER NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE product_categories (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
code TEXT NOT NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE uoms (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE products (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
product_category_id INTEGER NULL,
|
||||||
|
uom_id INTEGER NULL,
|
||||||
|
product_price NUMERIC(15,3) NOT NULL DEFAULT 0
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE flags (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
flagable_id INTEGER NOT NULL,
|
||||||
|
flagable_type TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE purchases (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
po_number TEXT NULL,
|
||||||
|
notes TEXT NULL,
|
||||||
|
deleted_at TIMESTAMP NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE purchase_items (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
purchase_id INTEGER NOT NULL,
|
||||||
|
product_id INTEGER NOT NULL,
|
||||||
|
warehouse_id INTEGER NOT NULL,
|
||||||
|
project_flock_kandang_id INTEGER NULL,
|
||||||
|
total_qty NUMERIC(15,3) NOT NULL DEFAULT 0,
|
||||||
|
price NUMERIC(15,3) NOT NULL DEFAULT 0,
|
||||||
|
received_date TIMESTAMP NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE recordings (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
project_flock_kandangs_id INTEGER NOT NULL,
|
||||||
|
deleted_at TIMESTAMP NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE recording_stocks (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
recording_id INTEGER NOT NULL,
|
||||||
|
product_warehouse_id INTEGER NOT NULL,
|
||||||
|
usage_qty NUMERIC(15,3) NOT NULL DEFAULT 0
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE project_chickins (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
project_flock_kandang_id INTEGER NOT NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE stock_allocations (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
product_warehouse_id INTEGER NOT NULL,
|
||||||
|
stockable_type TEXT NOT NULL,
|
||||||
|
stockable_id INTEGER NOT NULL,
|
||||||
|
usable_type TEXT NOT NULL,
|
||||||
|
usable_id INTEGER NOT NULL,
|
||||||
|
qty NUMERIC(15,3) NOT NULL DEFAULT 0,
|
||||||
|
allocation_purpose TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE product_warehouses (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
product_id INTEGER NOT NULL,
|
||||||
|
warehouse_id INTEGER NOT NULL,
|
||||||
|
project_flock_kandang_id INTEGER NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE stock_transfers (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
from_warehouse_id INTEGER NULL,
|
||||||
|
to_warehouse_id INTEGER NULL,
|
||||||
|
transfer_date TIMESTAMP NULL,
|
||||||
|
movement_number TEXT NULL,
|
||||||
|
reason TEXT NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE stock_transfer_details (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
stock_transfer_id INTEGER NOT NULL,
|
||||||
|
product_id INTEGER NOT NULL,
|
||||||
|
dest_product_warehouse_id INTEGER NULL,
|
||||||
|
source_product_warehouse_id INTEGER NULL,
|
||||||
|
total_qty NUMERIC(15,3) NOT NULL DEFAULT 0,
|
||||||
|
usage_qty NUMERIC(15,3) NOT NULL DEFAULT 0
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE adjustment_stocks (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
product_warehouse_id INTEGER NOT NULL,
|
||||||
|
total_qty NUMERIC(15,3) NOT NULL DEFAULT 0,
|
||||||
|
usage_qty NUMERIC(15,3) NOT NULL DEFAULT 0,
|
||||||
|
adj_number TEXT NULL,
|
||||||
|
created_at TIMESTAMP NULL
|
||||||
|
)`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stmt := range statements {
|
||||||
|
if err := db.Exec(stmt).Error; err != nil {
|
||||||
|
t.Fatalf("failed preparing schema: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
@@ -383,7 +383,7 @@ func (s closingService) GetClosingSapronak(c *fiber.Ctx, projectFlockID uint, pa
|
|||||||
var projectFlockKandangIDs []uint
|
var projectFlockKandangIDs []uint
|
||||||
if params.KandangID != nil && *params.KandangID > 0 {
|
if params.KandangID != nil && *params.KandangID > 0 {
|
||||||
projectFlockKandangIDs = []uint{*params.KandangID}
|
projectFlockKandangIDs = []uint{*params.KandangID}
|
||||||
} else if params.Type == validation.SapronakTypeOutgoing {
|
} else {
|
||||||
projectFlockKandangIDs, err = s.getProjectFlockKandangIDs(c.Context(), projectFlockID)
|
projectFlockKandangIDs, err = s.getProjectFlockKandangIDs(c.Context(), projectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to fetch project flock kandang IDs for project flock %d: %+v", projectFlockID, err)
|
s.Log.Errorf("Failed to fetch project flock kandang IDs for project flock %d: %+v", projectFlockID, err)
|
||||||
@@ -474,7 +474,7 @@ func (s closingService) GetClosingSapronakSummary(c *fiber.Ctx, projectFlockID u
|
|||||||
var projectFlockKandangIDs []uint
|
var projectFlockKandangIDs []uint
|
||||||
if params.KandangID != nil && *params.KandangID > 0 {
|
if params.KandangID != nil && *params.KandangID > 0 {
|
||||||
projectFlockKandangIDs = []uint{*params.KandangID}
|
projectFlockKandangIDs = []uint{*params.KandangID}
|
||||||
} else if params.Type == validation.SapronakTypeOutgoing {
|
} else {
|
||||||
projectFlockKandangIDs, err = s.getProjectFlockKandangIDs(c.Context(), projectFlockID)
|
projectFlockKandangIDs, err = s.getProjectFlockKandangIDs(c.Context(), projectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to fetch project flock kandang IDs for project flock %d: %+v", projectFlockID, err)
|
s.Log.Errorf("Failed to fetch project flock kandang IDs for project flock %d: %+v", projectFlockID, err)
|
||||||
@@ -1156,7 +1156,7 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint
|
|||||||
chickenDepletion = 0
|
chickenDepletion = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
chickenPerformance := calculatePerformanceMetrics(chickenAverageWeight, chickenSalesWeight, feedUsed, population, chickenDepletion, age)
|
chickenPerformance := calculatePerformanceMetrics(chickenAverageWeight, chickenSalesWeight, feedUsed, population, chickenDepletion, age)
|
||||||
if fcrActFromRecording != nil {
|
if fcrActFromRecording != nil {
|
||||||
chickenPerformance.FcrAct = *fcrActFromRecording
|
chickenPerformance.FcrAct = *fcrActFromRecording
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -382,11 +382,11 @@ func buildSapronakDetails(
|
|||||||
func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.ProjectFlockKandang, flagFilter string) ([]dto.SapronakItemDTO, []dto.SapronakGroupDTO, float64, float64, error) {
|
func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.ProjectFlockKandang, flagFilter string) ([]dto.SapronakItemDTO, []dto.SapronakGroupDTO, float64, float64, error) {
|
||||||
// Filter by project flock period (start = first chickin or pfk created_at, end = closed_at if any).
|
// Filter by project flock period (start = first chickin or pfk created_at, end = closed_at if any).
|
||||||
startDate, endDate := sapronakPeriodRange(pfk)
|
startDate, endDate := sapronakPeriodRange(pfk)
|
||||||
incomingRows, err := s.Repository.FetchSapronakIncoming(ctx, pfk.KandangId, startDate, endDate)
|
incomingRows, err := s.Repository.FetchSapronakIncoming(ctx, pfk.Id, pfk.KandangId, startDate, endDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, 0, err
|
return nil, nil, 0, 0, err
|
||||||
}
|
}
|
||||||
incomingDetailsRows, err := s.Repository.FetchSapronakIncomingDetails(ctx, pfk.KandangId, startDate, endDate)
|
incomingDetailsRows, err := s.Repository.FetchSapronakIncomingDetails(ctx, pfk.Id, pfk.KandangId, startDate, endDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, 0, err
|
return nil, nil, 0, 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user