@things-factory/operato-hub 4.3.185 → 4.3.187

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.
Files changed (24) hide show
  1. package/client/actions/hub.js +0 -5
  2. package/config.development.js +23 -8
  3. package/dist-server/graphql/resolvers/synchronization/get-lmd-awb.js +137 -0
  4. package/dist-server/graphql/resolvers/synchronization/get-lmd-awb.js.map +1 -0
  5. package/dist-server/graphql/resolvers/synchronization/index.js +2 -1
  6. package/dist-server/graphql/resolvers/synchronization/index.js.map +1 -1
  7. package/dist-server/graphql/types/synchronization/index.js +1 -0
  8. package/dist-server/graphql/types/synchronization/index.js.map +1 -1
  9. package/dist-server/routers/api/restful-apis/unstable/add-release-order.js +48 -6
  10. package/dist-server/routers/api/restful-apis/unstable/add-release-order.js.map +1 -1
  11. package/dist-server/routers/api/restful-apis/v1/utils/params.js +3 -1
  12. package/dist-server/routers/api/restful-apis/v1/utils/params.js.map +1 -1
  13. package/dist-server/routers/api/restful-apis/v1/warehouse/add-release-order.js +49 -7
  14. package/dist-server/routers/api/restful-apis/v1/warehouse/add-release-order.js.map +1 -1
  15. package/dist-server/routers/xilnex-router.js +1 -0
  16. package/dist-server/routers/xilnex-router.js.map +1 -1
  17. package/package.json +18 -18
  18. package/server/graphql/resolvers/synchronization/get-lmd-awb.ts +178 -0
  19. package/server/graphql/resolvers/synchronization/index.ts +3 -1
  20. package/server/graphql/types/synchronization/index.ts +1 -0
  21. package/server/routers/api/restful-apis/unstable/add-release-order.ts +69 -7
  22. package/server/routers/api/restful-apis/v1/utils/params.ts +3 -1
  23. package/server/routers/api/restful-apis/v1/warehouse/add-release-order.ts +70 -8
  24. package/server/routers/xilnex-router.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/operato-hub",
3
- "version": "4.3.185",
3
+ "version": "4.3.187",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "client/index.js",
6
6
  "things-factory": true,
@@ -62,7 +62,7 @@
62
62
  "@things-factory/auth-ui": "^4.3.179",
63
63
  "@things-factory/biz-base": "^4.3.179",
64
64
  "@things-factory/biz-ui": "^4.3.179",
65
- "@things-factory/board-service": "^4.3.179",
65
+ "@things-factory/board-service": "^4.3.186",
66
66
  "@things-factory/board-ui": "^4.3.179",
67
67
  "@things-factory/code-ui": "^4.3.179",
68
68
  "@things-factory/component-ui": "^4.3.159",
@@ -71,26 +71,26 @@
71
71
  "@things-factory/export-ui": "^4.3.179",
72
72
  "@things-factory/export-ui-csv": "^4.3.179",
73
73
  "@things-factory/export-ui-excel": "^4.3.179",
74
- "@things-factory/geography": "^4.3.179",
74
+ "@things-factory/geography": "^4.3.186",
75
75
  "@things-factory/grist-ui": "^4.3.179",
76
76
  "@things-factory/help": "^4.3.179",
77
77
  "@things-factory/i18n-base": "^4.3.179",
78
78
  "@things-factory/import-ui": "^4.3.179",
79
79
  "@things-factory/import-ui-excel": "^4.3.179",
80
- "@things-factory/integration-accounting": "^4.3.179",
81
- "@things-factory/integration-fulfillment": "^4.3.179",
82
- "@things-factory/integration-lmd": "^4.3.179",
83
- "@things-factory/integration-marketplace": "^4.3.179",
84
- "@things-factory/integration-pos": "^4.3.179",
85
- "@things-factory/integration-sellercraft": "^4.3.179",
86
- "@things-factory/integration-sftp": "^4.3.179",
87
- "@things-factory/lite-menu": "^4.3.179",
80
+ "@things-factory/integration-accounting": "^4.3.186",
81
+ "@things-factory/integration-fulfillment": "^4.3.186",
82
+ "@things-factory/integration-lmd": "^4.3.186",
83
+ "@things-factory/integration-marketplace": "^4.3.186",
84
+ "@things-factory/integration-pos": "^4.3.186",
85
+ "@things-factory/integration-sellercraft": "^4.3.186",
86
+ "@things-factory/integration-sftp": "^4.3.186",
87
+ "@things-factory/lite-menu": "^4.3.186",
88
88
  "@things-factory/more-ui": "^4.3.179",
