package repository import ( "context" "math" "testing" "time" "github.com/glebarez/sqlite" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" "gitlab.com/mbugroup/lti-api.git/internal/utils" "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo" "gorm.io/gorm" ) func TestHppV2RepositoryGetEggProduksiIncludesTransferredAdjustmentStock(t *testing.T) { db := setupHppV2RepositoryTestDB(t) mustExecHppV2(t, db, `INSERT INTO recordings (id, project_flock_kandangs_id, record_datetime) VALUES (1, 101, '2026-04-19 10:00:00')`, `INSERT INTO recording_eggs (id, recording_id, product_warehouse_id, qty, weight, project_flock_kandang_id) VALUES (1, 1, 401, 80, 8, 101)`, `INSERT INTO stock_transfers (id, transfer_date) VALUES (1, '2026-04-18 08:00:00')`, `INSERT INTO stock_transfer_details (id, stock_transfer_id, source_product_warehouse_id, dest_product_warehouse_id) VALUES (1, 1, 301, 401)`, `INSERT INTO stock_allocations (id, usable_type, usable_id, stockable_type, stockable_id, status, allocation_purpose) VALUES (1, 'STOCK_TRANSFER_OUT', 1, 'ADJUSTMENT_IN', 501, 'ACTIVE', 'CONSUME')`, `INSERT INTO adjustment_stocks (id, product_warehouse_id, total_qty, price, created_at) VALUES (501, 301, 20, 2.5, '2026-04-18 07:30:00')`, ) repo := &HppV2RepositoryImpl{db: db} endDate := mustJakartaTime(t, "2026-04-20 00:00:00") totalPieces, totalWeightKg, err := repo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{101}, &endDate) if err != nil { t.Fatalf("expected no error, got %v", err) } assertFloatEquals(t, totalPieces, 100) assertFloatEquals(t, totalWeightKg, 10.5) } func TestHppV2RepositoryGetEggTerjualUsesEndDateForSameDayFarmSales(t *testing.T) { db := setupHppV2RepositoryTestDB(t) mustExecHppV2(t, db, `INSERT INTO kandangs (id, location_id) VALUES (1, 10), (2, 10)`, `INSERT INTO project_flock_kandangs (id, kandang_id) VALUES (101, 1), (102, 2)`, `INSERT INTO warehouses (id, type, location_id) VALUES (201, 'LOKASI', 10)`, `INSERT INTO product_warehouses (id, warehouse_id, product_id, project_flock_kandang_id) VALUES (301, 201, 900, NULL)`, `INSERT INTO flags (id, flagable_type, flagable_id, name) VALUES (1, 'products', 900, 'TELUR')`, `INSERT INTO recordings (id, project_flock_kandangs_id, record_datetime, deleted_at) VALUES (1, 101, '2026-04-19 08:00:00', NULL), (2, 102, '2026-04-19 09:00:00', NULL)`, `INSERT INTO recording_eggs (id, recording_id, product_warehouse_id, qty, weight, project_flock_kandang_id) VALUES (1, 1, 301, 60, 6, 101), (2, 2, 301, 40, 4, 102)`, `INSERT INTO marketing_products (id, product_warehouse_id) VALUES (401, 301)`, `INSERT INTO marketing_delivery_products (id, marketing_product_id, usage_qty, total_weight, delivery_date) VALUES (501, 401, 50, 5, '2026-04-19 12:00:00')`, ) repo := &HppV2RepositoryImpl{db: db} startDate := mustJakartaTime(t, "2026-04-19 00:00:00") endDate := mustJakartaTime(t, "2026-04-20 00:00:00") totalPieces, totalWeightKg, err := repo.GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{101}, &startDate, &endDate) if err != nil { t.Fatalf("expected no error, got %v", err) } assertFloatEquals(t, totalPieces, 30) assertFloatEquals(t, totalWeightKg, 3) } func TestHppV2RepositoryGetEggTerjualProratesHistoricalFarmSalesFromAdjustments(t *testing.T) { db := setupHppV2RepositoryTestDB(t) mustExecHppV2(t, db, `INSERT INTO kandangs (id, location_id) VALUES (1, 10), (2, 10)`, `INSERT INTO project_flock_kandangs (id, kandang_id) VALUES (101, 1), (102, 2)`, `INSERT INTO warehouses (id, type, location_id) VALUES (201, 'LOKASI', 10), (211, 'KANDANG', 10), (212, 'KANDANG', 10)`, `INSERT INTO product_warehouses (id, warehouse_id, product_id, project_flock_kandang_id) VALUES (301, 201, 900, NULL), (311, 211, 900, 101), (312, 212, 900, 102)`, `INSERT INTO flags (id, flagable_type, flagable_id, name) VALUES (1, 'products', 900, 'TELUR')`, `INSERT INTO stock_transfers (id, transfer_date) VALUES (1, '2026-04-18 08:00:00'), (2, '2026-04-18 08:15:00')`, `INSERT INTO stock_transfer_details (id, stock_transfer_id, source_product_warehouse_id, dest_product_warehouse_id) VALUES (1, 1, 311, 301), (2, 2, 312, 301)`, `INSERT INTO adjustment_stocks (id, product_warehouse_id, usage_qty, price, function_code, transaction_type, created_at) VALUES (801, 311, 70, 7, 'RECORDING_EGG_IN', 'RECORDING', '2026-04-18 07:00:00'), (802, 312, 30, 3, 'RECORDING_EGG_IN', 'RECORDING', '2026-04-18 07:30:00')`, `INSERT INTO marketing_products (id, product_warehouse_id) VALUES (401, 301)`, `INSERT INTO marketing_delivery_products (id, marketing_product_id, usage_qty, total_weight, delivery_date) VALUES (501, 401, 20, 2, '2026-04-19 12:00:00')`, ) repo := &HppV2RepositoryImpl{db: db} startDate := mustJakartaTime(t, "2026-04-19 00:00:00") endDate := mustJakartaTime(t, "2026-04-20 00:00:00") totalPieces, totalWeightKg, err := repo.GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{101}, &startDate, &endDate) if err != nil { t.Fatalf("expected no error, got %v", err) } assertFloatEquals(t, totalPieces, 14) assertFloatEquals(t, totalWeightKg, 1.4) } func setupHppV2RepositoryTestDB(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) } mustExecHppV2(t, db, `CREATE TABLE recordings ( id INTEGER PRIMARY KEY, project_flock_kandangs_id INTEGER NULL, record_datetime DATETIME NULL, deleted_at DATETIME NULL )`, `CREATE TABLE recording_eggs ( id INTEGER PRIMARY KEY, recording_id INTEGER NULL, product_warehouse_id INTEGER NULL, qty NUMERIC(15,3) NULL, weight NUMERIC(15,3) NULL, project_flock_kandang_id INTEGER NULL )`, `CREATE TABLE stock_transfers ( id INTEGER PRIMARY KEY, transfer_date DATETIME NULL )`, `CREATE TABLE stock_transfer_details ( id INTEGER PRIMARY KEY, stock_transfer_id INTEGER NULL, source_product_warehouse_id INTEGER NULL, dest_product_warehouse_id INTEGER NULL )`, `CREATE TABLE stock_allocations ( id INTEGER PRIMARY KEY, usable_type TEXT NULL, usable_id INTEGER NULL, stockable_type TEXT NULL, stockable_id INTEGER NULL, status TEXT NULL, allocation_purpose TEXT NULL, qty NUMERIC(15,3) NULL )`, `CREATE TABLE adjustment_stocks ( id INTEGER PRIMARY KEY, product_warehouse_id INTEGER NULL, total_qty NUMERIC(15,3) NULL, usage_qty NUMERIC(15,3) NULL, price NUMERIC(15,3) NULL, grand_total NUMERIC(15,3) NULL, function_code TEXT NULL, transaction_type TEXT NULL, created_at DATETIME NULL )`, `CREATE TABLE kandangs ( id INTEGER PRIMARY KEY, location_id INTEGER NULL )`, `CREATE TABLE project_flock_kandangs ( id INTEGER PRIMARY KEY, kandang_id INTEGER NULL, project_flock_id INTEGER NULL )`, `CREATE TABLE warehouses ( id INTEGER PRIMARY KEY, type TEXT NULL, location_id INTEGER NULL )`, `CREATE TABLE product_warehouses ( id INTEGER PRIMARY KEY, warehouse_id INTEGER NULL, product_id INTEGER NULL, project_flock_kandang_id INTEGER NULL )`, `CREATE TABLE marketing_products ( id INTEGER PRIMARY KEY, product_warehouse_id INTEGER NULL )`, `CREATE TABLE marketing_delivery_products ( id INTEGER PRIMARY KEY, marketing_product_id INTEGER NULL, usage_qty NUMERIC(15,3) NULL, total_weight NUMERIC(15,3) NULL, delivery_date DATETIME NULL )`, `CREATE TABLE flags ( id INTEGER PRIMARY KEY, flagable_type TEXT NULL, flagable_id INTEGER NULL, name TEXT NULL )`, ) return db } func mustExecHppV2(t *testing.T, db *gorm.DB, statements ...string) { t.Helper() for _, statement := range statements { if err := db.Exec(statement).Error; err != nil { t.Fatalf("failed executing statement %q: %v", statement, err) } } } func mustJakartaTime(t *testing.T, raw string) time.Time { t.Helper() location, err := time.LoadLocation("Asia/Jakarta") if err != nil { t.Fatalf("failed loading timezone: %v", err) } value, err := time.ParseInLocation("2006-01-02 15:04:05", raw, location) if err != nil { t.Fatalf("failed parsing time %q: %v", raw, err) } return value } func assertFloatEquals(t *testing.T, got float64, want float64) { t.Helper() if math.Abs(got-want) > 0.000001 { t.Fatalf("expected %.6f, got %.6f", want, got) } } func TestHppV2RepositoryConstantsStayAlignedWithProductionQueries(t *testing.T) { if fifo.UsableKeyStockTransferOut.String() != "STOCK_TRANSFER_OUT" { t.Fatalf("unexpected stock transfer usable key: %s", fifo.UsableKeyStockTransferOut.String()) } if fifo.StockableKeyAdjustmentIn.String() != "ADJUSTMENT_IN" { t.Fatalf("unexpected adjustment stockable key: %s", fifo.StockableKeyAdjustmentIn.String()) } if entity.StockAllocationStatusActive != "ACTIVE" { t.Fatalf("unexpected active stock allocation status: %s", entity.StockAllocationStatusActive) } if entity.StockAllocationPurposeConsume != "CONSUME" { t.Fatalf("unexpected consume stock allocation purpose: %s", entity.StockAllocationPurposeConsume) } if string(utils.AdjustmentTransactionSubtypeRecordingEggIn) != "RECORDING_EGG_IN" { t.Fatalf("unexpected adjustment function code: %s", utils.AdjustmentTransactionSubtypeRecordingEggIn) } if string(utils.AdjustmentTransactionTypeRecording) != "RECORDING" { t.Fatalf("unexpected adjustment transaction type: %s", utils.AdjustmentTransactionTypeRecording) } }