mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
codex: initiated changes
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MarketingDeliveryAttributionRow struct {
|
||||
MarketingDeliveryProductID uint `gorm:"column:marketing_delivery_product_id"`
|
||||
ProjectFlockKandangID uint `gorm:"column:project_flock_kandang_id"`
|
||||
ProjectFlockID uint `gorm:"column:project_flock_id"`
|
||||
ProjectFlockCategory string `gorm:"column:project_flock_category"`
|
||||
AllocatedQty float64 `gorm:"column:allocated_qty"`
|
||||
}
|
||||
|
||||
func MarketingDeliveryAttributionRowsQuery(db *gorm.DB) *gorm.DB {
|
||||
sql := `
|
||||
WITH mapped AS (
|
||||
SELECT
|
||||
sa.usable_id AS marketing_delivery_product_id,
|
||||
pc.project_flock_kandang_id AS project_flock_kandang_id,
|
||||
pfk.project_flock_id AS project_flock_id,
|
||||
pf.category AS project_flock_category,
|
||||
SUM(sa.qty) AS allocated_qty
|
||||
FROM stock_allocations sa
|
||||
JOIN project_flock_populations pfp
|
||||
ON pfp.id = sa.stockable_id
|
||||
AND sa.stockable_type = ?
|
||||
JOIN project_chickins pc ON pc.id = pfp.project_chickin_id
|
||||
JOIN project_flock_kandangs pfk ON pfk.id = pc.project_flock_kandang_id
|
||||
JOIN project_flocks pf ON pf.id = pfk.project_flock_id
|
||||
WHERE sa.usable_type = ?
|
||||
AND sa.status = ?
|
||||
AND sa.allocation_purpose = ?
|
||||
GROUP BY sa.usable_id, pc.project_flock_kandang_id, pfk.project_flock_id, pf.category
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
sa.usable_id AS marketing_delivery_product_id,
|
||||
COALESCE(re.project_flock_kandang_id, r.project_flock_kandangs_id) AS project_flock_kandang_id,
|
||||
pfk.project_flock_id AS project_flock_id,
|
||||
pf.category AS project_flock_category,
|
||||
SUM(sa.qty) AS allocated_qty
|
||||
FROM stock_allocations sa
|
||||
JOIN recording_eggs re
|
||||
ON re.id = sa.stockable_id
|
||||
AND sa.stockable_type = ?
|
||||
LEFT JOIN recordings r ON r.id = re.recording_id
|
||||
JOIN project_flock_kandangs pfk ON pfk.id = COALESCE(re.project_flock_kandang_id, r.project_flock_kandangs_id)
|
||||
JOIN project_flocks pf ON pf.id = pfk.project_flock_id
|
||||
WHERE sa.usable_type = ?
|
||||
AND sa.status = ?
|
||||
AND sa.allocation_purpose = ?
|
||||
GROUP BY sa.usable_id, COALESCE(re.project_flock_kandang_id, r.project_flock_kandangs_id), pfk.project_flock_id, pf.category
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
sa.usable_id AS marketing_delivery_product_id,
|
||||
COALESCE(rd.source_project_flock_kandang_id, r.project_flock_kandangs_id) AS project_flock_kandang_id,
|
||||
pfk.project_flock_id AS project_flock_id,
|
||||
pf.category AS project_flock_category,
|
||||
SUM(sa.qty) AS allocated_qty
|
||||
FROM stock_allocations sa
|
||||
JOIN recording_depletions rd
|
||||
ON rd.id = sa.stockable_id
|
||||
AND sa.stockable_type = ?
|
||||
LEFT JOIN recordings r ON r.id = rd.recording_id
|
||||
JOIN project_flock_kandangs pfk ON pfk.id = COALESCE(rd.source_project_flock_kandang_id, r.project_flock_kandangs_id)
|
||||
JOIN project_flocks pf ON pf.id = pfk.project_flock_id
|
||||
WHERE sa.usable_type = ?
|
||||
AND sa.status = ?
|
||||
AND sa.allocation_purpose = ?
|
||||
GROUP BY sa.usable_id, COALESCE(rd.source_project_flock_kandang_id, r.project_flock_kandangs_id), pfk.project_flock_id, pf.category
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
sa.usable_id AS marketing_delivery_product_id,
|
||||
pi.project_flock_kandang_id AS project_flock_kandang_id,
|
||||
pfk.project_flock_id AS project_flock_id,
|
||||
pf.category AS project_flock_category,
|
||||
SUM(sa.qty) AS allocated_qty
|
||||
FROM stock_allocations sa
|
||||
JOIN purchase_items pi
|
||||
ON pi.id = sa.stockable_id
|
||||
AND sa.stockable_type = ?
|
||||
JOIN project_flock_kandangs pfk ON pfk.id = pi.project_flock_kandang_id
|
||||
JOIN project_flocks pf ON pf.id = pfk.project_flock_id
|
||||
WHERE sa.usable_type = ?
|
||||
AND sa.status = ?
|
||||
AND sa.allocation_purpose = ?
|
||||
AND pi.project_flock_kandang_id IS NOT NULL
|
||||
GROUP BY sa.usable_id, pi.project_flock_kandang_id, pfk.project_flock_id, pf.category
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
sa.usable_id AS marketing_delivery_product_id,
|
||||
source_pw.project_flock_kandang_id AS project_flock_kandang_id,
|
||||
pfk.project_flock_id AS project_flock_id,
|
||||
pf.category AS project_flock_category,
|
||||
SUM(sa.qty) AS allocated_qty
|
||||
FROM stock_allocations sa
|
||||
JOIN stock_transfer_details std
|
||||
ON std.id = sa.stockable_id
|
||||
AND sa.stockable_type = ?
|
||||
JOIN product_warehouses source_pw ON source_pw.id = std.source_product_warehouse_id
|
||||
JOIN project_flock_kandangs pfk ON pfk.id = source_pw.project_flock_kandang_id
|
||||
JOIN project_flocks pf ON pf.id = pfk.project_flock_id
|
||||
WHERE sa.usable_type = ?
|
||||
AND sa.status = ?
|
||||
AND sa.allocation_purpose = ?
|
||||
AND source_pw.project_flock_kandang_id IS NOT NULL
|
||||
GROUP BY sa.usable_id, source_pw.project_flock_kandang_id, pfk.project_flock_id, pf.category
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
sa.usable_id AS marketing_delivery_product_id,
|
||||
ltt.target_project_flock_kandang_id AS project_flock_kandang_id,
|
||||
pfk.project_flock_id AS project_flock_id,
|
||||
pf.category AS project_flock_category,
|
||||
SUM(sa.qty) AS allocated_qty
|
||||
FROM stock_allocations sa
|
||||
JOIN laying_transfer_targets ltt
|
||||
ON ltt.id = sa.stockable_id
|
||||
AND sa.stockable_type = ?
|
||||
JOIN project_flock_kandangs pfk ON pfk.id = ltt.target_project_flock_kandang_id
|
||||
JOIN project_flocks pf ON pf.id = pfk.project_flock_id
|
||||
WHERE sa.usable_type = ?
|
||||
AND sa.status = ?
|
||||
AND sa.allocation_purpose = ?
|
||||
GROUP BY sa.usable_id, ltt.target_project_flock_kandang_id, pfk.project_flock_id, pf.category
|
||||
)
|
||||
SELECT
|
||||
src.marketing_delivery_product_id,
|
||||
src.project_flock_kandang_id,
|
||||
src.project_flock_id,
|
||||
src.project_flock_category,
|
||||
SUM(src.allocated_qty) AS allocated_qty
|
||||
FROM (
|
||||
SELECT
|
||||
mapped.marketing_delivery_product_id,
|
||||
mapped.project_flock_kandang_id,
|
||||
mapped.project_flock_id,
|
||||
mapped.project_flock_category,
|
||||
mapped.allocated_qty
|
||||
FROM mapped
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
mdp.id AS marketing_delivery_product_id,
|
||||
pw.project_flock_kandang_id AS project_flock_kandang_id,
|
||||
pfk.project_flock_id AS project_flock_id,
|
||||
pf.category AS project_flock_category,
|
||||
COALESCE(mdp.usage_qty, 0) AS allocated_qty
|
||||
FROM marketing_delivery_products mdp
|
||||
JOIN marketing_products mp ON mp.id = mdp.marketing_product_id
|
||||
JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id
|
||||
JOIN project_flock_kandangs pfk ON pfk.id = pw.project_flock_kandang_id
|
||||
JOIN project_flocks pf ON pf.id = pfk.project_flock_id
|
||||
LEFT JOIN mapped ON mapped.marketing_delivery_product_id = mdp.id
|
||||
WHERE mapped.marketing_delivery_product_id IS NULL
|
||||
AND pw.project_flock_kandang_id IS NOT NULL
|
||||
AND COALESCE(mdp.usage_qty, 0) > 0
|
||||
) src
|
||||
GROUP BY
|
||||
src.marketing_delivery_product_id,
|
||||
src.project_flock_kandang_id,
|
||||
src.project_flock_id,
|
||||
src.project_flock_category
|
||||
`
|
||||
|
||||
return db.Raw(
|
||||
sql,
|
||||
fifo.StockableKeyProjectFlockPopulation.String(),
|
||||
fifo.UsableKeyMarketingDelivery.String(),
|
||||
entity.StockAllocationStatusActive,
|
||||
entity.StockAllocationPurposeConsume,
|
||||
fifo.StockableKeyRecordingEgg.String(),
|
||||
fifo.UsableKeyMarketingDelivery.String(),
|
||||
entity.StockAllocationStatusActive,
|
||||
entity.StockAllocationPurposeConsume,
|
||||
fifo.StockableKeyRecordingDepletion.String(),
|
||||
fifo.UsableKeyMarketingDelivery.String(),
|
||||
entity.StockAllocationStatusActive,
|
||||
entity.StockAllocationPurposeConsume,
|
||||
fifo.StockableKeyPurchaseItems.String(),
|
||||
fifo.UsableKeyMarketingDelivery.String(),
|
||||
entity.StockAllocationStatusActive,
|
||||
entity.StockAllocationPurposeConsume,
|
||||
fifo.StockableKeyStockTransferIn.String(),
|
||||
fifo.UsableKeyMarketingDelivery.String(),
|
||||
entity.StockAllocationStatusActive,
|
||||
entity.StockAllocationPurposeConsume,
|
||||
fifo.StockableKeyTransferToLayingIn.String(),
|
||||
fifo.UsableKeyMarketingDelivery.String(),
|
||||
entity.StockAllocationStatusActive,
|
||||
entity.StockAllocationPurposeConsume,
|
||||
)
|
||||
}
|
||||
|
||||
func MarketingDeliverySingleAttributionQuery(db *gorm.DB) *gorm.DB {
|
||||
return db.
|
||||
Table("(?) AS mda", MarketingDeliveryAttributionRowsQuery(db)).
|
||||
Select(`
|
||||
mda.marketing_delivery_product_id,
|
||||
CASE
|
||||
WHEN COUNT(DISTINCT mda.project_flock_kandang_id) = 1 THEN MIN(mda.project_flock_kandang_id)
|
||||
ELSE NULL
|
||||
END AS attributed_project_flock_kandang_id
|
||||
`).
|
||||
Group("mda.marketing_delivery_product_id")
|
||||
}
|
||||
|
||||
func MarketingDeliveryAttributionFilterSQL(column string) string {
|
||||
return fmt.Sprintf("EXISTS (SELECT 1 FROM (?) AS mda WHERE mda.marketing_delivery_product_id = %s)", column)
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestMarketingDeliveryAttributionRowsQueryIncludesMappedAndFallbackRows(t *testing.T) {
|
||||
db := setupMarketingAttributionTestDB(t)
|
||||
|
||||
statements := []string{
|
||||
`INSERT INTO project_flocks (id, category) VALUES (1, 'LAYING')`,
|
||||
`INSERT INTO project_flock_kandangs (id, project_flock_id) VALUES (101, 1), (102, 1)`,
|
||||
`INSERT INTO project_chickins (id, project_flock_kandang_id) VALUES (201, 101), (202, 102)`,
|
||||
`INSERT INTO project_flock_populations (id, project_chickin_id) VALUES (301, 201), (302, 202)`,
|
||||
`INSERT INTO product_warehouses (id, project_flock_kandang_id) VALUES (401, NULL), (402, 101)`,
|
||||
`INSERT INTO marketing_products (id, product_warehouse_id) VALUES (501, 401), (502, 402), (503, 401)`,
|
||||
`INSERT INTO marketing_delivery_products (id, marketing_product_id, usage_qty) VALUES (601, 501, 100), (602, 502, 25), (603, 503, 12)`,
|
||||
`INSERT INTO recording_eggs (id, recording_id, project_flock_kandang_id) VALUES (701, NULL, 101)`,
|
||||
`INSERT INTO stock_allocations (id, product_warehouse_id, stockable_type, stockable_id, usable_type, usable_id, qty, status, allocation_purpose) VALUES
|
||||
(1, 401, 'PROJECT_FLOCK_POPULATION', 301, 'MARKETING_DELIVERY', 601, 60, 'ACTIVE', 'CONSUME'),
|
||||
(2, 401, 'PROJECT_FLOCK_POPULATION', 302, 'MARKETING_DELIVERY', 601, 40, 'ACTIVE', 'CONSUME'),
|
||||
(3, 401, 'RECORDING_EGG', 701, 'MARKETING_DELIVERY', 603, 12, 'ACTIVE', 'CONSUME')`,
|
||||
}
|
||||
for _, stmt := range statements {
|
||||
if err := db.Exec(stmt).Error; err != nil {
|
||||
t.Fatalf("failed seeding fixtures: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var rows []MarketingDeliveryAttributionRow
|
||||
if err := db.Table("(?) AS mda", MarketingDeliveryAttributionRowsQuery(db)).
|
||||
Order("mda.marketing_delivery_product_id ASC, mda.project_flock_kandang_id ASC").
|
||||
Scan(&rows).Error; err != nil {
|
||||
t.Fatalf("failed scanning attribution rows: %v", err)
|
||||
}
|
||||
|
||||
if len(rows) != 4 {
|
||||
t.Fatalf("expected 4 attribution rows, got %d", len(rows))
|
||||
}
|
||||
if rows[0].MarketingDeliveryProductID != 601 || rows[0].ProjectFlockKandangID != 101 || rows[0].AllocatedQty != 60 {
|
||||
t.Fatalf("unexpected first attribution row: %+v", rows[0])
|
||||
}
|
||||
if rows[1].MarketingDeliveryProductID != 601 || rows[1].ProjectFlockKandangID != 102 || rows[1].AllocatedQty != 40 {
|
||||
t.Fatalf("unexpected second attribution row: %+v", rows[1])
|
||||
}
|
||||
if rows[2].MarketingDeliveryProductID != 602 || rows[2].ProjectFlockKandangID != 101 || rows[2].AllocatedQty != 25 {
|
||||
t.Fatalf("unexpected fallback attribution row: %+v", rows[2])
|
||||
}
|
||||
if rows[3].MarketingDeliveryProductID != 603 || rows[3].ProjectFlockKandangID != 101 || rows[3].AllocatedQty != 12 {
|
||||
t.Fatalf("unexpected egg attribution row: %+v", rows[3])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarketingDeliverySingleAttributionQueryOnlyReturnsSingleSourceRows(t *testing.T) {
|
||||
db := setupMarketingAttributionTestDB(t)
|
||||
|
||||
statements := []string{
|
||||
`INSERT INTO project_flocks (id, category) VALUES (1, 'LAYING')`,
|
||||
`INSERT INTO project_flock_kandangs (id, project_flock_id) VALUES (101, 1), (102, 1)`,
|
||||
`INSERT INTO project_chickins (id, project_flock_kandang_id) VALUES (201, 101), (202, 102)`,
|
||||
`INSERT INTO project_flock_populations (id, project_chickin_id) VALUES (301, 201), (302, 202)`,
|
||||
`INSERT INTO product_warehouses (id, project_flock_kandang_id) VALUES (401, NULL), (402, 101)`,
|
||||
`INSERT INTO marketing_products (id, product_warehouse_id) VALUES (501, 401), (502, 402), (503, 401)`,
|
||||
`INSERT INTO marketing_delivery_products (id, marketing_product_id, usage_qty) VALUES (601, 501, 100), (602, 502, 25), (603, 503, 12)`,
|
||||
`INSERT INTO recording_eggs (id, recording_id, project_flock_kandang_id) VALUES (701, NULL, 101)`,
|
||||
`INSERT INTO stock_allocations (id, product_warehouse_id, stockable_type, stockable_id, usable_type, usable_id, qty, status, allocation_purpose) VALUES
|
||||
(1, 401, 'PROJECT_FLOCK_POPULATION', 301, 'MARKETING_DELIVERY', 601, 60, 'ACTIVE', 'CONSUME'),
|
||||
(2, 401, 'PROJECT_FLOCK_POPULATION', 302, 'MARKETING_DELIVERY', 601, 40, 'ACTIVE', 'CONSUME'),
|
||||
(3, 401, 'RECORDING_EGG', 701, 'MARKETING_DELIVERY', 603, 12, 'ACTIVE', 'CONSUME')`,
|
||||
}
|
||||
for _, stmt := range statements {
|
||||
if err := db.Exec(stmt).Error; err != nil {
|
||||
t.Fatalf("failed seeding fixtures: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type singleRow struct {
|
||||
MarketingDeliveryProductID uint `gorm:"column:marketing_delivery_product_id"`
|
||||
AttributedProjectFlockKandangID *uint `gorm:"column:attributed_project_flock_kandang_id"`
|
||||
}
|
||||
|
||||
var rows []singleRow
|
||||
if err := db.Table("(?) AS mda", MarketingDeliverySingleAttributionQuery(db)).
|
||||
Order("mda.marketing_delivery_product_id ASC").
|
||||
Scan(&rows).Error; err != nil {
|
||||
t.Fatalf("failed scanning single attribution rows: %v", err)
|
||||
}
|
||||
|
||||
if len(rows) != 3 {
|
||||
t.Fatalf("expected 3 rows, got %d", len(rows))
|
||||
}
|
||||
if rows[0].MarketingDeliveryProductID != 601 || rows[0].AttributedProjectFlockKandangID != nil {
|
||||
t.Fatalf("expected pooled delivery 601 to have nil single attribution, got %+v", rows[0])
|
||||
}
|
||||
if rows[1].MarketingDeliveryProductID != 602 || rows[1].AttributedProjectFlockKandangID == nil || *rows[1].AttributedProjectFlockKandangID != 101 {
|
||||
t.Fatalf("expected fallback delivery 602 to map to kandang 101, got %+v", rows[1])
|
||||
}
|
||||
if rows[2].MarketingDeliveryProductID != 603 || rows[2].AttributedProjectFlockKandangID == nil || *rows[2].AttributedProjectFlockKandangID != 101 {
|
||||
t.Fatalf("expected egg delivery 603 to map to kandang 101, got %+v", rows[2])
|
||||
}
|
||||
}
|
||||
|
||||
func setupMarketingAttributionTestDB(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 stock_allocations (
|
||||
id INTEGER PRIMARY KEY,
|
||||
product_warehouse_id INTEGER,
|
||||
stockable_type TEXT,
|
||||
stockable_id INTEGER,
|
||||
usable_type TEXT,
|
||||
usable_id INTEGER,
|
||||
qty NUMERIC(15,3),
|
||||
status TEXT,
|
||||
allocation_purpose TEXT
|
||||
)`,
|
||||
`CREATE TABLE project_flock_populations (id INTEGER PRIMARY KEY, project_chickin_id INTEGER)`,
|
||||
`CREATE TABLE project_chickins (id INTEGER PRIMARY KEY, project_flock_kandang_id INTEGER)`,
|
||||
`CREATE TABLE project_flock_kandangs (id INTEGER PRIMARY KEY, project_flock_id INTEGER)`,
|
||||
`CREATE TABLE project_flocks (id INTEGER PRIMARY KEY, category TEXT)`,
|
||||
`CREATE TABLE marketing_delivery_products (id INTEGER PRIMARY KEY, marketing_product_id INTEGER, usage_qty NUMERIC(15,3))`,
|
||||
`CREATE TABLE marketing_products (id INTEGER PRIMARY KEY, product_warehouse_id INTEGER)`,
|
||||
`CREATE TABLE product_warehouses (id INTEGER PRIMARY KEY, project_flock_kandang_id INTEGER NULL)`,
|
||||
`CREATE TABLE recording_eggs (id INTEGER PRIMARY KEY, recording_id INTEGER, project_flock_kandang_id INTEGER NULL)`,
|
||||
`CREATE TABLE recordings (id INTEGER PRIMARY KEY, project_flock_kandangs_id INTEGER NULL)`,
|
||||
`CREATE TABLE recording_depletions (id INTEGER PRIMARY KEY, recording_id INTEGER, source_project_flock_kandang_id INTEGER NULL)`,
|
||||
`CREATE TABLE purchase_items (id INTEGER PRIMARY KEY, project_flock_kandang_id INTEGER NULL)`,
|
||||
`CREATE TABLE stock_transfer_details (id INTEGER PRIMARY KEY, source_product_warehouse_id INTEGER NULL)`,
|
||||
`CREATE TABLE laying_transfer_targets (id INTEGER PRIMARY KEY, target_project_flock_kandang_id INTEGER 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