89
- "@things-factory/notification": "^4.3.179",
89
+ "@things-factory/notification": "^4.3.186",
90
90
  "@things-factory/pdf": "^4.3.159",
91
- "@things-factory/product-ui": "^4.3.179",
91
+ "@things-factory/product-ui": "^4.3.187",
92
92
  "@things-factory/resource-ui": "^4.3.179",
93
- "@things-factory/sales-base": "^4.3.184",
93
+ "@things-factory/sales-base": "^4.3.187",
94
94
  "@things-factory/scene-data-transform": "^4.3.159",
95
95
  "@things-factory/scene-excel": "^4.3.159",
96
96
  "@things-factory/scene-firebase": "^4.3.159",
@@ -102,11 +102,11 @@
102
102
  "@things-factory/scene-mqtt": "^4.3.179",
103
103
  "@things-factory/scene-restful": "^4.3.159",
104
104
  "@things-factory/scene-visualizer": "^4.3.159",
105
- "@things-factory/setting-ui": "^4.3.179",
105
+ "@things-factory/setting-ui": "^4.3.186",
106
106
  "@things-factory/shell": "^4.3.179",
107
107
  "@things-factory/system-ui": "^4.3.179",
108
- "@things-factory/warehouse-base": "^4.3.183",
109
- "@things-factory/worksheet-base": "^4.3.185",
108
+ "@things-factory/warehouse-base": "^4.3.187",
109
+ "@things-factory/worksheet-base": "^4.3.187",
110
110
  "cron-parser": "^4.7.0",
111
111
  "koa2-swagger-ui": "^5.0.2",
112
112
  "swagger-jsdoc": "^5.0.0",
@@ -118,5 +118,5 @@
118
118
  "resolutions": {
119
119
  "core-js": "^3.16.0"
120
120
  },
121
- "gitHead": "c7cd806b0057f8c112417716216dc7a740fb0431"
121
+ "gitHead": "a6192bef4662aafeb4e9d4e715dca07aa1459b2d"
122
122
  }
