@things-factory/operato-hub 4.3.27 → 4.3.30

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.
@@ -1,15 +1,34 @@
1
- import gql from 'graphql-tag'
2
- import { EntityManager, getConnection, SelectQueryBuilder } from 'typeorm'
1
+ import { EntityManager, getConnection, getRepository, In, Not, Repository, SelectQueryBuilder } from 'typeorm'
2
+ import { v4 as uuidv4 } from 'uuid'
3
3
 
4
4
  import { restfulApiRouter as router } from '@things-factory/api'
5
+ import { User } from '@things-factory/auth-base'
5
6
  import { Bizplace, getCompanyBizplace } from '@things-factory/biz-base'
7
+ import { logger } from '@things-factory/env'
8
+ import { generateId } from '@things-factory/id-rule-base'
6
9
  import { createPayloadLog, PayloadType } from '@things-factory/integration-base'
7
- import { Product, ProductBundle, ProductDetail } from '@things-factory/product-base'
8
- import { ORDER_TYPES, OrderInventory } from '@things-factory/sales-base'
10
+ import { Sellercraft } from '@things-factory/integration-sellercraft'
11
+ import { MarketplaceOrder } from '@things-factory/marketplace-base'
12
+ import { ProductBundle, ProductDetail } from '@things-factory/product-base'
13
+ import {
14
+ createDraftReleaseGoodFunction,
15
+ ORDER_INVENTORY_STATUS,
16
+ ORDER_NUMBER_RULE_TYPE,
17
+ ORDER_NUMBER_SETTING_KEY,
18
+ ORDER_PRODUCT_STATUS,
19
+ ORDER_STATUS,
20
+ ORDER_TYPES,
21
+ OrderInventory,
22
+ OrderNoGenerator,
23
+ OrderProduct,
24
+ ReleaseGood,
25
+ SellercraftController
26
+ } from '@things-factory/sales-base'
9
27
  import { Setting } from '@things-factory/setting-base'
28
+ import { Domain } from '@things-factory/shell'
10
29
  import { Inventory, INVENTORY_STATUS, LOCATION_TYPE } from '@things-factory/warehouse-base'
11
- import { WorksheetDetail } from '@things-factory/worksheet-base'
12
30
 
31
+ import { ValidationError } from '../../../../errors/index'
13
32
  import { businessMiddleware } from '../../../../middlewares/business-middleware'
14
33
  import { warehouseMiddleware } from '../../../../middlewares/warehouse-middleware'
15
34
 
