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 }