@@ -0,0 +1,178 @@
1
+ import { getRepository, getConnection, Not, In, IsNull, SelectQueryBuilder, EntityManager } from 'typeorm'
2
+ import { v4 as uuidv4 } from 'uuid'
3
+
4
+ import { ReleaseGood, OrderPackage, ORDER_STATUS } from '@things-factory/sales-base'
5
+ import { LastMileAPI } from '@things-factory/integration-lmd'
6
+ import { Board } from '@things-factory/board-service'
7
+ import { logger } from '@things-factory/env'
8
+ import { headlessModel, pdf } from '@things-factory/board-service'
9
+ import { uploadAwb } from '@things-factory/attachment-base'
10
+ import { Attachment } from '@things-factory/attachment-base'
11
+ import { Sellercraft, SellercraftStatus } from '@things-factory/integration-sellercraft'
12
+ import { ApplicationType } from '@things-factory/auth-base'
13
+ import { SellercraftController } from '@things-factory/worksheet-base'
14
+ import { Setting } from '@things-factory/setting-base'
15
+
16
+ const LMD_BOARDS = {
17
+ dhl: 'operato-lmd-dhl-awb',
18
+ airspeed: 'operato-lmd-airspeed-awb'
19
+ }
20
+
21
+ export const getLmdAwbResolver = {
22
+ async getLmdAwb(_: any, {}, context: any) {
23
+ const qb: SelectQueryBuilder<OrderPackage> = getRepository(OrderPackage)
24
+ .createQueryBuilder('op')
25
+ .innerJoinAndSelect('op.releaseGood', 'rg')
26
+ .innerJoinAndSelect('rg.domain', 'd')
27
+ .innerJoinAndSelect('rg.bizplace', 'b')
28
+ .innerJoinAndSelect('b.domain', 'd2')
29
+ .leftJoinAndSelect('rg.lastMileDelivery', 'lmd')
30
+ .where('rg.lmd_option = true')
31
+ .andWhere('rg.status NOT IN (:...statuses)', {
32
+ statuses: [ORDER_STATUS.CANCELLED, ORDER_STATUS.PENDING_CANCEL, ORDER_STATUS.DONE]
33
+ })
34
+ .andWhere('op.parcel_id IS NOT NULL')
35
+ .andWhere('op.airway_bill IS NULL')
36
+
37
+ const orderPackages: OrderPackage[] = await qb.getMany()
38
+
39
+ let releaseOrders: ReleaseGood = []
40
+
41
+ for (let orderPackage of orderPackages) {
42
+ await getConnection().transaction(async tx => {
43
+ try {
44
+ let awbRes = await LastMileAPI.getAwb(
45
+ { ...orderPackage.releaseGood.lastMileDelivery, platform: 'lmdMiddleware' },
46
+ { parcelId: orderPackage.parcelId }
47
+ )
48
+
49
+ if (awbRes) {
50
+ await constructAndUploadAwb(orderPackage.releaseGood, orderPackage, awbRes)
51
+ releaseOrders.push(orderPackage.releaseGood)
52
+ }
53
+ } catch (e) {
54
+ logger.error(e)
55
+ }
56
+ })
57
+ }
58
+
59
+ // trigger RTS
60
+ try {
61
+ let uniqueReleaseOrders: ReleaseGood = [...new Map(releaseOrders.map(item => [item['id'], item])).values()]
62
+
63
+ for (let releaseOrder of uniqueReleaseOrders) {
64
+ const orderSource: string = releaseOrder.source
65
+
66
+ if (orderSource == ApplicationType.SELLERCRAFT) {
67
+ const sellercraft: Sellercraft = await getRepository(Sellercraft).findOne({
68
+ domain: releaseOrder.bizplace.domain,
69
+ status: SellercraftStatus.ACTIVE
70
+ })
71
+ if (sellercraft) {
72
+ await getConnection().transaction(async (tx: EntityManager) => {
73
+ const sellercraftCtrl: SellercraftController = new SellercraftController(tx, null, null)
74
+ const rtsTriggerLevel: Setting = await tx.getRepository(Setting).findOne({
75
+ where: { domain: releaseOrder.domain, category: 'id-rule', name: 'rts-trigger-level' }
76
+ })
77
+
78
+ if (rtsTriggerLevel && parseInt(rtsTriggerLevel?.value || 0) == 1) {
79
+ await sellercraftCtrl.initiateOrderShipment(sellercraft, releaseOrder)
80
+ }
81
+ })
82
+ }
83
+ }
84
+ }
85
+ } catch (e) {
86
+ logger.error(`[get-lmd-awb-sof-trigger]: ${e}`)
87
+ }
88
+ //
89
+ }
90
+ }
91
+
92
+ async function constructAndUploadAwb(releaseOrder, orderPackage, awbRes) {
93
+ let {
94
+ shipmentID,
95
+ deliveryConfirmationNo,
96
+ deliveryDepotCode,
97
+ primarySortCode,
98
+ secondarySortCode,
99
+ recipient,
100
+ sender,
101
+ tracking_no,
102
+ courier
103
+ } = awbRes
104
+
105
+ let senderFullAddress = `${sender?.address}${sender?.address2 ? ', ' + sender?.address2 : ''}${
106
+ sender?.district ? ', ' + sender?.district : ''
107
+ }${sender?.city ? ', ' + sender?.city : ''}${sender?.postcode ? ', ' + sender?.postcode : ''}${
108
+ sender?.state ? ', ' + sender?.state : ''
109
+ }`
110
+
111
+ let recipientAddress1 = `${recipient?.address}${recipient?.address2 ? ', ' + recipient?.address2 : ''}${
112
+ recipient?.district ? ', ' + recipient?.district : ''
113
+ }`
114
+
115
+ let recipientAddress2 = `${recipient?.state}${recipient?.city ? ', ' + recipient?.city : ''}${
116
+ recipient?.postcode ? ', ' + recipient?.postcode : ''
117
+ }`
118
+
119
+ let data = {
120
+ senderFullAddress,
121
+ senderName: sender?.name,
122
+ recipientAddress1,
123
+ recipientAddress2,
124
+ recipientPhone: recipient?.phone,
125
+ recipientName: recipient?.name,
126
+ refId: shipmentID,
127
+ deliveryConfirmationNo: deliveryConfirmationNo || tracking_no,
128
+ deliveryDepotCode: deliveryDepotCode,
129
+ primarySortCode: primarySortCode,
130
+ secondarySortCode: secondarySortCode,
131
+ orderNo: releaseOrder?.refNo
132
+ }
133
+
134
+ let lmdBoard: Board = await getRepository(Board).findOne({
135
+ where: { name: LMD_BOARDS[courier] },
136
+ relations: ['domain']
137
+ })
138
+
139
+ if (!lmdBoard) throw new Error(`[get-lmd-awb] board is not found (RO: ${releaseOrder.name})`)
140
+
141
+ const { model } = await headlessModel({ domain: lmdBoard.domain, id: lmdBoard.id })
142
+
143
+ const result = await pdf({
144
+ id: lmdBoard.id,
145
+ model,
146
+ data,
147
+ options: {
148
+ format: 'A4'
149
+ },
150
+ context: { state: { domain: lmdBoard.domain } }
151
+ })
152
+
153
+ let uuid = uuidv4()
154
+ let awb = await uploadAwb({
155
+ content: result,
156
+ title: uuid
157
+ })
158
+
159
+ if (!awb?.url) throw new Error(`[get-lmd-awb] awb was not uploaded for (RO: ${releaseOrder.name})`)
160
+
161
+ await getRepository(OrderPackage).update(
162
+ { id: orderPackage.id },
163
+ { airwayBill: awb.url, trackingNo: deliveryConfirmationNo || tracking_no }
164
+ )
165
+
166
+ // create attachment
167
+
168
+ await getRepository(Attachment).insert({
169
+ name: uuid,
170
+ mimetype: 'application/pdf',
171
+ category: 'LMD_AWB',
172
+ refBy: orderPackage.id,
173
+ path: awb.url,
174
+ domain: releaseOrder.domain,
175
+ size: 0,
176
+ encoding: ''
177
+ })
178
+ }
@@ -8,6 +8,7 @@ import { syncProductsToAccountResolver } from './sync-products-to-account'
8
8
  import { syncProductsToSellercraftResolver } from './sync-products-to-sellercraft'
