mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-25 07:45:44 +00:00
codex/fix: show farm stock usage on closing page
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user