@things-factory/operato-hub 4.3.770 → 4.3.771
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.
- package/config.development.js +107 -52
- package/dist-server/routers/api/restful-apis/unstable/add-release-order.js +81 -45
- package/dist-server/routers/api/restful-apis/unstable/add-release-order.js.map +1 -1
- package/dist-server/routers/api/restful-apis/v1/warehouse/add-release-order.js +55 -44
- package/dist-server/routers/api/restful-apis/v1/warehouse/add-release-order.js.map +1 -1
- package/dist-server/routers/api/restful-apis/v1/warehouse/update-release-order-details.js +65 -36
- package/dist-server/routers/api/restful-apis/v1/warehouse/update-release-order-details.js.map +1 -1
- package/dist-server/routers/xilnex-router.js +7 -2
- package/dist-server/routers/xilnex-router.js.map +1 -1
- package/package.json +5 -5
- package/server/routers/api/restful-apis/unstable/add-release-order.ts +90 -50
- package/server/routers/api/restful-apis/v1/warehouse/add-release-order.ts +63 -48
- package/server/routers/api/restful-apis/v1/warehouse/update-release-order-details.ts +69 -38
- package/server/routers/xilnex-router.ts +7 -2
|
@@ -1263,9 +1263,9 @@ async function assignToInventory(
|
|
|
1263
1263
|
const orderInventory = orderProduct.orderInventories
|
|
1264
1264
|
let productBundle = orderProduct?.productBundle
|
|
1265
1265
|
|
|
1266
|
-
let sortings: any = []
|
|
1267
|
-
|
|
1268
1266
|
for (let oiIdx = 0; oiIdx < orderInventory.length; oiIdx++) {
|
|
1267
|
+
let sortings: any = []
|
|
1268
|
+
|
|
1269
1269
|
if (worksheetPickingAssignment?.value !== 'true') {
|
|
1270
1270
|
switch (orderInventory[oiIdx].product.pickingStrategy) {
|
|
1271
1271
|
case 'LIFO':
|
|
@@ -1347,7 +1347,7 @@ async function assignToInventory(
|
|
|
1347
1347
|
acc.query.push(`${itm.query} ${itm?.operator ? itm.operator : '='} $${acc.values.length + 1}`)
|
|
1348
1348
|
break
|
|
1349
1349
|
}
|
|
1350
|
-
|
|
1350
|
+
return acc
|
|
1351
1351
|
},
|
|
1352
1352
|
{
|
|
1353
1353
|
query: [],
|
|
@@ -1385,6 +1385,20 @@ async function assignToInventory(
|
|
|
1385
1385
|
const releaseShelfLifeParamIndex = params.length + 1
|
|
1386
1386
|
params.push(releaseShelfLifeOverride)
|
|
1387
1387
|
|
|
1388
|
+
// Add seed row parameters to avoid SQL injection
|
|
1389
|
+
const seedUomIdx = params.length + 1
|
|
1390
|
+
const seedPackingTypeIdx = params.length + 2
|
|
1391
|
+
const seedPackingSizeIdx = params.length + 3
|
|
1392
|
+
const seedReleaseQtyIdx = params.length + 4
|
|
1393
|
+
const seedReleaseUomValueIdx = params.length + 5
|
|
1394
|
+
params.push(
|
|
1395
|
+
orderInventory[oiIdx].uom,
|
|
1396
|
+
orderInventory[oiIdx].packingType,
|
|
1397
|
+
orderInventory[oiIdx].packingSize,
|
|
1398
|
+
orderInventory[oiIdx].releaseQty,
|
|
1399
|
+
orderInventory[oiIdx].releaseUomValue
|
|
1400
|
+
)
|
|
1401
|
+
|
|
1388
1402
|
let query = `
|
|
1389
1403
|
update inventories tgt set locked_qty = coalesce(locked_qty,0) + src.reserve_qty,
|
|
1390
1404
|
locked_uom_value = coalesce(locked_uom_value,0) + src.reserve_uom_value,
|
|
@@ -1404,14 +1418,10 @@ async function assignToInventory(
|
|
|
1404
1418
|
) as available_qty,
|
|
1405
1419
|
sum(qty - locked_qty - release_qty - unassigned_qty) over (order by sort_seq asc rows between unbounded preceding and current row) as lock_amount
|
|
1406
1420
|
from (
|
|
1407
|
-
SELECT 0 as sort_seq, null as id, null as pallet_id, null as batch_id, null as batch_id_ref,
|
|
1408
|
-
null as unit,
|
|
1409
|
-
orderInventory[oiIdx].packingType
|
|
1410
|
-
}' as packing_type, '${orderInventory[oiIdx].packingSize}' as packing_size,
|
|
1421
|
+
SELECT 0 as sort_seq, null as id, null as pallet_id, null as batch_id, null as batch_id_ref,
|
|
1422
|
+
null as unit, $${seedUomIdx} as uom, $${seedPackingTypeIdx} as packing_type, $${seedPackingSizeIdx} as packing_size,
|
|
1411
1423
|
null as manufacture_year, null as carton_id, 0 as uom_value, 0 as locked_uom_value, 0 as qty, 0 as locked_qty, 0 as unassigned_uom_value, 0 as unassigned_qty, null as created_at,
|
|
1412
|
-
|
|
1413
|
-
orderInventory[oiIdx].releaseUomValue
|
|
1414
|
-
}::numeric as release_uom_value
|
|
1424
|
+
$${seedReleaseQtyIdx}::numeric as release_qty, $${seedReleaseUomValueIdx}::numeric as release_uom_value
|
|
1415
1425
|
UNION
|
|
1416
1426
|
(
|
|
1417
1427
|
SELECT ROW_NUMBER() OVER(PARTITION BY iv.domain_id ORDER BY wiar.rank ${
|
|
@@ -1444,56 +1454,59 @@ async function assignToInventory(
|
|
|
1444
1454
|
TRUE
|
|
1445
1455
|
END
|
|
1446
1456
|
)
|
|
1447
|
-
${queryStrings.query.length > 0 ? `AND ${queryStrings.join(' AND ')}` : ''}
|
|
1457
|
+
${queryStrings.query.length > 0 ? `AND ${queryStrings.query.join(' AND ')}` : ''}
|
|
1448
1458
|
ORDER BY wiar.rank ${sortQuery ? ', ' + sortQuery : ''}
|
|
1449
1459
|
)
|
|
1450
1460
|
) dt1
|
|
1451
1461
|
) dt2 where case when "lock_amount" < 0 then "available_qty" else "available_qty" - "lock_amount" end > 0
|
|
1452
1462
|
) dt3 where sort_seq > 0
|
|
1453
|
-
) src where src.id = tgt.id
|
|
1454
|
-
returning
|
|
1455
|
-
tgt."id", tgt."qty", tgt."pallet_id", tgt."carton_id", tgt."batch_id", tgt."batch_id_ref", tgt."unit",
|
|
1456
|
-
tgt."uom", tgt."packing_type", tgt."packing_size", tgt."manufacture_year",
|
|
1463
|
+
) src where src.id = tgt.id AND tgt.qty >= coalesce(tgt.locked_qty, 0) + src.reserve_qty
|
|
1464
|
+
returning
|
|
1465
|
+
tgt."id", tgt."qty", tgt."pallet_id", tgt."carton_id", tgt."batch_id", tgt."batch_id_ref", tgt."unit",
|
|
1466
|
+
tgt."uom", tgt."packing_type", tgt."packing_size", tgt."manufacture_year",
|
|
1457
1467
|
tgt."locked_qty", tgt."uom_value", tgt."locked_uom_value", src."reserve_qty", src."reserve_uom_value";`
|
|
1458
1468
|
|
|
1459
|
-
|
|
1469
|
+
const MAX_ASSIGN_RETRIES = 3
|
|
1470
|
+
const RETRY_DELAY_MS = 10
|
|
1471
|
+
let updatedInventories
|
|
1460
1472
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
user: user.id || '',
|
|
1464
|
-
domain: domain.id || '',
|
|
1465
|
-
bizplace: customerBizplace.id || '',
|
|
1466
|
-
packingType: orderInventory[oiIdx].packingType || '',
|
|
1467
|
-
packingSize: orderInventory[oiIdx].packingSize || '',
|
|
1468
|
-
productId: orderInventory[oiIdx].product.id || ''
|
|
1469
|
-
},
|
|
1470
|
-
result: updatedInventories[0] || '',
|
|
1471
|
-
location: 'add-release-order v1 assignInventory',
|
|
1472
|
-
time: new Date()
|
|
1473
|
-
})
|
|
1473
|
+
for (let attempt = 1; attempt <= MAX_ASSIGN_RETRIES; attempt++) {
|
|
1474
|
+
await tx.query('SAVEPOINT assign_retry')
|
|
1474
1475
|
|
|
1475
|
-
|
|
1476
|
-
return acc + inv.reserve_qty
|
|
1477
|
-
}, 0)
|
|
1476
|
+
updatedInventories = await tx.getRepository(Inventory).query(query, params)
|
|
1478
1477
|
|
|
1479
|
-
|
|
1478
|
+
const assignedRows = updatedInventories?.[0] || []
|
|
1479
|
+
let totalAssigned = assignedRows.reduce((acc, inv) => {
|
|
1480
|
+
return acc + Number(inv.reserve_qty)
|
|
1481
|
+
}, 0)
|
|
1480
1482
|
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1483
|
+
let opReleaseQty = productBundle ? orderInventory[oiIdx].releaseQty : orderProduct.releaseQty
|
|
1484
|
+
|
|
1485
|
+
// For non-decimal products, round the values before comparison
|
|
1486
|
+
if (!orderInventory[oiIdx].product.isInventoryDecimal) {
|
|
1487
|
+
opReleaseQty = Math.round(opReleaseQty)
|
|
1488
|
+
totalAssigned = Math.round(totalAssigned)
|
|
1489
|
+
} else {
|
|
1490
|
+
// For decimal products, round to 3 decimal places
|
|
1491
|
+
opReleaseQty = Math.round(opReleaseQty * 1000) / 1000
|
|
1492
|
+
totalAssigned = Math.round(totalAssigned * 1000) / 1000
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
if (Math.abs(opReleaseQty - totalAssigned) > 0.001) {
|
|
1496
|
+
await tx.query('ROLLBACK TO SAVEPOINT assign_retry')
|
|
1497
|
+
if (attempt < MAX_ASSIGN_RETRIES) {
|
|
1498
|
+
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS))
|
|
1499
|
+
continue
|
|
1500
|
+
}
|
|
1501
|
+
// Using small epsilon for float comparison
|
|
1502
|
+
throw new ApiError('E01', 'INSUFFICIENT_STOCK')
|
|
1503
|
+
}
|
|
1490
1504
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
throw new ApiError('E01', 'INSUFFICIENT_STOCK')
|
|
1505
|
+
await tx.query('RELEASE SAVEPOINT assign_retry')
|
|
1506
|
+
break
|
|
1494
1507
|
}
|
|
1495
1508
|
|
|
1496
|
-
updatedInventories[0].forEach(inv => {
|
|
1509
|
+
;(updatedInventories?.[0] || []).forEach(inv => {
|
|
1497
1510
|
let oi = {
|
|
1498
1511
|
...orderInventory[oiIdx],
|
|
1499
1512
|
inventory: { id: inv.id },
|
|
@@ -1520,9 +1533,11 @@ async function assignToInventory(
|
|
|
1520
1533
|
.createQueryBuilder()
|
|
1521
1534
|
.update(ProductDetailStock)
|
|
1522
1535
|
.set({
|
|
1523
|
-
unassignedQty: () => `"unassigned_qty" +
|
|
1524
|
-
unassignedUomValue: () => `"unassigned_uom_value" +
|
|
1536
|
+
unassignedQty: () => `"unassigned_qty" + :releaseQty::numeric`,
|
|
1537
|
+
unassignedUomValue: () => `"unassigned_uom_value" + :releaseUomValue::numeric`
|
|
1525
1538
|
})
|
|
1539
|
+
.setParameter('releaseQty', orderInventory[oiIdx].releaseQty)
|
|
1540
|
+
.setParameter('releaseUomValue', orderInventory[oiIdx].releaseUomValue)
|
|
1526
1541
|
.where({ productDetail: orderInventory[oiIdx].productDetail.id })
|
|
1527
1542
|
.execute()
|
|
1528
1543
|
}
|
|
@@ -448,10 +448,12 @@ async function removeOrderProducts(tx: EntityManager, orderProductIds: string[],
|
|
|
448
448
|
.createQueryBuilder()
|
|
449
449
|
.update(Inventory)
|
|
450
450
|
.set({
|
|
451
|
-
lockedQty: () => `COALESCE(locked_qty, 0) -
|
|
452
|
-
lockedUomValue: () => `COALESCE(locked_uom_value, 0) -
|
|
451
|
+
lockedQty: () => `GREATEST(COALESCE(locked_qty, 0) - :releaseQty::numeric, 0)`,
|
|
452
|
+
lockedUomValue: () => `GREATEST(COALESCE(locked_uom_value, 0) - :releaseUomValue::numeric, 0)`,
|
|
453
453
|
updater: orderProductEntity.updater
|
|
454
454
|
})
|
|
455
|
+
.setParameter('releaseQty', oiReleaseQty)
|
|
456
|
+
.setParameter('releaseUomValue', oiReleaseUomValue)
|
|
455
457
|
.where('id = :id', { id: inventoryEntity.id })
|
|
456
458
|
.execute()
|
|
457
459
|
}
|
|
@@ -490,9 +492,11 @@ async function removeOrderProducts(tx: EntityManager, orderProductIds: string[],
|
|
|
490
492
|
.createQueryBuilder()
|
|
491
493
|
.update(ProductDetailStock)
|
|
492
494
|
.set({
|
|
493
|
-
unassignedQty: () => `"unassigned_qty" -
|
|
494
|
-
unassignedUomValue: () => `"unassigned_uom_value" -
|
|
495
|
+
unassignedQty: () => `GREATEST("unassigned_qty" - :releaseQty::numeric, 0)`,
|
|
496
|
+
unassignedUomValue: () => `GREATEST("unassigned_uom_value" - :releaseUomValue::numeric, 0)`
|
|
495
497
|
})
|
|
498
|
+
.setParameter('releaseQty', releaseQty)
|
|
499
|
+
.setParameter('releaseUomValue', releaseUomValue)
|
|
496
500
|
.where({ productDetail: productDetail.id })
|
|
497
501
|
.execute()
|
|
498
502
|
}
|
|
@@ -560,9 +564,11 @@ async function updateExistingOrderProduct(
|
|
|
560
564
|
.createQueryBuilder()
|
|
561
565
|
.update(ProductDetailStock)
|
|
562
566
|
.set({
|
|
563
|
-
unassignedQty: () => `"unassigned_qty" +
|
|
564
|
-
unassignedUomValue: () => `"unassigned_uom_value" +
|
|
567
|
+
unassignedQty: () => `"unassigned_qty" + :qtyDiff::numeric`,
|
|
568
|
+
unassignedUomValue: () => `"unassigned_uom_value" + :uomValueDiff::numeric`
|
|
565
569
|
})
|
|
570
|
+
.setParameter('qtyDiff', qtyDiff)
|
|
571
|
+
.setParameter('uomValueDiff', uomValueDiff)
|
|
566
572
|
.where({ productDetail: existing.productDetail.id })
|
|
567
573
|
.execute()
|
|
568
574
|
}
|
|
@@ -634,10 +640,12 @@ async function updateOrderInventoriesForQtyChange(
|
|
|
634
640
|
.createQueryBuilder()
|
|
635
641
|
.update(Inventory)
|
|
636
642
|
.set({
|
|
637
|
-
lockedQty: () => `COALESCE(locked_qty, 0) -
|
|
638
|
-
lockedUomValue: () => `COALESCE(locked_uom_value, 0) -
|
|
643
|
+
lockedQty: () => `GREATEST(COALESCE(locked_qty, 0) - :deductQty::numeric, 0)`,
|
|
644
|
+
lockedUomValue: () => `GREATEST(COALESCE(locked_uom_value, 0) - :deductUomValue::numeric, 0)`,
|
|
639
645
|
updater: user
|
|
640
646
|
})
|
|
647
|
+
.setParameter('deductQty', deductQty)
|
|
648
|
+
.setParameter('deductUomValue', deductUomValue)
|
|
641
649
|
.where('id = :id', { id: inventory.id })
|
|
642
650
|
.execute()
|
|
643
651
|
}
|
|
@@ -843,7 +851,6 @@ async function assignAdditionalInventory(
|
|
|
843
851
|
acc.query.push(`${itm.query} ${itm?.operator ? itm.operator : '='} $${acc.values.length + 1}`)
|
|
844
852
|
break
|
|
845
853
|
}
|
|
846
|
-
acc.query.push(`${itm.query} ${itm?.operator ? itm.operator : '='} $${acc.values.length + 1}`)
|
|
847
854
|
return acc
|
|
848
855
|
},
|
|
849
856
|
{
|
|
@@ -895,6 +902,14 @@ async function assignAdditionalInventory(
|
|
|
895
902
|
const releaseShelfLifeParamIndex = params.length + 1
|
|
896
903
|
params.push(releaseShelfLifeOverride)
|
|
897
904
|
|
|
905
|
+
// Add seed row parameters to avoid SQL injection
|
|
906
|
+
const seedUomIdx = params.length + 1
|
|
907
|
+
const seedPackingTypeIdx = params.length + 2
|
|
908
|
+
const seedPackingSizeIdx = params.length + 3
|
|
909
|
+
const seedReleaseQtyIdx = params.length + 4
|
|
910
|
+
const seedReleaseUomValueIdx = params.length + 5
|
|
911
|
+
params.push(uom, orderProduct.packingType, orderProduct.packingSize, qtyDiff, uomValueDiff)
|
|
912
|
+
|
|
898
913
|
// Build the sophisticated SQL query with window functions
|
|
899
914
|
let query = `
|
|
900
915
|
update inventories tgt set locked_qty = coalesce(locked_qty,0) + src.reserve_qty,
|
|
@@ -915,12 +930,10 @@ async function assignAdditionalInventory(
|
|
|
915
930
|
) as available_qty,
|
|
916
931
|
sum(qty - locked_qty - release_qty - unassigned_qty) over (order by sort_seq asc rows between unbounded preceding and current row) as lock_amount
|
|
917
932
|
from (
|
|
918
|
-
SELECT 0 as sort_seq, null as id, null as pallet_id, null as batch_id, null as batch_id_ref,
|
|
919
|
-
null as unit,
|
|
920
|
-
orderProduct.packingSize
|
|
921
|
-
}' as packing_size,
|
|
933
|
+
SELECT 0 as sort_seq, null as id, null as pallet_id, null as batch_id, null as batch_id_ref,
|
|
934
|
+
null as unit, $${seedUomIdx} as uom, $${seedPackingTypeIdx} as packing_type, $${seedPackingSizeIdx} as packing_size,
|
|
922
935
|
null as manufacture_year, null as carton_id, 0 as uom_value, 0 as locked_uom_value, 0 as qty, 0 as locked_qty, 0 as unassigned_uom_value, 0 as unassigned_qty, null as created_at,
|
|
923
|
-
|
|
936
|
+
$${seedReleaseQtyIdx}::numeric as release_qty, $${seedReleaseUomValueIdx}::numeric as release_uom_value
|
|
924
937
|
UNION
|
|
925
938
|
(
|
|
926
939
|
SELECT ROW_NUMBER() OVER(PARTITION BY iv.domain_id ORDER BY wiar.rank ${
|
|
@@ -959,33 +972,49 @@ async function assignAdditionalInventory(
|
|
|
959
972
|
) dt1
|
|
960
973
|
) dt2 where case when "lock_amount" < 0 then "available_qty" else "available_qty" - "lock_amount" end > 0
|
|
961
974
|
) dt3 where sort_seq > 0
|
|
962
|
-
) src where src.id = tgt.id
|
|
963
|
-
returning
|
|
964
|
-
tgt."id", tgt."qty", tgt."pallet_id", tgt."carton_id", tgt."batch_id", tgt."batch_id_ref", tgt."unit",
|
|
965
|
-
tgt."uom", tgt."packing_type", tgt."packing_size", tgt."manufacture_year",
|
|
975
|
+
) src where src.id = tgt.id AND tgt.qty >= coalesce(tgt.locked_qty, 0) + src.reserve_qty
|
|
976
|
+
returning
|
|
977
|
+
tgt."id", tgt."qty", tgt."pallet_id", tgt."carton_id", tgt."batch_id", tgt."batch_id_ref", tgt."unit",
|
|
978
|
+
tgt."uom", tgt."packing_type", tgt."packing_size", tgt."manufacture_year",
|
|
966
979
|
tgt."locked_qty", tgt."uom_value", tgt."locked_uom_value", src."reserve_qty", src."reserve_uom_value";`
|
|
967
980
|
|
|
968
|
-
|
|
981
|
+
const MAX_ASSIGN_RETRIES = 3
|
|
982
|
+
const RETRY_DELAY_MS = 10
|
|
983
|
+
let updatedInventories
|
|
969
984
|
|
|
970
|
-
let
|
|
971
|
-
|
|
972
|
-
return acc + inv.reserve_qty
|
|
973
|
-
}, 0) || 0
|
|
985
|
+
for (let attempt = 1; attempt <= MAX_ASSIGN_RETRIES; attempt++) {
|
|
986
|
+
await tx.query('SAVEPOINT assign_retry')
|
|
974
987
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
988
|
+
updatedInventories = await tx.getRepository(Inventory).query(query, params)
|
|
989
|
+
|
|
990
|
+
let totalAssigned =
|
|
991
|
+
updatedInventories[0]?.reduce((acc: number, inv: any) => {
|
|
992
|
+
return acc + Number(inv.reserve_qty)
|
|
993
|
+
}, 0) || 0
|
|
994
|
+
|
|
995
|
+
// For non-decimal products, round the values before comparison
|
|
996
|
+
let roundedQtyDiff = qtyDiff
|
|
997
|
+
if (!product.isInventoryDecimal) {
|
|
998
|
+
roundedQtyDiff = Math.round(qtyDiff)
|
|
999
|
+
totalAssigned = Math.round(totalAssigned)
|
|
1000
|
+
} else {
|
|
1001
|
+
// For decimal products, round to 3 decimal places
|
|
1002
|
+
roundedQtyDiff = Math.round(qtyDiff * 1000) / 1000
|
|
1003
|
+
totalAssigned = Math.round(totalAssigned * 1000) / 1000
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
if (Math.abs(roundedQtyDiff - totalAssigned) > 0.001) {
|
|
1007
|
+
await tx.query('ROLLBACK TO SAVEPOINT assign_retry')
|
|
1008
|
+
if (attempt < MAX_ASSIGN_RETRIES) {
|
|
1009
|
+
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS))
|
|
1010
|
+
continue
|
|
1011
|
+
}
|
|
1012
|
+
// Using small epsilon for float comparison
|
|
1013
|
+
throw new ApiError('E01', 'INSUFFICIENT_STOCK')
|
|
1014
|
+
}
|
|
985
1015
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
throw new ApiError('E01', 'INSUFFICIENT_STOCK')
|
|
1016
|
+
await tx.query('RELEASE SAVEPOINT assign_retry')
|
|
1017
|
+
break
|
|
989
1018
|
}
|
|
990
1019
|
|
|
991
1020
|
// Create order inventory records for each assigned inventory
|
|
@@ -1225,9 +1254,11 @@ async function addNewOrderProduct(
|
|
|
1225
1254
|
.createQueryBuilder()
|
|
1226
1255
|
.update(ProductDetailStock)
|
|
1227
1256
|
.set({
|
|
1228
|
-
unassignedQty: () => `"unassigned_qty" +
|
|
1229
|
-
unassignedUomValue: () => `"unassigned_uom_value" +
|
|
1257
|
+
unassignedQty: () => `"unassigned_qty" + :releaseQty::numeric`,
|
|
1258
|
+
unassignedUomValue: () => `"unassigned_uom_value" + :releaseUomValue::numeric`
|
|
1230
1259
|
})
|
|
1260
|
+
.setParameter('releaseQty', roundedReleaseQty)
|
|
1261
|
+
.setParameter('releaseUomValue', releaseUomValue)
|
|
1231
1262
|
.where({ productDetail: productDetail.id })
|
|
1232
1263
|
.execute()
|
|
1233
1264
|
}
|
|
@@ -1281,6 +1281,7 @@ async function assignToInventory(orderProducts: OrderProduct[], customerBizplace
|
|
|
1281
1281
|
|
|
1282
1282
|
let queryFilters = []
|
|
1283
1283
|
|
|
1284
|
+
// TODO: Fix reducer bugs — typo itm.filtes -> itm.filters, duplicate acc.query.push, missing return acc
|
|
1284
1285
|
let queryStrings = queryFilters.reduce(
|
|
1285
1286
|
(acc, itm, idx, arr) => {
|
|
1286
1287
|
acc.values.push(itm.filters)
|
|
@@ -1332,7 +1333,9 @@ async function assignToInventory(orderProducts: OrderProduct[], customerBizplace
|
|
|
1332
1333
|
qty - locked_qty as available_qty,
|
|
1333
1334
|
sum(qty - locked_qty - release_qty) over (order by sort_seq asc rows between unbounded preceding and current row) as lock_amount
|
|
1334
1335
|
from (
|
|
1335
|
-
|
|
1336
|
+
// TODO: Replace template literal interpolation with $N positional parameters (see unstable/add-release-order.ts for pattern)
|
|
1337
|
+
// String values (uom, packingType, packingSize) are SQL injection vectors; numeric values need ::numeric casts
|
|
1338
|
+
SELECT 0 as sort_seq, null as id, null as pallet_id, null as batch_id, null as batch_id_ref,
|
|
1336
1339
|
null as unit, '${orderInventory.uom}' as uom, '${orderInventory.packingType}' as packing_type, '${
|
|
1337
1340
|
orderInventory.packingSize
|
|
1338
1341
|
}' as packing_size,
|
|
@@ -1352,12 +1355,14 @@ async function assignToInventory(orderProducts: OrderProduct[], customerBizplace
|
|
|
1352
1355
|
WHERE case when coalesce("iv"."locked_qty",0) < 0 then 0 else ("iv"."qty" - coalesce("iv"."locked_qty",0)) end > 0 AND
|
|
1353
1356
|
"iv"."domain_id" = $2 AND "iv"."bizplace_id" = $3 AND "iv"."packing_type" = $4 AND "iv"."packing_size" = $5 AND "iv"."product_id" = $6 AND "iv"."status" = $7 AND "loc"."type" NOT IN ($8, $9)
|
|
1354
1357
|
AND "iv"."obsolete" = false AND case when "iv"."expiration_date" is not null and "p"."min_outbound_shelf_life" is not null then CURRENT_DATE < "iv"."expiration_date" - "p"."min_outbound_shelf_life" else true end
|
|
1355
|
-
${queryStrings.query.length > 0 ? `AND ${queryStrings.join(' AND ')}` : ''}
|
|
1358
|
+
${queryStrings.query.length > 0 ? `AND ${queryStrings.query.join(' AND ')}` : ''}
|
|
1356
1359
|
ORDER BY ${sortQuery}
|
|
1357
1360
|
)
|
|
1358
1361
|
) dt1
|
|
1359
1362
|
) dt2 where case when "lock_amount" < 0 then "available_qty" else "available_qty" - "lock_amount" end > 0
|
|
1360
1363
|
) dt3 where sort_seq > 0
|
|
1364
|
+
// TODO: Add over-assignment guard: AND tgt.qty >= coalesce(tgt.locked_qty, 0) + src.reserve_qty
|
|
1365
|
+
// TODO: Add SAVEPOINT retry logic with Number() cast on reserve_qty (see v1/add-release-order.ts for pattern)
|
|
1361
1366
|
) src where src.id = tgt.id
|
|
1362
1367
|
returning
|
|
1363
1368
|
tgt."id", tgt."qty", tgt."pallet_id", tgt."carton_id", tgt."batch_id", tgt."batch_id_ref", tgt."unit",
|