@@ -22,10 +41,15 @@ router.post(
22
41
  warehouseMiddleware,
23
42
  async (context, next) => {
24
43
  return await getConnection().transaction(async (tx: EntityManager) => {
25
- const { client, domain } = context.state
44
+ const { client, domain, user } = context.state
26
45
  const { warehouseId: customerBizplaceId } = context.params
27
46
  const { t } = context
28
47
  const bodyReq = context.request.body
48
+ const requiredDraft: Boolean = bodyReq.hasOwnProperty('requiredDraft') ? bodyReq.requiredDraft : true
49
+ let releaseGood
50
+ let massagedData = { orderProducts: [], combinedItems: [] }
51
+ // const isAutoAssign: Boolean = bodyReq.hasOwnProperty('isAutoAssign') ? bodyReq.isAutoAssign : true
52
+
29
53
  try {
30
54
  if (!customerBizplaceId) context.throw(404, t('error.warehouse id not found'))
31
55
 
@@ -35,692 +59,165 @@ router.post(
35
59
  debug('post:/add-release-order request.body', bodyReq)
36
60
  await checkMandatoryKey(context, bodyReq)
37
61
 
38
- const isAutoAssign: Boolean = bodyReq.hasOwnProperty('isAutoAssign') ? bodyReq.isAutoAssign : true
39
- const requiredDraft: Boolean = bodyReq.hasOwnProperty('requiredDraft') ? bodyReq.requiredDraft : true
40
-
41
62
  // get customer company's bizplace
42
63
  const custCompanyBizplace: Bizplace = await getCompanyBizplace(null, null, customerBizplaceId, tx)
43
64
  const customerBizplace: Bizplace = await tx.getRepository(Bizplace).findOne({
44
65
  where: { id: customerBizplaceId }
45
66
  })
46
67
 
47
- let locationSortingRules = []
48
- const inventoryAssignmentSetting: Setting = await tx.getRepository(Setting).findOne({
49
- where: { domain, name: 'rule-for-inventory-assignment' }
50
- })
51
-
52
- if (inventoryAssignmentSetting) {
53
- let locationSetting = JSON.parse(inventoryAssignmentSetting.value)
54
- for (const key in locationSetting) {
55
- locationSortingRules.push({ name: key, desc: locationSetting[key] == 'ASC' ? false : true })
56
- }
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
- }
66
- }
68
+ releaseGood = {
69
+ domain,
70
+ bizplace: customerBizplace,
71
+ companyBizplace: custCompanyBizplace,
72
+ courierOption: bodyReq.courierOption,
73
+ collectionOrderNo: bodyReq.collectionOrderNo,
74
+ exportOption: bodyReq.exportOption,
75
+ ownTransport: bodyReq.ownTransport,
76
+ packingOption: bodyReq.packingOption,
77
+ refNo: bodyReq.refNo,
78
+ refNo2: bodyReq?.refNo2,
79
+ refNo3: bodyReq?.refNo3,
80
+ releaseDate: bodyReq.releaseDate,
81
+ type: bodyReq.type,
82
+ marketplaceOrderStatus: bodyReq?.marketplaceOrderStatus,
83
+ remark: bodyReq?.remark || null,
84
+ billingAddress: bodyReq?.billTo?.billingAddress || null,
85
+ deliveryAddress1: bodyReq?.deliverTo?.deliveryAddress1 || null,
86
+ deliveryAddress2: bodyReq?.deliverTo?.deliveryAddress2 || null,
87
+ deliveryAddress3: bodyReq?.deliverTo?.deliveryAddress3 || null,
88
+ deliveryAddress4: bodyReq?.deliverTo?.deliveryAddress4 || null,
89
+ deliveryAddress5: bodyReq?.deliverTo?.deliveryAddress5 || null,
90
+ attentionTo: bodyReq?.deliverTo?.attentionTo || null,
91
+ attentionCompany: bodyReq?.deliverTo?.attentionCompany || null,
92
+ city: bodyReq?.deliverTo?.city || null,
93
+ state: bodyReq?.deliverTo?.state || null,
94
+ postalCode: bodyReq?.deliverTo?.postalCode || null,
95
+ country: bodyReq?.deliverTo?.country || null,
96
+ phone1: bodyReq?.deliverTo?.phone1 || null,
97
+ phone2: bodyReq?.deliverTo?.phone2 || null,
98
+ email: bodyReq?.deliverTo?.email || null,
99
+ transporter: bodyReq?.transporter,
100
+ trackingNo: bodyReq?.trackingNo,
101
+ airwayBill: bodyReq?.airwayBill,
102
+ invoice: bodyReq?.invoice,
103
+ storeName: bodyReq?.storeName,
104
+ storeId: bodyReq?.storeId,
105
+ routeId: bodyReq?.routeId,
106
+ stopId: bodyReq?.stopId
67
107
  }
68
108
 
69
- const orderInventories: OrderInventory[] = bodyReq.orderInventories
70
- let newOrderInventories: OrderInventory[] = []
71
- let bundleInfo: any[] = []
72
-
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
78
-
79
- const qb: SelectQueryBuilder<ProductDetail> = tx
80
- .getRepository(ProductDetail)
81
- .createQueryBuilder('PD')
82
- .innerJoinAndSelect('PD.product', 'PROD')
83
- .where('PROD.bizplace_id = :bizplaceId', { bizplaceId: custCompanyBizplace.id })
84
-
85
- if (!sku && !refCode) {
86
- context.throw(404, t('error.sku or refCode not found'))
87
- }
88
-
89
- if (refCode) {
90
- qb.andWhere('PD.ref_code = :refCode', { refCode })
91
- }
92
-
93
- if (sku) {
94
- qb.andWhere('PROD.sku = :sku', { sku })
95
- }
96
-
97
- if (packingType) {
98
- qb.andWhere('PD.packing_type = :packingType', { packingType })
99
- }
100
-
101
- if (!packingType && !refCode) {
102
- qb.andWhere('PD.is_default = :isDefault', { isDefault: true })
103
- }
104
-
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 })
132
- }
133
-
134
- if (sku) {
135
- qb.andWhere('PB.sku = :sku', { sku })
136
- }
137
-
138
- if (packingType) {
139
- qb.andWhere('PB.packing_type = :packingType', { packingType })
140
- }
109
+ // check existing order
110
+ await checkExistingOrder(releaseGood, customerBizplace, context, tx)
141
111
 
142
- const foundProductBundle: ProductBundle = await qb.getOne()
112
+ //massage data
113
+ massagedData = await massageOrderItems(releaseGood, bodyReq.orderInventories, context)
143
114
 
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)
115
+ // validation
116
+ let errorList = []
117
+ await Promise.all(
118
+ massagedData.combinedItems.map(async item => {
119
+ let itemList = await tx.query(
120
+ `
121
+ select wboi.* from warehouse_bizplace_onhand_inventories wboi
122
+ where wboi."domainId"= $1 and
123
+ wboi."bizplaceId" = $2 and
124
+ wboi."groupType" = 'SINGLE' and
125
+ wboi."productId" = $3 and
126
+ wboi."packingType" = $4 and
127
+ wboi."packingSize" = $5 and
128
+ wboi."uom" = $6 and
129
+ wboi."remainQty" >= $7 and
130
+ wboi."remainUomValue" >= $8
131
+ `,
132
+ [
133
+ domain.id,
134
+ customerBizplace.id,
135
+ item.product.id,
136
+ item.packingType,
137
+ item.packingSize,
138
+ item.uom,
139
+ item.releaseQty,
140
+ item.releaseUomValue
141
+ ]
142
+ )
143
+ if (itemList.length <= 0) {
144
+ errorList.push(
145
+ JSON.stringify({
146
+ code: 'insufficient stock',
147
+ sku: item.product.sku,
148
+ packingType: item.packingType,
149
+ packingSize: item.packingSize,
150
+ uom: item.uom,
151
+ qty: item.releaseQty,
152
+ uom_value: item.releaseUomValue
161
153
  })
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
- }
174
- }
154
+ )
175
155
  }
156
+ console.log()
176
157
  })
177
158
  )
178
159
 
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
- ]
202
-
203
- let sortings: any = []
204
-
205
- switch (orderInventory.product.pickingStrategy) {
206
- case 'FIFO':
207
- sortings.push({ name: 'created_at', desc: false })
208
- break
209
-
210
- case 'LIFO':
211
- sortings.push({ name: 'created_at', desc: true })
212
- break
213
-
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
- }
222
-
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]
230
- ) {
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
246
- }
247
- total
248
- }
249
- }
250
- `,
251
- variables: { filters, sortings, locationSortingRules },
252
- context: {
253
- ...context,
254
- state: {
255
- ...context.state,
256
- bizplace: customerBizplace,
257
- domain
258
- }
259
- }
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
- }
280
- }
281
-
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
- }
300
-
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) {
313
- context.body = {
314
- result: await createDraftOrder(
315
- client,
316
- customerBizplace,
317
- bodyReq,
318
- bundleInfo,
319
- domain,
320
- context,
321
- orderInventories,
322
- tx
323
- )
324
- }
325
- return
326
- }
327
- }
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
- }
410
-
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
427
- }
428
- }
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)
459
- }
460
- }
461
- if (!targetInventories?.length) {
462
- context.throw(404, t(`error.no inventories found`))
463
- }
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
496
- }
497
- }
498
- `,
499
- variables: { filters },
500
- context: {
501
- ...context,
502
- state: {
503
- ...context.state,
504
- bizplace: customerBizplace,
505
- domain
506
- }
507
- }
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
- }
528
- }
529
-
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
- }
548
-
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) {
562
- context.body = {
563
- result: await createDraftOrder(
564
- client,
565
- customerBizplace,
566
- bodyReq,
567
- bundleInfo,
568
- domain,
569
- context,
570
- orderInventories,
571
- tx
572
- )
573
- }
574
- return
575
- }
576
- }
577
- }
578
- }
160
+ if (errorList.length > 0) {
161
+ throw new ValidationError({
162
+ errorCode: 'INSUFFICIENT_STOCK',
163
+ message: 'insufficient stock',
164
+ detail: errorList.join(', ')
165
+ })
579
166
  }
