@things-factory/warehouse-base 4.3.686 → 4.3.689

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1513,251 +1513,202 @@ async function massageInventoryPalletSummary(tx, params, bizplace, context) {
1513
1513
  let fromDate = params.filters.find(data => data.name === 'fromDate');
1514
1514
  let toDate = params.filters.find(data => data.name === 'toDate');
1515
1515
  let hasTransactionOrBalanceFilter = params.filters.find(data => data.name === 'hasTransactionOrBalance');
1516
- let hasTransactionOrBalanceQuery = '';
1516
+ let hasTransactionOrBalanceQuery = 'TRUE';
1517
1517
  if (hasTransactionOrBalanceFilter && hasTransactionOrBalanceFilter.value) {
1518
- hasTransactionOrBalanceQuery = 'and (c.opening_qty > 0 or c.total_in_qty > 0 or c.adjustment_qty > 0)';
1518
+ hasTransactionOrBalanceQuery = '(merged.opening_qty > 0 or merged.total_in_qty > 0 or merged.adjustment_qty > 0)';
1519
1519
  }
1520
+ // Build a slimmer history table for pallet summary to minimize scans
1520
1521
  await tx.query(`
1521
- create temp table temp_inventory_pallet_summary ON COMMIT DROP as
1522
- with params as (
1523
- select $1::timestamp as t1, $2::timestamp as t2
1524
- ),
1525
- base as (
1526
- select
1527
- invh.*,
1528
- prd.sku as product_sku,
1529
- prd.name as product_name,
1530
- prd.description as product_description,
1531
- prd.type as product_type
1532
- from temp_inv_history invh
1533
- join temp_products prd on prd.id = invh.product_id
1534
- ),
1535
-
1536
- /* ---- Opening snapshot as-of t1 (FIX) ----
1537
- For each pallet, take the last row <= t1; if status='STORED',
1538
- count it toward that row's product_id opening.
1539
- */
1540
- opening_last_asof as (
1541
- select *
1542
- from (
1543
- select h.*,
1544
- row_number() over (partition by h.pallet_id order by h.created_at desc, h.seq desc) as rn
1545
- from temp_inv_history h
1546
- join params p on true
1547
- where h.created_at <= p.t1
1548
- ) x
1549
- where rn = 1 and status = 'STORED'
1550
- ),
1551
- opening_agg as (
1552
- select
1553
- p.sku as product_sku,
1554
- p.id as product_id,
1555
- p.name as product_name,
1556
- p.description as product_description,
1557
- p.type as product_type,
1558
- count(*)::int as opening_qty
1559
- from opening_last_asof o
1560
- join temp_products p on p.id = o.product_id
1561
- group by p.sku, p.id, p.name, p.description, p.type
1562
- ),
1563
-
1564
- /* ---- Arrivals inside window: first arrival-like row per pallet (your rule) ---- */
1565
- invIn as (
1566
- select b.*,
1567
- row_number() over (partition by b.pallet_id order by b.created_at, b.seq) as rn
1568
- from base b
1569
- join params p on true
1570
- where b.transaction_type in ('UNLOADING','NEW','CANCEL_ORDER')
1571
- and b.created_at >= p.t1
1572
- ),
1573
- invIn_first as (
1574
- select
1575
- pallet_id, seq, status, transaction_type,
1576
- product_sku, product_id, product_name, product_description, product_type,
1577
- inventory_history_id, packing_type, packing_size, qty, opening_qty, created_at
1578
- from invIn
1579
- where rn = 1
1580
- ),
1581
-
1582
- /* ---- TERMINATED reps (incl. ADJUSTMENT terminations), using your runs logic ---- */
1583
- status_runs as (
1584
- select
1585
- endData.pallet_id,
1586
- min(endData.created_at) as started_at,
1587
- max(endData.created_at) as ended_at,
1588
- min(endData.seq) as min_seq,
1589
- max(endData.seq) as max_seq,
1590
- endData.status
1591
- from (
1592
- select startData.*,
1593
- sum(startflag) over (partition by startData.pallet_id order by startData.seq) as grp
1594
- from (
1595
- select s.*,
1596
- case when lag(s.status) over (partition by s.pallet_id order by s.seq) = s.status then 0 else 1 end as startflag
1597
- from temp_inv_history s
1598
- ) startData
1599
- ) endData
1600
- group by endData.pallet_id, endData.status, endData.grp
1601
- ),
1602
- invOut_base as (
1603
- select b.*, r.started_at
1604
- from base b
1605
- join status_runs r
1606
- on r.pallet_id = b.pallet_id
1607
- and r.min_seq <= b.seq
1608
- and r.max_seq >= b.seq
1609
- where b.status = 'TERMINATED'
1610
- and b.transaction_type in ('TERMINATED','ADJUSTMENT')
1611
- ),
1612
-
1613
- /* ---- Transfer pairing (seq-gap tolerant & tolerant txn tags) ---- */
1614
- /* STORED side in-window; pair to nearest prior TERMINATED (from raw history), different product */
1615
- adj_stored AS (
1616
- SELECT b.*
1617
- FROM base b
1618
- JOIN params p ON TRUE
1619
- WHERE b.status = 'STORED'
1620
- AND b.created_at BETWEEN p.t1 AND p.t2
1621
- ),
1622
- adj_pairs AS (
1623
- SELECT
1624
- s.pallet_id,
1625
- s.seq AS stored_seq,
1626
- t.seq AS terminated_seq,
1627
- s.product_id AS new_product_id,
1628
- t.product_id AS old_product_id,
1629
- s.inventory_history_id AS stored_inventory_history_id,
1630
- t.inventory_history_id AS terminated_inventory_history_id,
1631
- s.created_at AS stored_at,
1632
- t.created_at AS terminated_at,
1633
- s.transaction_type AS stored_txn,
1634
- t.transaction_type AS terminated_txn
1635
- FROM adj_stored s
1636
- JOIN LATERAL (
1637
- SELECT t.*
1638
- FROM temp_inv_history t -- use raw history (old product may be outside temp_products)
1639
- WHERE t.pallet_id = s.pallet_id
1640
- AND t.seq < s.seq
1641
- AND t.status = 'TERMINATED'
1642
- ORDER BY t.seq DESC
1643
- LIMIT 1
1644
- ) t ON TRUE
1645
- WHERE t.product_id IS DISTINCT FROM s.product_id
1646
- AND (s.transaction_type='ADJUSTMENT' OR t.transaction_type='ADJUSTMENT')
1647
- ),
1648
- adj_pairs_terminated_keys AS (
1649
- SELECT pallet_id, terminated_seq AS seq
1650
- FROM adj_pairs
1651
- ),
1522
+ create temp table temp_inv_history_ps on commit drop as (
1523
+ select pallet_id, product_id, seq, status, transaction_type, created_at
1524
+ from temp_inv_history
1525
+ where (transaction_type in ('UNLOADING','NEW','CANCEL_ORDER','ADJUSTMENT')
1526
+ or status in ('STORED','TERMINATED'))
1527
+ and created_at <= $1::timestamp
1528
+ );
1529
+ `, [toDate.value]);
1530
+ await tx.query(`
1531
+ CREATE INDEX IF NOT EXISTS idx_tihps_created_at ON temp_inv_history_ps (created_at);
1532
+ CREATE INDEX IF NOT EXISTS idx_tihps_pallet_seq ON temp_inv_history_ps (pallet_id, seq);
1533
+ CREATE INDEX IF NOT EXISTS idx_tihps_pallet_product_seq ON temp_inv_history_ps (pallet_id, product_id, seq);
1534
+ CREATE INDEX IF NOT EXISTS idx_tihps_product ON temp_inv_history_ps (product_id);
1535
+ CREATE INDEX IF NOT EXISTS idx_tihps_product_created ON temp_inv_history_ps (product_id, created_at);
1536
+ CREATE INDEX IF NOT EXISTS idx_tihps_status ON temp_inv_history_ps (status);
1537
+ CREATE INDEX IF NOT EXISTS idx_tihps_txn ON temp_inv_history_ps (transaction_type);
1538
+ ANALYZE temp_inv_history_ps;
1539
+ `);
1540
+ await tx.query(`
1541
+ -- Params:
1542
+ -- $1 = t1 ::timestamp
1543
+ -- $2 = t2 ::timestamp
1652
1544
 
1653
- /* Representative TERMINATED per (pallet, product), EXCLUDING those used by a pair (avoid double-count) */
1654
- invOut_rep AS (
1655
- SELECT *
1656
- FROM (
1657
- SELECT iob.*,
1658
- row_number() over (partition by iob.pallet_id, iob.product_id order by iob.seq desc) as rn
1659
- FROM invOut_base iob
1660
- LEFT JOIN adj_pairs_terminated_keys k
1661
- ON k.pallet_id = iob.pallet_id
1662
- AND k.seq = iob.seq
1663
- WHERE k.pallet_id IS NULL
1664
- ) z
1665
- WHERE rn = 1
1666
- ),
1545
+ CREATE TEMP TABLE temp_inventory_pallet_summary ON COMMIT DROP AS
1546
+ WITH
1547
+ params AS (SELECT $1::timestamp AS t1, $2::timestamp AS t2),
1667
1548
 
1668
- /* Materialize both sides of the paired transfer */
1669
- adj_pairs_stored_rows AS (
1670
- SELECT
1671
- b.pallet_id, b.seq, b.status, b.transaction_type,
1672
- b.product_sku, b.product_id, b.product_name, b.product_description, b.product_type,
1673
- b.inventory_history_id, b.packing_type, b.packing_size, b.qty, b.opening_qty, b.created_at
1674
- FROM base b
1675
- JOIN adj_pairs ap
1676
- ON ap.pallet_id = b.pallet_id
1677
- AND ap.stored_seq = b.seq
1678
- ),
1679
- adj_pairs_terminated_rows AS (
1680
- SELECT
1681
- b.pallet_id, b.seq, b.status, b.transaction_type,
1682
- b.product_sku, b.product_id, b.product_name, b.product_description, b.product_type,
1683
- b.inventory_history_id, b.packing_type, b.packing_size, b.qty, b.opening_qty, b.created_at
1684
- FROM base b
1685
- JOIN adj_pairs ap
1686
- ON ap.pallet_id = b.pallet_id
1687
- AND ap.terminated_seq = b.seq
1688
- ),
1549
+ /* Pre-filtered slices (minimal cols) */
1550
+ rows_le_t1 AS (
1551
+ SELECT pallet_id, product_id, seq, status
1552
+ FROM temp_inv_history_ps
1553
+ WHERE created_at <= (SELECT t1 FROM params)
1554
+ ),
1555
+ rows_win AS (
1556
+ SELECT pallet_id, product_id, seq, status, transaction_type
1557
+ FROM temp_inv_history_ps
1558
+ WHERE created_at BETWEEN (SELECT t1 FROM params) AND (SELECT t2 FROM params)
1559
+ ),
1560
+ rows_le_t2 AS (
1561
+ SELECT pallet_id, product_id, seq, status, transaction_type
1562
+ FROM temp_inv_history_ps
1563
+ WHERE created_at <= (SELECT t2 FROM params)
1564
+ ),
1689
1565
 
1690
- /* ---- Unified stream for window movement ---- */
1691
- invHistory as (
1692
- select * from invIn_first
1693
- union all
1694
- select
1695
- pallet_id, seq, status, transaction_type,
1696
- product_sku, product_id, product_name, product_description, product_type,
1697
- inventory_history_id, packing_type, packing_size, qty, opening_qty, created_at
1698
- from invOut_rep
1699
- union all
1700
- select * from adj_pairs_stored_rows
1701
- union all
1702
- select * from adj_pairs_terminated_rows
1703
- ),
1566
+ /* Opening (<= t1): last-by-seq per pallet, must be STORED */
1567
+ opening_last_asof AS (
1568
+ SELECT DISTINCT ON (h.pallet_id) h.pallet_id, h.product_id, h.seq, h.status
1569
+ FROM rows_le_t1 h
1570
+ ORDER BY h.pallet_id, h.seq DESC
1571
+ ),
1572
+ opening_anchor AS (
1573
+ SELECT pallet_id, product_id
1574
+ FROM opening_last_asof
1575
+ WHERE status = 'STORED'
1576
+ ),
1704
1577
 
1705
- /* ---- Movement aggregation only (no opening here) ---- */
1706
- movement_agg as (
1707
- select
1708
- ih.product_sku,
1709
- ih.product_id,
1710
- ih.product_name,
1711
- ih.product_description,
1712
- ih.product_type,
1713
-
1714
- sum(case when ih.created_at >= (select t1 from params)
1715
- and ih.created_at <= (select t2 from params) then
1716
- case when ih.transaction_type in ('UNLOADING','NEW','CANCEL_ORDER') then 1 else 0 end
1717
- else 0 end)::int as total_in_qty,
1578
+ /* Closing (<= t2): last STORED per (pallet, product) by seq + validity */
1579
+ closing_last_stored AS (
1580
+ SELECT DISTINCT ON (h.pallet_id, h.product_id) h.pallet_id, h.product_id, h.seq
1581
+ FROM rows_le_t2 h
1582
+ WHERE h.status = 'STORED'
1583
+ ORDER BY h.pallet_id, h.product_id, h.seq DESC
1584
+ ),
1585
+ last_term_by_pair AS (
1586
+ SELECT DISTINCT ON (h.pallet_id, h.product_id) h.pallet_id, h.product_id, h.seq AS last_term_seq
1587
+ FROM rows_le_t2 h
1588
+ WHERE h.status = 'TERMINATED'
1589
+ ORDER BY h.pallet_id, h.product_id, h.seq DESC
1590
+ ),
1591
+ inbound_after_term AS (
1592
+ SELECT cls.pallet_id, cls.product_id
1593
+ FROM closing_last_stored cls
1594
+ LEFT JOIN last_term_by_pair lt
1595
+ ON lt.pallet_id = cls.pallet_id AND lt.product_id = cls.product_id
1596
+ WHERE EXISTS (
1597
+ SELECT 1
1598
+ FROM rows_le_t2 h
1599
+ WHERE h.pallet_id = cls.pallet_id
1600
+ AND h.product_id = cls.product_id
1601
+ AND h.transaction_type IN ('UNLOADING','NEW','CANCEL_ORDER')
1602
+ AND h.seq <= cls.seq
1603
+ AND (lt.last_term_seq IS NULL OR h.seq > lt.last_term_seq)
1604
+ )
1605
+ ),
1606
+ valid_closing AS (
1607
+ SELECT cls.pallet_id, cls.product_id
1608
+ FROM closing_last_stored cls
1609
+ LEFT JOIN inbound_after_term ia
1610
+ ON ia.pallet_id = cls.pallet_id AND ia.product_id = cls.product_id
1611
+ LEFT JOIN last_term_by_pair lt
1612
+ ON lt.pallet_id = cls.pallet_id AND lt.product_id = cls.product_id
1613
+ WHERE lt.last_term_seq IS NULL OR ia.pallet_id IS NOT NULL
1614
+ ),
1615
+ closing_anchor AS (
1616
+ SELECT pallet_id, product_id FROM valid_closing
1617
+ ),
1718
1618
 
1719
- sum(case when ih.created_at >= (select t1 from params)
1720
- and ih.created_at <= (select t2 from params) then
1721
- case when ih.status = 'TERMINATED'
1722
- and ih.transaction_type <> 'ADJUSTMENT' then -1 else 0 end
1723
- else 0 end)::int as total_out_qty,
1619
+ /* Movement presence in window */
1620
+ inbound_pairs AS (
1621
+ SELECT pallet_id, product_id
1622
+ FROM rows_win
1623
+ WHERE transaction_type IN ('UNLOADING','NEW','CANCEL_ORDER')
1624
+ GROUP BY pallet_id, product_id
1625
+ ),
1626
+ terminated_pairs AS (
1627
+ SELECT pallet_id, product_id
1628
+ FROM rows_win
1629
+ WHERE status = 'TERMINATED' AND transaction_type <> 'ADJUSTMENT'
1630
+ GROUP BY pallet_id, product_id
1631
+ ),
1632
+ adjustment_last AS (
1633
+ SELECT x.pallet_id, x.product_id,
1634
+ CASE WHEN x.status = 'TERMINATED' THEN -1
1635
+ WHEN x.status = 'STORED' THEN 1
1636
+ ELSE 0 END AS adj_sign
1637
+ FROM (
1638
+ SELECT a.pallet_id, a.product_id, MAX(a.seq) AS max_seq
1639
+ FROM rows_win a
1640
+ WHERE a.transaction_type = 'ADJUSTMENT'
1641
+ GROUP BY a.pallet_id, a.product_id
1642
+ ) s
1643
+ JOIN rows_win x
1644
+ ON x.pallet_id = s.pallet_id AND x.product_id = s.product_id AND x.seq = s.max_seq
1645
+ ),
1724
1646
 
1725
- sum(case when ih.created_at >= (select t1 from params)
1726
- and ih.created_at <= (select t2 from params) then
1727
- case when ih.status = 'TERMINATED' and ih.transaction_type = 'ADJUSTMENT' then -1
1728
- when ih.status = 'STORED' and ih.transaction_type = 'ADJUSTMENT' then 1
1729
- else 0 end
1730
- else 0 end)::int as adjustment_qty
1647
+ /* Union anchor (opening OR any movement OR valid closing) */
1648
+ anchor_union AS (
1649
+ SELECT * FROM opening_anchor
1650
+ UNION ALL
1651
+ SELECT * FROM inbound_pairs
1652
+ UNION ALL
1653
+ SELECT * FROM terminated_pairs
1654
+ UNION ALL
1655
+ SELECT * FROM closing_anchor
1656
+ ),
1657
+ anchor_distinct AS (
1658
+ SELECT DISTINCT pallet_id, product_id FROM anchor_union
1659
+ ),
1731
1660
 
1732
- from invHistory ih
1733
- group by ih.product_sku, ih.product_id, ih.product_name, ih.product_description, ih.product_type
1734
- ),
1661
+ /* Opening aggregation (anchored) */
1662
+ opening_agg AS (
1663
+ SELECT ol.product_id,
1664
+ COUNT(*) FILTER (WHERE ol.status = 'STORED')::int AS opening_qty
1665
+ FROM opening_last_asof ol
1666
+ JOIN anchor_distinct au ON au.pallet_id = ol.pallet_id AND au.product_id = ol.product_id
1667
+ GROUP BY ol.product_id
1668
+ ),
1735
1669
 
1736
- /* ---- Combine opening snapshot + window movement ---- */
1737
- combined as (
1738
- select
1739
- coalesce(o.product_sku, m.product_sku) as product_sku,
1740
- coalesce(o.product_id, m.product_id) as product_id,
1741
- coalesce(o.product_name,m.product_name) as product_name,
1742
- coalesce(o.product_description,m.product_description) as product_description,
1743
- coalesce(o.product_type,m.product_type) as product_type,
1670
+ /* Movement aggregation (anchored) */
1671
+ movement_agg AS (
1672
+ SELECT
1673
+ COALESCE(inb.product_id, term.product_id, adj.product_id) AS product_id,
1674
+ SUM((inb.pallet_id IS NOT NULL)::int) AS total_in_qty,
1675
+ SUM((term.pallet_id IS NOT NULL)::int) AS total_out_qty,
1676
+ COALESCE(SUM(adj.adj_sign), 0)::int AS adjustment_qty
1677
+ FROM anchor_distinct au
1678
+ LEFT JOIN inbound_pairs inb ON inb.pallet_id = au.pallet_id AND inb.product_id = au.product_id
1679
+ LEFT JOIN terminated_pairs term ON term.pallet_id = au.pallet_id AND term.product_id = au.product_id
1680
+ LEFT JOIN adjustment_last adj ON adj.pallet_id = au.pallet_id AND adj.product_id = au.product_id
1681
+ GROUP BY COALESCE(inb.product_id, term.product_id, adj.product_id)
1682
+ ),
1744
1683
 
1745
- coalesce(o.opening_qty, 0) as opening_qty,
1746
- coalesce(m.total_in_qty, 0) as total_in_qty,
1747
- coalesce(m.total_out_qty, 0) as total_out_qty,
1748
- coalesce(m.adjustment_qty, 0) as adjustment_qty
1749
- from opening_agg o
1750
- full outer join movement_agg m
1751
- on m.product_id = o.product_id
1752
- )
1684
+ merged AS (
1685
+ SELECT
1686
+ COALESCE(o.product_id, m.product_id) AS product_id,
1687
+ COALESCE(o.opening_qty, 0) AS opening_qty,
1688
+ COALESCE(m.total_in_qty, 0) AS total_in_qty,
1689
+ COALESCE(m.total_out_qty, 0) AS total_out_qty,
1690
+ COALESCE(m.adjustment_qty, 0) AS adjustment_qty
1691
+ FROM opening_agg o
1692
+ FULL JOIN movement_agg m ON m.product_id = o.product_id
1693
+ )
1753
1694
 
1754
- select
1755
- c.*,
1756
- (c.opening_qty + c.total_in_qty + c.total_out_qty + c.adjustment_qty) as closing_qty
1757
- from combined c
1758
- where 1=1
1759
- ${hasTransactionOrBalanceQuery}
1760
- ;
1695
+ SELECT
1696
+ p.sku AS product_sku,
1697
+ p.id AS product_id,
1698
+ p.name AS product_name,
1699
+ p.description AS product_description,
1700
+ p.type AS product_type,
1701
+ COALESCE(merged.opening_qty, 0) AS opening_qty,
1702
+ COALESCE(merged.total_in_qty, 0) AS total_in_qty,
1703
+ COALESCE(merged.total_out_qty, 0) AS total_out_qty,
1704
+ COALESCE(merged.adjustment_qty, 0) AS adjustment_qty,
1705
+ (COALESCE(merged.opening_qty, 0)
1706
+ + COALESCE(merged.total_in_qty, 0)
1707
+ + COALESCE(merged.total_out_qty, 0)
1708
+ + COALESCE(merged.adjustment_qty, 0))::int AS closing_qty
1709
+ FROM merged
1710
+ JOIN temp_products p ON p.id = merged.product_id
1711
+ WHERE ${hasTransactionOrBalanceQuery};
1761
1712
  `, [fromDate.value, toDate.value]);
1762
1713
  const total = await tx.query(`select count(*) as count, sum(total_in_qty) as totalInQty, sum(opening_qty) as totalOpeningBal from temp_inventory_pallet_summary`);
1763
1714
  const result = await tx.query(`
@@ -1805,6 +1756,10 @@ async function productsQuery(tx, params, bizplace) {
1805
1756
  ${productTypeQuery}
1806
1757
  GROUP BY prd.id, prd.sku, prd.name, prd.description
1807
1758
  )`, [bizplace.id]);
1759
+ await tx.query(`
1760
+ CREATE INDEX IF NOT EXISTS idx_tprod_id ON temp_products (id);
1761
+ ANALYZE temp_products;
1762
+ `);
1808
1763
  }