9
9
  import { createDatShipments } from './create-dat-shipments'
10
10
  import { autoUpdateAllMarketplaceProductVariationQuantityResolver } from './auto-update-all-marketplace-product-variation-quantity'
11
+ import { getLmdAwbResolver } from './get-lmd-awb'
11
12
 
12
13
  export const Query = {
13
14
  ...fetchSyncProductsResolver,
@@ -22,5 +23,6 @@ export const Mutation = {
22
23
  ...syncAllProductsToSellercraftResolver,
23
24
  ...sellercraftPackMarketplaceOrderResolver,
24
25
  ...createDatShipments,
25
- ...autoUpdateAllMarketplaceProductVariationQuantityResolver
26
+ ...autoUpdateAllMarketplaceProductVariationQuantityResolver,
27
+ ...getLmdAwbResolver
26
28
  }
@@ -17,6 +17,7 @@ export const Mutation = /* GraphQL */ `
17
17
  syncProductsToAccount(accountId: String!, bizplaceId: String!): Boolean @transaction
18
18
  syncAllProductsToSellercraft: Boolean @transaction
19
19
  sellercraftPackMarketplaceOrder: Boolean @transaction
20
+ getLmdAwb: Boolean @transaction
20
21
  createDatShipments: Boolean @transaction
21
22
  autoUpdateAllMarketplaceProductVariationQuantity(companyDomainId: String, warehouseId: String): Boolean @transaction
22
23
  sendEmails: Boolean @transaction
@@ -1,4 +1,14 @@
1
- import { EntityManager, getConnection, getRepository, ILike, In, Not, Repository, SelectQueryBuilder } from 'typeorm'
1
+ import {
2
+ EntityManager,
3
+ getConnection,
4
+ getRepository,
5
+ In,
6
+ Not,
7
+ Repository,
8
+ SelectQueryBuilder,
9
+ ILike,
10
+ Brackets
11
+ } from 'typeorm'
2
12
  import { v4 as uuidv4 } from 'uuid'
3
13
 
4
14
  import { restfulApiRouter as router } from '@things-factory/api'
@@ -30,6 +40,7 @@ import {
30
40
  import { Setting } from '@things-factory/setting-base'
31
41
  import { Domain } from '@things-factory/shell'
32
42
  import { Inventory, INVENTORY_STATUS, LOCATION_TYPE } from '@things-factory/warehouse-base'
43
+ import { LastMileDelivery } from '@things-factory/integration-lmd'
33
44
 
34
45
  import { ValidationError } from '../../../../errors/index'
35
46
  import { businessMiddleware } from '../../../../middlewares/business-middleware'
@@ -116,7 +127,8 @@ router.post(
116
127
  paidAmount: bodyReq?.paidAmount,
117
128
  discountAmount: bodyReq?.discountAmount,
118
129
  taxAmount: bodyReq?.taxAmount,
119
- shippingFee: bodyReq?.shippingFee
130
+ shippingFee: bodyReq?.shippingFee,
131
+ lmdOption: bodyReq?.lmdOption
120
132
  }
121
133
 
122
134
  // check existing order
@@ -132,6 +144,14 @@ router.post(
132
144
  let itemList = await tx.query(
133
145
  `
134
146
  select wboi.* from warehouse_bizplace_onhand_inventories wboi
147
+ left join (
148
+ select i2.product_id, i2.domain_id, i2.bizplace_id, i2.packing_type, i2.packing_size, i2.uom, sum(i2.qty) as storage_qty, sum(i2.uom_value) as storage_uom_value from inventories i2
149
+ inner join locations l2 on l2.id = i2.location_id
150
+ where i2.domain_id = $1 and i2.bizplace_id = $2
151
+ and i2.product_id = $3 and i2.packing_type= $4 and i2.packing_size= $5 and i2.uom = $6 and i2.status='STORED'
152
+ and l2.type='STORAGE' group by i2.product_id, i2.domain_id, i2.bizplace_id, i2.packing_type, i2.packing_size, i2.uom
153
+ ) storageInv on storageInv.product_id = wboi.product_id and storageInv.domain_id=wboi.domain_id and storageInv.bizplace_id=wboi.bizplace_id
154
+ and storageInv.packing_type=wboi.packing_type and storageInv.packing_size=wboi.packing_size and storageInv.uom=wboi.uom
135
155
  where wboi."domain_id"= $1 and
136
156
  wboi."bizplace_id" = $2 and
137
157
  wboi."group_type" = 'SINGLE' and
@@ -139,8 +159,8 @@ router.post(
139
159
  wboi."packing_type" = $4 and
140
160
  wboi."packing_size" = $5 and
141
161
  wboi."uom" = $6 and
142
- wboi."remain_qty" >= $7 and
143
- wboi."remain_uom_value" >= $8
162
+ (wboi."remain_qty" - wboi."transfer_qty" - coalesce(storageInv.storage_qty, 0)) >= $7 and
163
+ (wboi."remain_uom_value" - wboi."transfer_uom_value" - coalesce(storageInv.storage_uom_value, 0)) >= $8
144
164
  `,
145
165
  [
146
166
  domain.id,
@@ -492,6 +512,12 @@ async function createReleaseGood(
492
512
  })
493
513
  : null
494
514
 
515
+ // assign lmd
516
+ let lmd: any = null
517
+ if (releaseGood?.lmdOption) {
518
+ lmd = await autoAssignLmd(domain, deliveryCountry?.id, releaseGood?.postalCode)
519
+ }
520
+
495
521
  newReleaseGood = {
496
522
  name: roNoSetting
497
523
  ? await generateId({ domain, type: ORDER_NUMBER_RULE_TYPE.RO_NUMBER, seed: {} })
@@ -545,6 +571,8 @@ async function createReleaseGood(
545
571
  discountAmount: releaseGood?.discountAmount,
546
572
  taxAmount: releaseGood?.taxAmount,
547
573
  shippingFee: releaseGood?.shippingFee,
574
+ lmdOption: releaseGood?.lmdOption,
575
+ lastMileDelivery: lmd,
548
576
  creator: user,
549
577
  updater: user,
550
578
  acceptedBy: user,
@@ -1040,7 +1068,9 @@ async function assignToInventory(orderProducts: OrderProduct[], customerBizplace
1040
1068
  '${orderInventory.releaseQty}' as release_qty, '${orderInventory.releaseUomValue}' as release_uom_value
1041
1069
  UNION
1042
1070
  (
1043
- SELECT ROW_NUMBER() OVER(PARTITION BY iv.domain_id ORDER BY ${sortQuery}) as sort_seq,
1071
+ SELECT ROW_NUMBER() OVER(PARTITION BY iv.domain_id ORDER BY wiar.rank ${
1072
+ sortQuery ? ', ' + sortQuery : ''
1073
+ }) as sort_seq,
1044
1074
  iv.id, iv.pallet_id, iv.batch_id, iv.batch_id_ref,
1045
1075
  iv.unit, iv.uom, iv.packing_type, iv.packing_size,
1046
1076
  iv.manufacture_year, iv.carton_id, iv.uom_value,
@@ -1049,11 +1079,12 @@ async function assignToInventory(orderProducts: OrderProduct[], customerBizplace
1049
1079
  FROM "inventories" "iv"
1050
1080
  INNER JOIN "locations" "loc" ON "loc"."id"="iv"."location_id"
1051
1081
  INNER JOIN "products" "p" ON "p"."id"="iv"."product_id"
1082
+ INNER JOIN "warehouse_inventory_assignment_rankings" "wiar" ON "wiar"."location_type"="loc"."type"
1052
1083
  WHERE case when coalesce("iv"."locked_qty",0) < 0 then 0 else ("iv"."qty" - coalesce("iv"."locked_qty",0)) end > 0 AND
1053
- "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)
1084
+ "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, $10)
1054
1085
  AND "iv"."obsolete" = false AND case when "iv"."expiration_date" is not null and "p"."shelf_life" is not null then CURRENT_DATE < "iv"."expiration_date" - "p"."shelf_life" else true end
1055
1086
  ${queryStrings.query.length > 0 ? `AND ${queryStrings.join(' AND ')}` : ''}
1056
- ORDER BY ${sortQuery}
1087
+ ORDER BY wiar.rank ${sortQuery ? ', ' + sortQuery : ''}
1057
1088
  )
1058
1089
  ) dt1
1059
1090
  ) dt2 where case when "lock_amount" < 0 then "available_qty" else "available_qty" - "lock_amount" end > 0
@@ -1073,6 +1104,7 @@ async function assignToInventory(orderProducts: OrderProduct[], customerBizplace
1073
1104
  INVENTORY_STATUS.STORED,
1074
1105
  LOCATION_TYPE.QUARANTINE,
1075
1106
  LOCATION_TYPE.RESERVE,
1107
+ LOCATION_TYPE.STORAGE,
1076
1108
  ...queryStrings.values
1077
1109
  ]
1078
1110
  )
@@ -1123,3 +1155,33 @@ async function assignToInventory(orderProducts: OrderProduct[], customerBizplace
1123
1155
 
1124
1156
  return orderProducts
1125
1157
  }
1158
+
1159
+ async function autoAssignLmd(domain, countryId, postalCode) {
1160
+ const qb: SelectQueryBuilder<LastMileDelivery> = getRepository(LastMileDelivery)
1161
+ .createQueryBuilder('lmd')
1162
+ .leftJoinAndSelect('lmd.lastMileDeliverySettings', 'lmds')
1163
+ .innerJoinAndSelect('lmds.geoCountry', 'gc')
1164
+ .where('lmd.domain_id = :domainId', { domainId: domain.id })
1165
+ .andWhere('lower(lmd.status) = :status', { status: 'active' })
1166
+ .andWhere('gc.id = :gcId', { gcId: countryId })
1167
+ .andWhere(
1168
+ new Brackets(qb => {
1169
+ qb.where(`lmds.include_postal_code = ''`)
1170
+ .orWhere('lmds.include_postal_code is null')
1171
+ .orWhere('lmds.include_postal_code like :includePostCode', { includePostCode: `%${postalCode}%` })
1172
+ })
1173
+ )
1174
+ .andWhere(
1175
+ new Brackets(qb => {
1176
+ qb.where(`lmds.exclude_postal_code = ''`)
1177
+ .orWhere('lmds.exclude_postal_code is null')
1178
+ .orWhere('lmds.exclude_postal_code not like :excludePostCode', { excludePostCode: `%${postalCode}%` })
1179
+ })
1180
+ )
1181
+ // .andWhere('lmds.exclude_postal_code not like :excludePostCode', { excludePostCode: `%${postalCode}%` })
1182
+ .orderBy('lmd.rank')
1183
+
1184
+ const lmdResult: LastMileDelivery = await qb.getOne()
1185
+
1186
+ return lmdResult
1187
+ }
@@ -188,6 +188,7 @@ export const params = {
188
188
  'deliverTo.state',
189
189
  'deliverTo.phone1',
190
190
  'deliverTo.phone2',
191
+ 'deliverTo.email',
191
192
  'marketplaceOrderStatus',
192
193
  'airwayBill',
193
194
  'invoice',
@@ -217,7 +218,8 @@ export const params = {
217
218
  'platformName',
218
219
  'discountAmount',
219
220
  'taxAmount',
220
- 'shippingFee'
221
+ 'shippingFee',
222
+ 'lmdOption'
221
223
  ],
222
224
  'cancel-release-order': ['bizplaceId', 'roId', 'orderNo', 'orderNo2', 'orderNo3', 'remark'],
223
225
  'update-release-order-details': [
@@ -1,9 +1,20 @@
1
- import { EntityManager, getConnection, getRepository, In, Not, Repository, SelectQueryBuilder, ILike } from 'typeorm'
1
+ import {
2
+ EntityManager,
3
+ getConnection,
4
+ getRepository,
5
+ In,
6
+ Not,
7
+ Repository,
8
+ SelectQueryBuilder,
9
+ ILike,
10
+ Brackets
11
+ } from 'typeorm'
2
12
  import { v4 as uuidv4 } from 'uuid'
3
13
 
4
14
  import { restfulApiRouter as router } from '@things-factory/api'
5
15
  import { User } from '@things-factory/auth-base'
6
16
  import { Bizplace, getCompanyBizplace } from '@things-factory/biz-base'
17
+ import { GeoCountry } from '@things-factory/geography'
7
18
  import { generateId } from '@things-factory/id-rule-base'
8
19
  import { Sellercraft } from '@things-factory/integration-sellercraft'
9
20
  import { MarketplaceOrder } from '@things-factory/marketplace-base'
@@ -28,7 +39,7 @@ import {
28
39
  import { Setting } from '@things-factory/setting-base'
29
40
  import { Domain } from '@things-factory/shell'
30
41
  import { Inventory, INVENTORY_STATUS, LOCATION_TYPE } from '@things-factory/warehouse-base'
31
- import { GeoCountry } from '@things-factory/geography'
42
+ import { LastMileDelivery } from '@things-factory/integration-lmd'
32
43
 
33
44
  import { businessMiddleware, loggingMiddleware, validationMiddleware } from '../middlewares'
34
45
  import { ApiError, ApiErrorHandler, throwInternalServerError } from '../utils/error-util'
@@ -120,6 +131,7 @@ router.post(
120
131
  discountAmount: bodyReq?.discountAmount,
121
132
  taxAmount: bodyReq?.taxAmount,
122
133
  shippingFee: bodyReq?.shippingFee,
134
+ lmdOption: bodyReq?.lmdOption,
123
135
  shippingOrder: {
124
136
  shipName: bodyReq?.shippingOrder.shipName,
125
137
  containerNo: bodyReq?.shippingOrder.containerNo,
@@ -143,6 +155,14 @@ router.post(
143
155
  let itemList = await tx.query(
144
156
  `
145
157
  select wboi.* from warehouse_bizplace_onhand_inventories wboi
158
+ left join (
159
+ select i2.product_id, i2.domain_id, i2.bizplace_id, i2.packing_type, i2.packing_size, i2.uom, sum(i2.qty) as storage_qty, sum(i2.uom_value) as storage_uom_value from inventories i2
160
+ inner join locations l2 on l2.id = i2.location_id
161
+ where i2.domain_id = $1 and i2.bizplace_id = $2
162
+ and i2.product_id = $3 and i2.packing_type= $4 and i2.packing_size= $5 and i2.uom = $6 and i2.status='STORED'
163
+ and l2.type='STORAGE' group by i2.product_id, i2.domain_id, i2.bizplace_id, i2.packing_type, i2.packing_size, i2.uom
164
+ ) storageInv on storageInv.product_id = wboi.product_id and storageInv.domain_id=wboi.domain_id and storageInv.bizplace_id=wboi.bizplace_id
165
+ and storageInv.packing_type=wboi.packing_type and storageInv.packing_size=wboi.packing_size and storageInv.uom=wboi.uom
146
166
  where wboi."domain_id"= $1 and
147
167
  wboi."bizplace_id" = $2 and
148
168
  wboi."group_type" = 'SINGLE' and
@@ -150,8 +170,8 @@ router.post(
150
170
  wboi."packing_type" = $4 and
151
171
  wboi."packing_size" = $5 and
152
172
  wboi."uom" = $6 and
153
- wboi."remain_qty" >= $7 and
154
- wboi."remain_uom_value" >= $8
173
+ (wboi."remain_qty" - wboi."transfer_qty" - coalesce(storageInv.storage_qty, 0)) >= $7 and
174
+ (wboi."remain_uom_value" - wboi."transfer_uom_value" - coalesce(storageInv.storage_uom_value, 0)) >= $8
155
175
  `,
156
176
  [
157
177
  domain.id,
@@ -414,6 +434,12 @@ async function createReleaseGood(
414
434
  })
415
435
  : null
416
436
 
437
+ // assign lmd
438
+ let lmd: any = null
439
+ if (releaseGood?.lmdOption) {
440
+ lmd = await autoAssignLmd(domain, deliveryCountry?.id, releaseGood?.postalCode)
441
+ }
442
+
417
443
  newReleaseGood = {
418
444
  name: roNoSetting
419
445
  ? await generateId({ domain, type: ORDER_NUMBER_RULE_TYPE.RO_NUMBER, seed: {} })
@@ -470,7 +496,9 @@ async function createReleaseGood(
470
496
  updater: user,
471
497
  acceptedBy: user,
472
498
  acceptedAt: new Date(),
473
- shippingOrder: shippingOrder ? shippingOrder : null
499
+ shippingOrder: shippingOrder ? shippingOrder : null,
500
+ lastMileDelivery: lmd,
501
+ lmdOption: releaseGood.lmdOption
474
502
  }
475
503
 
476
504
  let resultReleaseGood = await tx.getRepository(ReleaseGood).save(newReleaseGood)
@@ -910,7 +938,9 @@ async function assignToInventory(orderProducts: OrderProduct[], customerBizplace
910
938
  '${orderInventory.releaseQty}' as release_qty, '${orderInventory.releaseUomValue}' as release_uom_value
911
939
  UNION
912
940
  (
913
- SELECT ROW_NUMBER() OVER(PARTITION BY iv.domain_id ORDER BY ${sortQuery}) as sort_seq,
941
+ SELECT ROW_NUMBER() OVER(PARTITION BY iv.domain_id ORDER BY wiar.rank ${
942
+ sortQuery ? ', ' + sortQuery : ''
943
+ }) as sort_seq,
914
944
  iv.id, iv.pallet_id, iv.batch_id, iv.batch_id_ref,
915
945
  iv.unit, iv.uom, iv.packing_type, iv.packing_size,
916
946
  iv.manufacture_year, iv.carton_id, iv.uom_value,
@@ -919,11 +949,12 @@ async function assignToInventory(orderProducts: OrderProduct[], customerBizplace
919
949
  FROM "inventories" "iv"
920
950
  INNER JOIN "locations" "loc" ON "loc"."id"="iv"."location_id"
921
951
  INNER JOIN "products" "p" ON "p"."id"="iv"."product_id"
952
+ INNER JOIN "warehouse_inventory_assignment_rankings" "wiar" ON "wiar"."location_type"="loc"."type"
922
953
  WHERE case when coalesce("iv"."locked_qty",0) < 0 then 0 else ("iv"."qty" - coalesce("iv"."locked_qty",0)) end > 0 AND
923
- "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)
954
+ "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, $10)
924
955
  AND "iv"."obsolete" = false AND case when "iv"."expiration_date" is not null and "p"."shelf_life" is not null then CURRENT_DATE < "iv"."expiration_date" - "p"."shelf_life" else true end
925
956
  ${queryStrings.query.length > 0 ? `AND ${queryStrings.join(' AND ')}` : ''}
926
- ORDER BY ${sortQuery}
957
+ ORDER BY wiar.rank ${sortQuery ? ', ' + sortQuery : ''}
927
958
  )
928
959
  ) dt1
929
960
  ) dt2 where case when "lock_amount" < 0 then "available_qty" else "available_qty" - "lock_amount" end > 0
@@ -943,6 +974,7 @@ async function assignToInventory(orderProducts: OrderProduct[], customerBizplace
943
974
  INVENTORY_STATUS.STORED,
944
975
  LOCATION_TYPE.QUARANTINE,
945
976
  LOCATION_TYPE.RESERVE,
977
+ LOCATION_TYPE.STORAGE,
946
978
  ...queryStrings.values
947
979
  ]
948
980
  )
@@ -981,3 +1013,33 @@ async function assignToInventory(orderProducts: OrderProduct[], customerBizplace
981
1013
 
982
1014
  return orderProducts
983
1015
  }
1016
+
1017
+ async function autoAssignLmd(domain, countryId, postalCode) {
1018
+ const qb: SelectQueryBuilder<LastMileDelivery> = getRepository(LastMileDelivery)
1019
+ .createQueryBuilder('lmd')
1020
+ .leftJoinAndSelect('lmd.lastMileDeliverySettings', 'lmds')
1021
+ .innerJoinAndSelect('lmds.geoCountry', 'gc')
1022
+ .where('lmd.domain_id = :domainId', { domainId: domain.id })
1023
+ .andWhere('lower(lmd.status) = :status', { status: 'active' })
1024
+ .andWhere('gc.id = :gcId', { gcId: countryId })
1025
+ .andWhere(
1026
+ new Brackets(qb => {
1027
+ qb.where(`lmds.include_postal_code = ''`)
1028
+ .orWhere('lmds.include_postal_code is null')
1029
+ .orWhere('lmds.include_postal_code like :includePostCode', { includePostCode: `%${postalCode}%` })
1030
+ })
1031
+ )
1032
+ .andWhere(
1033
+ new Brackets(qb => {
1034
+ qb.where(`lmds.exclude_postal_code = ''`)
1035
+ .orWhere('lmds.exclude_postal_code is null')
1036
+ .orWhere('lmds.exclude_postal_code not like :excludePostCode', { excludePostCode: `%${postalCode}%` })
1037
+ })
1038
+ )
1039
+ // .andWhere('lmds.exclude_postal_code not like :excludePostCode', { excludePostCode: `%${postalCode}%` })
1040
+ .orderBy('lmd.rank')
1041
+
1042
+ const lmdResult: LastMileDelivery = await qb.getOne()
1043
+
1044
+ return lmdResult
1045
+ }
@@ -447,6 +447,7 @@ xilnexRouter.post('/webhook-xilnex-transfer-note', async (context, next) => {
447
447
  palletQty: 1,
448
448
  totalUomValue: `${(productDetail.uomValue * item.confirmedQty).toFixed(2)} ${productDetail.uom}`,
449
449
  status: 'PENDING',
450
+ refItemId: item.orderItemId,
450
451
  arrivalNotice,
451
452
  domain: warehouseDomain,
452
453
  bizplace: customerBizplace