580
-
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
592
- name
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 {
624
- name
625
- }
626
- exportOption
627
- releaseDate
628
- collectionOrderNo
629
- bizplace {
630
- name
631
- }
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
661
- description
662
- remark
663
- status
664
- operationGuide
665
- }
666
- }
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
- }
719
- }
720
- } as any)
721
- ).data
167
+ // assignment
168
+ console.time('assign')
169
+ let assignedOrderProducts = await assignToInventory(massagedData.orderProducts, customerBizplace, context, tx)
170
+ console.timeEnd('assign')
171
+
172
+ // create order
173
+ console.time('save')
174
+ let result = await createReleaseGood(releaseGood, assignedOrderProducts, customerBizplace, context, tx)
175
+ console.timeEnd('save')
176
+
177
+ let addReleaseGood = {
178
+ id: result.id,
179
+ name: result.name,
180
+ refNo: result.refNo,
181
+ refNo2: result.refNo2,
182
+ refNo3: result.refNo3,
183
+ status: result.status,
184
+ truckNo: result.truckNo,
185
+ ownTransport: result.ownTransport,
186
+ crossDocking: result.crossDocking,
187
+ marketplaceOrderStatus: result.marketplaceOrderStatus,
188
+ billingAddress: result.billingAddress,
189
+ deliveryAddress1: result.deliveryAddress1,
190
+ deliveryAddress2: result.deliveryAddress2,
191
+ deliveryAddress3: result.deliveryAddress3,
192
+ deliveryAddress4: result.deliveryAddress4,
193
+ deliveryAddress5: result.deliveryAddress5,
194
+ attentionTo: result.attentionTo,
195
+ attentionCompany: result.attentionCompany,
196
+ city: result.city,
197
+ state: result.state,
198
+ postalCode: result.postalCode,
199
+ country: result.country,
200
+ phone1: result.phone1,
201
+ phone2: result.phone2,
202
+ email: result.email,
203
+ transporter: result.transporter,
204
+ trackingNo: result.trackingNo,
205
+ airwayBill: result.airwayBill,
206
+ invoice: result.invoice,
207
+ type: result.type,
208
+ storeName: result.storeName,
209
+ storeId: result.storeId,
210
+ routeId: result.routeId,
211
+ stopId: result.stopId,
212
+ exportOption: result.exportOption,
213
+ releaseDate: result.releaseDate,
214
+ collectionOrderNo: result.collectionOrderNo,
215
+ bizplace: { name: result.bizplace.name },
216
+ domain: { name: result.domain.name },
217
+ orderInventories: result.orderInventories
722
218
  }
723
-
219
+ // return body
220
+ context.body = { result: addReleaseGood }
724
221
  createPayloadLog(
725
222
  customerBizplaceId,
726
223
  `/${apiVersion}/warehouse/:warehouseId/add-release-order`,
@@ -738,13 +235,20 @@ router.post(
738
235
  context,
739
236
  PayloadType.INGESTION
740
237
  )
741
- throw error
238
+
239
+ if (error.errorCode) {
240
+ if (error.errorCode == 'INSUFFICIENT_STOCK' && requiredDraft) {
241
+ await createDraftOrder(releaseGood, massagedData.orderProducts, context)
242
+ }
243
+ }
244
+ logger.error(`add-release-order: ${JSON.stringify(error)}`)
245
+ context.throw(400, context.t(error))
742
246
  }
743
247
  })
744
248
  }
745
249
  )
746
250
 