1809
1764
  async function filterInventoryQuery(tx, params, bizplace, context) {
1810
1765
  let toDate = params.filters.find(data => data.name === 'toDate');
@@ -1843,9 +1798,18 @@ async function filterInventoryQuery(tx, params, bizplace, context) {
1843
1798
  WHERE ih.domain_id = $1
1844
1799
  AND ih.bizplace_id = $2
1845
1800
  AND ih.created_at <= $3
1846
- AND (ih.qty <> 0 OR ih.opening_qty <> 0 OR ih.status = 'MISSING')
1801
+ AND EXISTS (SELECT 1 FROM temp_products prd WHERE prd.id = ih.product_id::uuid)
1802
+ ${batchNoQuery}
1847
1803
  )
1848
1804
  `, [context.state.domain.id, bizplace.id, toDate.value]);
1805
+ // Speed up joins and windowed lookups that follow
1806
+ await tx.query(`
1807
+ CREATE INDEX IF NOT EXISTS idx_tfh_pallet_domain ON temp_filtered_histories (pallet_id, domain_id);
1808
+ CREATE INDEX IF NOT EXISTS idx_tfh_product ON temp_filtered_histories (product_id);
1809
+ CREATE INDEX IF NOT EXISTS idx_tfh_created_at ON temp_filtered_histories (created_at);
1810
+ CREATE INDEX IF NOT EXISTS idx_tfh_pallet_seq ON temp_filtered_histories (pallet_id, seq);
1811
+ ANALYZE temp_filtered_histories;
1812
+ `);
1849
1813
  /* Step 2: build temp_inv_history by joining inventories with indexed temp_filtered_histories and temp_products */
1850
1814
  await tx.query(`
1851
1815
  create temp table temp_inv_history ON COMMIT DROP as (
@@ -1857,8 +1821,18 @@ async function filterInventoryQuery(tx, params, bizplace, context) {
1857
1821
  AND ih.domain_id = i2.domain_id
1858
1822
  INNER JOIN temp_products prd
1859
1823
  ON prd.id = ih.product_id::uuid
1860
- ${batchNoQuery}
1861
1824
  )
1862
1825
  `);
1826
+ // Critical indexes for the heavy pallet summary query
1827
+ await tx.query(`
1828
+ CREATE INDEX IF NOT EXISTS idx_tih_created_at ON temp_inv_history (created_at);
1829
+ CREATE INDEX IF NOT EXISTS idx_tih_pallet_seq ON temp_inv_history (pallet_id, seq);
1830
+ CREATE INDEX IF NOT EXISTS idx_tih_pallet_product_seq ON temp_inv_history (pallet_id, product_id, seq);
1831
+ CREATE INDEX IF NOT EXISTS idx_tih_product ON temp_inv_history (product_id);
1832
+ CREATE INDEX IF NOT EXISTS idx_tih_pallet_product ON temp_inv_history (pallet_id, product_id);
1833
+ CREATE INDEX IF NOT EXISTS idx_tih_status ON temp_inv_history (status);
1834
+ CREATE INDEX IF NOT EXISTS idx_tih_txn ON temp_inv_history (transaction_type);
1835
+ ANALYZE temp_inv_history;
1836
+ `);
1863
1837
  }
1864
1838
  //# sourceMappingURL=inventory-history-query.js.map