@things-factory/operato-hub 4.3.248 → 4.3.249

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.
@@ -35,11 +35,12 @@ import {
35
35
  OrderProduct,
36
36
  ReleaseGood,
37
37
  SellercraftController,
38
- ShippingOrder
38
+ ShippingOrder,
39
+ ORDER_METHOD
39
40
  } from '@things-factory/sales-base'
40
41
  import { Setting } from '@things-factory/setting-base'
41
42
  import { Domain } from '@things-factory/shell'
42
- import { Inventory, INVENTORY_STATUS, LOCATION_TYPE } from '@things-factory/warehouse-base'
43
+ import { Inventory, INVENTORY_STATUS, LOCATION_TYPE, ProductDetailStock } from '@things-factory/warehouse-base'
43
44
 
44
45
  import { businessMiddleware, loggingMiddleware, validationMiddleware } from '../middlewares'
45
46
  import { ApiError, ApiErrorHandler, throwInternalServerError } from '../utils/error-util'
@@ -78,6 +79,10 @@ router.post(
78
79
  relations: ['domain']
79
80
  })
80
81
 
82
+ const worksheetPickingAssignment: Setting = await tx.getRepository(Setting).findOne({
83
+ where: { domain, category: 'id-rule', name: 'enable-worksheet-picking-activation-assignment' }
84
+ })
85
+
81
86
  // conditional validation
82
87
  if (bodyReq.ownTransport && !bodyReq.collectionOrderNo) throw new ApiError('E03', 'collectionOrderNo')
83
88
  if (bodyReq.exportOption && !bodyReq.shippingOrder) throw new ApiError('E03', 'shippingOrder')
@@ -141,7 +146,8 @@ router.post(
141
146
  to: bodyReq?.shippingOrder?.to,
142
147
  containerArrivalDate: bodyReq?.shippingOrder.containerArrivalDate,
143
148
  containerLeavingDate: bodyReq?.shippingOrder.containerLeavingDate
144
- }
149
+ },
150
+ assignedInventory: worksheetPickingAssignment?.value == 'true' ? false : true
145
151
  }
146
152
 
147
153
  // check existing order
