mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
391 lines
15 KiB
Go
391 lines
15 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"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 TestHppV2RepositoryGetRecordingStockRoutingAdjustmentCostByProjectFlockID(t *testing.T) {
|
|
db := setupHppV2RepositoryTestDB(t)
|
|
approvalType := utils.ApprovalWorkflowTransferToLaying.String()
|
|
|
|
mustExecHppV2(t, db,
|
|
`INSERT INTO project_flock_kandangs (id, kandang_id, project_flock_id) VALUES
|
|
(101, 1, 1),
|
|
(102, 2, 1),
|
|
(103, 3, 1),
|
|
(104, 4, 1),
|
|
(105, 5, 1),
|
|
(201, 6, 2)`,
|
|
`INSERT INTO recordings (id, project_flock_kandangs_id, record_datetime, deleted_at) VALUES
|
|
(1, 101, '2026-04-10 08:00:00', NULL),
|
|
(2, 101, '2026-04-10 08:05:00', NULL),
|
|
(3, 101, '2026-04-10 08:10:00', NULL),
|
|
(4, 102, '2026-04-10 08:15:00', NULL),
|
|
(5, 102, '2026-04-10 08:20:00', NULL),
|
|
(6, 103, '2026-04-12 08:00:00', NULL),
|
|
(7, 103, '2026-04-12 08:05:00', NULL),
|
|
(8, 104, '2026-04-12 08:10:00', NULL),
|
|
(9, 104, '2026-04-12 08:15:00', NULL),
|
|
(10, 105, '2026-04-12 08:20:00', NULL),
|
|
(11, 105, '2026-04-12 08:25:00', NULL)`,
|
|
`INSERT INTO product_warehouses (id, warehouse_id, product_id, project_flock_kandang_id) VALUES
|
|
(501, 201, 10, NULL),
|
|
(502, 201, 10, NULL),
|
|
(503, 201, 10, NULL),
|
|
(504, 201, 10, NULL),
|
|
(505, 201, 10, NULL),
|
|
(506, 201, 10, NULL),
|
|
(507, 201, 10, NULL),
|
|
(508, 201, 10, NULL),
|
|
(509, 201, 10, NULL),
|
|
(510, 201, 10, NULL),
|
|
(511, 201, 10, NULL)`,
|
|
`INSERT INTO flags (id, flagable_type, flagable_id, name) VALUES
|
|
(10, 'products', 10, 'PAKAN')`,
|
|
`INSERT INTO recording_stocks (id, recording_id, product_warehouse_id, project_flock_kandang_id) VALUES
|
|
(101, 1, 501, NULL),
|
|
(102, 2, 502, 201),
|
|
(103, 3, 503, 101),
|
|
(104, 4, 504, NULL),
|
|
(105, 5, 505, 201),
|
|
(106, 6, 506, NULL),
|
|
(107, 7, 507, 201),
|
|
(108, 8, 508, NULL),
|
|
(109, 9, 509, 201),
|
|
(110, 10, 510, NULL),
|
|
(111, 11, 511, 201)`,
|
|
`INSERT INTO purchase_items (id, product_id, price) VALUES
|
|
(601, 10, 100),
|
|
(602, 10, 110),
|
|
(603, 10, 120),
|
|
(604, 10, 130),
|
|
(605, 10, 140),
|
|
(606, 10, 150),
|
|
(607, 10, 160),
|
|
(608, 10, 170),
|
|
(609, 10, 180),
|
|
(610, 10, 190),
|
|
(611, 10, 200)`,
|
|
`INSERT INTO stock_allocations (id, usable_type, usable_id, stockable_type, stockable_id, status, allocation_purpose, qty) VALUES
|
|
(9001, 'RECORDING_STOCK', 101, 'PURCHASE_ITEMS', 601, 'ACTIVE', 'CONSUME', 2),
|
|
(9002, 'RECORDING_STOCK', 102, 'PURCHASE_ITEMS', 602, 'ACTIVE', 'CONSUME', 1),
|
|
(9003, 'RECORDING_STOCK', 103, 'PURCHASE_ITEMS', 603, 'ACTIVE', 'CONSUME', 1),
|
|
(9004, 'RECORDING_STOCK', 104, 'PURCHASE_ITEMS', 604, 'ACTIVE', 'CONSUME', 1),
|
|
(9005, 'RECORDING_STOCK', 105, 'PURCHASE_ITEMS', 605, 'ACTIVE', 'CONSUME', 1),
|
|
(9006, 'RECORDING_STOCK', 106, 'PURCHASE_ITEMS', 606, 'ACTIVE', 'CONSUME', 1),
|
|
(9007, 'RECORDING_STOCK', 107, 'PURCHASE_ITEMS', 607, 'ACTIVE', 'CONSUME', 1),
|
|
(9008, 'RECORDING_STOCK', 108, 'PURCHASE_ITEMS', 608, 'ACTIVE', 'CONSUME', 1),
|
|
(9009, 'RECORDING_STOCK', 109, 'PURCHASE_ITEMS', 609, 'ACTIVE', 'CONSUME', 1),
|
|
(9010, 'RECORDING_STOCK', 110, 'PURCHASE_ITEMS', 610, 'ACTIVE', 'CONSUME', 1),
|
|
(9011, 'RECORDING_STOCK', 111, 'PURCHASE_ITEMS', 611, 'ACTIVE', 'CONSUME', 1)`,
|
|
`INSERT INTO laying_transfers (id, transfer_date, effective_move_date, economic_cutoff_date, executed_at, deleted_at) VALUES
|
|
(1001, '2026-04-04', '2026-04-05', NULL, '2026-04-05 00:00:00', NULL),
|
|
(1002, '2026-05-01', '2026-05-01', NULL, '2026-05-01 00:00:00', NULL),
|
|
(1003, '2026-04-03', '2026-04-05', NULL, '2026-04-05 00:00:00', NULL),
|
|
(1004, '2026-04-03', '2026-04-05', NULL, NULL, NULL)`,
|
|
`INSERT INTO laying_transfer_targets (id, laying_transfer_id, target_project_flock_kandang_id, deleted_at) VALUES
|
|
(2001, 1001, 101, NULL),
|
|
(2002, 1002, 103, NULL),
|
|
(2003, 1003, 104, NULL),
|
|
(2004, 1004, 105, NULL)`,
|
|
fmt.Sprintf(`INSERT INTO approvals (id, approvable_type, approvable_id, action) VALUES
|
|
(3001, '%s', 1001, 'APPROVED'),
|
|
(3002, '%s', 1002, 'APPROVED'),
|
|
(3003, '%s', 1003, 'APPROVED'),
|
|
(3004, '%s', 1003, 'REJECTED'),
|
|
(3005, '%s', 1004, 'APPROVED')`,
|
|
approvalType, approvalType, approvalType, approvalType, approvalType),
|
|
)
|
|
|
|
repo := &HppV2RepositoryImpl{db: db}
|
|
|
|
periodDate := mustJakartaTime(t, "2026-04-30 00:00:00")
|
|
total, err := repo.GetRecordingStockRoutingAdjustmentCostByProjectFlockID(context.Background(), 1, periodDate)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
assertFloatEquals(t, total, 750)
|
|
|
|
earlyPeriod := mustJakartaTime(t, "2026-04-10 23:59:59")
|
|
earlyTotal, err := repo.GetRecordingStockRoutingAdjustmentCostByProjectFlockID(context.Background(), 1, earlyPeriod)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
assertFloatEquals(t, earlyTotal, 240)
|
|
}
|
|
|
|
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_stocks (
|
|
id INTEGER PRIMARY KEY,
|
|
recording_id INTEGER NULL,
|
|
product_warehouse_id INTEGER NULL,
|
|
project_flock_kandang_id INTEGER 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 purchase_items (
|
|
id INTEGER PRIMARY KEY,
|
|
product_id INTEGER NULL,
|
|
price NUMERIC(15,3) 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
|
|
)`,
|
|
`CREATE TABLE laying_transfers (
|
|
id INTEGER PRIMARY KEY,
|
|
transfer_date DATETIME NULL,
|
|
effective_move_date DATETIME NULL,
|
|
economic_cutoff_date DATETIME NULL,
|
|
executed_at DATETIME NULL,
|
|
deleted_at DATETIME NULL
|
|
)`,
|
|
`CREATE TABLE laying_transfer_targets (
|
|
id INTEGER PRIMARY KEY,
|
|
laying_transfer_id INTEGER NULL,
|
|
target_project_flock_kandang_id INTEGER NULL,
|
|
deleted_at DATETIME NULL
|
|
)`,
|
|
`CREATE TABLE approvals (
|
|
id INTEGER PRIMARY KEY,
|
|
approvable_type TEXT NULL,
|
|
approvable_id INTEGER NULL,
|
|
action 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)
|
|
}
|
|
}
|