Merge branch 'fix/sapronak-cal' into 'development'

[FIX][BE]: fix perhitunga sapronak

See merge request mbugroup/lti-api!439
This commit is contained in:
Giovanni Gabriel Septriadi
2026-04-22 12:36:16 +00:00
8 changed files with 433 additions and 35 deletions
@@ -1588,11 +1588,20 @@ func splitStockLogs(rows []stockLogSapronakRow, refFn func(stockLogSapronakRow)
func (r *ClosingRepositoryImpl) FetchSapronakAdjustments(ctx context.Context, kandangID uint, start, end *time.Time) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) {
poByWarehouse := r.DB().
Table("purchase_items pi").
Select("DISTINCT ON (pi.product_warehouse_id) pi.product_warehouse_id, po.po_number, pi.received_date").
Joins("JOIN purchases po ON po.id = pi.purchase_id").
Where("pi.received_date IS NOT NULL").
Order("pi.product_warehouse_id, pi.received_date ASC")
Table("(?) AS ranked_po",
r.DB().
Table("purchase_items pi").
Select(`
pi.product_warehouse_id,
po.po_number,
pi.received_date,
ROW_NUMBER() OVER (PARTITION BY pi.product_warehouse_id ORDER BY pi.received_date ASC, pi.id ASC) AS rn
`).
Joins("JOIN purchases po ON po.id = pi.purchase_id").
Where("pi.received_date IS NOT NULL"),
).
Select("ranked_po.product_warehouse_id, ranked_po.po_number, ranked_po.received_date").
Where("ranked_po.rn = 1")
incomingQuery := r.withCtx(ctx).
Table("adjustment_stocks AS ast").
@@ -1601,10 +1610,10 @@ func (r *ClosingRepositoryImpl) FetchSapronakAdjustments(ctx context.Context, ka
p.name AS product_name,
f.name AS flag,
ast.created_at AS date,
CONCAT('ADJ-', ast.id) AS reference,
'ADJ-' || CAST(ast.id AS TEXT) AS reference,
COALESCE(ast.total_qty, 0) AS qty_in,
0 AS qty_out,
COALESCE(p.product_price, 0) AS price
COALESCE(ast.price, p.product_price, 0) AS price
`).
Joins("JOIN product_warehouses pw ON pw.id = ast.product_warehouse_id").
Joins("JOIN warehouses w ON w.id = pw.warehouse_id").
@@ -1627,10 +1636,18 @@ func (r *ClosingRepositoryImpl) FetchSapronakAdjustments(ctx context.Context, ka
p.name AS product_name,
f.name AS flag,
COALESCE(pi.received_date, st.transfer_date, lt.transfer_date, pfp_po.received_date, pc.chick_in_date, ast_in.created_at, ast.created_at) AS date,
COALESCE(po.po_number, st.movement_number, lt.transfer_number, pfp_po.po_number, CONCAT('CHICKIN-', pc.id), CONCAT('ADJ-', ast_in.id), CONCAT('ADJ-', ast.id)) AS reference,
COALESCE(
po.po_number,
st.movement_number,
lt.transfer_number,
pfp_po.po_number,
CASE WHEN ast_in.id IS NOT NULL THEN 'ADJ-' || CAST(ast_in.id AS TEXT) END,
CASE WHEN ast.id IS NOT NULL THEN 'ADJ-' || CAST(ast.id AS TEXT) END,
CASE WHEN pc.id IS NOT NULL THEN 'CHICKIN-' || CAST(pc.id AS TEXT) END
) AS reference,
0 AS qty_in,
COALESCE(SUM(sa.qty), 0) AS qty_out,
COALESCE(p.product_price, 0) AS price
COALESCE(pi.price, ast_in.price, ast.price, p.product_price, 0) AS price
`).
Joins("JOIN adjustment_stocks ast ON ast.id = sa.usable_id AND sa.usable_type = ?", fifo.UsableKeyAdjustmentOut.String()).
Joins("LEFT JOIN purchase_items pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyPurchaseItems.String()).
@@ -1651,7 +1668,7 @@ func (r *ClosingRepositoryImpl) FetchSapronakAdjustments(ctx context.Context, ka
Where("w.kandang_id = ?", kandangID).
Where("f.name IN ?", sapronakFlagsAll).
Where("f.name NOT IN ?", sapronakFlags(utils.FlagDOC, utils.FlagPullet)).
Group("pw.product_id, p.name, f.name, pi.received_date, st.transfer_date, lt.transfer_date, pfp_po.received_date, pc.chick_in_date, ast_in.created_at, ast.created_at, po.po_number, st.movement_number, lt.transfer_number, pfp_po.po_number, pc.id, ast_in.id, ast.id, p.product_price")
Group("pw.product_id, p.name, f.name, pi.received_date, st.transfer_date, lt.transfer_date, pfp_po.received_date, pc.chick_in_date, ast_in.created_at, ast.created_at, po.po_number, st.movement_number, lt.transfer_number, pfp_po.po_number, pc.id, ast_in.id, ast.id, pi.price, ast_in.price, ast.price, p.product_price")
outgoingQuery = r.joinSapronakProductFlag(outgoingQuery, "p")
outgoingQuery = applyDateRange(outgoingQuery, dateExpr, start, end)
outgoing, err := scanAndGroupDetails(outgoingQuery)
@@ -89,6 +89,63 @@ func TestFetchSapronakIncomingIncludesAttributedFarmPurchasesAndHistoricalWareho
}
}
func TestFetchSapronakAdjustmentsUsesAdjustmentReferenceAndPrice(t *testing.T) {
db := setupClosingRepositoryTestDB(t)
repo := NewClosingRepository(db)
ctx := context.Background()
statements := []string{
`INSERT INTO warehouses (id, kandang_id) VALUES (5, 5)`,
`INSERT INTO product_categories (id, code) VALUES (1, 'OBT')`,
`INSERT INTO products (id, name, product_category_id, product_price) VALUES (17, 'OVK CUT-OVER', 1, 1)`,
`INSERT INTO flags (id, flagable_id, flagable_type, name) VALUES (1, 17, 'products', 'OVK')`,
`INSERT INTO product_warehouses (id, product_id, warehouse_id, project_flock_kandang_id) VALUES (1365, 17, 5, 66)`,
`INSERT INTO adjustment_stocks (id, product_warehouse_id, total_qty, usage_qty, price) VALUES
(1139, 1365, 1, 0, 298594487),
(1140, 1365, 0, 1, 298594487)`,
fmt.Sprintf(`INSERT INTO stock_allocations (id, product_warehouse_id, stockable_type, stockable_id, usable_type, usable_id, qty, allocation_purpose, status) VALUES
(25990, 1365, '%s', 1139, '%s', 1140, 1, 'CONSUME', 'ACTIVE')`,
fifo.StockableKeyAdjustmentIn.String(),
fifo.UsableKeyAdjustmentOut.String(),
),
}
for _, stmt := range statements {
if err := db.Exec(stmt).Error; err != nil {
t.Fatalf("failed seeding schema: %v", err)
}
}
incoming, outgoing, err := repo.FetchSapronakAdjustments(ctx, 5, nil, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
incomingRows := incoming[17]
if len(incomingRows) != 1 {
t.Fatalf("expected 1 incoming row for product 17, got %d", len(incomingRows))
}
if incomingRows[0].Reference != "ADJ-1139" {
t.Fatalf("expected incoming reference ADJ-1139, got %q", incomingRows[0].Reference)
}
if incomingRows[0].Price != 298594487 {
t.Fatalf("expected incoming price 298594487 from adjustment_stocks.price, got %.3f", incomingRows[0].Price)
}
outgoingRows := outgoing[17]
if len(outgoingRows) != 1 {
t.Fatalf("expected 1 outgoing row for product 17, got %d", len(outgoingRows))
}
if outgoingRows[0].Reference != "ADJ-1139" {
t.Fatalf("expected outgoing reference ADJ-1139, got %q", outgoingRows[0].Reference)
}
if outgoingRows[0].Reference == "CHICKIN-" {
t.Fatalf("expected outgoing reference to avoid CHICKIN- placeholder")
}
if outgoingRows[0].Price != 298594487 {
t.Fatalf("expected outgoing price 298594487 from adjustment_stocks.price, got %.3f", outgoingRows[0].Price)
}
}
func setupClosingRepositoryTestDB(t *testing.T) *gorm.DB {
t.Helper()
@@ -134,6 +191,7 @@ func setupClosingRepositoryTestDB(t *testing.T) *gorm.DB {
purchase_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
warehouse_id INTEGER NOT NULL,
product_warehouse_id INTEGER NULL,
project_flock_kandang_id INTEGER NULL,
total_qty NUMERIC(15,3) NOT NULL DEFAULT 0,
price NUMERIC(15,3) NOT NULL DEFAULT 0,
@@ -153,7 +211,13 @@ func setupClosingRepositoryTestDB(t *testing.T) *gorm.DB {
)`,
`CREATE TABLE project_chickins (
id INTEGER PRIMARY KEY,
project_flock_kandang_id INTEGER NOT NULL
project_flock_kandang_id INTEGER NOT NULL,
chick_in_date TIMESTAMP NULL
)`,
`CREATE TABLE project_flock_populations (
id INTEGER PRIMARY KEY,
project_chickin_id INTEGER NULL,
product_warehouse_id INTEGER NULL
)`,
`CREATE TABLE stock_allocations (
id INTEGER PRIMARY KEY,
@@ -180,6 +244,16 @@ func setupClosingRepositoryTestDB(t *testing.T) *gorm.DB {
movement_number TEXT NULL,
reason TEXT NULL
)`,
`CREATE TABLE laying_transfers (
id INTEGER PRIMARY KEY,
transfer_date TIMESTAMP NULL,
transfer_number TEXT NULL
)`,
`CREATE TABLE laying_transfer_targets (
id INTEGER PRIMARY KEY,
laying_transfer_id INTEGER NOT NULL,
product_warehouse_id INTEGER NULL
)`,
`CREATE TABLE stock_transfer_details (
id INTEGER PRIMARY KEY,
stock_transfer_id INTEGER NOT NULL,
@@ -194,6 +268,7 @@ func setupClosingRepositoryTestDB(t *testing.T) *gorm.DB {
product_warehouse_id INTEGER NOT NULL,
total_qty NUMERIC(15,3) NOT NULL DEFAULT 0,
usage_qty NUMERIC(15,3) NOT NULL DEFAULT 0,
price NUMERIC(15,3) NOT NULL DEFAULT 0,
adj_number TEXT NULL,
created_at TIMESTAMP NULL
)`,