@@ -194,12 +200,25 @@ router.post(
194
200
 
195
201
  // assignment
196
202
  // console.time('assign')
197
- let assignedOrderProducts = await assignToInventory(massagedData.orderProducts, customerBizplace, context, tx)
203
+ let assignedOrderProducts = await assignToInventory(
204
+ massagedData.orderProducts,
205
+ customerBizplace,
206
+ context,
207
+ tx,
208
+ worksheetPickingAssignment
209
+ )
198
210
  // console.timeEnd('assign')
199
211
 
200
212
  // create order
201
213
  // console.time('save')
202
- let result = await createReleaseGood(releaseGood, assignedOrderProducts, customerBizplace, context, tx)
214
+ let result = await createReleaseGood(
215
+ releaseGood,
216
+ assignedOrderProducts,
217
+ customerBizplace,
218
+ context,
219
+ tx,
220
+ worksheetPickingAssignment
221
+ )
203
222
  // console.timeEnd('save')
204
223
 
205
224
  let data = {
@@ -387,7 +406,8 @@ async function createReleaseGood(
387
406
  orderProducts: OrderProduct[],
388
407
  bizplace: Bizplace,
389
408
  context: any,
390
- tx?: EntityManager
409
+ tx?: EntityManager,
410
+ worksheetPickingAssignment?: any
391
411
  ): Promise<ReleaseGood> {
392
412
  const { domain, user }: { domain: Domain; user: User } = context.state
393
413
  const settingRepo: Repository<Setting> = tx?.getRepository(Setting) || getRepository(Setting)
@@ -502,7 +522,9 @@ async function createReleaseGood(
502
522
  acceptedAt: new Date(),
503
523
  shippingOrder: shippingOrder ? shippingOrder : null,
504
524
  lastMileDelivery: lmd,
505
- lmdOption: releaseGood.lmdOption
525
+ lmdOption: releaseGood.lmdOption,
526
+ assignedInventory: worksheetPickingAssignment?.value == 'true' ? false : true,
527
+ orderMethod: ORDER_METHOD.SELECT_BY_PRODUCT
506
528
  }
507
529
 
508
530
  let resultReleaseGood = await tx.getRepository(ReleaseGood).save(newReleaseGood)
@@ -521,26 +543,28 @@ async function createReleaseGood(
521
543
  })
522
544
  let result = await tx.getRepository(OrderProduct).save(orderProduct)
523
545
 
524
- for (let oiIdx = 0; oiIdx < orderInventories.length; oiIdx++) {
525
- let orderInventory = Object.assign(new OrderInventory(), {
526
- ...orderInventories[oiIdx],
527
- name: uuidv4(),
528
- domain: domain,
529
- bizplace: bizplace,
530
- releaseGood: resultReleaseGood,
531
- type: ORDER_TYPES.RELEASE_OF_GOODS,
532
- status: ORDER_INVENTORY_STATUS.PENDING_WORKSHEET
533
- })
534
- await tx.getRepository(OrderInventory).save(orderInventory)
535
-
536
- combinedOrderInventories.push({
537
- product: { name: orderInventory.product.name, sku: orderInventory.product.sku },
538
- batchId: orderInventory.batchId,
539
- packingType: orderInventory.packingType,
540
- packingSize: orderInventory.packingSize,
541
- releaseQty: orderInventory.releaseQty,
542
- releaseUomValue: orderInventory.releaseUomValue
543
- })
546
+ if (worksheetPickingAssignment.value !== 'true') {
547
+ for (let oiIdx = 0; oiIdx < orderInventories.length; oiIdx++) {
548
+ let orderInventory = Object.assign(new OrderInventory(), {
549
+ ...orderInventories[oiIdx],
550
+ name: uuidv4(),
551
+ domain: domain,
552
+ bizplace: bizplace,
553
+ releaseGood: resultReleaseGood,
554
+ type: ORDER_TYPES.RELEASE_OF_GOODS,
555
+ status: ORDER_INVENTORY_STATUS.PENDING_WORKSHEET
556
+ })
557
+ await tx.getRepository(OrderInventory).save(orderInventory)
558
+
559
+ combinedOrderInventories.push({
560
+ product: { name: orderInventory.product.name, sku: orderInventory.product.sku },
561
+ batchId: orderInventory.batchId,
562
+ packingType: orderInventory.packingType,
563
+ packingSize: orderInventory.packingSize,
564
+ releaseQty: orderInventory.releaseQty,
565
+ releaseUomValue: orderInventory.releaseUomValue
566
+ })
567
+ }
544
568
  }
545
569
 
546
570
  return result
@@ -825,116 +849,125 @@ async function massageOrderItems(
825
849
  return { orderProducts, combinedItems }
826
850
  }
827
851
 
828
- async function assignToInventory(orderProducts: OrderProduct[], customerBizplace, context, tx) {
852
+ async function assignToInventory(
853
+ orderProducts: OrderProduct[],
854
+ customerBizplace,
855
+ context,
856
+ tx,
857
+ worksheetPickingAssignment
858
+ ) {
829
859
  const { client, domain, user } = context.state
860
+
861
+ const pickingProductSetting: Setting = await tx.getRepository(Setting).findOne({
862
+ where: { domain, name: 'rule-for-picking-product' }
863
+ })
864
+
830
865
  for (let opIdx = 0; opIdx < orderProducts.length; opIdx++) {
831
866
  let orderProduct = orderProducts[opIdx]
832
867
 
833
868
  let assignedOrderInventories = []
834
- const pickingProductSetting: Setting = await tx.getRepository(Setting).findOne({
835
- where: { domain, name: 'rule-for-picking-product' }
836
- })
837
869
 
838
870
  for (let oiIdx = 0; oiIdx < orderProducts[opIdx].orderInventories.length; oiIdx++) {
839
871
  const orderInventory = orderProduct.orderInventories[oiIdx]
840
872
  let sortings: any = []
841
873
 
842
- switch (orderInventory.product.pickingStrategy) {
843
- case 'LIFO':
844
- sortings.push({ name: 'iv.created_at', desc: true })
845
- if (pickingProductSetting?.value) {
846
- for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
847
- sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
874
+ if (worksheetPickingAssignment?.value !== 'true') {
875
+ switch (orderInventory.product.pickingStrategy) {
876
+ case 'LIFO':
877
+ sortings.push({ name: 'iv.created_at', desc: true })
878
+ if (pickingProductSetting?.value) {
879
+ for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
880
+ sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
881
+ }
882
+ } else {
883
+ sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false })
848
884
  }
849
- } else {
850
- sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false })
851
- }
852
885
 
853
- break
854
- case 'FEFO':
855
- sortings.push({ name: 'iv.expiration_date', desc: false }, { name: 'iv.created_at', desc: false })
856
- if (pickingProductSetting?.value) {
857
- for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
858
- sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
886
+ break
887
+ case 'FEFO':
888
+ sortings.push({ name: 'iv.expiration_date', desc: false }, { name: 'iv.created_at', desc: false })
889
+ if (pickingProductSetting?.value) {
890
+ for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
891
+ sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
892
+ }
893
+ } else {
894
+ sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false })
859
895
  }
860
- } else {
861
- sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false })
862
- }
863
896
 
