@things-factory/worksheet-base 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/dist-server/constants/template.js +2 -1
- package/dist-server/constants/template.js.map +1 -1
- package/dist-server/controllers/inbound/unloading-worksheet-controller.js +17 -0
- package/dist-server/controllers/inbound/unloading-worksheet-controller.js.map +1 -1
- package/dist-server/controllers/index.js +1 -0
- package/dist-server/controllers/index.js.map +1 -1
- package/dist-server/controllers/outbound/loading-worksheet-controller.js +53 -0
- package/dist-server/controllers/outbound/loading-worksheet-controller.js.map +1 -1
- package/dist-server/controllers/outbound/picking-worksheet-controller.js +151 -42
- package/dist-server/controllers/outbound/picking-worksheet-controller.js.map +1 -1
- package/dist-server/controllers/render-packing-label.js +160 -0
- package/dist-server/controllers/render-packing-label.js.map +1 -0
- package/dist-server/graphql/resolvers/worksheet/loading/create-group-loading-packages.js +20 -0
- package/dist-server/graphql/resolvers/worksheet/loading/create-group-loading-packages.js.map +1 -0
- package/dist-server/graphql/resolvers/worksheet/loading/index.js +2 -1
- package/dist-server/graphql/resolvers/worksheet/loading/index.js.map +1 -1
- package/dist-server/graphql/resolvers/worksheet/picking-worksheet.js +6 -1
- package/dist-server/graphql/resolvers/worksheet/picking-worksheet.js.map +1 -1
- package/dist-server/graphql/resolvers/worksheet/putaway-replenishment-worksheet.js +2 -1
- package/dist-server/graphql/resolvers/worksheet/putaway-replenishment-worksheet.js.map +1 -1
- package/dist-server/graphql/resolvers/worksheet/putaway-returning-worksheet.js +2 -1
- package/dist-server/graphql/resolvers/worksheet/putaway-returning-worksheet.js.map +1 -1
- package/dist-server/graphql/resolvers/worksheet/putaway-worksheet.js +1 -0
- package/dist-server/graphql/resolvers/worksheet/putaway-worksheet.js.map +1 -1
- package/dist-server/graphql/resolvers/worksheet/return-worksheet.js +2 -1
- package/dist-server/graphql/resolvers/worksheet/return-worksheet.js.map +1 -1
- package/dist-server/graphql/types/worksheet/group-loading-package-item.js +11 -0
- package/dist-server/graphql/types/worksheet/group-loading-package-item.js.map +1 -0
- package/dist-server/graphql/types/worksheet/index.js +8 -1
- package/dist-server/graphql/types/worksheet/index.js.map +1 -1
- package/dist-server/graphql/types/worksheet/worksheet-detail-info.js +1 -0
- package/dist-server/graphql/types/worksheet/worksheet-detail-info.js.map +1 -1
- package/dist-server/routes.js +11 -0
- package/dist-server/routes.js.map +1 -1
- package/package.json +13 -13
- package/server/constants/template.ts +2 -1
- package/server/controllers/inbound/unloading-worksheet-controller.ts +20 -0
- package/server/controllers/index.ts +1 -0
- package/server/controllers/outbound/loading-worksheet-controller.ts +67 -0
- package/server/controllers/outbound/picking-worksheet-controller.ts +167 -43
- package/server/controllers/render-packing-label.ts +199 -0
- package/server/graphql/resolvers/worksheet/loading/create-group-loading-packages.ts +32 -0
- package/server/graphql/resolvers/worksheet/loading/index.ts +3 -1
- package/server/graphql/resolvers/worksheet/picking-worksheet.ts +5 -1
- package/server/graphql/resolvers/worksheet/putaway-replenishment-worksheet.ts +2 -1
- package/server/graphql/resolvers/worksheet/putaway-returning-worksheet.ts +2 -1
- package/server/graphql/resolvers/worksheet/putaway-worksheet.ts +1 -0
- package/server/graphql/resolvers/worksheet/return-worksheet.ts +2 -1
- package/server/graphql/types/worksheet/group-loading-package-item.ts +8 -0
- package/server/graphql/types/worksheet/index.ts +8 -1
- package/server/graphql/types/worksheet/worksheet-detail-info.ts +1 -0
- package/server/routes.ts +11 -0
|
@@ -164,6 +164,7 @@ export class UnloadingWorksheetController extends VasWorksheetController {
|
|
|
164
164
|
expirationDate: inventory?.expirationDate ? new Date(inventory.expirationDate) : undefined,
|
|
165
165
|
status: INVENTORY_STATUS.UNLOADED,
|
|
166
166
|
qty: qty,
|
|
167
|
+
conditionOfGoods: inventory?.conditionOfGoods,
|
|
167
168
|
manufactureDate: inventory?.manufactureDate ? new Date(inventory.manufactureDate) : undefined,
|
|
168
169
|
}
|
|
169
170
|
|
|
@@ -357,6 +358,12 @@ export class UnloadingWorksheetController extends VasWorksheetController {
|
|
|
357
358
|
})
|
|
358
359
|
}
|
|
359
360
|
|
|
361
|
+
if (inventory?.conditionOfGoods) {
|
|
362
|
+
invQb.andWhere('INV.condition_of_goods = :conditionOfGoods', {
|
|
363
|
+
conditionOfGoods: inventory.conditionOfGoods
|
|
364
|
+
})
|
|
365
|
+
}
|
|
366
|
+
|
|
360
367
|
if (inventory?.cartonId) invQb.andWhere('INV.carton_id = :cartonId', { cartonId: inventory.cartonId })
|
|
361
368
|
else invQb.andWhere('INV.pallet_id = :palletId', { palletId: inventory.palletId })
|
|
362
369
|
|
|
@@ -375,6 +382,7 @@ export class UnloadingWorksheetController extends VasWorksheetController {
|
|
|
375
382
|
targetProduct.packQty
|
|
376
383
|
: 0
|
|
377
384
|
: null
|
|
385
|
+
const conditionOfGoods: string = inventory?.conditionOfGoods
|
|
378
386
|
|
|
379
387
|
if (!foundInventory) {
|
|
380
388
|
let newInventory: Partial<Inventory> = new Inventory()
|
|
@@ -413,6 +421,7 @@ export class UnloadingWorksheetController extends VasWorksheetController {
|
|
|
413
421
|
newInventory.location = location
|
|
414
422
|
newInventory.zone = zone
|
|
415
423
|
newInventory.status = INVENTORY_STATUS.CHECKED
|
|
424
|
+
newInventory.conditionOfGoods = conditionOfGoods
|
|
416
425
|
newInventory.creator = this.user
|
|
417
426
|
|
|
418
427
|
if (inventory?.expirationDate) {
|
|
@@ -451,6 +460,7 @@ export class UnloadingWorksheetController extends VasWorksheetController {
|
|
|
451
460
|
: targetInventory.returnUomValue / targetInventory.returnQty) *
|
|
452
461
|
1000
|
|
453
462
|
) / 1000
|
|
463
|
+
foundInventory.conditionOfGoods = conditionOfGoods ?? foundInventory.conditionOfGoods // DEFAULT BACK TO PREVIOUS INVENTORY'S CONDITION OF GOODS
|
|
454
464
|
|
|
455
465
|
//refer to scanUnload
|
|
456
466
|
foundInventory = await this.transactionInventory(
|
|
@@ -593,6 +603,12 @@ export class UnloadingWorksheetController extends VasWorksheetController {
|
|
|
593
603
|
})
|
|
594
604
|
}
|
|
595
605
|
|
|
606
|
+
if (inventory?.conditionOfGoods) {
|
|
607
|
+
invQb.andWhere('INV.condition_of_goods = :conditionOfGoods', {
|
|
608
|
+
conditionOfGoods: inventory.conditionOfGoods
|
|
609
|
+
})
|
|
610
|
+
}
|
|
611
|
+
|
|
596
612
|
if (inventory?.cartonId) invQb.andWhere('INV.carton_id = :cartonId', { cartonId: inventory.cartonId })
|
|
597
613
|
else invQb.andWhere('INV.pallet_id = :palletId', { palletId: inventory.palletId })
|
|
598
614
|
|
|
@@ -611,6 +627,7 @@ export class UnloadingWorksheetController extends VasWorksheetController {
|
|
|
611
627
|
targetProduct.packQty
|
|
612
628
|
: 0
|
|
613
629
|
: null
|
|
630
|
+
const conditionOfGoods: string = inventory?.conditionOfGoods
|
|
614
631
|
|
|
615
632
|
if (!foundInventory) {
|
|
616
633
|
let newInventory: Partial<Inventory> = new Inventory()
|
|
@@ -649,6 +666,7 @@ export class UnloadingWorksheetController extends VasWorksheetController {
|
|
|
649
666
|
newInventory.location = location
|
|
650
667
|
newInventory.zone = zone
|
|
651
668
|
newInventory.status = INVENTORY_STATUS.CHECKED
|
|
669
|
+
newInventory.conditionOfGoods = conditionOfGoods
|
|
652
670
|
newInventory.creator = this.user
|
|
653
671
|
|
|
654
672
|
if (inventory?.expirationDate) {
|
|
@@ -679,6 +697,8 @@ export class UnloadingWorksheetController extends VasWorksheetController {
|
|
|
679
697
|
1000
|
|
680
698
|
) / 1000
|
|
681
699
|
foundInventory.productDetail = productDetail
|
|
700
|
+
foundInventory.conditionOfGoods = conditionOfGoods ?? foundInventory.conditionOfGoods // DEFAULT BACK TO PREVIOUS INVENTORY'S CONDITION OF GOODS
|
|
701
|
+
|
|
682
702
|
if (arrivalNotice) targetProduct.actualPackQty = targetProduct.actualPackQty + qty
|
|
683
703
|
else if (returnOrder) targetInventory.actualPackQty = targetInventory.actualPackQty + qty
|
|
684
704
|
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { Equal, In, Not, IsNull } from 'typeorm'
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
2
3
|
|
|
3
4
|
import { Bizplace } from '@things-factory/biz-base'
|
|
4
5
|
import {
|
|
5
6
|
DeliveryOrder,
|
|
7
|
+
LoadingPackages,
|
|
8
|
+
LoadingPackageItems,
|
|
6
9
|
ORDER_INVENTORY_STATUS,
|
|
7
10
|
ORDER_STATUS,
|
|
8
11
|
ORDER_TYPES,
|
|
9
12
|
OrderInventory,
|
|
10
13
|
OrderNoGenerator,
|
|
14
|
+
OrderProduct,
|
|
11
15
|
OrderTote,
|
|
12
16
|
OrderToteItem,
|
|
13
17
|
OrderVas,
|
|
@@ -443,4 +447,67 @@ export class LoadingWorksheetController extends VasWorksheetController {
|
|
|
443
447
|
|
|
444
448
|
return completeWorksheet
|
|
445
449
|
}
|
|
450
|
+
|
|
451
|
+
async createGroupLoadingPackages(
|
|
452
|
+
releaseGood: ReleaseGood,
|
|
453
|
+
groupedItems: Array<{ orderProductId: string; groupQty: number }>
|
|
454
|
+
): Promise<LoadingPackages> {
|
|
455
|
+
// Create LoadingPackages
|
|
456
|
+
const loadingPackageName = OrderNoGenerator.loadingPackage()
|
|
457
|
+
const loadingPackage: LoadingPackages = await this.trxMgr.getRepository(LoadingPackages).save({
|
|
458
|
+
name: loadingPackageName,
|
|
459
|
+
domain: this.domain,
|
|
460
|
+
bizplace: releaseGood.bizplace,
|
|
461
|
+
releaseGood: releaseGood,
|
|
462
|
+
status: ORDER_STATUS.DONE,
|
|
463
|
+
creator: this.user,
|
|
464
|
+
updater: this.user
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
// Create LoadingPackageItems for each grouped item
|
|
468
|
+
const loadingPackageItems: LoadingPackageItems[] = []
|
|
469
|
+
|
|
470
|
+
for (const groupedItem of groupedItems) {
|
|
471
|
+
// Validate groupQty
|
|
472
|
+
if (groupedItem.groupQty == null || isNaN(groupedItem.groupQty)) {
|
|
473
|
+
throw new Error('Invalid group quantity')
|
|
474
|
+
}
|
|
475
|
+
if (groupedItem.groupQty <= 0) {
|
|
476
|
+
throw new Error('Group quantity must be greater than zero')
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Fetch the OrderProduct directly
|
|
480
|
+
const orderProduct: OrderProduct | null = await this.trxMgr.getRepository(OrderProduct).findOne({
|
|
481
|
+
where: { id: groupedItem.orderProductId, domain: this.domain, releaseGood: releaseGood },
|
|
482
|
+
relations: ['productDetail']
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
if (!orderProduct) {
|
|
486
|
+
throw new Error(`Order product not found: ${groupedItem.orderProductId}`)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const loadingPackageItemName = OrderNoGenerator.loadingPackageItem()
|
|
490
|
+
loadingPackageItems.push({
|
|
491
|
+
name: loadingPackageItemName,
|
|
492
|
+
domain: this.domain,
|
|
493
|
+
bizplace: releaseGood.bizplace,
|
|
494
|
+
loadingPackage: loadingPackage,
|
|
495
|
+
orderProduct,
|
|
496
|
+
packedQty: groupedItem.groupQty,
|
|
497
|
+
productDetail: orderProduct.productDetail ?? null,
|
|
498
|
+
status: ORDER_STATUS.DONE,
|
|
499
|
+
creator: this.user,
|
|
500
|
+
updater: this.user
|
|
501
|
+
} as LoadingPackageItems)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Save all loading package items
|
|
505
|
+
await this.trxMgr.getRepository(LoadingPackageItems).save(loadingPackageItems)
|
|
506
|
+
|
|
507
|
+
// Reload with relations
|
|
508
|
+
return await this.trxMgr.getRepository(LoadingPackages).findOne({
|
|
509
|
+
where: { id: loadingPackage.id },
|
|
510
|
+
relations: ['loadingPackageItems', 'loadingPackageItems.orderProduct']
|
|
511
|
+
})
|
|
512
|
+
}
|
|
446
513
|
}
|
|
@@ -795,10 +795,28 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
795
795
|
newTargetInventory.updater = this.user
|
|
796
796
|
newTargetInventory = await this.trxMgr.getRepository(OrderInventory).save(newTargetInventory)
|
|
797
797
|
|
|
798
|
-
//
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
798
|
+
// Atomic update of locked qty and uomValue of inventory
|
|
799
|
+
const lockResult = await this.trxMgr
|
|
800
|
+
.getRepository(Inventory)
|
|
801
|
+
.createQueryBuilder()
|
|
802
|
+
.update(Inventory)
|
|
803
|
+
.set({
|
|
804
|
+
lockedQty: () => `COALESCE("locked_qty", 0) + :releaseQty::numeric`,
|
|
805
|
+
lockedUomValue: () => `COALESCE("locked_uom_value", 0) + :releaseUomValue::numeric`,
|
|
806
|
+
updater: this.user,
|
|
807
|
+
updatedAt: new Date()
|
|
808
|
+
})
|
|
809
|
+
.setParameter('releaseQty', targetInventory.releaseQty)
|
|
810
|
+
.setParameter('releaseUomValue', targetInventory.releaseUomValue)
|
|
811
|
+
.where('id = :id AND qty >= COALESCE(locked_qty, 0) + :newQty', {
|
|
812
|
+
id: inventory.id,
|
|
813
|
+
newQty: targetInventory.releaseQty
|
|
814
|
+
})
|
|
815
|
+
.execute()
|
|
816
|
+
|
|
817
|
+
if (lockResult.affected === 0) {
|
|
818
|
+
throw new Error(`Insufficient inventory for picking assignment`)
|
|
819
|
+
}
|
|
802
820
|
|
|
803
821
|
// Create worksheet details
|
|
804
822
|
await this.createWorksheetDetails(worksheet, WORKSHEET_TYPE.PICKING, [newTargetInventory])
|
|
@@ -837,12 +855,21 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
837
855
|
const targetInventory: OrderInventory = worksheetDetail.targetInventory
|
|
838
856
|
targetInventoryIds.push(targetInventory.id)
|
|
839
857
|
|
|
840
|
-
|
|
858
|
+
// Atomic update of locked qty and uomValue of inventory
|
|
859
|
+
await this.trxMgr
|
|
841
860
|
.getRepository(Inventory)
|
|
842
|
-
.
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
861
|
+
.createQueryBuilder()
|
|
862
|
+
.update(Inventory)
|
|
863
|
+
.set({
|
|
864
|
+
lockedQty: () => `GREATEST(COALESCE("locked_qty", 0) - :releaseQty::numeric, 0)`,
|
|
865
|
+
lockedUomValue: () => `GREATEST(COALESCE("locked_uom_value", 0) - :releaseUomValue::numeric, 0)`,
|
|
866
|
+
updater: this.user,
|
|
867
|
+
updatedAt: new Date()
|
|
868
|
+
})
|
|
869
|
+
.setParameter('releaseQty', targetInventory.releaseQty)
|
|
870
|
+
.setParameter('releaseUomValue', targetInventory.releaseUomValue)
|
|
871
|
+
.where('id = :id', { id: worksheetDetail.targetInventory.inventory.id })
|
|
872
|
+
.execute()
|
|
846
873
|
|
|
847
874
|
await this.trxMgr
|
|
848
875
|
.getRepository(OrderProduct)
|
|
@@ -1008,6 +1035,14 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
1008
1035
|
pickedUomValue = matchingProduct.uomValue
|
|
1009
1036
|
}
|
|
1010
1037
|
|
|
1038
|
+
// validation to prevent decimal quantities for non-decimal products
|
|
1039
|
+
const scanPickProduct: Product = await this.trxMgr.getRepository(Product).findOne({
|
|
1040
|
+
where: { id: worksheetDetailInfos.productId }
|
|
1041
|
+
})
|
|
1042
|
+
if (pickedQty % 1 !== 0 && !scanPickProduct?.isInventoryDecimal) {
|
|
1043
|
+
throw new Error('Decimal quantities are not allowed for this product')
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1011
1046
|
// //validation to prevent over release
|
|
1012
1047
|
if (!targetInventory)
|
|
1013
1048
|
throw new Error(this.ERROR_MSG.VALIDITY.CANT_PROCEED_STEP_BY('picking', `inventory not assigned`))
|
|
@@ -1088,14 +1123,14 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
1088
1123
|
targetInventory.pickedQty = (targetInventory?.pickedQty || 0) + pickedQty
|
|
1089
1124
|
|
|
1090
1125
|
let updateOiObj = {
|
|
1091
|
-
pickedQty: () => `"picked_qty" +
|
|
1126
|
+
pickedQty: () => `"picked_qty" + :pickedQty`,
|
|
1092
1127
|
updatedAt: new Date(),
|
|
1093
1128
|
updater: this.user,
|
|
1094
1129
|
pickedBy: this.user.name,
|
|
1095
1130
|
pickedByUser: this.user,
|
|
1096
1131
|
pickedAt: new Date(),
|
|
1097
1132
|
status: () =>
|
|
1098
|
-
`case when release_qty = "picked_qty" +
|
|
1133
|
+
`case when release_qty = "picked_qty" + :pickedQty then '${ORDER_INVENTORY_STATUS.PICKED}' else status end`
|
|
1099
1134
|
}
|
|
1100
1135
|
|
|
1101
1136
|
if (targetInventory.binLocation) {
|
|
@@ -1107,6 +1142,7 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
1107
1142
|
.createQueryBuilder()
|
|
1108
1143
|
.update(OrderInventory)
|
|
1109
1144
|
.set(updateOiObj)
|
|
1145
|
+
.setParameter('pickedQty', pickedQty)
|
|
1110
1146
|
.where({ id: targetInventory.id })
|
|
1111
1147
|
.andWhere(`picked_qty + :pickedQty <= release_qty`, { pickedQty })
|
|
1112
1148
|
.execute()
|
|
@@ -1130,12 +1166,12 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
1130
1166
|
let releaseUomValue = Math.trunc((pickedUomValue / pickedQty) * releaseQty * 1000) / 1000
|
|
1131
1167
|
|
|
1132
1168
|
let updateInvObj = {
|
|
1133
|
-
qty: () => `"qty" -
|
|
1134
|
-
lockedQty: () => `"locked_qty" -
|
|
1135
|
-
uomValue: () => `"uom_value" -
|
|
1136
|
-
lockedUomValue: () => `"locked_uom_value" -
|
|
1169
|
+
qty: () => `"qty" - :deductQty::numeric`,
|
|
1170
|
+
lockedQty: () => `GREATEST("locked_qty" - :deductQty::numeric, 0)`,
|
|
1171
|
+
uomValue: () => `"uom_value" - :deductUomValue::numeric`,
|
|
1172
|
+
lockedUomValue: () => `GREATEST("locked_uom_value" - :deductUomValue::numeric, 0)`,
|
|
1137
1173
|
status: () =>
|
|
1138
|
-
`case when "qty" -
|
|
1174
|
+
`case when "qty" - :deductQty::numeric <= 0 then '${INVENTORY_STATUS.TERMINATED}' else status end`,
|
|
1139
1175
|
updater: this.user,
|
|
1140
1176
|
updatedAt: new Date()
|
|
1141
1177
|
}
|
|
@@ -1145,6 +1181,8 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
1145
1181
|
.createQueryBuilder()
|
|
1146
1182
|
.update(Inventory)
|
|
1147
1183
|
.set(updateInvObj)
|
|
1184
|
+
.setParameter('deductQty', releaseQty)
|
|
1185
|
+
.setParameter('deductUomValue', releaseUomValue)
|
|
1148
1186
|
.where('id = :id', { id: worksheetDetailInfos.inventoryId })
|
|
1149
1187
|
.returning(['qty'])
|
|
1150
1188
|
.execute()
|
|
@@ -1254,6 +1292,11 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
1254
1292
|
})
|
|
1255
1293
|
if (!oiValidate) throw new Error(this.ERROR_MSG.VALIDITY.CANT_PROCEED_STEP_BY('picking', `is done`))
|
|
1256
1294
|
|
|
1295
|
+
// validation to prevent decimal quantities for non-decimal products
|
|
1296
|
+
if (pickedQty % 1 !== 0 && !product.isInventoryDecimal) {
|
|
1297
|
+
throw new Error('Decimal quantities are not allowed for this product')
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1257
1300
|
//validation to prevent over release
|
|
1258
1301
|
if (inventory.qty <= 0) throw new Error(this.ERROR_MSG.VALIDITY.CANT_PROCEED_STEP_BY('picking', `over release`))
|
|
1259
1302
|
|
|
@@ -1426,6 +1469,16 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
1426
1469
|
if (sumOfReleaseQty != releaseQty)
|
|
1427
1470
|
throw new Error(this.ERROR_MSG.VALIDITY.CANT_PROCEED_STEP_BY('picking', `insufficient picking quantity`))
|
|
1428
1471
|
|
|
1472
|
+
// validation to prevent decimal quantities for non-decimal products
|
|
1473
|
+
if (targetInventories.length > 0) {
|
|
1474
|
+
const batchPickProduct: Product = await this.trxMgr.getRepository(Product).findOne({
|
|
1475
|
+
where: { id: targetInventories[0].productId }
|
|
1476
|
+
})
|
|
1477
|
+
if (releaseQty % 1 !== 0 && !batchPickProduct?.isInventoryDecimal) {
|
|
1478
|
+
throw new Error('Decimal quantities are not allowed for this product')
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1429
1482
|
for (var i = 0; i < targetInventories.length; i++) {
|
|
1430
1483
|
let targetInventory: OrderInventory = targetInventories[i]
|
|
1431
1484
|
let inventory: Inventory = await this.trxMgr.getRepository(Inventory).findOne({
|
|
@@ -1480,10 +1533,12 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
1480
1533
|
await this.updateOrderTargets([targetInventory])
|
|
1481
1534
|
|
|
1482
1535
|
let updateInvObj = {
|
|
1483
|
-
qty: () => `"qty" -
|
|
1484
|
-
lockedQty: () => `"locked_qty" -
|
|
1485
|
-
uomValue: () => `"uom_value" -
|
|
1486
|
-
lockedUomValue: () => `"locked_uom_value" -
|
|
1536
|
+
qty: () => `"qty" - :deductQty::numeric`,
|
|
1537
|
+
lockedQty: () => `GREATEST("locked_qty" - :deductQty::numeric, 0)`,
|
|
1538
|
+
uomValue: () => `"uom_value" - :deductUomValue::numeric`,
|
|
1539
|
+
lockedUomValue: () => `GREATEST("locked_uom_value" - :deductUomValue::numeric, 0)`,
|
|
1540
|
+
status: () =>
|
|
1541
|
+
`case when "qty" - :deductQty::numeric <= 0 then '${INVENTORY_STATUS.TERMINATED}' else status end`,
|
|
1487
1542
|
updater: this.user,
|
|
1488
1543
|
updatedAt: new Date()
|
|
1489
1544
|
}
|
|
@@ -1493,6 +1548,8 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
1493
1548
|
.createQueryBuilder()
|
|
1494
1549
|
.update(Inventory)
|
|
1495
1550
|
.set(updateInvObj)
|
|
1551
|
+
.setParameter('deductQty', targetInventory.releaseQty)
|
|
1552
|
+
.setParameter('deductUomValue', targetInventory.releaseUomValue)
|
|
1496
1553
|
.where('id = :id', { id: targetInventory.inventory.id })
|
|
1497
1554
|
.returning(['qty'])
|
|
1498
1555
|
.execute()
|
|
@@ -1599,6 +1656,11 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
1599
1656
|
|
|
1600
1657
|
pickedQty = matchingProduct.qty
|
|
1601
1658
|
|
|
1659
|
+
// validation to prevent decimal quantities for non-decimal products
|
|
1660
|
+
if (pickedQty % 1 !== 0 && !product?.isInventoryDecimal) {
|
|
1661
|
+
throw new Error('Decimal quantities are not allowed for this product')
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1602
1664
|
const sumOfReleaseQty: number = parseFloat(
|
|
1603
1665
|
targetInventories
|
|
1604
1666
|
.map((oi: OrderInventory) => oi.releaseQty)
|
|
@@ -1678,10 +1740,12 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
1678
1740
|
await this.updateOrderTargets([targetInventory])
|
|
1679
1741
|
|
|
1680
1742
|
let updateInvObj = {
|
|
1681
|
-
qty: () => `"qty" -
|
|
1682
|
-
lockedQty: () => `"locked_qty" -
|
|
1683
|
-
uomValue: () => `"uom_value" -
|
|
1684
|
-
lockedUomValue: () => `"locked_uom_value" -
|
|
1743
|
+
qty: () => `"qty" - :deductQty::numeric`,
|
|
1744
|
+
lockedQty: () => `GREATEST("locked_qty" - :deductQty::numeric, 0)`,
|
|
1745
|
+
uomValue: () => `"uom_value" - :deductUomValue::numeric`,
|
|
1746
|
+
lockedUomValue: () => `GREATEST("locked_uom_value" - :deductUomValue::numeric, 0)`,
|
|
1747
|
+
status: () =>
|
|
1748
|
+
`case when "qty" - :deductQty::numeric <= 0 then '${INVENTORY_STATUS.TERMINATED}' else status end`,
|
|
1685
1749
|
updater: this.user,
|
|
1686
1750
|
updatedAt: new Date()
|
|
1687
1751
|
}
|
|
@@ -1691,6 +1755,8 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
1691
1755
|
.createQueryBuilder()
|
|
1692
1756
|
.update(Inventory)
|
|
1693
1757
|
.set(updateInvObj)
|
|
1758
|
+
.setParameter('deductQty', targetInventory.releaseQty)
|
|
1759
|
+
.setParameter('deductUomValue', targetInventory.releaseUomValue)
|
|
1694
1760
|
.where('id = :id', { id: targetInventory.inventory.id })
|
|
1695
1761
|
.returning(['qty'])
|
|
1696
1762
|
.execute()
|
|
@@ -1966,6 +2032,14 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
1966
2032
|
}
|
|
1967
2033
|
|
|
1968
2034
|
private async updatePickingTransaction(releaseGood, orderInventory, worksheetDetail, inventory, pickedQty) {
|
|
2035
|
+
// validation to prevent decimal quantities for non-decimal products
|
|
2036
|
+
const pickTxProduct: Product = await this.trxMgr.getRepository(Product).findOne({
|
|
2037
|
+
where: { id: orderInventory.productId }
|
|
2038
|
+
})
|
|
2039
|
+
if (pickedQty % 1 !== 0 && !pickTxProduct?.isInventoryDecimal) {
|
|
2040
|
+
throw new Error('Decimal quantities are not allowed for this product')
|
|
2041
|
+
}
|
|
2042
|
+
|
|
1969
2043
|
const releaseQty: number = orderInventory.releaseQty
|
|
1970
2044
|
|
|
1971
2045
|
orderInventory.pickedQty = (orderInventory?.pickedQty || 0) + pickedQty
|
|
@@ -1980,18 +2054,40 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
1980
2054
|
orderInventory.pickedByUser = this.user
|
|
1981
2055
|
orderInventory.pickedAt = new Date()
|
|
1982
2056
|
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
2057
|
+
// Atomic SQL update instead of stale-read pattern
|
|
2058
|
+
await this.trxMgr
|
|
2059
|
+
.getRepository(Inventory)
|
|
2060
|
+
.createQueryBuilder()
|
|
2061
|
+
.update(Inventory)
|
|
2062
|
+
.set({
|
|
2063
|
+
qty: () => `"qty" - :deductQty::numeric`,
|
|
2064
|
+
uomValue: () => `"uom_value" - :deductUomValue::numeric`,
|
|
2065
|
+
lockedQty: () => `GREATEST("locked_qty" - :deductQty::numeric, 0)`,
|
|
2066
|
+
lockedUomValue: () => `GREATEST("locked_uom_value" - :deductUomValue::numeric, 0)`,
|
|
2067
|
+
status: () =>
|
|
2068
|
+
`case when "qty" - :deductQty::numeric <= 0 then '${INVENTORY_STATUS.TERMINATED}' else status end`,
|
|
2069
|
+
updater: this.user,
|
|
2070
|
+
updatedAt: new Date()
|
|
2071
|
+
})
|
|
2072
|
+
.setParameter('deductQty', orderInventory.releaseQty)
|
|
2073
|
+
.setParameter('deductUomValue', orderInventory.releaseUomValue)
|
|
2074
|
+
.where('id = :id', { id: inventory.id })
|
|
2075
|
+
.execute()
|
|
2076
|
+
|
|
2077
|
+
// Generate inventory history separately
|
|
2078
|
+
await generateInventoryHistory(
|
|
1988
2079
|
inventory,
|
|
1989
2080
|
releaseGood,
|
|
2081
|
+
INVENTORY_TRANSACTION_TYPE.PICKING,
|
|
1990
2082
|
-orderInventory.releaseQty,
|
|
1991
2083
|
-orderInventory.releaseUomValue,
|
|
1992
|
-
|
|
2084
|
+
this.user,
|
|
2085
|
+
this.trxMgr
|
|
1993
2086
|
)
|
|
1994
2087
|
|
|
2088
|
+
// Re-read inventory for downstream use (status check for TERMINATED)
|
|
2089
|
+
inventory = await this.trxMgr.getRepository(Inventory).findOne({ where: { id: inventory.id } })
|
|
2090
|
+
|
|
1995
2091
|
worksheetDetail.status = WORKSHEET_STATUS.DONE
|
|
1996
2092
|
worksheetDetail.updater = this.user
|
|
1997
2093
|
await this.trxMgr.getRepository(WorksheetDetail).save(worksheetDetail)
|
|
@@ -2545,20 +2641,37 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
2545
2641
|
// update inventory locked qty and uom value
|
|
2546
2642
|
oi = await transaction.getRepository(OrderInventory).save({ ...oi })
|
|
2547
2643
|
|
|
2548
|
-
await transaction
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2644
|
+
const lockResult = await transaction
|
|
2645
|
+
.getRepository(Inventory)
|
|
2646
|
+
.createQueryBuilder()
|
|
2647
|
+
.update(Inventory)
|
|
2648
|
+
.set({
|
|
2649
|
+
lockedQty: () => `COALESCE("locked_qty", 0) + :releaseQty::numeric`,
|
|
2650
|
+
lockedUomValue: () => `COALESCE("locked_uom_value", 0) + :releaseUomValue::numeric`,
|
|
2651
|
+
updater: this.user
|
|
2652
|
+
})
|
|
2653
|
+
.setParameter('releaseQty', oi.releaseQty)
|
|
2654
|
+
.setParameter('releaseUomValue', oi.releaseUomValue)
|
|
2655
|
+
.where('id = :id AND qty >= COALESCE(locked_qty, 0) + :newQty', {
|
|
2656
|
+
id: oi.inventory.id,
|
|
2657
|
+
newQty: oi.releaseQty
|
|
2658
|
+
})
|
|
2659
|
+
.execute()
|
|
2660
|
+
|
|
2661
|
+
if (lockResult.affected === 0) {
|
|
2662
|
+
throw new Error(`Insufficient inventory for picking assignment`)
|
|
2663
|
+
}
|
|
2553
2664
|
|
|
2554
2665
|
await transaction
|
|
2555
2666
|
.getRepository(ProductDetailStock)
|
|
2556
2667
|
.createQueryBuilder()
|
|
2557
2668
|
.update(ProductDetailStock)
|
|
2558
2669
|
.set({
|
|
2559
|
-
unassignedQty: () => `"unassigned_qty" -
|
|
2560
|
-
unassignedUomValue: () => `"unassigned_uom_value" -
|
|
2670
|
+
unassignedQty: () => `GREATEST("unassigned_qty" - :oiReleaseQty::numeric, 0)`,
|
|
2671
|
+
unassignedUomValue: () => `GREATEST("unassigned_uom_value" - :oiReleaseUomValue::numeric, 0)`
|
|
2561
2672
|
})
|
|
2673
|
+
.setParameter('oiReleaseQty', oi.releaseQty)
|
|
2674
|
+
.setParameter('oiReleaseUomValue', oi.releaseUomValue)
|
|
2562
2675
|
.where({ productDetail: oi.productDetail.id })
|
|
2563
2676
|
.execute()
|
|
2564
2677
|
|
|
@@ -2643,26 +2756,37 @@ export class PickingWorksheetController extends VasWorksheetController {
|
|
|
2643
2756
|
releaseUomValue = releaseUomValue - allocatedUomValue
|
|
2644
2757
|
|
|
2645
2758
|
//// Update inventory locked quantity
|
|
2646
|
-
await this.trxMgr
|
|
2759
|
+
const lockResult = await this.trxMgr
|
|
2647
2760
|
.getRepository(Inventory)
|
|
2648
2761
|
.createQueryBuilder('inv')
|
|
2649
2762
|
.update(Inventory)
|
|
2650
2763
|
.set({
|
|
2651
|
-
lockedUomValue: () => `COALESCE(locked_uom_value,0) +
|
|
2652
|
-
lockedQty: () => `COALESCE(locked_qty,0) +
|
|
2764
|
+
lockedUomValue: () => `COALESCE(locked_uom_value,0) + :allocatedUomValue::numeric`,
|
|
2765
|
+
lockedQty: () => `COALESCE(locked_qty,0) + :allocatedQty::numeric`
|
|
2766
|
+
})
|
|
2767
|
+
.setParameter('allocatedUomValue', allocatedUomValue)
|
|
2768
|
+
.setParameter('allocatedQty', allocatedQty)
|
|
2769
|
+
.where('id = :id AND qty >= COALESCE(locked_qty, 0) + :newQty', {
|
|
2770
|
+
id: targetInventory.id,
|
|
2771
|
+
newQty: allocatedQty
|
|
2653
2772
|
})
|
|
2654
|
-
.where('id = :id', { id: targetInventory.id })
|
|
2655
2773
|
.execute()
|
|
2656
2774
|
|
|
2775
|
+
if (lockResult.affected === 0) {
|
|
2776
|
+
throw new Error(`Insufficient inventory for picking assignment`)
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2657
2779
|
// update product detail stock deduct unassigned qty and unassigned uom value
|
|
2658
2780
|
await this.trxMgr
|
|
2659
2781
|
.getRepository(ProductDetailStock)
|
|
2660
2782
|
.createQueryBuilder()
|
|
2661
2783
|
.update(ProductDetailStock)
|
|
2662
2784
|
.set({
|
|
2663
|
-
unassignedQty: () => `"unassigned_qty" -
|
|
2664
|
-
unassignedUomValue: () => `"unassigned_uom_value" -
|
|
2785
|
+
unassignedQty: () => `GREATEST("unassigned_qty" - :deductQty::numeric, 0)`,
|
|
2786
|
+
unassignedUomValue: () => `GREATEST("unassigned_uom_value" - :deductUomValue::numeric, 0)`
|
|
2665
2787
|
})
|
|
2788
|
+
.setParameter('deductQty', allocatedQty)
|
|
2789
|
+
.setParameter('deductUomValue', allocatedUomValue)
|
|
2666
2790
|
.where({ productDetail: orderProducts[i].productDetail.id })
|
|
2667
2791
|
.execute()
|
|
2668
2792
|
|