@things-factory/operato-hub 4.3.14 → 4.3.17

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.
@@ -3,6 +3,7 @@ import { EntityManager, getConnection, SelectQueryBuilder } from 'typeorm'
3
3
 
4
4
  import { restfulApiRouter as router } from '@things-factory/api'
5
5
  import { Bizplace, getCompanyBizplace } from '@things-factory/biz-base'
6
+ import { createPayloadLog, PayloadType } from '@things-factory/integration-base'
6
7
  import { Product, ProductBundle, ProductDetail } from '@things-factory/product-base'
7
8
  import { ORDER_TYPES, OrderInventory } from '@things-factory/sales-base'
8
9
  import { Setting } from '@things-factory/setting-base'
@@ -12,10 +13,11 @@ import { WorksheetDetail } from '@things-factory/worksheet-base'
12
13
  import { businessMiddleware } from '../../../../middlewares/business-middleware'
13
14
  import { warehouseMiddleware } from '../../../../middlewares/warehouse-middleware'
14
15
 
15
- const debug = require('debug')('things-factory:operato-hub:restful-api:v1:add-release-order')
16
+ const debug = require('debug')('things-factory:operato-hub:restful-api:unstable:add-release-order')
17
+ const apiVersion = 'v1'
16
18
 
17
19
  router.post(
18
- '/v1/warehouse/:warehouseId/add-release-order',
20
+ `/${apiVersion}/warehouse/:warehouseId/add-release-order`,
19
21
  businessMiddleware,
20
22
  warehouseMiddleware,
21
23
  async (context, next) => {
@@ -23,259 +25,291 @@ router.post(
23
25
  const { client, domain } = context.state
24
26
  const { warehouseId: customerBizplaceId } = context.params
25
27
  const { t } = context
28
+ const bodyReq = context.request.body
29
+ try {
30
+ if (!customerBizplaceId) context.throw(404, t('error.warehouse id not found'))
26
31
 
27
- if (!customerBizplaceId) context.throw(404, t('error.warehouse id not found'))
28
-
29
- let bodyReq = context.request.body
30
- if (typeof bodyReq === undefined || !Object.keys(bodyReq)?.length) {
31
- context.throw(400, t('error.invalid body request format'))
32
- }
33
- debug('post:/add-release-order request.body', bodyReq)
34
- await checkMandatoryKey(context, bodyReq)
32
+ if (typeof bodyReq === undefined || !Object.keys(bodyReq)?.length) {
33
+ context.throw(400, t('error.invalid body request format'))
34
+ }
35
+ debug('post:/add-release-order request.body', bodyReq)
36
+ await checkMandatoryKey(context, bodyReq)
35
37
 
36
- const isAutoAssign: Boolean = bodyReq.hasOwnProperty('isAutoAssign') ? bodyReq.isAutoAssign : true
38
+ const isAutoAssign: Boolean = bodyReq.hasOwnProperty('isAutoAssign') ? bodyReq.isAutoAssign : true
39
+ const requiredDraft: Boolean = bodyReq.hasOwnProperty('requiredDraft') ? bodyReq.requiredDraft : true
37
40
 
38
- // get customer company's bizplace
39
- const custCompanyBizplace: Bizplace = await getCompanyBizplace(null, null, customerBizplaceId, tx)
40
- const customerBizplace: Bizplace = await tx.getRepository(Bizplace).findOne({
41
- where: { id: customerBizplaceId }
42
- })
43
-
44
- let locationSortingRules = []
45
- const inventoryAssignmentSetting: Setting = await tx.getRepository(Setting).findOne({
46
- where: { domain, name: 'rule-for-inventory-assignment' }
47
- })
41
+ // get customer company's bizplace
42
+ const custCompanyBizplace: Bizplace = await getCompanyBizplace(null, null, customerBizplaceId, tx)
43
+ const customerBizplace: Bizplace = await tx.getRepository(Bizplace).findOne({
44
+ where: { id: customerBizplaceId }
45
+ })
48
46
 
49
- if (inventoryAssignmentSetting) {
50
- let locationSetting = JSON.parse(inventoryAssignmentSetting.value)
51
- for (const key in locationSetting) {
52
- locationSortingRules.push({ name: key, desc: locationSetting[key] == 'ASC' ? false : true })
53
- }
54
- } else {
55
- const pickingProductSetting: Setting = await tx.getRepository(Setting).findOne({
56
- where: { domain, name: 'rule-for-picking-product' }
47
+ let locationSortingRules = []
48
+ const inventoryAssignmentSetting: Setting = await tx.getRepository(Setting).findOne({
49
+ where: { domain, name: 'rule-for-inventory-assignment' }
57
50
  })
58
- if (pickingProductSetting) {
59
- let locationSetting = JSON.parse(pickingProductSetting.value)
51
+
52
+ if (inventoryAssignmentSetting) {
53
+ let locationSetting = JSON.parse(inventoryAssignmentSetting.value)
60
54
  for (const key in locationSetting) {
61
55
  locationSortingRules.push({ name: key, desc: locationSetting[key] == 'ASC' ? false : true })
62
56
  }
63
- }
64
- }
65
-
66
- const orderInventories: OrderInventory[] = bodyReq.orderInventories
67
- let newOrderInventories: OrderInventory[] = []
68
- let bundleInfo: any[] = []
69
-
70
- await Promise.all(
71
- orderInventories.map(async orderInventory => {
72
- const sku: string = orderInventory.product?.sku
73
- const refCode: string = orderInventory.product?.refCode
74
- const { packingType, packingSize }: { packingType: string; packingSize: number } = orderInventory
75
-
76
- const qb: SelectQueryBuilder<ProductDetail> = tx
77
- .getRepository(ProductDetail)
78
- .createQueryBuilder('PD')
79
- .innerJoinAndSelect('PD.product', 'PROD')
80
- .where('PROD.bizplace_id = :bizplaceId', { bizplaceId: custCompanyBizplace.id })
81
-
82
- if (!sku && !refCode) {
83
- context.throw(404, t('error.sku or refCode not found'))
84
- }
85
-
86
- if (refCode) {
87
- qb.andWhere('PD.ref_code = :refCode', { refCode })
88
- }
89
-
90
- if (sku) {
91
- qb.andWhere('PROD.sku = :sku', { sku })
92
- }
93
-
94
- if (packingType) {
95
- qb.andWhere('PD.packing_type = :packingType', { packingType })
96
- }
97
-
98
- if (packingSize) {
99
- qb.andWhere('PD.packing_size = :packingSize', { packingSize })
100
- }
101
-
102
- if (!packingType && !refCode) {
103
- qb.andWhere('PD.is_default = :isDefault', { isDefault: true })
57
+ } else {
58
+ const pickingProductSetting: Setting = await tx.getRepository(Setting).findOne({
59
+ where: { domain, name: 'rule-for-picking-product' }
60
+ })
61
+ if (pickingProductSetting) {
62
+ let locationSetting = JSON.parse(pickingProductSetting.value)
63
+ for (const key in locationSetting) {
64
+ locationSortingRules.push({ name: key, desc: locationSetting[key] == 'ASC' ? false : true })
65
+ }
104
66
  }
67
+ }
105
68
 
106
- const productDetail: ProductDetail = await qb.getOne()
107
-
108
- if (productDetail) {
109
- let newOrderInventory: OrderInventory = Object.assign({}, orderInventory)
69
+ const orderInventories: OrderInventory[] = bodyReq.orderInventories
70
+ let newOrderInventories: OrderInventory[] = []
71
+ let bundleInfo: any[] = []
110
72
 
111
- newOrderInventory.product.sku = productDetail.product.sku
112
- newOrderInventory.product = productDetail.product
113
- newOrderInventory.packingType = productDetail.packingType
114
- newOrderInventory.uomValue = productDetail.uomValue
115
- newOrderInventories.push(newOrderInventory)
116
- }
73
+ await Promise.all(
74
+ orderInventories.map(async orderInventory => {
75
+ const sku: string = orderInventory.product?.sku
76
+ const refCode: string = orderInventory.product?.refCode
77
+ const packingType: string = orderInventory.packingType
117
78
 
118
- if (!productDetail) {
119
79
  const qb: SelectQueryBuilder<ProductDetail> = tx
120
- .getRepository(ProductBundle)
121
- .createQueryBuilder('PB')
122
- .innerJoinAndSelect('PB.productBundleSettings', 'PBS')
123
- .innerJoinAndSelect('PBS.productDetail', 'PBD')
124
- .innerJoinAndSelect('PBD.product', 'PROD')
125
- .where('PB.bizplace_id = :bizplaceId', { bizplaceId: custCompanyBizplace.id })
80
+ .getRepository(ProductDetail)
81
+ .createQueryBuilder('PD')
82
+ .innerJoinAndSelect('PD.product', 'PROD')
83
+ .where('PROD.bizplace_id = :bizplaceId', { bizplaceId: custCompanyBizplace.id })
126
84
 
127
85
  if (!sku && !refCode) {
128
86
  context.throw(404, t('error.sku or refCode not found'))
129
87
  }
130
88
 
131
89
  if (refCode) {
132
- qb.andWhere('PB.ref_code = :refCode', { refCode })
90
+ qb.andWhere('PD.ref_code = :refCode', { refCode })
133
91
  }
134
92
 
135
93
  if (sku) {
136
- qb.andWhere('PB.sku = :sku', { sku })
94
+ qb.andWhere('PROD.sku = :sku', { sku })
137
95
  }
138
96
 
139
97
  if (packingType) {
140
- qb.andWhere('PB.packing_type = :packingType', { packingType })
98
+ qb.andWhere('PD.packing_type = :packingType', { packingType })
141
99
  }
142
100
 
143
- const foundProductBundle: ProductBundle = await qb.getOne()
101
+ if (!packingType && !refCode) {
102
+ qb.andWhere('PD.is_default = :isDefault', { isDefault: true })
103
+ }
144
104
 
145
- if (foundProductBundle) {
146
- bundleInfo.push({
147
- productBundleId: foundProductBundle.id,
148
- sku: foundProductBundle.sku,
149
- releaseQty: orderInventory.releaseQty
150
- })
151
- const bundleProductSettings: any[] = foundProductBundle.productBundleSettings
152
- bundleProductSettings.map(bundleProductSetting => {
153
- const productDetailBundle: ProductDetail = bundleProductSetting.productDetail
154
-
155
- let newOrderInventory: OrderInventory = Object.assign({}, orderInventory)
156
- newOrderInventory.product.sku = productDetailBundle.product.sku
157
- newOrderInventory.packingType = productDetailBundle.packingType
158
- newOrderInventory.uomValue = productDetailBundle.uomValue
159
- newOrderInventory.releaseQty = orderInventory.releaseQty * bundleProductSetting.bundleQty
160
- newOrderInventory.product = productDetailBundle.product
161
- newOrderInventories.push(newOrderInventory)
162
- })
163
- } else {
164
- if (sku && refCode) {
165
- context.throw(404, t(`error.sku ${sku} and refCode ${refCode} not exist in product / bundle master`))
105
+ const productDetail: ProductDetail = await qb.getOne()
106
+
107
+ if (productDetail) {
108
+ let newOrderInventory: OrderInventory = Object.assign({}, orderInventory)
109
+
110
+ newOrderInventory.product.sku = productDetail.product.sku
111
+ newOrderInventory.product = productDetail.product
112
+ newOrderInventory.packingType = productDetail.packingType
113
+ newOrderInventory.uomValue = productDetail.uomValue
114
+ newOrderInventories.push(newOrderInventory)
115
+ }
116
+
117
+ if (!productDetail) {
118
+ const qb: SelectQueryBuilder<ProductDetail> = tx
119
+ .getRepository(ProductBundle)
120
+ .createQueryBuilder('PB')
121
+ .innerJoinAndSelect('PB.productBundleSettings', 'PBS')
122
+ .innerJoinAndSelect('PBS.productDetail', 'PBD')
123
+ .innerJoinAndSelect('PBD.product', 'PROD')
124
+ .where('PB.bizplace_id = :bizplaceId', { bizplaceId: custCompanyBizplace.id })
125
+
126
+ if (!sku && !refCode) {
127
+ context.throw(404, t('error.sku or refCode not found'))
128
+ }
129
+
130
+ if (refCode) {
131
+ qb.andWhere('PB.ref_code = :refCode', { refCode })
166
132
  }
167
133
 
168
134
  if (sku) {
169
- context.throw(404, t(`error.sku ${sku} not exist in product / bundle master`))
135
+ qb.andWhere('PB.sku = :sku', { sku })
170
136
  }
171
137
 
172
- if (refCode) {
173
- context.throw(404, t(`error.refCode ${refCode} not exist in product / bundle master`))
138
+ if (packingType) {
139
+ qb.andWhere('PB.packing_type = :packingType', { packingType })
174
140
  }
175
- }
176
- }
177
- })
178
- )
179
-
180
- let finalOrderInventories: OrderInventory[] = []
181
-
182
- // Case 1. Every order inventories has specified batch id
183
- if (newOrderInventories.every((orderInventory: OrderInventory) => orderInventory.batchId)) {
184
- finalOrderInventories = await Promise.all(
185
- newOrderInventories.map(async (orderInventory: OrderInventory) => {
186
- let filters: any = [
187
- {
188
- name: 'batchId',
189
- operator: 'eq',
190
- value: orderInventory.batchId
191
- },
192
- {
193
- name: 'productName',
194
- operator: 'eq',
195
- value: orderInventory.product.sku
196
- },
197
- {
198
- name: 'bizplace',
199
- operator: 'in',
200
- value: [customerBizplaceId]
141
+
142
+ const foundProductBundle: ProductBundle = await qb.getOne()
143
+
144
+ if (foundProductBundle) {
145
+ bundleInfo.push({
146
+ productBundleId: foundProductBundle.id,
147
+ sku: foundProductBundle.sku,
148
+ releaseQty: orderInventory.releaseQty
149
+ })
150
+ const bundleProductSettings: any[] = foundProductBundle.productBundleSettings
151
+ bundleProductSettings.map(bundleProductSetting => {
152
+ const productDetailBundle: ProductDetail = bundleProductSetting.productDetail
153
+
154
+ let newOrderInventory: OrderInventory = Object.assign({}, orderInventory)
155
+ newOrderInventory.product.sku = productDetailBundle.product.sku
156
+ newOrderInventory.packingType = productDetailBundle.packingType
157
+ newOrderInventory.uomValue = productDetailBundle.uomValue
158
+ newOrderInventory.releaseQty = orderInventory.releaseQty * bundleProductSetting.bundleQty
159
+ newOrderInventory.product = productDetailBundle.product
160
+ newOrderInventories.push(newOrderInventory)
161
+ })
162
+ } else {
163
+ if (sku && refCode) {
164
+ context.throw(404, t(`error.sku ${sku} and refCode ${refCode} not exist in product / bundle master`))
165
+ }
166
+
167
+ if (sku) {
168
+ context.throw(404, t(`error.sku ${sku} not exist in product / bundle master`))
169
+ }
170
+
171
+ if (refCode) {
172
+ context.throw(404, t(`error.refCode ${refCode} not exist in product / bundle master`))
173
+ }
201
174
  }
202
- ]
175
+ }
176
+ })
177
+ )
203
178
 
204
- let sortings: any = []
179
+ let finalOrderInventories: OrderInventory[] = []
180
+
181
+ // Case 1. Every order inventories has specified batch id
182
+ if (newOrderInventories.every((orderInventory: OrderInventory) => orderInventory.batchId)) {
183
+ finalOrderInventories = await Promise.all(
184
+ newOrderInventories.map(async (orderInventory: OrderInventory) => {
185
+ let filters: any = [
186
+ {
187
+ name: 'batchId',
188
+ operator: 'eq',
189
+ value: orderInventory.batchId
190
+ },
191
+ {
192
+ name: 'productName',
193
+ operator: 'eq',
194
+ value: orderInventory.product.sku
195
+ },
196
+ {
197
+ name: 'bizplace',
198
+ operator: 'in',
199
+ value: [customerBizplaceId]
200
+ }
201
+ ]
205
202
 
206
- switch (orderInventory.product.pickingStrategy) {
207
- case 'FIFO':
208
- sortings.push({ name: 'created_at', desc: false })
209
- break
203
+ let sortings: any = []
210
204
 
211
- case 'LIFO':
212
- sortings.push({ name: 'created_at', desc: true })
213
- break
205
+ switch (orderInventory.product.pickingStrategy) {
206
+ case 'FIFO':
207
+ sortings.push({ name: 'created_at', desc: false })
208
+ break
214
209
 
215
- case 'FEFO':
216
- sortings.push({ name: 'expiration_date', desc: false })
217
- break
210
+ case 'LIFO':
211
+ sortings.push({ name: 'created_at', desc: true })
212
+ break
218
213
 
219
- case 'FMFO':
220
- sortings.push({ name: 'manufacture_date', desc: false })
221
- break
222
- }
214
+ case 'FEFO':
215
+ sortings.push({ name: 'expiration_date', desc: false })
216
+ break
217
+
218
+ case 'FMFO':
219
+ sortings.push({ name: 'manufacture_date', desc: false })
220
+ break
221
+ }
223
222
 
224
- // 2. get all inventory data according to SKU and batchId
225
- const response = await client.query({
226
- query: gql`
227
- query inventoriesByPallet($filters: [Filter], $sortings: [Sorting], $locationSortingRules: [Sorting]) {
228
- inventoriesByPallet(
229
- filters: $filters
230
- sortings: $sortings
231
- locationSortingRules: $locationSortingRules
223
+ // 2. get all inventory data according to SKU and batchId
224
+ const response = await client.query({
225
+ query: gql`
226
+ query inventoriesByPallet(
227
+ $filters: [Filter]
228
+ $sortings: [Sorting]
229
+ $locationSortingRules: [Sorting]
232
230
  ) {
233
- items {
234
- product {
235
- id
236
- name
237
- description
231
+ inventoriesByPallet(
232
+ filters: $filters
233
+ sortings: $sortings
234
+ locationSortingRules: $locationSortingRules
235
+ ) {
236
+ items {
237
+ product {
238
+ id
239
+ name
240
+ description
241
+ }
242
+ batchId
243
+ packingType
244
+ remainQty
245
+ remainUomValue
238
246
  }
239
- batchId
240
- packingType
241
- remainQty
242
- remainUomValue
247
+ total
243
248
  }
244
- total
249
+ }
250
+ `,
251
+ variables: { filters, sortings, locationSortingRules },
252
+ context: {
253
+ ...context,
254
+ state: {
255
+ ...context.state,
256
+ bizplace: customerBizplace,
257
+ domain
245
258
  }
246
259
  }
247
- `,
248
- variables: { filters, sortings, locationSortingRules },
249
- context: {
250
- ...context,
251
- state: {
252
- ...context.state,
253
- bizplace: customerBizplace,
254
- domain
260
+ } as any)
261
+
262
+ if (!response.errors) {
263
+ const items = response.data.inventoriesByPallet.items || []
264
+ if (requiredDraft) {
265
+ if (!items?.length) {
266
+ context.body = {
267
+ result: await createDraftOrder(
268
+ client,
269
+ customerBizplace,
270
+ bodyReq,
271
+ bundleInfo,
272
+ domain,
273
+ context,
274
+ orderInventories,
275
+ tx
276
+ )
277
+ }
278
+ return
279
+ }
255
280
  }
256
- }
257
- } as any)
258
281
 
259
- if (!response.errors) {
260
- const items = response.data.inventoriesByPallet.items || []
261
- if (!items?.length) {
262
- context.body = {
263
- result: await createDraftOrder(
264
- client,
265
- customerBizplace,
266
- bodyReq,
267
- bundleInfo,
268
- domain,
269
- context,
270
- orderInventories,
271
- tx
272
- )
273
- }
274
- return
275
- }
282
+ items.forEach(async item => {
283
+ if (requiredDraft) {
284
+ if (orderInventory.releaseQty > item.remainQty || orderInventory.releaseQty <= 0) {
285
+ context.body = {
286
+ result: await createDraftOrder(
287
+ client,
288
+ customerBizplace,
289
+ bodyReq,
290
+ bundleInfo,
291
+ domain,
292
+ context,
293
+ orderInventories,
294
+ tx
295
+ )
296
+ }
297
+ return
298
+ }
299
+ }
276
300
 
277
- items.forEach(async item => {
278
- if (orderInventory.releaseQty > item.remainQty || orderInventory.releaseQty <= 0) {
301
+ // 3. add more information to orderInventory
302
+ orderInventory.product.id = item.product.id
303
+ orderInventory.packingType = item.packingType
304
+ orderInventory.batchId = item.batchId
305
+ orderInventory.type = ORDER_TYPES.RELEASE_OF_GOODS
306
+ orderInventory.releaseUomValue = orderInventory.releaseQty * orderInventory.uomValue
307
+ orderInventory.uom = item.uom
308
+ })
309
+
310
+ return orderInventory
311
+ } else {
312
+ if (requiredDraft) {
279
313
  context.body = {
280
314
  result: await createDraftOrder(
281
315
  client,
@@ -290,225 +324,241 @@ router.post(
290
324
  }
291
325
  return
292
326
  }
293
-
294
- // 3. add more information to orderInventory
295
- orderInventory.product.id = item.product.id
296
- orderInventory.packingType = item.packingType
297
- orderInventory.batchId = item.batchId
298
- orderInventory.type = ORDER_TYPES.RELEASE_OF_GOODS
299
- orderInventory.releaseUomValue = orderInventory.releaseQty * orderInventory.uomValue
300
- orderInventory.uom = item.uom
301
- })
302
-
303
- return orderInventory
304
- } else {
305
- context.body = {
306
- result: await createDraftOrder(
307
- client,
308
- customerBizplace,
309
- bodyReq,
310
- bundleInfo,
311
- domain,
312
- context,
313
- orderInventories,
314
- tx
315
- )
316
327
  }
317
- return
318
- }
319
- })
320
- )
321
- } else {
322
- // Case 2. Some of order inventories doesn't have specified batch id
323
- // MMS order doesn't have batch ID field.
324
- // To cover this case first result of found inventory will be a target of RO
325
-
326
- // To combine items from different bundles
327
- newOrderInventories = combineItems(newOrderInventories)
328
-
329
- for (let i: number = 0; i < newOrderInventories.length; i++) {
330
- const orderInventory: OrderInventory = newOrderInventories[i]
331
- const { product, packingType }: { product: Product; packingType: string } = orderInventory
332
- const pickingStrategy: string = product.pickingStrategy
333
-
334
- if (isAutoAssign) {
335
- let qb: SelectQueryBuilder<Inventory> = tx.getRepository(Inventory).createQueryBuilder('iv')
336
- qb.leftJoinAndSelect('iv.location', 'loc')
337
- .andWhere('"iv"."domain_id" = :domainId')
338
- .andWhere('"iv"."bizplace_id" = :bizplaceId')
339
- .andWhere('"iv"."packing_type" = :packingType')
340
- .andWhere('"iv"."product_id" = :productId')
341
- .andWhere('"iv"."status" = :status')
342
- .andWhere('"loc"."type" NOT IN (:...locationTypes)')
343
- .setParameters({
344
- domainId: domain.id,
345
- bizplaceId: customerBizplace.id,
346
- packingType: packingType,
347
- productId: product.id,
348
- status: INVENTORY_STATUS.STORED,
349
- locationTypes: [LOCATION_TYPE.QUARANTINE, LOCATION_TYPE.RESERVE]
350
- })
328
+ })
329
+ )
330
+ } else {
331
+ // Case 2. Some of order inventories doesn't have specified batch id
332
+ // MMS order doesn't have batch ID field.
333
+ // To cover this case first result of found inventory will be a target of RO
334
+
335
+ // To combine items from different bundles
336
+ newOrderInventories = combineItems(newOrderInventories)
337
+
338
+ for (let i: number = 0; i < newOrderInventories.length; i++) {
339
+ const orderInventory: OrderInventory = newOrderInventories[i]
340
+ const { product, packingType }: { product: Product; packingType: string } = orderInventory
341
+ const pickingStrategy: string = product.pickingStrategy
342
+
343
+ if (isAutoAssign) {
344
+ let qb: SelectQueryBuilder<Inventory> = tx.getRepository(Inventory).createQueryBuilder('iv')
345
+ qb.leftJoinAndSelect('iv.location', 'loc')
346
+ .andWhere('"iv"."domain_id" = :domainId')
347
+ .andWhere('"iv"."bizplace_id" = :bizplaceId')
348
+ .andWhere('"iv"."packing_type" = :packingType')
349
+ .andWhere('"iv"."product_id" = :productId')
350
+ .andWhere('"iv"."status" = :status')
351
+ .andWhere('"loc"."type" NOT IN (:...locationTypes)')
352
+ .setParameters({
353
+ domainId: domain.id,
354
+ bizplaceId: customerBizplace.id,
355
+ packingType: packingType,
356
+ productId: product.id,
357
+ status: INVENTORY_STATUS.STORED,
358
+ locationTypes: [LOCATION_TYPE.QUARANTINE, LOCATION_TYPE.RESERVE]
359
+ })
360
+
361
+ switch (pickingStrategy) {
362
+ case 'FIFO':
363
+ qb.orderBy('"iv"."created_at"', 'ASC')
364
+ if (locationSortingRules?.length > 0) {
365
+ locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
366
+ qb.addOrderBy(`loc.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
367
+ })
368
+ }
369
+ break
370
+
371
+ case 'LIFO':
372
+ qb.orderBy('"iv"."created_at"', 'DESC')
373
+ if (locationSortingRules?.length > 0) {
374
+ locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
375
+ qb.addOrderBy(`loc.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
376
+ })
377
+ }
378
+ break
379
+
380
+ case 'FEFO':
381
+ qb.orderBy('"iv"."expiration_date"', 'ASC')
382
+ if (locationSortingRules?.length > 0) {
383
+ locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
384
+ qb.addOrderBy(`loc.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
385
+ })
386
+ }
387
+ break
388
+
389
+ case 'FMFO':
390
+ qb.orderBy('"iv"."manufacture_date"', 'ASC')
391
+ if (locationSortingRules?.length > 0) {
392
+ locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
393
+ qb.addOrderBy(`loc.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
394
+ })
395
+ }
396
+ break
397
+
398
+ case 'LOCATION':
399
+ if (locationSortingRules?.length > 0) {
400
+ locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
401
+ idx === 0
402
+ ? qb.orderBy(`loc.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
403
+ : qb.addOrderBy(`loc.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
404
+ })
405
+ } else {
406
+ qb.orderBy('"loc"."name"', 'DESC')
407
+ }
408
+ break
409
+ }
351
410
 
352
- switch (pickingStrategy) {
353
- case 'FIFO':
354
- qb.orderBy('"iv"."created_at"', 'ASC')
355
- if (locationSortingRules?.length > 0) {
356
- locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
357
- qb.addOrderBy(`loc.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
358
- })
359
- }
360
- break
361
-
362
- case 'LIFO':
363
- qb.orderBy('"iv"."created_at"', 'DESC')
364
- if (locationSortingRules?.length > 0) {
365
- locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
366
- qb.addOrderBy(`loc.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
367
- })
368
- }
369
- break
370
-
371
- case 'FEFO':
372
- qb.orderBy('"iv"."expiration_date"', 'ASC')
373
- if (locationSortingRules?.length > 0) {
374
- locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
375
- qb.addOrderBy(`loc.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
376
- })
377
- }
378
- break
379
-
380
- case 'FMFO':
381
- qb.orderBy('"iv"."manufacture_date"', 'ASC')
382
- if (locationSortingRules?.length > 0) {
383
- locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
384
- qb.addOrderBy(`loc.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
385
- })
386
- }
387
- break
388
-
389
- case 'LOCATION':
390
- if (locationSortingRules?.length > 0) {
391
- locationSortingRules.forEach((rule: { name: string; desc: boolean }, idx: number) => {
392
- idx === 0
393
- ? qb.orderBy(`loc.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
394
- : qb.addOrderBy(`loc.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
395
- })
396
- } else {
397
- qb.orderBy('"loc"."name"', 'DESC')
411
+ let inventories: Inventory[] = await qb.getMany()
412
+ if (requiredDraft) {
413
+ if (!inventories?.length) {
414
+ context.body = {
415
+ result: await createDraftOrder(
416
+ client,
417
+ customerBizplace,
418
+ bodyReq,
419
+ bundleInfo,
420
+ domain,
421
+ context,
422
+ orderInventories,
423
+ tx
424
+ )
425
+ }
426
+ return
398
427
  }
399
- break
400
- }
401
-
402
- let inventories: Inventory[] = await qb.getMany()
403
- if (!inventories?.length) {
404
- context.body = {
405
- result: await createDraftOrder(
406
- client,
407
- customerBizplace,
408
- bodyReq,
409
- bundleInfo,
410
- domain,
411
- context,
412
- orderInventories,
413
- tx
414
- )
415
428
  }
416
- return
417
- }
418
- inventories = inventories
419
- .map(inventory => {
420
- return {
421
- ...inventory,
422
- remainQty: inventory.qty - inventory.lockedQty,
423
- remainUomValue: inventory.uomValue - inventory.lockedUomValue
429
+ inventories = inventories
430
+ .map(inventory => {
431
+ return {
432
+ ...inventory,
433
+ remainQty: inventory.qty - inventory.lockedQty,
434
+ remainUomValue: inventory.uomValue - inventory.lockedUomValue
435
+ }
436
+ })
437
+ .filter(inventory => inventory.remainQty > 0)
438
+
439
+ let targetInventories: WorksheetDetail[]
440
+ try {
441
+ targetInventories = _composeTargetInventories(context, product, orderInventory, inventories)
442
+ } catch (e) {
443
+ if (requiredDraft) {
444
+ context.body = {
445
+ result: await createDraftOrder(
446
+ client,
447
+ customerBizplace,
448
+ bodyReq,
449
+ bundleInfo,
450
+ domain,
451
+ context,
452
+ orderInventories,
453
+ tx
454
+ )
455
+ }
456
+ return
457
+ } else {
458
+ targetInventories = _composeTargetInventories(context, product, orderInventory, inventories)
424
459
  }
425
- })
426
- .filter(inventory => inventory.remainQty > 0)
427
-
428
- let targetInventories: WorksheetDetail[]
429
- try {
430
- targetInventories = _composeTargetInventories(context, product, orderInventory, inventories)
431
- } catch (e) {
432
- context.body = {
433
- result: await createDraftOrder(
434
- client,
435
- customerBizplace,
436
- bodyReq,
437
- bundleInfo,
438
- domain,
439
- context,
440
- orderInventories,
441
- tx
442
- )
443
460
  }
444
- return
445
- }
446
- targetInventories.map(item => {
447
- finalOrderInventories.push(item.targetInventory)
448
- })
449
- } else {
450
- let filters: any = [
451
- {
452
- name: 'productName',
453
- operator: 'eq',
454
- value: orderInventory.product.sku
455
- },
456
- {
457
- name: 'bizplace_id',
458
- operator: 'eq',
459
- value: customerBizplaceId
461
+ if (!targetInventories?.length) {
462
+ context.throw(404, t(`error.no inventories found`))
460
463
  }
461
- ]
462
-
463
- // 2. get all inventory data according to SKU and batchId
464
- const response = await client.query({
465
- query: gql`
466
- query inventoryProductGroup($filters: [Filter]) {
467
- inventoryProductGroup(filters: $filters) {
468
- items {
469
- productId
470
- productSKU
471
- productName
472
- batchId
473
- packingType
474
- remainQty
475
- remainUomValue
464
+ targetInventories.map(item => {
465
+ finalOrderInventories.push(item.targetInventory)
466
+ })
467
+ } else {
468
+ let filters: any = [
469
+ {
470
+ name: 'productName',
471
+ operator: 'eq',
472
+ value: orderInventory.product.sku
473
+ },
474
+ {
475
+ name: 'bizplace_id',
476
+ operator: 'eq',
477
+ value: customerBizplaceId
478
+ }
479
+ ]
480
+
481
+ // 2. get all inventory data according to SKU and batchId
482
+ const response = await client.query({
483
+ query: gql`
484
+ query inventoryProductGroup($filters: [Filter]) {
485
+ inventoryProductGroup(filters: $filters) {
486
+ items {
487
+ productId
488
+ productSKU
489
+ productName
490
+ batchId
491
+ packingType
492
+ remainQty
493
+ remainUomValue
494
+ }
495
+ total
476
496
  }
477
- total
497
+ }
498
+ `,
499
+ variables: { filters },
500
+ context: {
501
+ ...context,
502
+ state: {
503
+ ...context.state,
504
+ bizplace: customerBizplace,
505
+ domain
478
506
  }
479
507
  }
480
- `,
481
- variables: { filters },
482
- context: {
483
- ...context,
484
- state: {
485
- ...context.state,
486
- bizplace: customerBizplace,
487
- domain
508
+ } as any)
509
+
510
+ if (!response.errors) {
511
+ const items = response.data.inventoryProductGroup.items || []
512
+ if (requiredDraft) {
513
+ if (!items?.length) {
514
+ context.body = {
515
+ result: await createDraftOrder(
516
+ client,
517
+ customerBizplace,
518
+ bodyReq,
519
+ bundleInfo,
520
+ domain,
521
+ context,
522
+ orderInventories,
523
+ tx
524
+ )
525
+ }
526
+ return
527
+ }
488
528
  }
489
- }
490
- } as any)
491
529
 
492
- if (!response.errors) {
493
- const items = response.data.inventoryProductGroup.items || []
494
- if (!items?.length) {
495
- context.body = {
496
- result: await createDraftOrder(
497
- client,
498
- customerBizplace,
499
- bodyReq,
500
- bundleInfo,
501
- domain,
502
- context,
503
- orderInventories,
504
- tx
505
- )
506
- }
507
- return
508
- }
530
+ items.forEach(async item => {
531
+ if (requiredDraft) {
532
+ if (orderInventory.releaseQty > item.remainQty || orderInventory.releaseQty <= 0) {
533
+ context.body = {
534
+ result: await createDraftOrder(
535
+ client,
536
+ customerBizplace,
537
+ bodyReq,
538
+ bundleInfo,
539
+ domain,
540
+ context,
541
+ orderInventories,
542
+ tx
543
+ )
544
+ }
545
+ return
546
+ }
547
+ }
509
548
 
510
- items.forEach(async item => {
511
- if (orderInventory.releaseQty > item.remainQty || orderInventory.releaseQty <= 0) {
549
+ // 3. add more information to orderInventory
550
+ orderInventory.product = { id: item.productId }
551
+ orderInventory.packingType = item.packingType
552
+ orderInventory.batchId = item.batchId
553
+ orderInventory.type = ORDER_TYPES.RELEASE_OF_GOODS
554
+ orderInventory.releaseUomValue = orderInventory.releaseQty * orderInventory.uomValue
555
+ orderInventory.uom = item.uom
556
+ })
557
+
558
+ delete orderInventory.uomValue
559
+ finalOrderInventories.push(orderInventory)
560
+ } else {
561
+ if (requiredDraft) {
512
562
  context.body = {
513
563
  result: await createDraftOrder(
514
564
  client,
@@ -523,174 +573,172 @@ router.post(
523
573
  }
524
574
  return
525
575
  }
526
-
527
- // 3. add more information to orderInventory
528
- orderInventory.product = { id: item.productId }
529
- orderInventory.packingType = item.packingType
530
- orderInventory.batchId = item.batchId
531
- orderInventory.type = ORDER_TYPES.RELEASE_OF_GOODS
532
- orderInventory.releaseUomValue = orderInventory.releaseQty * orderInventory.uomValue
533
- orderInventory.uom = item.uom
534
- })
535
-
536
- delete orderInventory.uomValue
537
- finalOrderInventories.push(orderInventory)
538
- } else {
539
- context.body = {
540
- result: await createDraftOrder(
541
- client,
542
- customerBizplace,
543
- bodyReq,
544
- bundleInfo,
545
- domain,
546
- context,
547
- orderInventories,
548
- tx
549
- )
550
576
  }
551
- return
552
577
  }
553
578
  }
554
579
  }
555
- }
556
580
 
557
- context.body = {
558
- result: (
559
- await client.mutate({
560
- mutation: gql`
561
- mutation addReleaseGood($releaseGood: NewReleaseGood, $shippingOrder: ShippingOrderPatch, $file: Upload) {
562
- addReleaseGood(releaseGood: $releaseGood, shippingOrder: $shippingOrder, file: $file) {
563
- id
564
- name
565
- refNo
566
- status
567
- truckNo
568
- ownTransport
569
- crossDocking
570
- marketplaceOrderStatus
571
- billingAddress
572
- deliveryAddress1
573
- deliveryAddress2
574
- deliveryAddress3
575
- deliveryAddress4
576
- deliveryAddress5
577
- attentionTo
578
- attentionCompany
579
- city
580
- state
581
- postalCode
582
- country
583
- phone1
584
- phone2
585
- email
586
- transporter
587
- trackingNo
588
- airwayBill
589
- invoice
590
- type
591
- storeName
592
- storeId
593
- routeId
594
- stopId
595
- arrivalNotice {
596
- name
597
- }
598
- exportOption
599
- releaseDate
600
- collectionOrderNo
601
- bizplace {
581
+ context.body = {
582
+ result: (
583
+ await client.mutate({
584
+ mutation: gql`
585
+ mutation addReleaseGood(
586
+ $releaseGood: NewReleaseGood
587
+ $shippingOrder: ShippingOrderPatch
588
+ $file: Upload
589
+ ) {
590
+ addReleaseGood(releaseGood: $releaseGood, shippingOrder: $shippingOrder, file: $file) {
591
+ id
602
592
  name
603
- }
604
- orderInventories {
605
- product {
606
- sku
593
+ refNo
594
+ status
595
+ truckNo
596
+ ownTransport
597
+ crossDocking
598
+ marketplaceOrderStatus
599
+ billingAddress
600
+ deliveryAddress1
601
+ deliveryAddress2
602
+ deliveryAddress3
603
+ deliveryAddress4
604
+ deliveryAddress5
605
+ attentionTo
606
+ attentionCompany
607
+ city
608
+ state
609
+ postalCode
610
+ country
611
+ phone1
612
+ phone2
613
+ email
614
+ transporter
615
+ trackingNo
616
+ airwayBill
617
+ invoice
618
+ type
619
+ storeName
620
+ storeId
621
+ routeId
622
+ stopId
623
+ arrivalNotice {
607
624
  name
608
- description
609
625
  }
610
- batchId
611
- packingType
612
- packingSize
613
- releaseQty
614
- releaseUomValue
615
- }
616
- orderVass {
617
- vas {
626
+ exportOption
627
+ releaseDate
628
+ collectionOrderNo
629
+ bizplace {
618
630
  name
619
- operationGuide
620
- operationGuideType
621
631
  }
622
- set
623
- targetType
624
- targetBatchId
625
- targetProduct {
626
- name
632
+ orderInventories {
633
+ product {
634
+ sku
635
+ name
636
+ description
637
+ }
638
+ batchId
639
+ packingType
640
+ packingSize
641
+ releaseQty
642
+ releaseUomValue
643
+ }
644
+ orderVass {
645
+ vas {
646
+ name
647
+ operationGuide
648
+ operationGuideType
649
+ }
650
+ set
651
+ targetType
652
+ targetBatchId
653
+ targetProduct {
654
+ name
655
+ description
656
+ }
657
+ packingType
658
+ qty
659
+ uomValue
660
+ otherTarget
627
661
  description
662
+ remark
663
+ status
664
+ operationGuide
628
665
  }
629
- packingType
630
- qty
631
- uomValue
632
- otherTarget
633
- description
634
- remark
635
- status
636
- operationGuide
637
666
  }
638
667
  }
668
+ `,
669
+ variables: {
670
+ releaseGood: {
671
+ customerBizplaceId: customerBizplace.id,
672
+ courierOption: bodyReq.courierOption,
673
+ collectionOrderNo: bodyReq.collectionOrderNo,
674
+ exportOption: bodyReq.exportOption,
675
+ orderInventories: finalOrderInventories,
676
+ ownTransport: bodyReq.ownTransport,
677
+ packingOption: bodyReq.packingOption,
678
+ refNo: bodyReq.refNo,
679
+ refNo2: bodyReq?.refNo2,
680
+ releaseDate: bodyReq.releaseDate,
681
+ type: bodyReq.type,
682
+ marketplaceOrderStatus: bodyReq?.marketplaceOrderStatus,
683
+ remark: bodyReq?.remark || null,
684
+ billingAddress: bodyReq?.billTo?.billingAddress || null,
685
+ deliveryAddress1: bodyReq?.deliverTo?.deliveryAddress1 || null,
686
+ deliveryAddress2: bodyReq?.deliverTo?.deliveryAddress2 || null,
687
+ deliveryAddress3: bodyReq?.deliverTo?.deliveryAddress3 || null,
688
+ deliveryAddress4: bodyReq?.deliverTo?.deliveryAddress4 || null,
689
+ deliveryAddress5: bodyReq?.deliverTo?.deliveryAddress5 || null,
690
+ attentionTo: bodyReq?.deliverTo?.attentionTo || null,
691
+ attentionCompany: bodyReq?.deliverTo?.attentionCompany || null,
692
+ city: bodyReq?.deliverTo?.city || null,
693
+ state: bodyReq?.deliverTo?.state || null,
694
+ postalCode: bodyReq?.deliverTo?.postalCode || null,
695
+ country: bodyReq?.deliverTo?.country || null,
696
+ phone1: bodyReq?.deliverTo?.phone1 || null,
697
+ phone2: bodyReq?.deliverTo?.phone2 || null,
698
+ email: bodyReq?.deliverTo?.email || null,
699
+ transporter: bodyReq?.transporter,
700
+ trackingNo: bodyReq?.trackingNo,
701
+ airwayBill: bodyReq?.airwayBill,
702
+ invoice: bodyReq?.invoice,
703
+ bundleInfo: JSON.stringify(bundleInfo),
704
+ storeName: bodyReq?.storeName,
705
+ storeId: bodyReq?.storeId,
706
+ routeId: bodyReq?.routeId,
707
+ stopId: bodyReq?.stopId
708
+ }
709
+ },
710
+ context: {
711
+ ...context,
712
+ hasUpload: true,
713
+ state: {
714
+ ...context.state,
715
+ customerBizplace,
716
+ domain,
717
+ type: 'api'
718
+ }
639
719
  }
640
- `,
641
- variables: {
642
- releaseGood: {
643
- customerBizplaceId: customerBizplace.id,
644
- courierOption: bodyReq.courierOption,
645
- collectionOrderNo: bodyReq.collectionOrderNo,
646
- exportOption: bodyReq.exportOption,
647
- orderInventories: finalOrderInventories,
648
- ownTransport: bodyReq.ownTransport,
649
- packingOption: bodyReq.packingOption,
650
- refNo: bodyReq.refNo,
651
- refNo2: bodyReq?.refNo2,
652
- releaseDate: bodyReq.releaseDate,
653
- type: bodyReq.type,
654
- marketplaceOrderStatus: bodyReq?.marketplaceOrderStatus,
655
- remark: bodyReq?.remark || null,
656
- billingAddress: bodyReq?.billTo?.billingAddress || null,
657
- deliveryAddress1: bodyReq?.deliverTo?.deliveryAddress1 || null,
658
- deliveryAddress2: bodyReq?.deliverTo?.deliveryAddress2 || null,
659
- deliveryAddress3: bodyReq?.deliverTo?.deliveryAddress3 || null,
660
- deliveryAddress4: bodyReq?.deliverTo?.deliveryAddress4 || null,
661
- deliveryAddress5: bodyReq?.deliverTo?.deliveryAddress5 || null,
662
- attentionTo: bodyReq?.deliverTo?.attentionTo || null,
663
- attentionCompany: bodyReq?.deliverTo?.attentionCompany || null,
664
- city: bodyReq?.deliverTo?.city || null,
665
- state: bodyReq?.deliverTo?.state || null,
666
- postalCode: bodyReq?.deliverTo?.postalCode || null,
667
- country: bodyReq?.deliverTo?.country || null,
668
- phone1: bodyReq?.deliverTo?.phone1 || null,
669
- phone2: bodyReq?.deliverTo?.phone2 || null,
670
- email: bodyReq?.deliverTo?.email || null,
671
- transporter: bodyReq?.transporter,
672
- trackingNo: bodyReq?.trackingNo,
673
- airwayBill: bodyReq?.airwayBill,
674
- invoice: bodyReq?.invoice,
675
- bundleInfo: JSON.stringify(bundleInfo),
676
- storeName: bodyReq?.storeName,
677
- storeId: bodyReq?.storeId,
678
- routeId: bodyReq?.routeId,
679
- stopId: bodyReq?.stopId
680
- }
681
- },
682
- context: {
683
- ...context,
684
- hasUpload: true,
685
- state: {
686
- ...context.state,
687
- customerBizplace,
688
- domain,
689
- type: 'api'
690
- }
691
- }
692
- } as any)
693
- ).data
720
+ } as any)
721
+ ).data
722
+ }
723
+
724
+ createPayloadLog(
725
+ customerBizplaceId,
726
+ `/${apiVersion}/warehouse/:warehouseId/add-release-order`,
727
+ bodyReq,
728
+ context.body,
729
+ context,
730
+ PayloadType.INGESTION
731
+ )
732
+ } catch (error) {
733
+ createPayloadLog(
734
+ customerBizplaceId,
735
+ `/${apiVersion}/warehouse/:warehouseId/add-release-order`,
736
+ bodyReq,
737
+ error,
738
+ context,
739
+ PayloadType.INGESTION
740
+ )
741
+ throw error
694
742
  }
695
743
  })
696
744
  }
@@ -757,7 +805,7 @@ export function _composeTargetInventories(context, product, record, inventories)
757
805
  })
758
806
 
759
807
  if (totalInventoryQty < record.releaseQty) {
760
- context.throw(404, t(`error.invalid release qty for ${product?.sku}`))
808
+ context.throw(404, t(`error.invalid release qty for ${product?.sku}`)) //
761
809
  }
762
810
 
763
811
  let targetInventories = []