@things-factory/warehouse-base 4.3.678 → 4.3.681

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.
@@ -235,7 +235,8 @@ export class InventoryHistoryQuery {
235
235
  OR Lower(prd.description) LIKE ANY(ARRAY[${productValue}])
236
236
  OR Lower(prd.brand) LIKE ANY(ARRAY[${productValue}])
237
237
  OR Lower(prd.brand_sku) LIKE ANY(ARRAY[${productValue}])
238
- )`
238
+ )
239
+ `
239
240
  }
240
241
 
241
242
  let productDescQuery = ''
@@ -247,12 +248,11 @@ export class InventoryHistoryQuery {
247
248
  if (hasTransactionOrBalanceFilter && hasTransactionOrBalanceFilter.value) {
248
249
  hasTransactionOrBalanceQuery = 'and (src.totalRow > 1 or src.totalQty <> 0)'
249
250
  }
250
-
251
251
  await tx.query(
252
252
  `
253
253
  create temp table temp_products AS
254
254
  (
255
- select prd.*, prd.id::varchar as product_id from products prd
255
+ select prd.id, prd.sku, prd.name, prd.description, prd.type, prd.aux_value_1 from products prd
256
256
  inner join bizplaces b on b.id = prd.bizplace_id
257
257
  inner join companies c on c.domain_id = b.domain_id
258
258
  inner join bizplaces b2 on b2.company_id = c.id
@@ -261,20 +261,20 @@ export class InventoryHistoryQuery {
261
261
  )`,
262
262
  [bizplace.id]
263
263
  )
264
-
265
264
  await tx.query(
266
265
  `
267
266
  create temp table temp_data_src AS
268
267
  (
269
268
  SELECT prd.sku as sku, prd.name AS product_name, prd.description AS product_description, prd.type as product_type, prd.aux_value_1 AS product_aux_value_1, trim(invh.batch_id) as batch_id, invh.product_id,
270
- invh.packing_type, invh.bizplace_id, invh.domain_id,
271
- invh.ref_order_id, invh.order_no, invh.order_ref_no, invh.transaction_type, invh.status, invh.created_at,
269
+ invh.packing_type, invh.bizplace_id,
270
+ invh.order_no, invh.order_ref_no, invh.transaction_type, invh.status, invh.created_at,
272
271
  TRUNC(CAST(invh.qty AS NUMERIC), 3) as qty, TRUNC(CAST(invh.opening_qty AS NUMERIC), 3) as opening_qty,
273
272
  invh.uom, TRUNC(CAST(COALESCE(invh.uom_value, 0) AS NUMERIC), 3) as uom_value, TRUNC(CAST(COALESCE(invh.opening_uom_value, 0) AS NUMERIC), 3) as opening_uom_value
274
- FROM reduced_inventory_histories invh
273
+ FROM inventory_histories invh
275
274
  INNER JOIN temp_products prd ON prd.id = invh.product_id::uuid
276
275
  WHERE
277
- invh.domain_id = $1
276
+ (invh.qty <> 0 OR invh.uom_value <> 0)
277
+ AND invh.domain_id = $1
278
278
  AND invh.bizplace_id = $2
279
279
  AND invh.created_at <= $3::timestamp
280
280
  ${batchNoQuery}
@@ -285,105 +285,94 @@ export class InventoryHistoryQuery {
285
285
 
286
286
  await tx.query(
287
287
  `
288
- create temp table temp_inv_history as (
289
- SELECT src.sku, src.product_name, src.product_description, src.product_type, src.product_aux_value_1, src.batch_id, src.product_id, src.packing_type, src.uom,
290
- src.bizplace_id, src.domain_id,
291
- TRUNC(CAST(SUM(COALESCE(src.qty,0)) AS NUMERIC), 3) AS qty,
292
- TRUNC(CAST(SUM(COALESCE(src.opening_qty,0)) AS NUMERIC), 3) AS opening_qty,
293
- TRUNC(CAST(SUM(COALESCE(src.uom_value,0)) AS NUMERIC), 3) AS uom_value,
294
- TRUNC(CAST(SUM(COALESCE(src.opening_uom_value,0)) AS NUMERIC), 3) AS opening_uom_value,
288
+ CREATE TEMP TABLE temp_inv_history AS (
289
+ SELECT
290
+ src.sku,
291
+ src.product_name,
292
+ src.product_description,
293
+ src.product_type,
294
+ src.product_aux_value_1,
295
+ src.batch_id,
296
+ src.product_id,
297
+ src.packing_type,
298
+ src.uom,
299
+ src.bizplace_id,
300
+ TRUNC(CAST(SUM(COALESCE(src.qty, 0)) AS NUMERIC), 3) AS qty,
301
+ TRUNC(CAST(SUM(COALESCE(src.opening_qty, 0)) AS NUMERIC), 3) AS opening_qty,
302
+ TRUNC(CAST(SUM(COALESCE(src.uom_value, 0)) AS NUMERIC), 3) AS uom_value,
303
+ TRUNC(CAST(SUM(COALESCE(src.opening_uom_value, 0)) AS NUMERIC), 3) AS opening_uom_value,
295
304
  'Opening Balance' AS order_name,
296
305
  '-' AS ref_no,
297
- '-' as transaction_type,
306
+ '-' AS transaction_type,
298
307
  0 AS rn,
299
- $1::timestamp AS created_at,
300
- $1::date AS created_date
301
- FROM temp_data_src src
302
- WHERE src.created_at < $1::timestamp
303
- GROUP BY src.sku, src.product_name, src.product_description, src.product_type, src.product_aux_value_1, src.batch_id, src.product_id, src.packing_type, src.uom,
304
- src.bizplace_id, src.domain_id
305
- UNION ALL
306
- SELECT sku, product_name, product_description, product_type, product_aux_value_1, batch_id, product_id, packing_type, uom, bizplace_id,
307
- domain_id, TRUNC(CAST(sum(qty) AS NUMERIC), 3) as qty, TRUNC(CAST(sum(opening_qty) AS NUMERIC), 3) as opening_qty, TRUNC(CAST(sum(uom_value) AS NUMERIC), 3) as uom_value, TRUNC(CAST(sum(opening_uom_value) AS NUMERIC), 3) as opening_uom_value,
308
- order_name, ref_no, '-' as transaction_type ,1 AS rn, MIN(created_at) - $2::interval as created_at, (MIN(created_at) - $2::interval)::DATE as created_date
309
- FROM (
310
- SELECT invh.sku, invh.product_name, invh.product_description, invh.product_type, invh.product_aux_value_1, invh.batch_id, invh.product_id, invh.packing_type, invh.bizplace_id,
311
- invh.domain_id,
312
- invh.qty, invh.opening_qty, invh.uom_value, invh.uom, invh.opening_uom_value,
313
- COALESCE(order_no, '-') AS order_name,
314
- COALESCE(order_ref_no, '-') AS ref_no,
315
- COALESCE(invh.transaction_type, '-') AS transaction_type,
316
- invh.created_at,
317
- invh.created_at::date as created_date
318
- FROM temp_data_src invh
319
- WHERE (invh.qty <> 0 OR invh.uom_value <> 0) AND
320
- invh.transaction_type <> 'ADJUSTMENT' AND
321
- invh.transaction_type <> 'PICKING' AND
322
- invh.transaction_type <> 'CANCEL_ORDER' AND
323
- invh.transaction_type <>'NEW'
324
- AND invh.created_at >= $1::timestamp
325
- ) AS inv_movement
326
- GROUP BY sku, product_name, product_description, product_type, product_aux_value_1, batch_id, product_id, packing_type, uom, bizplace_id,
327
- domain_id, order_name, ref_no, rn
328
- UNION ALL
329
- SELECT sku, product_name, product_description, product_type, product_aux_value_1, batch_id, product_id, packing_type, uom, bizplace_id,
330
- domain_id, TRUNC(CAST(sum(qty) AS NUMERIC), 3) as qty, TRUNC(CAST(sum(opening_qty) AS NUMERIC), 3) as opening_qty, TRUNC(CAST(sum(uom_value) AS NUMERIC), 3) as uom_value, TRUNC(CAST(sum(opening_uom_value) AS NUMERIC), 3) as opening_uom_value,
331
- CASE
332
- WHEN transaction_type IN ('PICKING', 'CANCEL_ORDER') THEN order_no
333
- ELSE order_name
334
- END AS order_name,
308
+ $1::timestamp AS created_at
309
+ FROM temp_data_src src
310
+ WHERE src.created_at < $1::timestamp
311
+ GROUP BY
312
+ src.sku,
313
+ src.product_name,
314
+ src.product_description,
315
+ src.product_type,
316
+ src.product_aux_value_1,
317
+ src.batch_id,
318
+ src.product_id,
319
+ src.packing_type,
320
+ src.uom,
321
+ src.bizplace_id
322
+
323
+ UNION ALL
324
+
325
+ -- Part 2: All Inventory Movements (combined into a single scan)
326
+ -- This SELECT statement replaces the three separate UNION ALLs for
327
+ -- transaction movements, using CASE statements to handle different
328
+ -- transaction types and statuses in a single pass.
329
+ SELECT
330
+ sku,
331
+ product_name,
332
+ product_description,
333
+ product_type,
334
+ product_aux_value_1,
335
+ batch_id,
336
+ product_id,
337
+ packing_type,
338
+ uom,
339
+ bizplace_id,
340
+ TRUNC(CAST(SUM(
341
+ -- qty and uom_value logic for all cases combined
342
+ CASE
343
+ WHEN transaction_type = 'ADJUSTMENT' AND status = 'MISSING' THEN -opening_qty
344
+ ELSE qty
345
+ END
346
+ ) AS NUMERIC), 3) AS qty,
347
+ TRUNC(CAST(SUM(opening_qty) AS NUMERIC), 3) AS opening_qty,
348
+ TRUNC(CAST(SUM(
349
+ CASE
350
+ WHEN transaction_type = 'ADJUSTMENT' AND status = 'MISSING' THEN -opening_uom_value
351
+ ELSE uom_value
352
+ END
353
+ ) AS NUMERIC), 3) AS uom_value,
354
+ TRUNC(CAST(SUM(opening_uom_value) AS NUMERIC), 3) AS opening_uom_value,
335
355
  CASE
336
- WHEN transaction_type IN ('PICKING', 'CANCEL_ORDER') THEN ref_no
337
- ELSE order_name
338
- END AS ref_no,
339
- transaction_type,
340
- 1 AS rn,
341
- created_at - $2::interval as created_at,
342
- created_at::date as created_date
343
- FROM (
344
- SELECT
345
- invh.sku, invh.product_name, invh.product_description, invh.product_type, invh.product_aux_value_1, invh.batch_id, invh.product_id,
346
- invh.packing_type, invh.uom, invh.bizplace_id, invh.domain_id,
347
- invh.qty, invh.opening_qty, invh.uom_value, invh.opening_uom_value,
348
- invh.transaction_type,
349
- invh.transaction_type AS order_name,
350
- order_no,
351
- order_ref_no AS ref_no,
352
- invh.created_at,
353
- invh.created_at::date as created_date
354
- FROM temp_data_src invh
355
- WHERE (invh.qty <> 0 OR invh.uom_value <> 0) AND
356
- (invh.transaction_type = 'ADJUSTMENT'
357
- OR invh.transaction_type = 'PICKING'
358
- OR invh.transaction_type = 'CANCEL_ORDER'
359
- OR invh.transaction_type = 'NEW')
360
- AND invh.created_at >= $1::timestamp
361
- ) AS inv_movement
362
- GROUP BY
363
- sku, product_name, product_description, product_type, product_aux_value_1, batch_id, product_id, packing_type, uom, bizplace_id,
364
- domain_id, inv_movement.transaction_type,inv_movement.order_no,order_name, ref_no, rn, created_at
365
- UNION ALL
366
- SELECT sku, product_name, product_description, product_type, product_aux_value_1, batch_id, product_id, packing_type, uom, bizplace_id,
367
- domain_id, TRUNC(CAST(sum(-opening_qty) AS NUMERIC), 3) as qty, TRUNC(CAST(sum(opening_qty) AS NUMERIC), 3) as opening_qty, TRUNC(CAST(sum(-opening_uom_value) AS NUMERIC), 3) as uom_value, TRUNC(CAST(sum(opening_uom_value) AS NUMERIC), 3) as opening_uom_value,
368
- order_name, ref_no, transaction_type,1 AS rn, created_at - $2::interval as created_at, created_at::date as created_date
369
- FROM (
370
- SELECT invh.sku, invh.product_name, invh.product_description, invh.product_type, invh.product_aux_value_1, invh.batch_id, invh.product_id, invh.packing_type, invh.uom, invh.bizplace_id,
371
- invh.domain_id,
372
- invh.qty, invh.opening_qty, invh.uom_value, invh.opening_uom_value,
373
- invh.transaction_type as transaction_type,
374
- invh.transaction_type AS order_name,
375
- invh.transaction_type AS ref_no,
376
- invh.created_at,
377
- invh.created_at:: date as created_date
378
- FROM temp_data_src invh
379
- WHERE
380
- invh.transaction_type = 'ADJUSTMENT'
381
- AND invh.status = 'MISSING'
382
- AND invh.created_at >= $1::timestamp
383
- ) AS inv_movement
384
- GROUP BY sku, product_name, product_description, product_type, product_aux_value_1, batch_id, product_id, packing_type, uom, bizplace_id,
385
- domain_id, order_name, ref_no, inv_movement.transaction_type,rn, created_at
386
- )`,
356
+ WHEN transaction_type IN ('PICKING', 'CANCEL_ORDER') THEN order_no
357
+ WHEN transaction_type IN ('ADJUSTMENT', 'NEW') THEN transaction_type
358
+ ELSE COALESCE(order_no, '-')
359
+ END AS order_name,
360
+ CASE
361
+ WHEN transaction_type IN ('PICKING', 'CANCEL_ORDER') THEN order_ref_no
362
+ WHEN transaction_type IN ('ADJUSTMENT', 'NEW') THEN transaction_type
363
+ ELSE COALESCE(order_ref_no, '-')
364
+ END AS ref_no,
365
+ transaction_type,
366
+ 1 AS rn,
367
+ MIN(created_at) - $2::interval AS created_at
368
+ FROM temp_data_src
369
+ WHERE
370
+ created_at >= $1::timestamp
371
+ GROUP BY
372
+ sku, product_name, product_description, product_type, product_aux_value_1, batch_id, product_id,
373
+ packing_type, uom, bizplace_id,
374
+ order_name, ref_no, transaction_type
375
+ )`,
387
376
  [fromDate.value, tzoffset]
388
377
  )
389
378
 
@@ -393,7 +382,7 @@ export class InventoryHistoryQuery {
393
382
  result = await tx.query(
394
383
  `
395
384
  select sku, product_name, product_description, product_type, product_aux_value_1, batch_id, product_id, packing_type, uom, bizplace_id,
396
- domain_id, qty, opening_qty, round(uom_value::decimal,2) as uom_value, opening_uom_value, order_name, ref_no, transaction_type,created_at::date
385
+ qty, opening_qty, round(uom_value::decimal,2) as uom_value, opening_uom_value, order_name, ref_no, transaction_type,created_at::date
397
386
  from temp_inv_history invh where
398
387
  exists (
399
388
  select 1 from (
@@ -410,10 +399,10 @@ export class InventoryHistoryQuery {
410
399
  result = await tx.query(
411
400
  `
412
401
  select sku, product_name, product_description, product_type, product_aux_value_1, batch_id, product_id, packing_type, uom, bizplace_id,
413
- domain_id, sum(qty) as qty, sum(opening_qty) as opening_qty, sum(uom_value) as uom_value, sum(opening_uom_value) as opening_uom_value, order_name, ref_no, transaction_type, created_at::date
402
+ sum(qty) as qty, sum(opening_qty) as opening_qty, sum(uom_value) as uom_value, sum(opening_uom_value) as opening_uom_value, order_name, ref_no, transaction_type, created_at::date
414
403
  from(
415
404
  select sku, product_name, product_description, product_type, product_aux_value_1, batch_id, product_id, packing_type, uom, bizplace_id,
416
- domain_id, qty, opening_qty, TRUNC(CAST(uom_value AS NUMERIC), 3) as uom_value, opening_uom_value, order_name, ref_no, transaction_type,created_at::date
405
+ qty, opening_qty, TRUNC(CAST(uom_value AS NUMERIC), 3) as uom_value, opening_uom_value, order_name, ref_no, transaction_type,created_at
417
406
  from temp_inv_history invh where
418
407
  exists (
419
408
  select 1 from (
@@ -425,7 +414,7 @@ export class InventoryHistoryQuery {
425
414
  )
426
415
  ) foo
427
416
  GROUP BY sku, product_name, product_description, product_type, product_aux_value_1, batch_id, product_id, packing_type, uom, bizplace_id,
428
- domain_id, order_name, ref_no, transaction_type, created_at
417
+ order_name, ref_no, transaction_type, created_at
429
418
  ORDER BY sku asc, product_name asc, product_description asc, packing_type asc, batch_id asc, uom asc, created_at asc,
430
419
  CASE
431
420
  WHEN transaction_type = 'CANCEL_ORDER' THEN 2
@@ -1712,92 +1701,252 @@ async function massageInventoryPalletSummary(tx: EntityManager, params: ListPara
1712
1701
 
1713
1702
  let hasTransactionOrBalanceQuery = ''
1714
1703
  if (hasTransactionOrBalanceFilter && hasTransactionOrBalanceFilter.value) {
1715
- hasTransactionOrBalanceQuery = 'and (opening_qty > 0 or total_in_qty > 0 or adjustment_qty > 0)'
1704
+ hasTransactionOrBalanceQuery = 'and (c.opening_qty > 0 or c.total_in_qty > 0 or c.adjustment_qty > 0)'
1716
1705
  }
1706
+
1717
1707
  await tx.query(
1718
1708
  `
1719
- create temp table temp_inventory_pallet_summary ON COMMIT DROP as (
1720
- select invh.*,
1721
- invh.opening_qty + invh.total_in_qty + invh.total_out_qty + invh.adjustment_qty as closing_qty from (
1722
- select product_sku, product_id, product_name, product_description, product_type,
1723
- SUM(case when invHistory.created_at < $1 then
1724
- case when invHistory.status = 'STORED' then 1
1725
- when invhistory.status = 'TERMINATED' then -1
1726
- else 0 end
1727
- else 0 end) as opening_qty,
1728
- SUM(case when invHistory.created_at >= $1 and invHistory.created_at <= $2 then
1729
- case when (invHistory.transaction_type = 'UNLOADING' or invHistory.transaction_type = 'NEW' or invHistory.transaction_type = 'CANCEL_ORDER') then 1 else 0 end
1730
- else 0 end) as total_in_qty,
1731
- SUM(case when invHistory.created_at >= $1 and invHistory.created_at <= $2 then
1732
- case when invHistory.status = 'TERMINATED' AND invHistory.transaction_type <> 'ADJUSTMENT' then -1 else 0 end
1733
- else 0 end) as total_out_qty,
1734
- SUM(case when invHistory.created_at >= $1 and invHistory.created_at <= $2 then
1735
- case when invHistory.status = 'TERMINATED' AND invHistory.transaction_type = 'ADJUSTMENT' then -1
1736
- when invHistory.status = 'STORED' AND invHistory.transaction_type = 'ADJUSTMENT' then 1 else 0 end
1737
- else 0 end) as adjustment_qty
1738
- from(
1739
- select pallet_id, seq, status, transaction_type, product_sku, product_id, product_name, product_description, product_type,
1740
- inventory_history_id, packing_type, packing_size, qty, opening_qty, created_at from (
1741
- select row_number() over(partition by invh.pallet_id order by invh.created_at asc) as rn, invh.* ,
1742
- prd.sku as product_sku, prd.name as product_name, prd.description as product_description, prd.type AS product_type
1743
- from temp_inv_history invh
1744
- inner join temp_products prd on prd.id = invh.product_id
1745
- where (transaction_type = 'UNLOADING' or invh.transaction_type = 'NEW' or invh.transaction_type = 'CANCEL_ORDER')
1746
- and invh.created_at >= $1
1747
- ) as invIn where rn = 1
1748
- union all
1749
- select pallet_id, seq, status, transaction_type, product_sku, product_id, product_name, product_description, product_type,
1750
- inventory_history_id, packing_type, packing_size, qty, opening_qty, created_at from (
1751
- SELECT row_number() over(partition by pallet_id, invh.product_id order by seq asc) as rn,
1752
- invh.pallet_id, invh.product_id, invh.packing_type, invh.packing_size,
1753
- invh.batch_id, invh.inventory_history_id,
1754
- invh.seq, 'STORED' AS status, invh.transaction_type, invh.qty, invh.opening_qty,
1755
- invh.created_at,
1756
- prd.sku as product_sku, prd.name as product_name, prd.description as product_description, prd.type AS product_type
1757
- from temp_inv_history invh
1758
- inner join temp_products prd on prd.id = invh.product_id
1759
- where invh.created_at < $1
1760
- and (invh.status = 'STORED' or invh.status = 'UNLOADED')
1761
- ) as invStored where rn = 1
1762
- union all
1763
- select pallet_id, seq, status, transaction_type, product_sku, product_id, product_name, product_description, product_type,
1764
- inventory_history_id, packing_type, packing_size, qty, opening_qty, started_at as created_at from (
1765
- select row_number() over(partition by invh.pallet_id, invh.product_id order by invh.seq desc) as rn, invh.*,
1766
- prd.sku as product_sku, prd.name as product_name, prd.description as product_description, prd.type AS product_type, repeatedGroup.started_at
1767
- from temp_inv_history invh
1768
- inner join (
1769
- select pallet_id, min(created_at) as started_at, max(created_at) as ended_at, min(seq) as min_seq, max(seq) as max_seq, status from (
1770
- select startData.*, sum(startflag) over (partition by pallet_id order by seq) as grp from (
1771
- select s.*,
1772
- (case when lag(status) over (partition by pallet_id order by seq) = status then 0 else 1 end) as startflag
1773
- from temp_inv_history s
1774
- ) startData
1775
- ) endData
1776
- group by pallet_id, grp, status
1777
- ) repeatedGroup on repeatedGroup.pallet_id = invh.pallet_id and repeatedGroup.min_seq <= invh.seq and repeatedGroup.max_seq >= invh.seq
1778
- inner join temp_products prd on prd.id = invh.product_id
1779
- where invh.status = 'TERMINATED' and invh.transaction_type IN ('TERMINATED', 'ADJUSTMENT')
1780
- ) as invOut where rn=1
1781
- union all
1782
- SELECT invh.pallet_id, invh.seq, invh.status, invh.transaction_type,
1783
- prd.sku AS product_sku, invh.product_id, prd.name AS product_name, prd.description AS product_description, prd.type AS product_type,
1784
- invh.inventory_history_id, invh.packing_type, invh.packing_size, invh.qty, invh.opening_qty, invh.created_at
1785
- FROM temp_inv_history invh
1786
- INNER JOIN (
1787
- SELECT invh.pallet_id, invh.seq AS stored_seq, invh2.seq AS terminated_seq FROM temp_inv_history invh
1788
- INNER JOIN temp_inv_history invh2 ON invh2.pallet_id = invh.pallet_id AND invh2.status='STORED' AND invh2.seq = invh.seq + 1 AND invh2.product_id <> invh.product_id
1789
- WHERE invh.transaction_type = 'ADJUSTMENT'
1790
- ) dt ON dt.pallet_id = invh.pallet_id AND (dt.stored_seq = invh.seq OR dt.terminated_seq = invh.seq)
1791
- inner join temp_products prd on prd.id = invh.product_id
1792
- where invh.created_at >= $1
1793
- ORDER BY product_name, pallet_id, seq
1794
- ) as invHistory
1795
- group by product_sku, product_id, product_name, product_description, product_type
1796
- ) invh
1797
- where 1=1
1798
- ${hasTransactionOrBalanceQuery}
1709
+ create temp table temp_inventory_pallet_summary ON COMMIT DROP as
1710
+ with params as (
1711
+ select $1::timestamp as t1, $2::timestamp as t2
1712
+ ),
1713
+ base as (
1714
+ select
1715
+ invh.*,
1716
+ prd.sku as product_sku,
1717
+ prd.name as product_name,
1718
+ prd.description as product_description,
1719
+ prd.type as product_type
1720
+ from temp_inv_history invh
1721
+ join temp_products prd on prd.id = invh.product_id
1722
+ ),
1723
+
1724
+ /* ---- Opening snapshot as-of t1 (FIX) ----
1725
+ For each pallet, take the last row <= t1; if status='STORED',
1726
+ count it toward that row's product_id opening.
1727
+ */
1728
+ opening_last_asof as (
1729
+ select *
1730
+ from (
1731
+ select h.*,
1732
+ row_number() over (partition by h.pallet_id order by h.created_at desc, h.seq desc) as rn
1733
+ from temp_inv_history h
1734
+ join params p on true
1735
+ where h.created_at <= p.t1
1736
+ ) x
1737
+ where rn = 1 and status = 'STORED'
1738
+ ),
1739
+ opening_agg as (
1740
+ select
1741
+ p.sku as product_sku,
1742
+ p.id as product_id,
1743
+ p.name as product_name,
1744
+ p.description as product_description,
1745
+ p.type as product_type,
1746
+ count(*)::int as opening_qty
1747
+ from opening_last_asof o
1748
+ join temp_products p on p.id = o.product_id
1749
+ group by p.sku, p.id, p.name, p.description, p.type
1750
+ ),
1751
+
1752
+ /* ---- Arrivals inside window: first arrival-like row per pallet (your rule) ---- */
1753
+ invIn as (
1754
+ select b.*,
1755
+ row_number() over (partition by b.pallet_id order by b.created_at, b.seq) as rn
1756
+ from base b
1757
+ join params p on true
1758
+ where b.transaction_type in ('UNLOADING','NEW','CANCEL_ORDER')
1759
+ and b.created_at >= p.t1
1760
+ ),
1761
+ invIn_first as (
1762
+ select
1763
+ pallet_id, seq, status, transaction_type,
1764
+ product_sku, product_id, product_name, product_description, product_type,
1765
+ inventory_history_id, packing_type, packing_size, qty, opening_qty, created_at
1766
+ from invIn
1767
+ where rn = 1
1768
+ ),
1769
+
1770
+ /* ---- TERMINATED reps (incl. ADJUSTMENT terminations), using your runs logic ---- */
1771
+ status_runs as (
1772
+ select
1773
+ endData.pallet_id,
1774
+ min(endData.created_at) as started_at,
1775
+ max(endData.created_at) as ended_at,
1776
+ min(endData.seq) as min_seq,
1777
+ max(endData.seq) as max_seq,
1778
+ endData.status
1779
+ from (
1780
+ select startData.*,
1781
+ sum(startflag) over (partition by startData.pallet_id order by startData.seq) as grp
1782
+ from (
1783
+ select s.*,
1784
+ case when lag(s.status) over (partition by s.pallet_id order by s.seq) = s.status then 0 else 1 end as startflag
1785
+ from temp_inv_history s
1786
+ ) startData
1787
+ ) endData
1788
+ group by endData.pallet_id, endData.status, endData.grp
1789
+ ),
1790
+ invOut_base as (
1791
+ select b.*, r.started_at
1792
+ from base b
1793
+ join status_runs r
1794
+ on r.pallet_id = b.pallet_id
1795
+ and r.min_seq <= b.seq
1796
+ and r.max_seq >= b.seq
1797
+ where b.status = 'TERMINATED'
1798
+ and b.transaction_type in ('TERMINATED','ADJUSTMENT')
1799
+ ),
1800
+
1801
+ /* ---- Transfer pairing (seq-gap tolerant & tolerant txn tags) ---- */
1802
+ /* STORED side in-window; pair to nearest prior TERMINATED (from raw history), different product */
1803
+ adj_stored AS (
1804
+ SELECT b.*
1805
+ FROM base b
1806
+ JOIN params p ON TRUE
1807
+ WHERE b.status = 'STORED'
1808
+ AND b.created_at BETWEEN p.t1 AND p.t2
1809
+ ),
1810
+ adj_pairs AS (
1811
+ SELECT
1812
+ s.pallet_id,
1813
+ s.seq AS stored_seq,
1814
+ t.seq AS terminated_seq,
1815
+ s.product_id AS new_product_id,
1816
+ t.product_id AS old_product_id,
1817
+ s.inventory_history_id AS stored_inventory_history_id,
1818
+ t.inventory_history_id AS terminated_inventory_history_id,
1819
+ s.created_at AS stored_at,
1820
+ t.created_at AS terminated_at,
1821
+ s.transaction_type AS stored_txn,
1822
+ t.transaction_type AS terminated_txn
1823
+ FROM adj_stored s
1824
+ JOIN LATERAL (
1825
+ SELECT t.*
1826
+ FROM temp_inv_history t -- use raw history (old product may be outside temp_products)
1827
+ WHERE t.pallet_id = s.pallet_id
1828
+ AND t.seq < s.seq
1829
+ AND t.status = 'TERMINATED'
1830
+ ORDER BY t.seq DESC
1831
+ LIMIT 1
1832
+ ) t ON TRUE
1833
+ WHERE t.product_id IS DISTINCT FROM s.product_id
1834
+ AND (s.transaction_type='ADJUSTMENT' OR t.transaction_type='ADJUSTMENT')
1835
+ ),
1836
+ adj_pairs_terminated_keys AS (
1837
+ SELECT pallet_id, terminated_seq AS seq
1838
+ FROM adj_pairs
1839
+ ),
1840
+
1841
+ /* Representative TERMINATED per (pallet, product), EXCLUDING those used by a pair (avoid double-count) */
1842
+ invOut_rep AS (
1843
+ SELECT *
1844
+ FROM (
1845
+ SELECT iob.*,
1846
+ row_number() over (partition by iob.pallet_id, iob.product_id order by iob.seq desc) as rn
1847
+ FROM invOut_base iob
1848
+ LEFT JOIN adj_pairs_terminated_keys k
1849
+ ON k.pallet_id = iob.pallet_id
1850
+ AND k.seq = iob.seq
1851
+ WHERE k.pallet_id IS NULL
1852
+ ) z
1853
+ WHERE rn = 1
1854
+ ),
1855
+
1856
+ /* Materialize both sides of the paired transfer */
1857
+ adj_pairs_stored_rows AS (
1858
+ SELECT
1859
+ b.pallet_id, b.seq, b.status, b.transaction_type,
1860
+ b.product_sku, b.product_id, b.product_name, b.product_description, b.product_type,
1861
+ b.inventory_history_id, b.packing_type, b.packing_size, b.qty, b.opening_qty, b.created_at
1862
+ FROM base b
1863
+ JOIN adj_pairs ap
1864
+ ON ap.pallet_id = b.pallet_id
1865
+ AND ap.stored_seq = b.seq
1866
+ ),
1867
+ adj_pairs_terminated_rows AS (
1868
+ SELECT
1869
+ b.pallet_id, b.seq, b.status, b.transaction_type,
1870
+ b.product_sku, b.product_id, b.product_name, b.product_description, b.product_type,
1871
+ b.inventory_history_id, b.packing_type, b.packing_size, b.qty, b.opening_qty, b.created_at
1872
+ FROM base b
1873
+ JOIN adj_pairs ap
1874
+ ON ap.pallet_id = b.pallet_id
1875
+ AND ap.terminated_seq = b.seq
1876
+ ),
1877
+
1878
+ /* ---- Unified stream for window movement ---- */
1879
+ invHistory as (
1880
+ select * from invIn_first
1881
+ union all
1882
+ select
1883
+ pallet_id, seq, status, transaction_type,
1884
+ product_sku, product_id, product_name, product_description, product_type,
1885
+ inventory_history_id, packing_type, packing_size, qty, opening_qty, created_at
1886
+ from invOut_rep
1887
+ union all
1888
+ select * from adj_pairs_stored_rows
1889
+ union all
1890
+ select * from adj_pairs_terminated_rows
1891
+ ),
1892
+
1893
+ /* ---- Movement aggregation only (no opening here) ---- */
1894
+ movement_agg as (
1895
+ select
1896
+ ih.product_sku,
1897
+ ih.product_id,
1898
+ ih.product_name,
1899
+ ih.product_description,
1900
+ ih.product_type,
1901
+
1902
+ sum(case when ih.created_at >= (select t1 from params)
1903
+ and ih.created_at <= (select t2 from params) then
1904
+ case when ih.transaction_type in ('UNLOADING','NEW','CANCEL_ORDER') then 1 else 0 end
1905
+ else 0 end)::int as total_in_qty,
1906
+
1907
+ sum(case when ih.created_at >= (select t1 from params)
1908
+ and ih.created_at <= (select t2 from params) then
1909
+ case when ih.status = 'TERMINATED'
1910
+ and ih.transaction_type <> 'ADJUSTMENT' then -1 else 0 end
1911
+ else 0 end)::int as total_out_qty,
1912
+
1913
+ sum(case when ih.created_at >= (select t1 from params)
1914
+ and ih.created_at <= (select t2 from params) then
1915
+ case when ih.status = 'TERMINATED' and ih.transaction_type = 'ADJUSTMENT' then -1
1916
+ when ih.status = 'STORED' and ih.transaction_type = 'ADJUSTMENT' then 1
1917
+ else 0 end
1918
+ else 0 end)::int as adjustment_qty
1919
+
1920
+ from invHistory ih
1921
+ group by ih.product_sku, ih.product_id, ih.product_name, ih.product_description, ih.product_type
1922
+ ),
1923
+
1924
+ /* ---- Combine opening snapshot + window movement ---- */
1925
+ combined as (
1926
+ select
1927
+ coalesce(o.product_sku, m.product_sku) as product_sku,
1928
+ coalesce(o.product_id, m.product_id) as product_id,
1929
+ coalesce(o.product_name,m.product_name) as product_name,
1930
+ coalesce(o.product_description,m.product_description) as product_description,
1931
+ coalesce(o.product_type,m.product_type) as product_type,
1932
+
1933
+ coalesce(o.opening_qty, 0) as opening_qty,
1934
+ coalesce(m.total_in_qty, 0) as total_in_qty,
1935
+ coalesce(m.total_out_qty, 0) as total_out_qty,
1936
+ coalesce(m.adjustment_qty, 0) as adjustment_qty
1937
+ from opening_agg o
1938
+ full outer join movement_agg m
1939
+ on m.product_id = o.product_id
1799
1940
  )
1800
- `,
1941
+
1942
+ select
1943
+ c.*,
1944
+ (c.opening_qty + c.total_in_qty + c.total_out_qty + c.adjustment_qty) as closing_qty
1945
+ from combined c
1946
+ where 1=1
1947
+ ${hasTransactionOrBalanceQuery}
1948
+ ;
1949
+ `,
1801
1950
  [fromDate.value, toDate.value]
1802
1951
  )
1803
1952