feat: reimplement with plan hppv2 flow and logics

This commit is contained in:
Adnan Zahir
2026-04-19 14:06:42 +07:00
parent 187e497f97
commit 58fbceea24
9 changed files with 1864 additions and 52 deletions
@@ -2,6 +2,7 @@ package repository
import (
"context"
"fmt"
"time"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
@@ -10,8 +11,61 @@ import (
"gorm.io/gorm"
)
type HppV2ProjectFlockKandangContext struct {
ProjectFlockKandangID uint
ProjectFlockID uint
ProjectFlockCategory string
KandangID uint
KandangName string
LocationID uint
HouseType string
}
type HppV2UsageCostRow struct {
StockableType string
StockableID uint
SourceProductID uint
SourceProductName string
Qty float64
UnitPrice float64
TotalCost float64
FirstUsedAt time.Time
LastUsedAt time.Time
}
type HppV2AdjustmentCostRow struct {
AdjustmentID uint
ProjectFlockKandangID *uint
ProductWarehouseID uint
ProductID uint
ProductName string
WarehouseID uint
WarehouseType string
Qty float64
Price float64
GrandTotal float64
CreatedAt time.Time
}
type HppV2ExpenseCostRow struct {
ExpenseRealizationID uint
ExpenseNonstockID uint
ExpenseID uint
NonstockID uint
NonstockName string
Qty float64
Price float64
TotalCost float64
RealizationDate time.Time
}
type HppV2CostRepository interface {
GetProjectFlockKandangContext(ctx context.Context, projectFlockKandangId uint) (*HppV2ProjectFlockKandangContext, error)
GetProjectFlockKandangIDs(ctx context.Context, projectFlockId uint) ([]uint, error)
ListUsageCostRowsByProductFlags(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string, date *time.Time) ([]HppV2UsageCostRow, error)
ListAdjustmentCostRowsByProductFlags(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string, date *time.Time) ([]HppV2AdjustmentCostRow, error)
ListExpenseRealizationRowsByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time, ekspedisi bool) ([]HppV2ExpenseCostRow, error)
ListExpenseRealizationRowsByProjectFlockID(ctx context.Context, projectFlockID uint, date *time.Time, ekspedisi bool) ([]HppV2ExpenseCostRow, error)
GetFeedUsageCost(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, error)
GetTotalPopulation(ctx context.Context, projectFlockKandangIDs []uint) (float64, error)
GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, float64, error)
@@ -27,6 +81,33 @@ func NewHppV2CostRepository(db *gorm.DB) HppV2CostRepository {
return &HppV2RepositoryImpl{db: db}
}
func (r *HppV2RepositoryImpl) GetProjectFlockKandangContext(ctx context.Context, projectFlockKandangId uint) (*HppV2ProjectFlockKandangContext, error) {
var row HppV2ProjectFlockKandangContext
err := r.db.WithContext(ctx).
Table("project_flock_kandangs AS pfk").
Select(`
pfk.id AS project_flock_kandang_id,
pf.id AS project_flock_id,
pf.category AS project_flock_category,
k.id AS kandang_id,
k.name AS kandang_name,
k.location_id AS location_id,
k.house_type::text AS house_type
`).
Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id").
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
Where("pfk.id = ?", projectFlockKandangId).
Scan(&row).Error
if err != nil {
return nil, err
}
if row.ProjectFlockKandangID == 0 {
return nil, gorm.ErrRecordNotFound
}
return &row, nil
}
func (r *HppV2RepositoryImpl) GetProjectFlockKandangIDs(ctx context.Context, projectFlockId uint) ([]uint, error) {
var ids []uint
err := r.db.WithContext(ctx).
@@ -41,6 +122,241 @@ func (r *HppV2RepositoryImpl) GetProjectFlockKandangIDs(ctx context.Context, pro
return ids, nil
}
func (r *HppV2RepositoryImpl) ListUsageCostRowsByProductFlags(
ctx context.Context,
projectFlockKandangIDs []uint,
flagNames []string,
date *time.Time,
) ([]HppV2UsageCostRow, error) {
if len(projectFlockKandangIDs) == 0 || len(flagNames) == 0 {
return []HppV2UsageCostRow{}, nil
}
if date == nil {
now := time.Now()
date = &now
}
stockablePurchase := fifo.StockableKeyPurchaseItems.String()
stockableAdjustment := fifo.StockableKeyAdjustmentIn.String()
usableRecordingStock := fifo.UsableKeyRecordingStock.String()
rows := make([]HppV2UsageCostRow, 0)
err := r.db.WithContext(ctx).
Table("recordings AS r").
Select(`
sa.stockable_type AS stockable_type,
sa.stockable_id AS stockable_id,
COALESCE(pi.product_id, ast_pw.product_id, 0) AS source_product_id,
COALESCE(pi_prod.name, ast_prod.name, '') AS source_product_name,
COALESCE(SUM(sa.qty), 0) AS qty,
CASE
WHEN sa.stockable_type = ? THEN COALESCE(pi.price, 0)
WHEN sa.stockable_type = ? THEN COALESCE(ast.price, 0)
ELSE 0
END AS unit_price,
COALESCE(SUM(sa.qty * CASE
WHEN sa.stockable_type = ? THEN COALESCE(pi.price, 0)
WHEN sa.stockable_type = ? THEN COALESCE(ast.price, 0)
ELSE 0
END), 0) AS total_cost,
MIN(r.record_datetime) AS first_used_at,
MAX(r.record_datetime) AS last_used_at
`,
stockablePurchase,
stockableAdjustment,
stockablePurchase,
stockableAdjustment,
).
Joins("JOIN recording_stocks AS rs ON rs.recording_id = r.id").
Joins("JOIN product_warehouses AS pw ON pw.id = rs.product_warehouse_id").
Joins(
"JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND (sa.stockable_type = ? OR sa.stockable_type = ?) AND sa.status = ? AND sa.allocation_purpose = ?",
usableRecordingStock,
stockablePurchase,
stockableAdjustment,
entity.StockAllocationStatusActive,
entity.StockAllocationPurposeConsume,
).
Joins("LEFT JOIN purchase_items AS pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", stockablePurchase).
Joins("LEFT JOIN products AS pi_prod ON pi_prod.id = pi.product_id").
Joins("LEFT JOIN adjustment_stocks AS ast ON ast.id = sa.stockable_id AND sa.stockable_type = ?", stockableAdjustment).
Joins("LEFT JOIN product_warehouses AS ast_pw ON ast_pw.id = ast.product_warehouse_id").
Joins("LEFT JOIN products AS ast_prod ON ast_prod.id = ast_pw.product_id").
Where("r.project_flock_kandangs_id IN ?", projectFlockKandangIDs).
Where("r.record_datetime <= ?", *date).
Where("EXISTS (SELECT 1 FROM flags f WHERE f.flagable_id = pw.product_id AND f.flagable_type = ? AND f.name IN ?)", entity.FlagableTypeProduct, flagNames).
Group(`
sa.stockable_type,
sa.stockable_id,
COALESCE(pi.product_id, ast_pw.product_id, 0),
COALESCE(pi_prod.name, ast_prod.name, ''),
CASE
WHEN sa.stockable_type = '` + stockablePurchase + `' THEN COALESCE(pi.price, 0)
WHEN sa.stockable_type = '` + stockableAdjustment + `' THEN COALESCE(ast.price, 0)
ELSE 0
END
`).
Order("MIN(r.record_datetime) ASC, sa.stockable_type ASC, sa.stockable_id ASC").
Scan(&rows).Error
if err != nil {
return nil, err
}
return rows, nil
}
func (r *HppV2RepositoryImpl) ListAdjustmentCostRowsByProductFlags(
ctx context.Context,
projectFlockKandangIDs []uint,
flagNames []string,
date *time.Time,
) ([]HppV2AdjustmentCostRow, error) {
if len(projectFlockKandangIDs) == 0 || len(flagNames) == 0 {
return []HppV2AdjustmentCostRow{}, nil
}
if date == nil {
now := time.Now()
date = &now
}
rows := make([]HppV2AdjustmentCostRow, 0)
err := r.db.WithContext(ctx).
Table("adjustment_stocks AS ast").
Select(`
ast.id AS adjustment_id,
pw.project_flock_kandang_id AS project_flock_kandang_id,
ast.product_warehouse_id AS product_warehouse_id,
pw.product_id AS product_id,
p.name AS product_name,
w.id AS warehouse_id,
w.type AS warehouse_type,
COALESCE(ast.total_qty, 0) AS qty,
COALESCE(ast.price, 0) AS price,
COALESCE(ast.grand_total, 0) AS grand_total,
ast.created_at AS created_at
`).
Joins("JOIN product_warehouses AS pw ON pw.id = ast.product_warehouse_id").
Joins("JOIN products AS p ON p.id = pw.product_id").
Joins("JOIN warehouses AS w ON w.id = pw.warehouse_id").
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
Where("ast.created_at <= ?", *date).
Where("COALESCE(ast.total_qty, 0) > 0").
Where("EXISTS (SELECT 1 FROM flags f WHERE f.flagable_id = pw.product_id AND f.flagable_type = ? AND f.name IN ?)", entity.FlagableTypeProduct, flagNames).
Order("ast.created_at ASC, ast.id ASC").
Scan(&rows).Error
if err != nil {
return nil, err
}
return rows, nil
}
func (r *HppV2RepositoryImpl) ListExpenseRealizationRowsByProjectFlockKandangIDs(
ctx context.Context,
projectFlockKandangIDs []uint,
date *time.Time,
ekspedisi bool,
) ([]HppV2ExpenseCostRow, error) {
if len(projectFlockKandangIDs) == 0 {
return []HppV2ExpenseCostRow{}, nil
}
if date == nil {
now := time.Now()
date = &now
}
rows := make([]HppV2ExpenseCostRow, 0)
query := r.db.WithContext(ctx).
Table("expense_realizations AS er").
Select(`
er.id AS expense_realization_id,
en.id AS expense_nonstock_id,
e.id AS expense_id,
COALESCE(n.id, 0) AS nonstock_id,
COALESCE(n.name, '') AS nonstock_name,
COALESCE(er.qty, 0) AS qty,
COALESCE(er.price, 0) AS price,
COALESCE(er.qty, 0) * COALESCE(er.price, 0) AS total_cost,
COALESCE(e.realization_date, DATE(er.created_at)) AS realization_date
`).
Joins("JOIN expense_nonstocks AS en ON en.id = er.expense_nonstock_id").
Joins("JOIN expenses AS e ON e.id = en.expense_id").
Joins("LEFT JOIN nonstocks AS n ON n.id = en.nonstock_id").
Joins("LEFT JOIN flags AS f ON f.flagable_id = n.id AND f.flagable_type = ? AND f.name = ?", entity.FlagableTypeNonstock, utils.FlagEkspedisi).
Where("e.deleted_at IS NULL").
Where("e.category = ?", utils.ExpenseCategoryBOP).
Where("en.project_flock_kandang_id IN ?", projectFlockKandangIDs).
Where("COALESCE(e.realization_date, DATE(er.created_at)) <= ?", *date)
if ekspedisi {
query = query.Where("f.id IS NOT NULL")
} else {
query = query.Where("f.id IS NULL")
}
if err := query.
Order("COALESCE(e.realization_date, DATE(er.created_at)) ASC, er.id ASC").
Scan(&rows).Error; err != nil {
return nil, err
}
return rows, nil
}
func (r *HppV2RepositoryImpl) ListExpenseRealizationRowsByProjectFlockID(
ctx context.Context,
projectFlockID uint,
date *time.Time,
ekspedisi bool,
) ([]HppV2ExpenseCostRow, error) {
if projectFlockID == 0 {
return []HppV2ExpenseCostRow{}, nil
}
if date == nil {
now := time.Now()
date = &now
}
rows := make([]HppV2ExpenseCostRow, 0)
query := r.db.WithContext(ctx).
Table("expense_realizations AS er").
Select(`
er.id AS expense_realization_id,
en.id AS expense_nonstock_id,
e.id AS expense_id,
COALESCE(n.id, 0) AS nonstock_id,
COALESCE(n.name, '') AS nonstock_name,
COALESCE(er.qty, 0) AS qty,
COALESCE(er.price, 0) AS price,
COALESCE(er.qty, 0) * COALESCE(er.price, 0) AS total_cost,
COALESCE(e.realization_date, DATE(er.created_at)) AS realization_date
`).
Joins("JOIN expense_nonstocks AS en ON en.id = er.expense_nonstock_id").
Joins("JOIN expenses AS e ON e.id = en.expense_id").
Joins("LEFT JOIN nonstocks AS n ON n.id = en.nonstock_id").
Joins("LEFT JOIN flags AS f ON f.flagable_id = n.id AND f.flagable_type = ? AND f.name = ?", entity.FlagableTypeNonstock, utils.FlagEkspedisi).
Where("e.deleted_at IS NULL").
Where("e.category = ?", utils.ExpenseCategoryBOP).
Where("en.project_flock_kandang_id IS NULL").
Where("e.project_flock_id IS NOT NULL").
Where("e.project_flock_id::jsonb @> ?::jsonb", fmt.Sprintf("[%d]", projectFlockID)).
Where("COALESCE(e.realization_date, DATE(er.created_at)) <= ?", *date)
if ekspedisi {
query = query.Where("f.id IS NOT NULL")
} else {
query = query.Where("f.id IS NULL")
}
if err := query.
Order("COALESCE(e.realization_date, DATE(er.created_at)) ASC, er.id ASC").
Scan(&rows).Error; err != nil {
return nil, err
}
return rows, nil
}
func (r *HppV2RepositoryImpl) GetFeedUsageCost(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, error) {
if date == nil {
now := time.Now()
@@ -122,10 +438,13 @@ func (r *HppV2RepositoryImpl) GetEggProduksiPiecesAndWeightKgByProjectFlockKanda
return 0, 0, err
}
var adjustmentTotalWeight float64
var adjustmentTotals struct {
TotalQty float64
TotalWeight float64
}
adjustmentSubQuery := r.db.WithContext(ctx).
Table("recordings AS r").
Select("DISTINCT ast.id AS adjustment_id, ast.price AS price").
Select("DISTINCT ast.id AS adjustment_id, ast.total_qty AS total_qty, ast.price AS price").
Joins("JOIN recording_eggs AS re ON re.recording_id = r.id").
Joins("JOIN stock_transfer_details AS std ON std.dest_product_warehouse_id = re.product_warehouse_id").
Joins(
@@ -141,13 +460,14 @@ func (r *HppV2RepositoryImpl) GetEggProduksiPiecesAndWeightKgByProjectFlockKanda
err = r.db.WithContext(ctx).
Table("(?) AS adjustment_sources", adjustmentSubQuery).
Select("COALESCE(SUM(adjustment_sources.price), 0)").
Scan(&adjustmentTotalWeight).Error
Select("COALESCE(SUM(adjustment_sources.total_qty), 0) AS total_qty, COALESCE(SUM(adjustment_sources.price), 0) AS total_weight").
Scan(&adjustmentTotals).Error
if err != nil {
return 0, 0, err
}
totals.TotalWeightKg += adjustmentTotalWeight
totals.TotalPieces += adjustmentTotals.TotalQty
totals.TotalWeightKg += adjustmentTotals.TotalWeight
return totals.TotalPieces, totals.TotalWeightKg, nil
}
@@ -200,7 +520,7 @@ sales_kandang AS (
JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id
JOIN warehouses w ON w.id = pw.warehouse_id
WHERE mdp.delivery_date IS NOT NULL
AND mdp.delivery_date <= ?
AND mdp.delivery_date <= ?
AND UPPER(COALESCE(w.type, '')) = 'KANDANG'
AND pw.project_flock_kandang_id IN (SELECT id FROM selected_pfk)
AND EXISTS (
@@ -234,7 +554,7 @@ sales_lokasi AS (
JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id
JOIN warehouses w ON w.id = pw.warehouse_id
WHERE mdp.delivery_date IS NOT NULL
AND mdp.delivery_date <= ?
AND mdp.delivery_date <= ?
AND UPPER(COALESCE(w.type, '')) = 'LOKASI'
AND w.location_id IN (SELECT location_id FROM selected_locations)
AND EXISTS (
@@ -390,10 +710,10 @@ CROSS JOIN lokasi_rec_totals lrt
Raw(
query,
projectFlockKandangIDs,
*startDate,
*endDate,
entity.FlagableTypeProduct,
eggFlags,
*startDate,
*endDate,
entity.FlagableTypeProduct,
eggFlags,
string(utils.AdjustmentTransactionSubtypeRecordingEggIn),
@@ -0,0 +1,248 @@
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)
}
}