864
- break
865
- case 'FMFO':
866
- sortings.push({ name: 'iv.manufacture_date', desc: false }, { name: 'iv.created_at', desc: false })
867
- if (pickingProductSetting?.value) {
868
- for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
869
- sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
897
+ break
898
+ case 'FMFO':
899
+ sortings.push({ name: 'iv.manufacture_date', desc: false }, { name: 'iv.created_at', desc: false })
900
+ if (pickingProductSetting?.value) {
901
+ for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
902
+ sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
903
+ }
904
+ } else {
905
+ sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false })
870
906
  }
871
- } else {
872
- sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false })
873
- }
874
907
 
875
- break
876
- case 'LOCATION':
877
- if (pickingProductSetting?.value) {
878
- for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
879
- sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
908
+ break
909
+ case 'LOCATION':
910
+ if (pickingProductSetting?.value) {
911
+ for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
912
+ sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
913
+ }
914
+ } else {
915
+ sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false })
880
916
  }
881
- } else {
882
- sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false })
883
- }
884
917
 
885
- break
886
- //Every other case includes 'FIFO' will be applicable for this case
887
- default:
888
- sortings.push({ name: 'iv.created_at', desc: false })
889
- if (pickingProductSetting?.value) {
890
- for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
891
- sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
918
+ break
919
+ //Every other case includes 'FIFO' will be applicable for this case
920
+ default:
921
+ sortings.push({ name: 'iv.created_at', desc: false })
922
+ if (pickingProductSetting?.value) {
923
+ for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
924
+ sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
925
+ }
926
+ } else {
927
+ sortings.push({ name: 'loc.name', desc: false })
928
+ sortings.push({ name: 'iv.pallet_id', desc: false })
892
929
  }