747
- export async function checkMandatoryKey(context, params) {
251
+ async function checkMandatoryKey(context, params) {
748
252
  let noparams = ['releaseDate', 'exportOption'].filter(param => !(param in params))
749
253
 
750
254
  if ('ownTransport' in params) {
@@ -792,172 +296,665 @@ export async function checkMandatoryKey(context, params) {
792
296
  return params
793
297
  }
794
298
 
795
- export function _composeTargetInventories(context, product, record, inventories) {
796
- const { t } = context
797
- let leftReleaseQty = record.releaseQty
798
- let leftReleaseUomValue = record.releaseUomValue
799
- let compReleaseQty = 0
800
- let compReleaseUomValue = 0
801
- let totalInventoryQty: number = 0
802
-
803
- inventories.map(inventory => {
804
- totalInventoryQty += inventory.remainQty
805
- })
299
+ async function checkExistingOrder(releaseGood, bizplace, context, tx) {
300
+ const { domain, user }: { domain: Domain; user: User } = context.state
301
+ if (releaseGood.type == 'b2c') {
302
+ const sellercraft: Sellercraft = await tx.getRepository(Sellercraft).findOne({ domain: bizplace.domain })
806
303
 
807
- if (totalInventoryQty < record.releaseQty) {
808
- context.throw(404, t(`error.invalid release qty for ${product?.sku}`)) //
809
- }
304
+ if (sellercraft) {
305
+ const sellercraftCtrl: SellercraftController = new SellercraftController(tx, bizplace.domain, user)
306
+ await sellercraftCtrl.checkExistingReleaseGood(sellercraft, releaseGood)
307
+ } else {
308
+ const refNo: string = releaseGood.refNo
309
+ const foundReleaseGood: ReleaseGood = await tx.getRepository(ReleaseGood).findOne({
310
+ where: {
311
+ domain: domain,
312
+ refNo,
313
+ status: Not(In([ORDER_STATUS.CANCELLED, ORDER_STATUS.PENDING_CANCEL]))
314
+ },
315
+ relations: ['bizplace', 'bizplace.domain', 'bizplace.company', 'bizplace.company.domain']
316
+ })
810
317
 
811
- let targetInventories = []
812
- let idx = 0
813
- while (compReleaseQty < record.releaseQty) {
814
- const inv = inventories[idx]
318
+ if (foundReleaseGood) {
319
+ const customerCompanyDomain: Domain = foundReleaseGood.bizplace.company.domain
320
+ const marketplaceOrder: MarketplaceOrder = await tx.getRepository(MarketplaceOrder).findOne({
321
+ where: { orderNo: refNo, domain: customerCompanyDomain }
322
+ })
815
323
 
816
- if (inv.remainQty > leftReleaseQty) {
817
- compReleaseQty += leftReleaseQty
818
- compReleaseUomValue += leftReleaseUomValue
324
+ // Need to restructure the validation
325
+ if (marketplaceOrder?.isSplitted) {
326
+ const refNo2: string = releaseGood.refNo2
327
+ const foundSplittedReleaseGood = await tx.getRepository(ReleaseGood).findOne({
328
+ where: {
329
+ domain,
330
+ refNo2,
331
+ status: Not(In([ORDER_STATUS.CANCELLED, ORDER_STATUS.PENDING_CANCEL]))
332
+ },
333
+ relations: ['bizplace', 'bizplace.domain', 'bizplace.company', 'bizplace.company.domain']
334
+ })
819
335
 
820
- targetInventories.push({
821
- targetInventory: {
822
- inventory: { id: inv.id },
823
- packingType: inv.packingType,
824
- batchId: inv.batchId,
825
- releaseQty: leftReleaseQty,
826
- releaseUomValue: leftReleaseQty * record.uomValue,
827
- uom: inv.uom,
828
- product: { id: product.id },
829
- type: ORDER_TYPES.RELEASE_OF_GOODS
336
+ if (foundSplittedReleaseGood)
337
+ throw new ValidationError({
338
+ errorCode: 'EXISTING_ORDER',
339
+ message: 'existing release order found',
340
+ detail: foundSplittedReleaseGood.name
341
+ })
342
+ } else {
343
+ throw new ValidationError({
344
+ errorCode: 'EXISTING_ORDER',
345
+ message: 'existing release order found',
346
+ detail: foundReleaseGood.name
347
+ })
830
348
  }
349
+ }
350
+ }
351
+ }
352
+ }
353
+
354
+ async function createDraftOrder(releaseGood, orderProducts, context) {
355
+ return await getConnection().transaction(async (tx: EntityManager) => {
356
+ return await createDraftReleaseGoodFunction(null, releaseGood, orderProducts, null, [], context, tx)
357
+ })
358
+ }
359
+
360
+ async function createReleaseGood(
361
+ releaseGood: any,
362
+ orderProducts: OrderProduct[],
363
+ bizplace: Bizplace,
364
+ context: any,
365
+ tx?: EntityManager
366
+ ): Promise<ReleaseGood> {
367
+ try {
368
+ const { domain, user }: { domain: Domain; user: User } = context.state
369
+ const settingRepo: Repository<Setting> = tx?.getRepository(Setting) || getRepository(Setting)
370
+
371
+ if (orderProducts.length <= 0) {
372
+ throw new ValidationError({
373
+ errorCode: 'ITEM_NOT_FOUND',
374
+ message: 'no order items found',
375
+ detail: JSON.stringify(releaseGood)
831
376
  })
832
- } else {
833
- compReleaseQty += inv.remainQty
834
- compReleaseUomValue += inv.remainUomValue
835
- leftReleaseQty -= inv.remainQty
836
- leftReleaseUomValue -= inv.remainUomValue
377
+ }
837
378
 
838
- targetInventories.push({
839
- targetInventory: {
840
- inventory: { id: inv.id },
841
- packingType: inv.packingType,
842
- batchId: inv.batchId,
843
- releaseQty: inv.remainQty,
844
- releaseUomValue: inv.remainUomValue,
845
- uom: inv.uom,
846
- product: { id: product.id },
847
- type: ORDER_TYPES.RELEASE_OF_GOODS
379
+ let newReleaseGood: ReleaseGood = new ReleaseGood()
380
+
381
+ // find RO number rule setting
382
+ const roNoSetting: Setting = await settingRepo.findOne({
383
+ where: {
384
+ domain,
385
+ name: ORDER_NUMBER_SETTING_KEY.RO_NUMBER_RULE
386
+ }
387
+ })
388
+
389
+ newReleaseGood = {
390
+ ...newReleaseGood,
391
+ name: roNoSetting
392
+ ? await generateId({ domain, type: ORDER_NUMBER_RULE_TYPE.RO_NUMBER, seed: {} })
393
+ : OrderNoGenerator.releaseGood(),
394
+ domain: domain,
395
+ bizplace: bizplace,
396
+ collectionOrderNo: releaseGood.collectionOrderNo,
397
+ courierOption: releaseGood.courierOption,
398
+ exportOption: releaseGood.exportOption,
399
+ ownTransport: releaseGood.ownTransport,
400
+ packingOption: releaseGood.packingOption,
401
+ marketplaceOrderStatus: releaseGood?.marketplaceOrderStatus || null,
402
+ billingAddress: releaseGood?.billingAddress || null,
403
+ deliveryAddress1: releaseGood?.deliveryAddress1 || null,
404
+ deliveryAddress2: releaseGood?.deliveryAddress2 || null,
405
+ deliveryAddress3: releaseGood?.deliveryAddress3 || null,
406
+ deliveryAddress4: releaseGood?.deliveryAddress4 || null,
407
+ deliveryAddress5: releaseGood?.deliveryAddress5 || null,
408
+ attentionTo: releaseGood?.attentionTo || null,
409
+ attentionCompany: releaseGood?.attentionCompany || null,
410
+ city: releaseGood?.city || null,
411
+ state: releaseGood?.state || null,
412
+ postalCode: releaseGood?.postalCode || null,
413
+ country: releaseGood?.country || null,
414
+ phone1: releaseGood?.phone1 || null,
415
+ phone2: releaseGood?.phone2 || null,
416
+ email: releaseGood?.email || null,
417
+ transporter: releaseGood?.transporter,
418
+ trackingNo: releaseGood?.trackingNo,
419
+ airwayBill: releaseGood?.airwayBill,
420
+ invoice: releaseGood?.invoice,
421
+ refNo: releaseGood.refNo,
422
+ refNo2: releaseGood.refNo2 || null,
423
+ refNo3: releaseGood.refNo3 || null,
424
+ remark: releaseGood?.remark || null,
425
+ releaseDate: releaseGood.releaseDate,
426
+ truckNo: releaseGood.truckNo,
427
+ type: releaseGood?.type ? releaseGood.type : 'b2b',
428
+ status: ORDER_STATUS.PENDING_WORKSHEET,
429
+ storeName: releaseGood?.storeName,
430
+ storeId: releaseGood?.storeId,
431
+ routeId: releaseGood?.routeId,
432
+ stopId: releaseGood?.stopId,
433
+ noOfItems: orderProducts.length,
434
+ creator: user,
435
+ updater: user,
436
+ acceptedBy: user,
437
+ acceptedAt: new Date()
438
+ }
439
+
440
+ let resultReleaseGood = await tx.getRepository(ReleaseGood).save(newReleaseGood)
441
+ let combinedOrderInventories: OrderInventory[] = []
442
+ await Promise.all(
443
+ orderProducts.map(async orderProduct => {
444
+ let orderInventories: OrderInventory[] = orderProduct.orderInventories
445
+ orderProduct = Object.assign(new OrderProduct(), {
446
+ ...orderProduct,
447
+ name: uuidv4(),
448
+ domain: domain,
449
+ bizplace: bizplace,
450
+ releaseGood: resultReleaseGood,
451
+ type: ORDER_TYPES.RELEASE_OF_GOODS,
452
+ status: ORDER_PRODUCT_STATUS.ASSIGNED
453
+ })
454
+ await tx.getRepository(OrderProduct).save(orderProduct)
455
+
456
+ for (let oiIdx = 0; oiIdx < orderInventories.length; oiIdx++) {
457
+ let orderInventory = Object.assign(new OrderInventory(), {
458
+ ...orderInventories[oiIdx],
459
+ name: uuidv4(),
460
+ domain: domain,
461
+ bizplace: bizplace,
462
+ releaseGood: resultReleaseGood,
463
+ type: ORDER_TYPES.RELEASE_OF_GOODS,
464
+ status: ORDER_PRODUCT_STATUS.ASSIGNED
465
+ })
466
+ await tx.getRepository(OrderInventory).save(orderInventory)
467
+
468
+ combinedOrderInventories.push({
469
+ product: { name: orderInventory.product.name, sku: orderInventory.product.sku },
470
+ batchId: orderInventory.batchId,
471
+ packingType: orderInventory.packingType,
472
+ packingSize: orderInventory.packingSize,
473
+ releaseQty: orderInventory.releaseQty,
474
+ releaseUomValue: orderInventory.releaseUomValue
475
+ })
848
476
  }
849
477
  })
850
- }
478
+ )
851
479
 
852
- idx++
853
- }
480
+ newReleaseGood.orderInventories = combinedOrderInventories
854
481
 
855
- return targetInventories
482
+ return newReleaseGood
483
+ } catch (error) {
484
+ throw error
485
+ }
856
486
  }
857
487
 
858
- function combineItems(inventoryList) {
859
- let mappedList = []
488
+ async function massageOrderItems(
489
+ releaseGood,
490
+ inputOrderProducts,
491
+ context
492
+ ): Promise<{ orderProducts: OrderProduct[]; combinedItems: OrderProduct[] }> {
493
+ const { t } = context
494
+ try {
495
+ let orderProducts: OrderProduct[] = []
496
+ let errorList = []
497
+ //combine any repeated skus
498
+ inputOrderProducts = inputOrderProducts.reduce((acc, itm, idx) => {
499
+ let { sku, refCode } = itm.product
500
+ let { packingType, packingSize, batchId, releaseQty } = itm
501
+
502
+ if (!sku && !refCode) {
503
+ errorList.push(
504
+ JSON.stringify({
505
+ code: 'INVALID_DATA',
506
+ message: 'sku or refCode not found',
507
+ sku: sku,
508
+ refCode: refCode,
509
+ packingType: packingType?.trim(),
510
+ packingSize: packingSize,
511
+ qty: releaseQty,
512
+ })
513
+ )
514
+ }
860
515
 
861
- inventoryList.map(e => {
862
- let item = mappedList.find(i => i.product.id === e.product.id)
863
- if (item) {
864
- mappedList = mappedList.filter(m => m != item)
865
- item.releaseQty += e.releaseQty
866
- mappedList.push(item)
867
- } else {
868
- mappedList.push(e)
516
+ if (releaseQty <= 0) {
517
+ errorList.push(
518
+ JSON.stringify({
519
+ code: 'INVALID_DATA',
520
+ message: 'negative stock request',
521
+ sku: sku,
522
+ refCode: refCode,
523
+ packingType: packingType?.trim(),
524
+ packingSize: packingSize,
525
+ qty: releaseQty,
526
+ })
527
+ )
528
+ }
529
+
530
+ let existingIndex = acc.findIndex(
531
+ itm =>
532
+ itm?.sku == sku?.trim() &&
533
+ itm?.refCode == refCode?.trim() &&
534
+ itm?.packingType == packingType?.trim() &&
535
+ itm?.packingSize == packingSize
536
+ )
537
+
538
+ if (existingIndex >= 0 && releaseQty > 0) {
539
+ acc[existingIndex].releaseQty = acc[existingIndex].releaseQty + releaseQty
540
+ } else {
541
+ acc.push({
542
+ sku: sku?.trim(),
543
+ refCode: refCode?.trim(),
544
+ packingType: packingType?.trim(),
545
+ packingSize: packingSize,
546
+ releaseQty: releaseQty
547
+ })
548
+ }
549
+
550
+ return acc
551
+ }, [])
552
+
553
+ if (errorList.length > 0) {
554
+ throw new ValidationError({
555
+ errorCode: 'INVALID_DATA',
556
+ message: 'invalid data',
557
+ detail: errorList.join(', ')
558
+ })
869
559
  }
870
- })
871
560
 
872
- return mappedList
873
- }
561
+ //find customer sku in system
562
+ await Promise.all(
563
+ inputOrderProducts.map(async inputOrderItem => {
564
+ const sku: string = inputOrderItem.sku
565
+ const refCode: string = inputOrderItem.refCode
566
+ const packingType: string = inputOrderItem.packingType
567
+ const packingSize: string = inputOrderItem.packingSize
568
+
569
+ const qb: SelectQueryBuilder<ProductDetail> = getRepository(ProductDetail)
570
+ .createQueryBuilder('PD')
571
+ .innerJoinAndSelect('PD.product', 'PROD')
572
+ .where('PROD.bizplace_id = :bizplaceId', { bizplaceId: releaseGood.companyBizplace.id })
573
+
574
+ if (!sku && !refCode) {
575
+ context.throw(404, t('error.sku or refCode not found'))
576
+ }
577
+
578
+ if (refCode) {
579
+ qb.andWhere('PD.ref_code = :refCode', { refCode })
580
+ }
581
+
582
+ if (sku) {
583
+ qb.andWhere('PROD.sku = :sku', { sku })
584
+ }
585
+
586
+ if (packingType) {
587
+ qb.andWhere('PD.packing_type = :packingType', { packingType })
588
+ }
589
+
590
+ if ((!packingType && !refCode) || !packingSize) {
591
+ qb.andWhere('PD.is_default = :isDefault', { isDefault: true })
592
+ }
874
593
 
875
- async function createDraftOrder(client, customerBizplace, bodyReq, bundleInfo, domain, context, orderInventories, tx) {
876
- return (
877
- await client.mutate({
878
- mutation: gql`
879
- mutation upsertDraftReleaseGood(
880
- $draftReleaseGood: DraftReleaseGoodPatch!
881
- $orderProducts: [OrderProductPatch!]!
882
- ) {
883
- upsertDraftReleaseGood(draftReleaseGood: $draftReleaseGood, orderProducts: $orderProducts) {
884
- id
885
- name
886
- refNo
887
- refNo2
888
- status
889
- truckNo
890
- ownTransport
891
- marketplaceOrderStatus
892
- billingAddress
893
- deliveryAddress1
894
- deliveryAddress2
895
- deliveryAddress3
896
- deliveryAddress4
897
- deliveryAddress5
898
- attentionTo
899
- attentionCompany
900
- city
901
- state
902
- postalCode
903
- country
904
- phone1
905
- phone2
906
- email
907
- type
908
- exportOption
909
- releaseDate
910
- collectionOrderNo
911
- bizplace {
912
- name
594
+ const productDetail: ProductDetail = await qb.getOne()
595
+
596
+ // order Product based on product detail
597
+ if (productDetail) {
598
+ let newOrderProduct: OrderInventory = Object.assign({}, inputOrderItem)
599
+ newOrderProduct = {
600
+ ...newOrderProduct,
601
+ product: productDetail.product,
602
+ productDetail: productDetail,
603
+ packingType: productDetail.packingType,
604
+ packingSize: packingSize || productDetail.packingSize,
605
+ uom: productDetail.uom,
606
+ uomValue: productDetail.uomValue,
607
+ refCode: inputOrderItem.refCode,
608
+ releaseQty: inputOrderItem.releaseQty,
609
+ releaseUomValue: inputOrderItem.releaseQty * (productDetail.uomValue || 1),
610
+ packQty: 0,
611
+ actualPackQty: 0,
612
+ palletQty: 0,
613
+ actualPalletQty: 0,
614
+ type: 'RELEASE_OF_GOODS',
615
+ status: ORDER_PRODUCT_STATUS.ASSIGNED,
616
+ batchId: '-'
617
+ }
618
+
619
+ newOrderProduct.orderInventories = [
620
+ {
621
+ ...newOrderProduct,
622
+ packQty: 0,
623
+ actualPackQty: 0,
624
+ palletQty: 0,
625
+ actualPalletQty: 0,
626
+ type: 'RELEASE_OF_GOODS',
627
+ status: ORDER_INVENTORY_STATUS.PENDING_WORKSHEET
628
+ }
629
+ ]
630
+
631
+ orderProducts.push(newOrderProduct)
632
+ }
633
+
634
+ if (!productDetail) {
635
+ const qb: SelectQueryBuilder<ProductDetail> = getRepository(ProductBundle)
636
+ .createQueryBuilder('PB')
637
+ .innerJoinAndSelect('PB.productBundleSettings', 'PBS')
638
+ .innerJoinAndSelect('PBS.productDetail', 'PBD')
639
+ .innerJoinAndSelect('PBD.product', 'PROD')
640
+ .where('PB.bizplace_id = :bizplaceId', { bizplaceId: releaseGood.companyBizplace.id })
641
+
642
+ if (!sku && !refCode) {
643
+ context.throw(404, t('error.sku or refCode not found'))
644
+ }
645
+
646
+ if (refCode) {
647
+ qb.andWhere('PB.ref_code = :refCode', { refCode })
648
+ }
649
+
650
+ if (sku) {
651
+ qb.andWhere('PB.sku = :sku', { sku })
652
+ }
653
+
654
+ if (packingType) {
655
+ qb.andWhere('PB.packing_type = :packingType', { packingType })
656
+ }
657
+
658
+ const foundProductBundle: ProductBundle = await qb.getOne()
659
+
660
+ if (foundProductBundle) {
661
+ let orderProduct = {
662
+ productBundle: foundProductBundle,
663
+ refCode,
664
+ packingType: foundProductBundle.packingType,
665
+ packingSize: packingSize || 1,
666
+ uom: inputOrderItem?.uom,
667
+ releaseQty: inputOrderItem.releaseQty,
668
+ releaseUomValue: inputOrderItem.releaseQty,
669
+ packQty: 0,
670
+ actualPackQty: 0,
671
+ palletQty: 0,
672
+ actualPalletQty: 0,
673
+ orderInventories: [],
674
+ type: 'RELEASE_OF_GOODS',
675
+ status: ORDER_PRODUCT_STATUS.ASSIGNED,
676
+ batchId: '-'
677
+ }
678
+
679
+ const bundleProductSettings: any[] = foundProductBundle.productBundleSettings
680
+ bundleProductSettings.map(bundleProductSetting => {
681
+ const productDetailBundle: ProductDetail = bundleProductSetting.productDetail
682
+
683
+ let newOrderInventory: OrderInventory = {
684
+ product: productDetailBundle.product,
685
+ productDetail: productDetailBundle,
686
+ sku: productDetailBundle.product.sku,
687
+ packingType: productDetailBundle.packingType,
688
+ packingSize: productDetailBundle.packingSize,
689
+ uom: productDetailBundle.uom,
690
+ packQty: 0,
691
+ actualPackQty: 0,
692
+ palletQty: 0,
693
+ actualPalletQty: 0,
694
+ releaseQty: inputOrderItem.releaseQty * bundleProductSetting.bundleQty,
695
+ releaseUomValue:
696
+ inputOrderItem.releaseQty *
697
+ bundleProductSetting.bundleQty *
698
+ (productDetailBundle.uomValue <= 0 ? 1 : productDetailBundle.uomValue),
699
+ type: 'RELEASE_OF_GOODS',
700
+ status: ORDER_INVENTORY_STATUS.PENDING_WORKSHEET
701
+ }
702
+
703
+ orderProduct.orderInventories.push(newOrderInventory)
704
+ })
705
+ orderProducts.push(orderProduct)
706
+ } else {
707
+ if (sku && refCode) {
708
+ context.throw(404, t(`error.sku ${sku} and refCode ${refCode} not exist in product / bundle master`))
709
+ }
710
+
711
+ if (sku) {
712
+ context.throw(404, t(`error.sku ${sku} not exist in product / bundle master`))
713
+ }
714
+
715
+ if (refCode) {
716
+ context.throw(404, t(`error.refCode ${refCode} not exist in product / bundle master`))
913
717
  }
914
718
  }
915
719
  }
916
- `,
917
- variables: {
918
- draftReleaseGood: {
919
- bizplace: {
920
- id: customerBizplace.id
921
- },
922
- courierOption: bodyReq.courierOption,
923
- collectionOrderNo: bodyReq.collectionOrderNo,
924
- exportOption: bodyReq.exportOption,
925
- ownTransport: bodyReq.ownTransport,
926
- packingOption: bodyReq.packingOption,
927
- refNo: bodyReq.refNo,
928
- refNo2: bodyReq?.refNo2,
929
- releaseDate: bodyReq.releaseDate,
930
- type: bodyReq.type,
931
- marketplaceOrderStatus: bodyReq?.marketplaceOrderStatus,
932
- remark: bodyReq?.remark || null,
933
- billingAddress: bodyReq?.billTo?.billingAddress || null,
934
- deliveryAddress1: bodyReq?.deliverTo?.deliveryAddress1 || null,
935
- deliveryAddress2: bodyReq?.deliverTo?.deliveryAddress2 || null,
936
- deliveryAddress3: bodyReq?.deliverTo?.deliveryAddress3 || null,
937
- deliveryAddress4: bodyReq?.deliverTo?.deliveryAddress4 || null,
938
- deliveryAddress5: bodyReq?.deliverTo?.deliveryAddress5 || null,
939
- attentionTo: bodyReq?.deliverTo?.attentionTo || null,
940
- attentionCompany: bodyReq?.deliverTo?.attentionCompany || null,
941
- city: bodyReq?.deliverTo?.city || null,
942
- state: bodyReq?.deliverTo?.state || null,
943
- postalCode: bodyReq?.deliverTo?.postalCode || null,
944
- country: bodyReq?.deliverTo?.country || null,
945
- phone1: bodyReq?.deliverTo?.phone1 || null,
946
- phone2: bodyReq?.deliverTo?.phone2 || null,
947
- email: bodyReq?.deliverTo?.email || null
720
+ })
721
+ )
722
+
723
+ //combine any repeated skus
724
+ let combinedItems: OrderProduct[] = orderProducts.reduce((acc, op, idx) => {
725
+ let oi = op?.orderInventories || []
726
+ oi.forEach(itm => {
727
+ let { sku, refCode } = itm.product
728
+ let { packingType, packingSize, batchId, releaseQty, product, productDetail, uom } = itm
729
+
730
+ if (!sku && !refCode) {
731
+ context.throw(404, t('error.sku or refCode not found'))
732
+ }
733
+ let existingIndex = acc.findIndex(
734
+ itm =>
735
+ itm?.sku == sku?.trim() &&
736
+ itm?.refCode == refCode?.trim() &&
737
+ itm?.packingType == packingType?.trim() &&
738
+ itm?.packingSize == packingSize &&
739
+ itm?.batchId == batchId?.trim() &&
740
+ itm?.uom == uom?.trim()
741
+ )
742
+
743
+ if (existingIndex >= 0 && releaseQty > 0) {
744
+ acc[existingIndex].releaseQty = acc[existingIndex].releaseQty + releaseQty
745
+ acc[existingIndex].releaseUomValue =
746
+ acc[existingIndex].releaseUomValue + releaseQty * (productDetail.uomValue <= 0 ? 1 : productDetail.uomValue)
747
+ } else {
748
+ let updateObj = {
749
+ sku: sku?.trim(),
750
+ packingType: packingType?.trim(),
751
+ packingSize: packingSize,
752
+ uom: uom?.trim(),
753
+ releaseQty: releaseQty,
754
+ releaseUomValue: releaseQty * (productDetail.uomValue <= 0 ? 1 : productDetail.uomValue),
755
+ product,
756
+ productDetail
757
+ }
758
+
759
+ if (refCode) updateObj['refCode'] = refCode.trim()
760
+ updateObj['batchId'] = batchId ? batchId?.trim() : '-'
761
+
762
+ acc.push(updateObj)
763
+ }
764
+ })
765
+ return acc
766
+ }, [])
767
+
768
+ return { orderProducts, combinedItems }
769
+ } catch (error) {
770
+ throw error
771
+ }
772
+ }
773
+
774
+ async function assignToInventory(orderProducts: OrderProduct[], customerBizplace, context, tx) {
775
+ const { client, domain, user } = context.state
776
+ for (let opIdx = 0; opIdx < orderProducts.length; opIdx++) {
777
+ let orderProduct = orderProducts[opIdx]
778
+
779
+ let assignedOrderInventories = []
780
+ for (let oiIdx = 0; oiIdx < orderProducts[opIdx].orderInventories.length; oiIdx++) {
781
+ const orderInventory = orderProduct.orderInventories[oiIdx]
782
+ let sortings: any = []
783
+
784
+ switch (orderInventory.product.pickingStrategy) {
785
+ case 'LIFO':
786
+ sortings.push({ name: 'iv.created_at', desc: true })
787
+
788
+ break
789
+ case 'FEFO':
790
+ sortings.push({ name: 'iv.expiration_date', desc: false })
791
+ sortings.push({ name: 'iv.created_at', desc: false })
792
+
793
+ break
794
+ case 'FMFO':
795
+ sortings.push({ name: 'iv.manufacture_date', desc: false })
796
+ sortings.push({ name: 'iv.created_at', desc: false })
797
+
798
+ break
799
+ case 'LOCATION':
800
+ const pickingProductSetting: Setting = await tx.getRepository(Setting).findOne({
801
+ where: { domain, name: 'rule-for-picking-product' }
802
+ })
803
+ if (pickingProductSetting?.value) {
804
+ for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
805
+ sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
806
+ }
807
+ } else {
808
+ sortings.push({ name: 'loc.name', desc: true })
809
+ }
810
+
811
+ break
812
+ //Every other case includes 'FIFO' will be applicable for this case
813
+ default:
814
+ sortings.push({ name: 'iv.created_at', desc: false })
815
+ break
816
+ }
817
+
818
+ let queryFilters = []
819
+
820
+ let queryStrings = queryFilters.reduce(
821
+ (acc, itm, idx, arr) => {
822
+ acc.values.push(itm.filters)
823
+
824
+ switch (itm?.operator) {
825
+ case 'notin':
826
+ case 'in':
827
+ acc.query.push(
828
+ `${itm.query} ${itm.operator == 'notin' ? 'NOT IN' : 'IN'} (${itm.filtes
829
+ .map((itm, idx) => {
830
+ return `$${acc.values.length + 1}`
831
+ })
832
+ .join(',')})`
833
+ )
834
+ break
835
+
836
+ default:
837
+ acc.query.push(`${itm.query} ${itm?.operator ? itm.operator : '='} $${acc.values.length + 1}`)
838
+ break
839
+ }
840
+ acc.query.push(`${itm.query} ${itm?.operator ? itm.operator : '='} $${acc.values.length + 1}`)
948
841
  },
949
- orderProducts: orderInventories
950
- },
951
- context: {
952
- ...context,
953
- hasUpload: true,
954
- state: {
955
- ...context.state,
956
- customerBizplace,
957
- domain,
958
- type: 'api'
842
+ {
843
+ query: [],
844
+ values: []
959
845
  }
846
+ )
847
+
848
+ let sortQuery = sortings
849
+ .map(itm => {
850
+ return `${itm.name} ${itm.desc ? 'DESC' : 'ASC'}`
851
+ })
852
+ .join(`,`)
853
+
854
+ let updatedInventories = await tx.getRepository(Inventory).query(
855
+ `
856
+ update inventories tgt set locked_qty = coalesce(locked_qty,0) + src.reserve_qty,
857
+ locked_uom_value = coalesce(locked_uom_value,0) + src.reserve_uom_value,
858
+ updated_at = NOW(),
859
+ updater_id = $1
860
+ from (
861
+ select "id", "pallet_id","carton_id", "batch_id", "batch_id_ref", "unit", "uom", "packing_type", "packing_size", "manufacture_year",
862
+ "reserve_qty", (("uom_value"/"qty") * "reserve_qty") as "reserve_uom_value" from (
863
+ select "sort_seq", "id", "pallet_id", "batch_id", "batch_id_ref", "unit", "uom", "packing_type", "packing_size", "manufacture_year", "carton_id", "uom_value", "locked_uom_value", "qty", "locked_qty", "created_at",
864
+ "release_qty", "release_uom_value", lock_amount,
865
+ case when "lock_amount" < 0 then "available_qty" else "available_qty" - "lock_amount" end as "reserve_qty"
866
+ from (
867
+ select *,
868
+ qty - locked_qty as available_qty,
869
+ sum(qty - locked_qty - release_qty) over (order by sort_seq asc rows between unbounded preceding and current row) as lock_amount
870
+ from (
871
+ SELECT 0 as sort_seq, null as id, null as pallet_id, null as batch_id, null as batch_id_ref,
872
+ null as unit, '${orderInventory.uom}' as uom, '${orderInventory.packingType}' as packing_type, '${orderInventory.packingSize
873
+ }' as packing_size,
874
+ 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,
875
+ '${orderInventory.releaseQty}' as release_qty, '${orderInventory.releaseUomValue}' as release_uom_value
876
+ UNION
877
+ (
878
+ SELECT ROW_NUMBER() OVER(PARTITION BY iv.domain_id ORDER BY ${sortQuery}) as sort_seq,
879
+ iv.id, iv.pallet_id, iv.batch_id, iv.batch_id_ref,
880
+ iv.unit, iv.uom, iv.packing_type, iv.packing_size,
881
+ iv.manufacture_year, iv.carton_id, iv.uom_value,
882
+ coalesce(iv.locked_uom_value,0) as locked_uom_value, iv.qty, coalesce(iv.locked_qty,0) as locked_qty, iv.created_at,
883
+ 0 as release_qty, 0 as release_uom_value
884
+ FROM "inventories" "iv"
885
+ INNER JOIN "locations" "loc" ON "loc"."id"="iv"."location_id"
886
+ WHERE case when coalesce("iv"."locked_qty",0) < 0 then 0 else ("iv"."qty" - coalesce("iv"."locked_qty",0)) end > 0 AND
887
+ "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)
888
+ ${queryStrings.query.length > 0 ? `AND ${queryStrings.join(' AND ')}` : ''}
889
+ ORDER BY ${sortQuery}
890
+ )
891
+ ) dt1
892
+ ) dt2 where case when "lock_amount" < 0 then "available_qty" else "available_qty" - "lock_amount" end > 0
893
+ ) dt3 where sort_seq > 0
894
+ ) src where src.id = tgt.id
895
+ returning
896
+ tgt."id", tgt."qty", tgt."pallet_id", tgt."carton_id", tgt."batch_id", tgt."batch_id_ref", tgt."unit",
897
+ tgt."uom", tgt."packing_type", tgt."packing_size", tgt."manufacture_year",
898
+ tgt."locked_qty", tgt."uom_value", tgt."locked_uom_value", src."reserve_qty", src."reserve_uom_value";`,
899
+ [
900
+ user.id,
901
+ domain.id,
902
+ customerBizplace.id,
903
+ orderInventory.packingType,
904
+ orderInventory.packingSize,
905
+ orderInventory.product.id,
906
+ INVENTORY_STATUS.STORED,
907
+ LOCATION_TYPE.QUARANTINE,
908
+ LOCATION_TYPE.RESERVE,
909
+ ...queryStrings.values
910
+ ]
911
+ )
912
+
913
+ // let data = await qb.getMany()
914
+
915
+ let totalAssigned = updatedInventories[0].reduce((acc, inv) => {
916
+ return acc + inv.reserve_qty
917
+ }, 0)
918
+
919
+ if (orderInventory.releaseQty != totalAssigned) {
920
+ throw new ValidationError({
921
+ errorCode: 'INSUFFICIENT_STOCK',
922
+ message: 'insufficient stock',
923
+ detail: JSON.stringify({
924
+ code: 'insufficient stock',
925
+ sku: orderInventory.product.sku,
926
+ packingType: orderInventory.packingType,
927
+ packingSize: orderInventory.packingSize,
928
+ uom: orderInventory.uom,
929
+ qty: orderInventory.releaseQty,
930
+ uom_value: orderInventory.releaseUomValue
931
+ })
932
+ })
960
933
  }
961
- } as any)
962
- ).data
934
+
935
+ updatedInventories[0].forEach(inv => {
936
+ let oi = {
937
+ ...orderInventory,
938
+ inventory: { id: inv.id },
939
+ uom: inv.uom,
940
+ packingType: inv.packing_type,
941
+ batchId: inv.batch_id,
942
+ releaseQty: inv.reserve_qty,
943
+ releaseUomValue: inv.reserve_uom_value,
944
+ pickedQty: 0,
945
+ packedQty: 0,
946
+ type: ORDER_TYPES.RELEASE_OF_GOODS,
947
+ status: ORDER_INVENTORY_STATUS.PENDING_WORKSHEET,
948
+ domain: { id: domain.id },
949
+ bizplace: { id: customerBizplace.id }
950
+ }
951
+
952
+ assignedOrderInventories.push(oi)
953
+ })
954
+ }
955
+
956
+ orderProducts[opIdx].orderInventories = assignedOrderInventories
957
+ }
958
+
959
+ return orderProducts
963
960
  }