@things-factory/operato-hub 4.3.769 → 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/utils/params.js +3 -0
- package/dist-server/routers/api/restful-apis/v1/utils/params.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/get-inbound-order-details.js +4 -3
- package/dist-server/routers/api/restful-apis/v1/warehouse/get-inbound-order-details.js.map +1 -1
- package/dist-server/routers/api/restful-apis/v1/warehouse/get-inventory-warehouse-group-list.js +91 -0
- package/dist-server/routers/api/restful-apis/v1/warehouse/get-inventory-warehouse-group-list.js.map +1 -0
- package/dist-server/routers/api/restful-apis/v1/warehouse/get-release-order-details.js +2 -1
- package/dist-server/routers/api/restful-apis/v1/warehouse/get-release-order-details.js.map +1 -1
- package/dist-server/routers/api/restful-apis/v1/warehouse/get-return-order-details.js +3 -2
- package/dist-server/routers/api/restful-apis/v1/warehouse/get-return-order-details.js.map +1 -1
- package/dist-server/routers/api/restful-apis/v1/warehouse/index.js +1 -0
- package/dist-server/routers/api/restful-apis/v1/warehouse/index.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/openapi/v1/inbound.yaml +3 -0
- package/openapi/v1/outbound.yaml +7 -0
- package/openapi/v1/return-order.yaml +3 -0
- package/package.json +25 -25
- package/server/routers/api/restful-apis/unstable/add-release-order.ts +90 -50
- package/server/routers/api/restful-apis/v1/utils/params.ts +3 -0
- package/server/routers/api/restful-apis/v1/warehouse/add-release-order.ts +63 -48
- package/server/routers/api/restful-apis/v1/warehouse/get-inbound-order-details.ts +2 -1
- package/server/routers/api/restful-apis/v1/warehouse/get-inventory-warehouse-group-list.ts +114 -0
- package/server/routers/api/restful-apis/v1/warehouse/get-release-order-details.ts +2 -1
- package/server/routers/api/restful-apis/v1/warehouse/get-return-order-details.ts +2 -1
- package/server/routers/api/restful-apis/v1/warehouse/index.ts +1 -0
- package/server/routers/api/restful-apis/v1/warehouse/update-release-order-details.ts +69 -38
- package/server/routers/xilnex-router.ts +7 -2
|
@@ -141,7 +141,8 @@ router.get(
|
|
|
141
141
|
zone: src.loc_zone,
|
|
142
142
|
warehouse: {
|
|
143
143
|
name: src.warehouse_name,
|
|
144
|
-
description: src.warehouse_description
|
|
144
|
+
description: src.warehouse_description,
|
|
145
|
+
refCode: src.warehouse_ref_code
|
|
145
146
|
}
|
|
146
147
|
},
|
|
147
148
|
orderProducts: orderProducts.find(op => op.id === src.oi_order_product_id),
|
|
@@ -121,7 +121,8 @@ router.get(
|
|
|
121
121
|
warehouse: {
|
|
122
122
|
id: orderInv.inventory?.location?.warehouse?.id,
|
|
123
123
|
name: orderInv.inventory?.location?.warehouse?.name,
|
|
124
|
-
description: orderInv.inventory?.location?.warehouse?.description
|
|
124
|
+
description: orderInv.inventory?.location?.warehouse?.description,
|
|
125
|
+
refCode: orderInv.inventory?.location?.warehouse?.refCode
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
}
|
|
@@ -17,6 +17,7 @@ import './get-inbound-order-list'
|
|
|
17
17
|
import './get-inventory-adjustment-details'
|
|
18
18
|
import './get-inventory-adjustment-list'
|
|
19
19
|
import './get-inventory-product-group-list'
|
|
20
|
+
import './get-inventory-warehouse-group-list'
|
|
20
21
|
import './get-manifest-details'
|
|
21
22
|
import './get-onhand-inventory-list'
|
|
22
23
|
import './get-release-order-details'
|
|
@@ -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",
|