893
- } else {
894
- sortings.push({ name: 'loc.name', desc: false })
895
- sortings.push({ name: 'iv.pallet_id', desc: false })
896
- }
897
- break
898
- }
930
+ break
931
+ }
932
+
933
+ let queryFilters = []
934
+
935
+ let queryStrings = queryFilters.reduce(
936
+ (acc, itm, idx, arr) => {
937
+ acc.values.push(itm.filters)
938
+
939
+ switch (itm?.operator) {
940
+ case 'notin':
941
+ case 'in':
942
+ acc.query.push(
943
+ `${itm.query} ${itm.operator == 'notin' ? 'NOT IN' : 'IN'} (${itm.filtes
944
+ .map((itm, idx) => {
945
+ return `$${acc.values.length + 1}`
946
+ })
947
+ .join(',')})`
948
+ )
949
+ break
899
950
 
900
- let queryFilters = []
901
-
902
- let queryStrings = queryFilters.reduce(
903
- (acc, itm, idx, arr) => {
904
- acc.values.push(itm.filters)
905
-
906
- switch (itm?.operator) {
907
- case 'notin':
908
- case 'in':
909
- acc.query.push(
910
- `${itm.query} ${itm.operator == 'notin' ? 'NOT IN' : 'IN'} (${itm.filtes
911
- .map((itm, idx) => {
912
- return `$${acc.values.length + 1}`
913
- })
914
- .join(',')})`
915
- )
916
- break
917
-
918
- default:
919
- acc.query.push(`${itm.query} ${itm?.operator ? itm.operator : '='} $${acc.values.length + 1}`)
920
- break
951
+ default:
952
+ acc.query.push(`${itm.query} ${itm?.operator ? itm.operator : '='} $${acc.values.length + 1}`)
953
+ break
954
+ }
955
+ acc.query.push(`${itm.query} ${itm?.operator ? itm.operator : '='} $${acc.values.length + 1}`)
956
+ },
957
+ {
958
+ query: [],
959
+ values: []
921
960
  }
922
- acc.query.push(`${itm.query} ${itm?.operator ? itm.operator : '='} $${acc.values.length + 1}`)
923
- },
924
- {
925
- query: [],
926
- values: []
927
- }
928
- )
961
+ )
929
962
 
930
- let sortQuery = sortings
931
- .map(itm => {
932
- return `${itm.name} ${itm.desc ? 'DESC' : 'ASC'}`
933
- })
934
- .join(`,`)
963
+ let sortQuery = sortings
964
+ .map(itm => {
965
+ return `${itm.name} ${itm.desc ? 'DESC' : 'ASC'}`
966
+ })
967
+ .join(`,`)
935
968
 
936
- let updatedInventories = await tx.getRepository(Inventory).query(
937
- `
969
+ let updatedInventories = await tx.getRepository(Inventory).query(
970
+ `
938
971
  update inventories tgt set locked_qty = coalesce(locked_qty,0) + src.reserve_qty,
939
972
  locked_uom_value = coalesce(locked_uom_value,0) + src.reserve_uom_value,
940
973
  updated_at = NOW(),
@@ -947,14 +980,14 @@ async function assignToInventory(orderProducts: OrderProduct[], customerBizplace
947
980
  case when "lock_amount" < 0 then "available_qty" else "available_qty" - "lock_amount" end as "reserve_qty"
948
981
  from (
949
982
  select *,
950
- qty - locked_qty as available_qty,
951
- sum(qty - locked_qty - release_qty) over (order by sort_seq asc rows between unbounded preceding and current row) as lock_amount
983
+ qty - locked_qty - unassigned_qty as available_qty,
984
+ sum(qty - locked_qty - release_qty - unassigned_qty) over (order by sort_seq asc rows between unbounded preceding and current row) as lock_amount
952
985
  from (
953
986
  SELECT 0 as sort_seq, null as id, null as pallet_id, null as batch_id, null as batch_id_ref,
954
987
  null as unit, '${orderInventory.uom}' as uom, '${orderInventory.packingType}' as packing_type, '${
955
- orderInventory.packingSize
956
- }' as packing_size,
957
- null as manufacture_year, null as carton_id, 0 as uom_value, 0 as locked_uom_value, 0 as qty, 0 as locked_qty, null as created_at,
988
+ orderInventory.packingSize
989
+ }' as packing_size,
990
+ 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,
958
991
  '${orderInventory.releaseQty}' as release_qty, '${orderInventory.releaseUomValue}' as release_uom_value
959
992
  UNION
960
993
  (
@@ -964,9 +997,10 @@ async function assignToInventory(orderProducts: OrderProduct[], customerBizplace
964
997
  iv.id, iv.pallet_id, iv.batch_id, iv.batch_id_ref,
965
998
  iv.unit, iv.uom, iv.packing_type, iv.packing_size,
966
999
  iv.manufacture_year, iv.carton_id, iv.uom_value,
967
- coalesce(iv.locked_uom_value,0) as locked_uom_value, iv.qty, coalesce(iv.locked_qty,0) as locked_qty, iv.created_at,
968
- 0 as release_qty, 0 as release_uom_value
1000
+ coalesce(iv.locked_uom_value,0) as locked_uom_value, iv.qty, coalesce(iv.locked_qty,0) as locked_qty, coalesce(pds.unassigned_uom_value,0) as unassigned_uom_value, coalesce(pds.unassigned_qty,0) as unassigned_qty,
1001
+ iv.created_at, 0 as release_qty, 0 as release_uom_value
969
1002
  FROM "inventories" "iv"
1003
+ LEFT JOIN "product_detail_stocks" "pds" ON "pds"."product_detail_id" = "iv"."product_detail_id"
970
1004
  INNER JOIN "locations" "loc" ON "loc"."id"="iv"."location_id"
971
1005
  INNER JOIN "products" "p" ON "p"."id"="iv"."product_id"
972
1006
  INNER JOIN "warehouse_inventory_assignment_rankings" "wiar" ON "wiar"."location_type"="loc"."type"
@@ -984,51 +1018,63 @@ async function assignToInventory(orderProducts: OrderProduct[], customerBizplace
984
1018
  tgt."id", tgt."qty", tgt."pallet_id", tgt."carton_id", tgt."batch_id", tgt."batch_id_ref", tgt."unit",
985
1019
  tgt."uom", tgt."packing_type", tgt."packing_size", tgt."manufacture_year",
986
1020
  tgt."locked_qty", tgt."uom_value", tgt."locked_uom_value", src."reserve_qty", src."reserve_uom_value";`,
987
- [
988
- user.id,
989
- domain.id,
990
- customerBizplace.id,
991
- orderInventory.packingType,
992
- orderInventory.packingSize,
993
- orderInventory.product.id,
994
- INVENTORY_STATUS.STORED,
995
- LOCATION_TYPE.QUARANTINE,
996
- LOCATION_TYPE.RESERVE,
997
- LOCATION_TYPE.STORAGE,
998
- ...queryStrings.values
999
- ]
1000
- )
1001
-
1002
- let totalAssigned = updatedInventories[0].reduce((acc, inv) => {
1003
- return acc + inv.reserve_qty
1004
- }, 0)
1021
+ [
1022
+ user.id,
1023
+ domain.id,
1024
+ customerBizplace.id,
1025
+ orderInventory.packingType,
1026
+ orderInventory.packingSize,
1027
+ orderInventory.product.id,
1028
+ INVENTORY_STATUS.STORED,
1029
+ LOCATION_TYPE.QUARANTINE,
1030
+ LOCATION_TYPE.RESERVE,
1031
+ LOCATION_TYPE.STORAGE,
1032
+ ...queryStrings.values
1033
+ ]
1034
+ )
1005
1035
 
1006
- if (orderInventory.releaseQty != totalAssigned) {
1007
- throw new ApiError('E01', 'INSUFFICIENT_STOCK')
1008
- }
1036
+ let totalAssigned = updatedInventories[0].reduce((acc, inv) => {
1037
+ return acc + inv.reserve_qty
1038
+ }, 0)
1009
1039
 
1010
- updatedInventories[0].forEach(inv => {
1011
- let oi = {
1012
- ...orderInventory,
1013
- inventory: { id: inv.id },
1014
- uom: inv.uom,
1015
- packingType: inv.packing_type,
1016
- batchId: inv.batch_id,
1017
- releaseQty: inv.reserve_qty,
1018
- releaseUomValue: inv.reserve_uom_value,
1019
- pickedQty: 0,
1020
- packedQty: 0,
1021
- type: ORDER_TYPES.RELEASE_OF_GOODS,
1022
- status: ORDER_INVENTORY_STATUS.PENDING_WORKSHEET,
1023
- domain: { id: domain.id },
1024
- bizplace: { id: customerBizplace.id }
1040
+ if (orderInventory.releaseQty != totalAssigned) {
1041
+ throw new ApiError('E01', 'INSUFFICIENT_STOCK')
1025
1042
  }
1026
1043
 
1027
- assignedOrderInventories.push(oi)
1028
- })
1029
- }
1044
+ updatedInventories[0].forEach(inv => {
1045
+ let oi = {
1046
+ ...orderInventory,
1047
+ inventory: { id: inv.id },
1048
+ uom: inv.uom,
1049
+ packingType: inv.packing_type,
1050
+ batchId: inv.batch_id,
1051
+ releaseQty: inv.reserve_qty,
1052
+ releaseUomValue: inv.reserve_uom_value,
1053
+ pickedQty: 0,
1054
+ packedQty: 0,
1055
+ type: ORDER_TYPES.RELEASE_OF_GOODS,
1056
+ status: ORDER_INVENTORY_STATUS.PENDING_WORKSHEET,
1057
+ domain: { id: domain.id },
1058
+ bizplace: { id: customerBizplace.id }
1059
+ }
1030
1060
 
1031
- orderProducts[opIdx].orderInventories = assignedOrderInventories
1061
+ assignedOrderInventories.push(oi)
1062
+ })
1063
+
1064
+ orderProducts[opIdx].orderInventories = assignedOrderInventories
1065
+ } else {
1066
+ await tx
1067
+ .getRepository(ProductDetailStock)
1068
+ .createQueryBuilder()
1069
+ .update(ProductDetailStock)
1070
+ .set({
1071
+ unassignedQty: () => `"unassigned_qty" + ${orderInventory.releaseQty}`,
1072
+ unassignedUomValue: () => `"unassigned_uom_value" + ${orderInventory.releaseUomValue}`
1073
+ })
1074
+ .where({ productDetail: orderInventory.productDetail.id })
1075
+ .execute()
1076
+ }
1077
+ }
1032
1078
  }
1033
1079
 
1034
1080
  return orderProducts