@things-factory/operato-hub 4.3.129 → 4.3.131
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.development.js +11 -11
- package/dist-server/routers/xilnex-router.js +1043 -81
- package/dist-server/routers/xilnex-router.js.map +1 -1
- package/package.json +55 -56
- package/server/routers/xilnex-router.ts +1308 -93
- package/LICENSE.md +0 -21
|
@@ -1,28 +1,41 @@
|
|
|
1
1
|
import Router from 'koa-router'
|
|
2
|
-
import { getRepository } from 'typeorm'
|
|
2
|
+
import { EntityManager, getConnection, getRepository, In, Not, SelectQueryBuilder } from 'typeorm'
|
|
3
3
|
import { v4 as uuidv4 } from 'uuid'
|
|
4
4
|
|
|
5
5
|
import { Bizplace, getCompanyBizplace } from '@things-factory/biz-base'
|
|
6
6
|
import { logger } from '@things-factory/env'
|
|
7
7
|
import { generateId } from '@things-factory/id-rule-base'
|
|
8
8
|
import {
|
|
9
|
-
Account,
|
|
10
9
|
ACCOUNT_PLATFORM,
|
|
11
10
|
ACCOUNT_SYNC_STATUS,
|
|
12
11
|
AccountingAPI,
|
|
13
12
|
AccountWebhookOrder,
|
|
14
13
|
Xilnex
|
|
15
14
|
} from '@things-factory/integration-accounting'
|
|
16
|
-
import {
|
|
15
|
+
// import { createPayloadLog, PayloadType } from '@things-factory/integration-base'
|
|
16
|
+
import { Product, ProductBundle, ProductDetail } from '@things-factory/product-base'
|
|
17
17
|
import {
|
|
18
18
|
ArrivalNotice,
|
|
19
|
+
DRAFT_RELEASE_ORDER_STATUS,
|
|
20
|
+
DraftReleaseGood,
|
|
21
|
+
ORDER_INVENTORY_STATUS,
|
|
19
22
|
ORDER_NUMBER_RULE_TYPE,
|
|
20
23
|
ORDER_NUMBER_SETTING_KEY,
|
|
24
|
+
ORDER_PRODUCT_STATUS,
|
|
25
|
+
ORDER_STATUS,
|
|
26
|
+
ORDER_TYPES,
|
|
27
|
+
OrderInventory,
|
|
21
28
|
OrderNoGenerator,
|
|
22
|
-
OrderProduct
|
|
29
|
+
OrderProduct,
|
|
30
|
+
PURCHASE_ORDER_STATUS,
|
|
31
|
+
PurchaseOrder,
|
|
32
|
+
ReleaseGood
|
|
23
33
|
} from '@things-factory/sales-base'
|
|
24
34
|
import { Setting } from '@things-factory/setting-base'
|
|
25
35
|
import { Domain } from '@things-factory/shell'
|
|
36
|
+
import { Inventory, INVENTORY_STATUS, LOCATION_TYPE } from '@things-factory/warehouse-base'
|
|
37
|
+
|
|
38
|
+
import { ValidationError } from '../errors/index'
|
|
26
39
|
|
|
27
40
|
const debug = require('debug')('things-factory:integration-accounting:xilnex-router')
|
|
28
41
|
|
|
@@ -30,127 +43,1329 @@ export const xilnexRouter = new Router()
|
|
|
30
43
|
|
|
31
44
|
xilnexRouter.post('/webhook-xilnex-po', async (context, next) => {
|
|
32
45
|
try {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
await getConnection().transaction(async (tx: EntityManager) => {
|
|
47
|
+
const { id, warehouseId } = context.query
|
|
48
|
+
const { body, headers } = context.request
|
|
49
|
+
const data = body?.Data ? JSON.parse(body.Data) : {}
|
|
36
50
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
const account = await AccountingAPI.getAccounting(id)
|
|
52
|
+
if (!account) {
|
|
53
|
+
throw new Error(`${id} not found in Account`)
|
|
54
|
+
}
|
|
41
55
|
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
const { webhookKey } = account
|
|
57
|
+
const signature = headers['xilnex-webhook-signature']
|
|
44
58
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
const xilnex = new Xilnex({})
|
|
60
|
+
if (!xilnex.isValidSignature(body, webhookKey, signature)) {
|
|
61
|
+
context.throw(400, 'Signature validation failed')
|
|
62
|
+
}
|
|
49
63
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
const customerDomain: Domain = account.domain
|
|
65
|
+
const customerBizplace: Bizplace = await tx.getRepository(Bizplace).findOne({
|
|
66
|
+
where: { domain: customerDomain }
|
|
67
|
+
})
|
|
68
|
+
const custCompanyBizplace: Bizplace = await getCompanyBizplace(null, null, customerBizplace.id, tx)
|
|
55
69
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
70
|
+
const warehouseDomain: Domain = await tx.getRepository(Domain).findOne({
|
|
71
|
+
where: { id: warehouseId, extType: 'warehouse' }
|
|
72
|
+
})
|
|
59
73
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
if (!warehouseDomain) {
|
|
75
|
+
context.throw(400, `${warehouseId} not found in warehouse domain`)
|
|
76
|
+
}
|
|
63
77
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
let accountWebhookOrder: any = {
|
|
79
|
+
name: uuidv4(),
|
|
80
|
+
request: body,
|
|
81
|
+
platform: ACCOUNT_PLATFORM.XILNEX,
|
|
82
|
+
domain: account.domain,
|
|
83
|
+
status: ACCOUNT_SYNC_STATUS.FAILED
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (data?.id) {
|
|
87
|
+
const purchaseOrder: any = await AccountingAPI.getPurchaseOrder(account, { orderId: data.id })
|
|
88
|
+
const { orderInfo, orderItems } = purchaseOrder
|
|
71
89
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
90
|
+
let accountInfo: any = account?.accountInfo ? JSON.parse(account.accountInfo) : {}
|
|
91
|
+
|
|
92
|
+
if (accountInfo.outlet == orderInfo.toOutlet) {
|
|
93
|
+
const poNoSetting: Setting = await tx.getRepository(Setting).findOne({
|
|
94
|
+
where: {
|
|
95
|
+
domain: warehouseDomain,
|
|
96
|
+
name: ORDER_NUMBER_SETTING_KEY.PO_NUMBER_RULE
|
|
97
|
+
}
|
|
98
|
+
})
|
|
75
99
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
100
|
+
let orderNo: string = poNoSetting
|
|
101
|
+
? await generateId({ domain: warehouseDomain, type: ORDER_NUMBER_RULE_TYPE.PO_NUMBER, seed: {} })
|
|
102
|
+
: OrderNoGenerator.purchaseOrder()
|
|
103
|
+
|
|
104
|
+
let purchaseOrder: any = {
|
|
105
|
+
name: orderNo,
|
|
106
|
+
refNo: orderInfo.orderId,
|
|
107
|
+
refNo2: null,
|
|
108
|
+
refNo3: null,
|
|
109
|
+
etaDate: orderInfo.etaDate,
|
|
110
|
+
ownTransport: orderInfo.ownTransport,
|
|
111
|
+
importCargo: orderInfo.importCargo,
|
|
112
|
+
container: orderInfo.container,
|
|
113
|
+
remark: orderInfo.remark,
|
|
114
|
+
currency: orderInfo?.currency || 'MYR',
|
|
115
|
+
source: 'XILNEX',
|
|
116
|
+
orderSourceType: 'XILNEX_PO',
|
|
117
|
+
domain: warehouseDomain,
|
|
118
|
+
bizplace: customerBizplace,
|
|
119
|
+
status: PURCHASE_ORDER_STATUS.DRAFT
|
|
120
|
+
}
|
|
121
|
+
purchaseOrder = await tx.getRepository(PurchaseOrder).save(purchaseOrder)
|
|
122
|
+
|
|
123
|
+
let orderProducts: any[] = []
|
|
124
|
+
await Promise.all(
|
|
125
|
+
orderItems.map(async item => {
|
|
126
|
+
const refCode: string = item.product.refCode
|
|
127
|
+
let productDetail: ProductDetail = await tx.getRepository(ProductDetail).findOne({
|
|
128
|
+
where: { domain: custCompanyBizplace.domain, refCode: refCode },
|
|
129
|
+
relations: ['product']
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
if (!productDetail) {
|
|
133
|
+
const sku: string = item.product.sku
|
|
134
|
+
const product: Product = await tx.getRepository(Product).findOne({
|
|
135
|
+
where: { domain: custCompanyBizplace.domain, sku }
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
if (product) {
|
|
139
|
+
productDetail = await tx.getRepository(ProductDetail).findOne({
|
|
140
|
+
where: { domain: custCompanyBizplace.domain, product },
|
|
141
|
+
relations: ['product']
|
|
142
|
+
})
|
|
143
|
+
} else {
|
|
144
|
+
context.throw(404, `error.sku ${sku} and refCode ${refCode} not exist in product master`)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let orderProduct: any = {
|
|
149
|
+
name: OrderNoGenerator.orderProduct(),
|
|
150
|
+
product: productDetail?.product,
|
|
151
|
+
productDetail,
|
|
152
|
+
batchId: item?.batchId || '-',
|
|
153
|
+
packingType: productDetail?.packingType,
|
|
154
|
+
packingSize: productDetail?.packingSize,
|
|
155
|
+
uom: productDetail?.uom,
|
|
156
|
+
uomValue: productDetail?.uomValue,
|
|
157
|
+
packQty: item.confirmedQty,
|
|
158
|
+
totalUomValue: `${(productDetail.uomValue * item.confirmedQty).toFixed(2)} ${productDetail.uom}`,
|
|
159
|
+
status: PURCHASE_ORDER_STATUS.DRAFT,
|
|
160
|
+
unitPrice: item.unitCost || 1,
|
|
161
|
+
taxRate: item.taxAmount,
|
|
162
|
+
refItemId: item.refItemId,
|
|
163
|
+
purchaseOrder,
|
|
164
|
+
domain: warehouseDomain,
|
|
165
|
+
bizplace: customerBizplace
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
orderProducts.push(orderProduct)
|
|
169
|
+
})
|
|
170
|
+
)
|
|
171
|
+
await tx.getRepository(OrderProduct).save(orderProducts)
|
|
80
172
|
}
|
|
173
|
+
|
|
174
|
+
accountWebhookOrder.status = ACCOUNT_SYNC_STATUS.SUCCESS
|
|
175
|
+
await tx.getRepository(AccountWebhookOrder).save(accountWebhookOrder)
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
context.status = 200
|
|
180
|
+
} catch (err) {
|
|
181
|
+
logger.error(`xilnex-router[webhook-xilnex-po]: ${err}`)
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
xilnexRouter.post('/webhook-xilnex-transfer-note', async (context, next) => {
|
|
186
|
+
try {
|
|
187
|
+
await getConnection().transaction(async (tx: EntityManager) => {
|
|
188
|
+
const { id, warehouseId } = context.query
|
|
189
|
+
const { body, headers } = context.request
|
|
190
|
+
const data = body?.Data ? JSON.parse(body.Data) : {}
|
|
191
|
+
|
|
192
|
+
const account = await AccountingAPI.getAccounting(id)
|
|
193
|
+
if (!account) {
|
|
194
|
+
throw new Error(`${id} not found in Account`)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const { webhookKey } = account
|
|
198
|
+
const signature = headers['xilnex-webhook-signature']
|
|
199
|
+
|
|
200
|
+
const xilnex = new Xilnex({})
|
|
201
|
+
if (!xilnex.isValidSignature(body, webhookKey, signature)) {
|
|
202
|
+
context.throw(400, 'Signature validation failed')
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const customerDomain: Domain = account.domain
|
|
206
|
+
const customerBizplace: Bizplace = await tx.getRepository(Bizplace).findOne({
|
|
207
|
+
where: { domain: customerDomain }
|
|
81
208
|
})
|
|
209
|
+
const custCompanyBizplace: Bizplace = await getCompanyBizplace(null, null, customerBizplace.id, tx)
|
|
82
210
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
211
|
+
const warehouseDomain: Domain = await tx.getRepository(Domain).findOne({
|
|
212
|
+
where: { id: warehouseId, extType: 'warehouse' }
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
if (!warehouseDomain) {
|
|
216
|
+
context.throw(400, `${warehouseId} not found in warehouse domain`)
|
|
88
217
|
}
|
|
89
218
|
|
|
90
|
-
let
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
domain:
|
|
95
|
-
|
|
96
|
-
status: 'INTRANSIT'
|
|
219
|
+
let accountWebhookOrder: any = {
|
|
220
|
+
name: uuidv4(),
|
|
221
|
+
request: body,
|
|
222
|
+
platform: ACCOUNT_PLATFORM.XILNEX,
|
|
223
|
+
domain: account.domain,
|
|
224
|
+
status: ACCOUNT_SYNC_STATUS.FAILED
|
|
97
225
|
}
|
|
98
|
-
arrivalNotice = await getRepository(ArrivalNotice).save(arrivalNotice)
|
|
99
226
|
|
|
100
|
-
let
|
|
101
|
-
await Promise.all(
|
|
102
|
-
orderItems.map(async item => {
|
|
103
|
-
const productDetail: ProductDetail = await getRepository(ProductDetail).findOne({
|
|
104
|
-
where: { domain: custCompanyBizplace.domain, refCode: item.product.sku },
|
|
105
|
-
relations: ['product']
|
|
106
|
-
})
|
|
227
|
+
let accountInfo: any = account?.accountInfo ? JSON.parse(account.accountInfo) : {}
|
|
107
228
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
229
|
+
if (data?.id) {
|
|
230
|
+
const transferNote: any = await AccountingAPI.getTransferNote(account, { orderId: data.id })
|
|
231
|
+
const { orderInfo, orderItems } = transferNote
|
|
232
|
+
|
|
233
|
+
if (orderInfo.fromLocation == accountInfo.outlet || orderInfo.toLocation == accountInfo.outlet) {
|
|
234
|
+
// VDC release
|
|
235
|
+
if (accountInfo.outlet == orderInfo.fromLocation) {
|
|
236
|
+
let releaseGood = {
|
|
237
|
+
domain: warehouseDomain,
|
|
238
|
+
bizplace: customerBizplace,
|
|
239
|
+
companyBizplace: custCompanyBizplace,
|
|
240
|
+
courierOption: false,
|
|
241
|
+
collectionOrderNo: orderInfo.orderId,
|
|
242
|
+
exportOption: false,
|
|
243
|
+
ownTransport: orderInfo.ownTransport,
|
|
244
|
+
packingOption: false,
|
|
245
|
+
refNo: orderInfo.orderId,
|
|
246
|
+
refNo2: orderInfo?.fromLocation,
|
|
247
|
+
refNo3: orderInfo?.toLocation,
|
|
248
|
+
releaseDate: orderInfo.issueDate,
|
|
249
|
+
type: 'b2b',
|
|
250
|
+
source: 'XILNEX',
|
|
251
|
+
orderSourceType: 'XILNEX_TRANSFER_NOTE',
|
|
252
|
+
marketplaceOrderStatus: orderInfo?.status,
|
|
253
|
+
remark: orderInfo?.remark || null
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// check existing order
|
|
257
|
+
await checkExistingOrder(releaseGood, customerBizplace, true, context, tx)
|
|
258
|
+
|
|
259
|
+
//massage data
|
|
260
|
+
let massagedData = await massageOrderItems(releaseGood, orderItems, context)
|
|
261
|
+
|
|
262
|
+
// validation
|
|
263
|
+
let errorList = []
|
|
264
|
+
await Promise.all(
|
|
265
|
+
massagedData.combinedItems.map(async item => {
|
|
266
|
+
let itemList = await tx.query(
|
|
267
|
+
`
|
|
268
|
+
select wboi.* from warehouse_bizplace_onhand_inventories wboi
|
|
269
|
+
where wboi."domainId"= $1 and
|
|
270
|
+
wboi."bizplaceId" = $2 and
|
|
271
|
+
wboi."groupType" = 'SINGLE' and
|
|
272
|
+
wboi."productId" = $3 and
|
|
273
|
+
wboi."packingType" = $4 and
|
|
274
|
+
wboi."packingSize" = $5 and
|
|
275
|
+
wboi."uom" = $6 and
|
|
276
|
+
wboi."remainQty" >= $7 and
|
|
277
|
+
wboi."remainUomValue" >= $8
|
|
278
|
+
`,
|
|
279
|
+
[
|
|
280
|
+
warehouseDomain.id,
|
|
281
|
+
customerBizplace.id,
|
|
282
|
+
item.product.id,
|
|
283
|
+
item.packingType,
|
|
284
|
+
item.packingSize,
|
|
285
|
+
item.uom,
|
|
286
|
+
item.releaseQty,
|
|
287
|
+
item.releaseUomValue
|
|
288
|
+
]
|
|
289
|
+
)
|
|
290
|
+
if (itemList.length <= 0) {
|
|
291
|
+
errorList.push(
|
|
292
|
+
JSON.stringify({
|
|
293
|
+
code: 'insufficient stock',
|
|
294
|
+
sku: item.product.sku,
|
|
295
|
+
packingType: item.packingType,
|
|
296
|
+
packingSize: item.packingSize,
|
|
297
|
+
uom: item.uom,
|
|
298
|
+
qty: item.releaseQty,
|
|
299
|
+
uom_value: item.releaseUomValue
|
|
300
|
+
})
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
console.log()
|
|
304
|
+
})
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
if (errorList.length > 0) {
|
|
308
|
+
context.throw(404, errorList.join(', '))
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let assignedOrderProducts = await assignToInventory(
|
|
312
|
+
massagedData.orderProducts,
|
|
313
|
+
customerBizplace,
|
|
314
|
+
warehouseDomain,
|
|
315
|
+
context,
|
|
316
|
+
tx
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
let result = await createReleaseGood(
|
|
320
|
+
releaseGood,
|
|
321
|
+
assignedOrderProducts,
|
|
322
|
+
customerBizplace,
|
|
323
|
+
warehouseDomain,
|
|
324
|
+
context,
|
|
325
|
+
tx
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
let addReleaseGood = {
|
|
329
|
+
id: result.id,
|
|
330
|
+
name: result.name,
|
|
331
|
+
refNo: result.refNo,
|
|
332
|
+
refNo2: result.refNo2,
|
|
333
|
+
refNo3: result.refNo3,
|
|
334
|
+
status: result.status,
|
|
335
|
+
truckNo: result.truckNo,
|
|
336
|
+
ownTransport: result.ownTransport,
|
|
337
|
+
crossDocking: result.crossDocking,
|
|
338
|
+
type: result.type,
|
|
339
|
+
exportOption: result.exportOption,
|
|
340
|
+
releaseDate: result.releaseDate,
|
|
341
|
+
collectionOrderNo: result.collectionOrderNo,
|
|
342
|
+
paidAmount: result?.paidAmount,
|
|
343
|
+
discountAmount: result?.discountAmount,
|
|
344
|
+
taxAmount: result?.taxAmount,
|
|
345
|
+
shippingFee: result?.shippingFee,
|
|
346
|
+
bizplace: { name: result.bizplace.name },
|
|
347
|
+
domain: { name: result.domain.name },
|
|
348
|
+
orderInventories: result.orderInventories
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
context.body = { result: { addReleaseGood } }
|
|
352
|
+
// createPayloadLog(
|
|
353
|
+
// customerBizplace.id,
|
|
354
|
+
// `/webhook-xilnex-stock-request`,
|
|
355
|
+
// stockRequest,
|
|
356
|
+
// context.body,
|
|
357
|
+
// context,
|
|
358
|
+
// PayloadType.INGESTION
|
|
359
|
+
// )
|
|
127
360
|
}
|
|
128
361
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
362
|
+
// VDC inbound
|
|
363
|
+
if (accountInfo.outlet == orderInfo.toLocation) {
|
|
364
|
+
const ganNoSetting: Setting = await tx.getRepository(Setting).findOne({
|
|
365
|
+
where: {
|
|
366
|
+
domain: warehouseDomain,
|
|
367
|
+
name: ORDER_NUMBER_SETTING_KEY.GAN_NUMBER_RULE
|
|
368
|
+
}
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
let orderNo: string = ''
|
|
372
|
+
if (ganNoSetting) {
|
|
373
|
+
orderNo = await generateId({ domain: warehouseDomain, type: ORDER_NUMBER_RULE_TYPE.GAN_NUMBER, seed: {} })
|
|
374
|
+
} else {
|
|
375
|
+
orderNo = OrderNoGenerator.arrivalNotice()
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
let arrivalNotice: any = {
|
|
379
|
+
name: orderNo,
|
|
380
|
+
refNo: orderInfo.orderId,
|
|
381
|
+
refNo2: orderInfo.fromLocation,
|
|
382
|
+
refNo3: orderInfo.toLocation,
|
|
383
|
+
etaDate: orderInfo.etaDate,
|
|
384
|
+
ownTransport: orderInfo.ownTransport,
|
|
385
|
+
importCargo: orderInfo.importCargo,
|
|
386
|
+
container: orderInfo.container,
|
|
387
|
+
remark: orderInfo.remark,
|
|
388
|
+
source: 'XILNEX',
|
|
389
|
+
orderSourceType: 'XILNEX_TRANSFER_NOTE',
|
|
390
|
+
domain: warehouseDomain,
|
|
391
|
+
bizplace: customerBizplace,
|
|
392
|
+
status: 'INTRANSIT'
|
|
393
|
+
}
|
|
394
|
+
arrivalNotice = await tx.getRepository(ArrivalNotice).save(arrivalNotice)
|
|
395
|
+
|
|
396
|
+
let orderProducts: any[] = []
|
|
397
|
+
await Promise.all(
|
|
398
|
+
orderItems.map(async item => {
|
|
399
|
+
const refCode: string = item.product.refCode
|
|
400
|
+
let productDetail: ProductDetail = await tx.getRepository(ProductDetail).findOne({
|
|
401
|
+
where: { domain: custCompanyBizplace.domain, refCode: refCode },
|
|
402
|
+
relations: ['product']
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
if (!productDetail) {
|
|
406
|
+
const sku: string = item.product.sku
|
|
407
|
+
const product: Product = await tx.getRepository(Product).findOne({
|
|
408
|
+
where: { domain: custCompanyBizplace.domain, sku }
|
|
409
|
+
})
|
|
134
410
|
|
|
135
|
-
|
|
136
|
-
|
|
411
|
+
if (product) {
|
|
412
|
+
productDetail = await tx.getRepository(ProductDetail).findOne({
|
|
413
|
+
where: { domain: custCompanyBizplace.domain, product },
|
|
414
|
+
relations: ['product']
|
|
415
|
+
})
|
|
416
|
+
} else {
|
|
417
|
+
context.throw(404, `error.sku ${sku} and refCode ${refCode} not exist in product master`)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
let orderProduct: any = {
|
|
422
|
+
name: OrderNoGenerator.orderProduct(),
|
|
423
|
+
product: productDetail?.product,
|
|
424
|
+
productDetail,
|
|
425
|
+
batchId: item?.batchId || '-',
|
|
426
|
+
packingType: productDetail?.packingType,
|
|
427
|
+
packingSize: productDetail?.packingSize,
|
|
428
|
+
uom: productDetail?.uom,
|
|
429
|
+
uomValue: productDetail?.uomValue,
|
|
430
|
+
packQty: item.confirmedQty,
|
|
431
|
+
totalUomValue: `${(productDetail.uomValue * item.confirmedQty).toFixed(2)} ${productDetail.uom}`,
|
|
432
|
+
status: 'PENDING',
|
|
433
|
+
arrivalNotice,
|
|
434
|
+
domain: warehouseDomain,
|
|
435
|
+
bizplace: customerBizplace
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
orderProducts.push(orderProduct)
|
|
439
|
+
})
|
|
440
|
+
)
|
|
441
|
+
await tx.getRepository(OrderProduct).save(orderProducts)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
accountWebhookOrder.status = ACCOUNT_SYNC_STATUS.SUCCESS
|
|
447
|
+
await tx.getRepository(AccountWebhookOrder).save(accountWebhookOrder)
|
|
448
|
+
})
|
|
137
449
|
|
|
138
450
|
context.status = 200
|
|
139
451
|
} catch (err) {
|
|
140
|
-
logger.error(`xilnex-router[webhook-xilnex-
|
|
452
|
+
logger.error(`xilnex-router[webhook-xilnex-transfer-note]: ${err}`)
|
|
141
453
|
}
|
|
142
454
|
})
|
|
143
455
|
|
|
144
456
|
xilnexRouter.post('/webhook-xilnex-stock-request', async (context, next) => {
|
|
145
457
|
try {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
458
|
+
await getConnection().transaction(async (tx: EntityManager) => {
|
|
459
|
+
const { id, warehouseId } = context.query
|
|
460
|
+
const { body, headers } = context.request
|
|
461
|
+
const data = body?.Data ? JSON.parse(body.Data) : {}
|
|
462
|
+
|
|
463
|
+
const account = await AccountingAPI.getAccounting(id)
|
|
464
|
+
if (!account) {
|
|
465
|
+
throw new Error(`${id} not found in Account`)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const { webhookKey } = account
|
|
469
|
+
const signature = headers['xilnex-webhook-signature']
|
|
470
|
+
|
|
471
|
+
const xilnex = new Xilnex({})
|
|
472
|
+
if (!xilnex.isValidSignature(body, webhookKey, signature)) {
|
|
473
|
+
context.throw(400, 'Signature validation failed')
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const customerDomain: Domain = account.domain
|
|
477
|
+
const customerBizplace: Bizplace = await tx.getRepository(Bizplace).findOne({
|
|
478
|
+
where: { domain: customerDomain }
|
|
479
|
+
})
|
|
480
|
+
const custCompanyBizplace: Bizplace = await getCompanyBizplace(null, null, customerBizplace.id, tx)
|
|
481
|
+
|
|
482
|
+
const warehouseDomain: Domain = await tx.getRepository(Domain).findOne({
|
|
483
|
+
where: { id: warehouseId, extType: 'warehouse' }
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
if (!warehouseDomain) {
|
|
487
|
+
context.throw(400, `${warehouseId} not found in warehouse domain`)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
let accountWebhookOrder: any = {
|
|
491
|
+
name: uuidv4(),
|
|
492
|
+
request: body,
|
|
493
|
+
platform: ACCOUNT_PLATFORM.XILNEX,
|
|
494
|
+
domain: account.domain,
|
|
495
|
+
status: ACCOUNT_SYNC_STATUS.FAILED
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
let accountInfo: any = account?.accountInfo ? JSON.parse(account.accountInfo) : {}
|
|
499
|
+
|
|
500
|
+
if (data?.id) {
|
|
501
|
+
const stockRequest: any = await AccountingAPI.getStockRequest(account, { orderId: data.id })
|
|
502
|
+
const { orderInfo, orderItems } = stockRequest
|
|
503
|
+
|
|
504
|
+
if (orderInfo.fromLocation == accountInfo.outlet || orderInfo.toLocation == accountInfo.outlet) {
|
|
505
|
+
// VDC release
|
|
506
|
+
if (accountInfo.outlet == orderInfo.toLocation) {
|
|
507
|
+
let releaseGood = {
|
|
508
|
+
domain: warehouseDomain,
|
|
509
|
+
bizplace: customerBizplace,
|
|
510
|
+
companyBizplace: custCompanyBizplace,
|
|
511
|
+
courierOption: false,
|
|
512
|
+
collectionOrderNo: orderInfo.orderId,
|
|
513
|
+
exportOption: false,
|
|
514
|
+
ownTransport: orderInfo.ownTransport,
|
|
515
|
+
packingOption: false,
|
|
516
|
+
refNo: orderInfo.orderId,
|
|
517
|
+
refNo2: orderInfo?.fromLocation,
|
|
518
|
+
refNo3: orderInfo?.toLocation,
|
|
519
|
+
releaseDate: orderInfo.issueDate,
|
|
520
|
+
type: 'b2b',
|
|
521
|
+
source: 'XILNEX',
|
|
522
|
+
orderSourceType: 'XILNEX_STOCK_REQUEST',
|
|
523
|
+
marketplaceOrderStatus: orderInfo?.status,
|
|
524
|
+
remark: orderInfo?.remark || null
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// check existing order
|
|
528
|
+
await checkExistingOrder(releaseGood, customerBizplace, true, context, tx)
|
|
529
|
+
|
|
530
|
+
//massage data
|
|
531
|
+
let massagedData = await massageOrderItems(releaseGood, orderItems, context)
|
|
532
|
+
|
|
533
|
+
// validation
|
|
534
|
+
let errorList = []
|
|
535
|
+
await Promise.all(
|
|
536
|
+
massagedData.combinedItems.map(async item => {
|
|
537
|
+
let itemList = await tx.query(
|
|
538
|
+
`
|
|
539
|
+
select wboi.* from warehouse_bizplace_onhand_inventories wboi
|
|
540
|
+
where wboi."domainId"= $1 and
|
|
541
|
+
wboi."bizplaceId" = $2 and
|
|
542
|
+
wboi."groupType" = 'SINGLE' and
|
|
543
|
+
wboi."productId" = $3 and
|
|
544
|
+
wboi."packingType" = $4 and
|
|
545
|
+
wboi."packingSize" = $5 and
|
|
546
|
+
wboi."uom" = $6 and
|
|
547
|
+
wboi."remainQty" >= $7 and
|
|
548
|
+
wboi."remainUomValue" >= $8
|
|
549
|
+
`,
|
|
550
|
+
[
|
|
551
|
+
warehouseDomain.id,
|
|
552
|
+
customerBizplace.id,
|
|
553
|
+
item.product.id,
|
|
554
|
+
item.packingType,
|
|
555
|
+
item.packingSize,
|
|
556
|
+
item.uom,
|
|
557
|
+
item.releaseQty,
|
|
558
|
+
item.releaseUomValue
|
|
559
|
+
]
|
|
560
|
+
)
|
|
561
|
+
if (itemList.length <= 0) {
|
|
562
|
+
errorList.push(
|
|
563
|
+
JSON.stringify({
|
|
564
|
+
code: 'insufficient stock',
|
|
565
|
+
sku: item.product.sku,
|
|
566
|
+
packingType: item.packingType,
|
|
567
|
+
packingSize: item.packingSize,
|
|
568
|
+
uom: item.uom,
|
|
569
|
+
qty: item.releaseQty,
|
|
570
|
+
uom_value: item.releaseUomValue
|
|
571
|
+
})
|
|
572
|
+
)
|
|
573
|
+
}
|
|
574
|
+
console.log()
|
|
575
|
+
})
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
if (errorList.length > 0) {
|
|
579
|
+
context.throw(404, errorList.join(', '))
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
let assignedOrderProducts = await assignToInventory(
|
|
583
|
+
massagedData.orderProducts,
|
|
584
|
+
customerBizplace,
|
|
585
|
+
warehouseDomain,
|
|
586
|
+
context,
|
|
587
|
+
tx
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
let result = await createReleaseGood(
|
|
591
|
+
releaseGood,
|
|
592
|
+
assignedOrderProducts,
|
|
593
|
+
customerBizplace,
|
|
594
|
+
warehouseDomain,
|
|
595
|
+
context,
|
|
596
|
+
tx
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
let addReleaseGood = {
|
|
600
|
+
id: result.id,
|
|
601
|
+
name: result.name,
|
|
602
|
+
refNo: result.refNo,
|
|
603
|
+
refNo2: result.refNo2,
|
|
604
|
+
refNo3: result.refNo3,
|
|
605
|
+
status: result.status,
|
|
606
|
+
truckNo: result.truckNo,
|
|
607
|
+
ownTransport: result.ownTransport,
|
|
608
|
+
crossDocking: result.crossDocking,
|
|
609
|
+
type: result.type,
|
|
610
|
+
exportOption: result.exportOption,
|
|
611
|
+
releaseDate: result.releaseDate,
|
|
612
|
+
collectionOrderNo: result.collectionOrderNo,
|
|
613
|
+
paidAmount: result?.paidAmount,
|
|
614
|
+
discountAmount: result?.discountAmount,
|
|
615
|
+
taxAmount: result?.taxAmount,
|
|
616
|
+
shippingFee: result?.shippingFee,
|
|
617
|
+
bizplace: { name: result.bizplace.name },
|
|
618
|
+
domain: { name: result.domain.name },
|
|
619
|
+
orderInventories: result.orderInventories
|
|
620
|
+
}
|
|
149
621
|
|
|
150
|
-
|
|
622
|
+
context.body = { result: { addReleaseGood } }
|
|
623
|
+
// createPayloadLog(
|
|
624
|
+
// customerBizplace.id,
|
|
625
|
+
// `/webhook-xilnex-stock-request`,
|
|
626
|
+
// stockRequest,
|
|
627
|
+
// context.body,
|
|
628
|
+
// context,
|
|
629
|
+
// PayloadType.INGESTION
|
|
630
|
+
// )
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// VDC inbound
|
|
634
|
+
if (accountInfo.outlet == orderInfo.fromLocation) {
|
|
635
|
+
const ganNoSetting: Setting = await tx.getRepository(Setting).findOne({
|
|
636
|
+
where: {
|
|
637
|
+
domain: warehouseDomain,
|
|
638
|
+
name: ORDER_NUMBER_SETTING_KEY.GAN_NUMBER_RULE
|
|
639
|
+
}
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
let orderNo: string = ''
|
|
643
|
+
if (ganNoSetting) {
|
|
644
|
+
orderNo = await generateId({ domain: warehouseDomain, type: ORDER_NUMBER_RULE_TYPE.GAN_NUMBER, seed: {} })
|
|
645
|
+
} else {
|
|
646
|
+
orderNo = OrderNoGenerator.arrivalNotice()
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
let arrivalNotice: any = {
|
|
650
|
+
name: orderNo,
|
|
651
|
+
refNo: orderInfo.orderId,
|
|
652
|
+
refNo2: orderInfo.fromLocation,
|
|
653
|
+
refNo3: orderInfo.toLocation,
|
|
654
|
+
etaDate: orderInfo.etaDate,
|
|
655
|
+
ownTransport: orderInfo.ownTransport,
|
|
656
|
+
importCargo: orderInfo.importCargo,
|
|
657
|
+
container: orderInfo.container,
|
|
658
|
+
remark: orderInfo.remark,
|
|
659
|
+
source: 'XILNEX',
|
|
660
|
+
orderSourceType: 'XILNEX_STOCK_REQUEST',
|
|
661
|
+
domain: warehouseDomain,
|
|
662
|
+
bizplace: customerBizplace,
|
|
663
|
+
status: 'INTRANSIT'
|
|
664
|
+
}
|
|
665
|
+
arrivalNotice = await tx.getRepository(ArrivalNotice).save(arrivalNotice)
|
|
666
|
+
|
|
667
|
+
let orderProducts: any[] = []
|
|
668
|
+
await Promise.all(
|
|
669
|
+
orderItems.map(async item => {
|
|
670
|
+
const productDetail: ProductDetail = await getRepository(ProductDetail).findOne({
|
|
671
|
+
where: { domain: custCompanyBizplace.domain, refCode: item.product.refCode },
|
|
672
|
+
relations: ['product']
|
|
673
|
+
})
|
|
674
|
+
|
|
675
|
+
if (!productDetail) {
|
|
676
|
+
context.throw(404, `no matching product found`)
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
let orderProduct: any = {
|
|
680
|
+
name: OrderNoGenerator.orderProduct(),
|
|
681
|
+
product: productDetail?.product,
|
|
682
|
+
productDetail,
|
|
683
|
+
batchId: item?.batchId || '-',
|
|
684
|
+
packingType: productDetail?.packingType,
|
|
685
|
+
packingSize: productDetail?.packingSize,
|
|
686
|
+
uom: productDetail?.uom,
|
|
687
|
+
uomValue: productDetail?.uomValue,
|
|
688
|
+
packQty: item.confirmedQty,
|
|
689
|
+
totalUomValue: `${(productDetail.uomValue * item.confirmedQty).toFixed(2)} ${productDetail.uom}`,
|
|
690
|
+
status: 'PENDING',
|
|
691
|
+
arrivalNotice,
|
|
692
|
+
domain: warehouseDomain,
|
|
693
|
+
bizplace: customerBizplace
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
orderProducts.push(orderProduct)
|
|
697
|
+
})
|
|
698
|
+
)
|
|
699
|
+
await tx.getRepository(OrderProduct).save(orderProducts)
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
accountWebhookOrder.status = ACCOUNT_SYNC_STATUS.SUCCESS
|
|
705
|
+
await tx.getRepository(AccountWebhookOrder).save(accountWebhookOrder)
|
|
706
|
+
})
|
|
151
707
|
|
|
152
708
|
context.status = 200
|
|
153
709
|
} catch (err) {
|
|
154
710
|
logger.error(`xilnex-router[webhook-xilnex-stock-request]: ${err}`)
|
|
155
711
|
}
|
|
156
712
|
})
|
|
713
|
+
|
|
714
|
+
async function checkExistingOrder(releaseGood, bizplace, checkDuplicationValidation = true, context, tx) {
|
|
715
|
+
const { domain }: { domain: Domain } = context.state
|
|
716
|
+
const refNo: string = releaseGood.refNo
|
|
717
|
+
const refNo2: string = releaseGood.refNo2
|
|
718
|
+
const refNo3: string = releaseGood.refNo3
|
|
719
|
+
|
|
720
|
+
if (checkDuplicationValidation) {
|
|
721
|
+
const foundDraftReleaseGood: DraftReleaseGood = await tx.getRepository(DraftReleaseGood).findOne({
|
|
722
|
+
where: {
|
|
723
|
+
domain,
|
|
724
|
+
refNo: refNo || null,
|
|
725
|
+
refNo2: refNo2 || null,
|
|
726
|
+
refNo3: refNo3 || null,
|
|
727
|
+
status: Not(In([DRAFT_RELEASE_ORDER_STATUS.CANCELLED]))
|
|
728
|
+
}
|
|
729
|
+
})
|
|
730
|
+
|
|
731
|
+
if (foundDraftReleaseGood) {
|
|
732
|
+
throw new ValidationError({
|
|
733
|
+
errorCode: 'EXISTING_ORDER',
|
|
734
|
+
message: 'existing draft release order found',
|
|
735
|
+
detail: foundDraftReleaseGood.name
|
|
736
|
+
})
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const foundReleaseGood: ReleaseGood = await tx.getRepository(ReleaseGood).findOne({
|
|
740
|
+
where: {
|
|
741
|
+
domain: domain,
|
|
742
|
+
refNo,
|
|
743
|
+
status: Not(In([ORDER_STATUS.CANCELLED, ORDER_STATUS.PENDING_CANCEL]))
|
|
744
|
+
},
|
|
745
|
+
relations: ['bizplace', 'bizplace.domain', 'bizplace.company', 'bizplace.company.domain']
|
|
746
|
+
})
|
|
747
|
+
|
|
748
|
+
if (foundReleaseGood) {
|
|
749
|
+
throw new ValidationError({
|
|
750
|
+
errorCode: 'EXISTING_ORDER',
|
|
751
|
+
message: 'existing release order found',
|
|
752
|
+
detail: foundReleaseGood.name
|
|
753
|
+
})
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
async function massageOrderItems(
|
|
759
|
+
releaseGood,
|
|
760
|
+
inputOrderProducts,
|
|
761
|
+
context
|
|
762
|
+
): Promise<{ orderProducts: OrderProduct[]; combinedItems: OrderProduct[] }> {
|
|
763
|
+
try {
|
|
764
|
+
let orderProducts: OrderProduct[] = []
|
|
765
|
+
let errorList = []
|
|
766
|
+
let totalReleaseQty = 0
|
|
767
|
+
//combine any repeated skus
|
|
768
|
+
inputOrderProducts = inputOrderProducts.reduce((acc, itm, idx) => {
|
|
769
|
+
let { sku, refCode } = itm.product
|
|
770
|
+
let { packingType, packingSize, confirmedQty, orderItemId } = itm
|
|
771
|
+
|
|
772
|
+
if (!sku && !refCode) {
|
|
773
|
+
errorList.push(
|
|
774
|
+
JSON.stringify({
|
|
775
|
+
code: 'INVALID_DATA',
|
|
776
|
+
message: 'sku or refCode not found',
|
|
777
|
+
sku: sku,
|
|
778
|
+
refCode: refCode,
|
|
779
|
+
packingType: packingType?.trim(),
|
|
780
|
+
packingSize: packingSize,
|
|
781
|
+
qty: confirmedQty
|
|
782
|
+
})
|
|
783
|
+
)
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (confirmedQty <= 0) {
|
|
787
|
+
errorList.push(
|
|
788
|
+
JSON.stringify({
|
|
789
|
+
code: 'INVALID_DATA',
|
|
790
|
+
message: 'negative stock request',
|
|
791
|
+
sku: sku,
|
|
792
|
+
refCode: refCode,
|
|
793
|
+
packingType: packingType?.trim(),
|
|
794
|
+
packingSize: packingSize,
|
|
795
|
+
qty: confirmedQty
|
|
796
|
+
})
|
|
797
|
+
)
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
let existingIndex = acc.findIndex(
|
|
801
|
+
itm =>
|
|
802
|
+
itm?.sku == sku?.trim() &&
|
|
803
|
+
itm?.refCode == refCode?.trim() &&
|
|
804
|
+
itm?.packingType == packingType?.trim() &&
|
|
805
|
+
itm?.packingSize == packingSize &&
|
|
806
|
+
itm?.refItemId == orderItemId.trim()
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
if (existingIndex >= 0 && confirmedQty > 0) {
|
|
810
|
+
acc[existingIndex].confirmedQty = acc[existingIndex].confirmedQty + confirmedQty
|
|
811
|
+
totalReleaseQty += confirmedQty
|
|
812
|
+
} else {
|
|
813
|
+
acc.push({
|
|
814
|
+
sku: sku?.trim(),
|
|
815
|
+
refCode: refCode?.trim(),
|
|
816
|
+
packingType: packingType?.trim(),
|
|
817
|
+
packingSize: packingSize,
|
|
818
|
+
releaseQty: confirmedQty,
|
|
819
|
+
refItemId: orderItemId
|
|
820
|
+
})
|
|
821
|
+
|
|
822
|
+
totalReleaseQty += confirmedQty
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return acc
|
|
826
|
+
}, [])
|
|
827
|
+
|
|
828
|
+
if (errorList.length > 0) {
|
|
829
|
+
throw new ValidationError({
|
|
830
|
+
errorCode: 'INVALID_DATA',
|
|
831
|
+
message: 'invalid data',
|
|
832
|
+
detail: errorList.join(', ')
|
|
833
|
+
})
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
//find customer sku in system
|
|
837
|
+
await Promise.all(
|
|
838
|
+
inputOrderProducts.map(async inputOrderItem => {
|
|
839
|
+
const sku: string = inputOrderItem.sku
|
|
840
|
+
const refCode: string = inputOrderItem.refCode
|
|
841
|
+
const packingType: string = inputOrderItem.packingType
|
|
842
|
+
const packingSize: string = inputOrderItem.packingSize
|
|
843
|
+
|
|
844
|
+
const qb: SelectQueryBuilder<ProductDetail> = getRepository(ProductDetail)
|
|
845
|
+
.createQueryBuilder('PD')
|
|
846
|
+
.innerJoinAndSelect('PD.product', 'PROD')
|
|
847
|
+
.where('PROD.bizplace_id = :bizplaceId', { bizplaceId: releaseGood.companyBizplace.id })
|
|
848
|
+
|
|
849
|
+
if (!sku && !refCode) {
|
|
850
|
+
context.throw(404, `error.sku ${sku} or refCode ${refCode} not found`)
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
if (refCode) {
|
|
854
|
+
qb.andWhere('PD.ref_code = :refCode', { refCode })
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (sku) {
|
|
858
|
+
qb.andWhere('PROD.sku = :sku', { sku })
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
if (packingType) {
|
|
862
|
+
qb.andWhere('PD.packing_type = :packingType', { packingType })
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
if ((!packingType && !refCode) || !packingSize) {
|
|
866
|
+
qb.andWhere('PD.is_default = :isDefault', { isDefault: true })
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const productDetail: ProductDetail = await qb.getOne()
|
|
870
|
+
|
|
871
|
+
// order Product based on product detail
|
|
872
|
+
if (productDetail) {
|
|
873
|
+
let newOrderProduct: OrderInventory = Object.assign({}, inputOrderItem)
|
|
874
|
+
newOrderProduct = {
|
|
875
|
+
...newOrderProduct,
|
|
876
|
+
product: productDetail.product,
|
|
877
|
+
productDetail: productDetail,
|
|
878
|
+
packingType: productDetail.packingType,
|
|
879
|
+
packingSize: packingSize || productDetail.packingSize,
|
|
880
|
+
uom: productDetail.uom,
|
|
881
|
+
uomValue: productDetail.uomValue,
|
|
882
|
+
refCode: inputOrderItem.refCode,
|
|
883
|
+
releaseQty: inputOrderItem.releaseQty,
|
|
884
|
+
releaseUomValue: inputOrderItem.releaseQty * (productDetail.uomValue || 1),
|
|
885
|
+
packQty: 0,
|
|
886
|
+
actualPackQty: 0,
|
|
887
|
+
palletQty: 0,
|
|
888
|
+
actualPalletQty: 0,
|
|
889
|
+
type: 'RELEASE_OF_GOODS',
|
|
890
|
+
status: ORDER_PRODUCT_STATUS.ASSIGNED,
|
|
891
|
+
batchId: '-',
|
|
892
|
+
sellingPrice: inputOrderItem?.sellingPrice
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
newOrderProduct.orderInventories = [
|
|
896
|
+
{
|
|
897
|
+
...newOrderProduct,
|
|
898
|
+
packQty: 0,
|
|
899
|
+
actualPackQty: 0,
|
|
900
|
+
palletQty: 0,
|
|
901
|
+
actualPalletQty: 0,
|
|
902
|
+
type: 'RELEASE_OF_GOODS',
|
|
903
|
+
status: ORDER_INVENTORY_STATUS.PENDING_WORKSHEET
|
|
904
|
+
}
|
|
905
|
+
]
|
|
906
|
+
|
|
907
|
+
orderProducts.push(newOrderProduct)
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
if (!productDetail) {
|
|
911
|
+
const qb: SelectQueryBuilder<ProductDetail> = getRepository(ProductBundle)
|
|
912
|
+
.createQueryBuilder('PB')
|
|
913
|
+
.innerJoinAndSelect('PB.productBundleSettings', 'PBS')
|
|
914
|
+
.innerJoinAndSelect('PBS.productDetail', 'PBD')
|
|
915
|
+
.innerJoinAndSelect('PBD.product', 'PROD')
|
|
916
|
+
.where('PB.bizplace_id = :bizplaceId', { bizplaceId: releaseGood.companyBizplace.id })
|
|
917
|
+
|
|
918
|
+
if (!sku && !refCode) {
|
|
919
|
+
context.throw(404, `error.sku ${sku} or refCode ${refCode} not found`)
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
if (refCode) {
|
|
923
|
+
qb.andWhere('PB.ref_code = :refCode', { refCode })
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
if (sku) {
|
|
927
|
+
qb.andWhere('PB.sku = :sku', { sku })
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (packingType) {
|
|
931
|
+
qb.andWhere('PB.packing_type = :packingType', { packingType })
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const foundProductBundle: ProductBundle = await qb.getOne()
|
|
935
|
+
|
|
936
|
+
if (foundProductBundle) {
|
|
937
|
+
let orderProduct = {
|
|
938
|
+
productBundle: foundProductBundle,
|
|
939
|
+
refCode,
|
|
940
|
+
packingType: foundProductBundle.packingType,
|
|
941
|
+
packingSize: packingSize || 1,
|
|
942
|
+
uom: inputOrderItem?.uom,
|
|
943
|
+
releaseQty: inputOrderItem.releaseQty,
|
|
944
|
+
releaseUomValue: inputOrderItem.releaseQty,
|
|
945
|
+
packQty: 0,
|
|
946
|
+
actualPackQty: 0,
|
|
947
|
+
palletQty: 0,
|
|
948
|
+
actualPalletQty: 0,
|
|
949
|
+
orderInventories: [],
|
|
950
|
+
type: 'RELEASE_OF_GOODS',
|
|
951
|
+
status: ORDER_PRODUCT_STATUS.ASSIGNED,
|
|
952
|
+
batchId: '-',
|
|
953
|
+
sellingPrice: inputOrderItem?.sellingPrice
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
const bundleProductSettings: any[] = foundProductBundle.productBundleSettings
|
|
957
|
+
bundleProductSettings.map(bundleProductSetting => {
|
|
958
|
+
const productDetailBundle: ProductDetail = bundleProductSetting.productDetail
|
|
959
|
+
|
|
960
|
+
let newOrderInventory: OrderInventory = {
|
|
961
|
+
product: productDetailBundle.product,
|
|
962
|
+
productDetail: productDetailBundle,
|
|
963
|
+
sku: productDetailBundle.product.sku,
|
|
964
|
+
packingType: productDetailBundle.packingType,
|
|
965
|
+
packingSize: productDetailBundle.packingSize,
|
|
966
|
+
uom: productDetailBundle.uom,
|
|
967
|
+
packQty: 0,
|
|
968
|
+
actualPackQty: 0,
|
|
969
|
+
palletQty: 0,
|
|
970
|
+
actualPalletQty: 0,
|
|
971
|
+
releaseQty: inputOrderItem.confirmedQty * bundleProductSetting.bundleQty,
|
|
972
|
+
releaseUomValue:
|
|
973
|
+
inputOrderItem.confirmedQty *
|
|
974
|
+
bundleProductSetting.bundleQty *
|
|
975
|
+
(productDetailBundle.uomValue <= 0 ? 1 : productDetailBundle.uomValue),
|
|
976
|
+
type: 'RELEASE_OF_GOODS',
|
|
977
|
+
status: ORDER_INVENTORY_STATUS.PENDING_WORKSHEET
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
orderProduct.orderInventories.push(newOrderInventory)
|
|
981
|
+
})
|
|
982
|
+
orderProducts.push(orderProduct)
|
|
983
|
+
} else {
|
|
984
|
+
if (sku && refCode) {
|
|
985
|
+
context.throw(404, `error.sku ${sku} and refCode ${refCode} not exist in product / bundle master`)
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
if (sku) {
|
|
989
|
+
context.throw(404, `error.sku ${sku} not exist in product / bundle master`)
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
if (refCode) {
|
|
993
|
+
context.throw(404, `error.refCode ${refCode} not exist in product / bundle master`)
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
})
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
//combine any repeated skus
|
|
1001
|
+
let combinedItems: OrderProduct[] = orderProducts.reduce((acc, op, idx) => {
|
|
1002
|
+
let oi = op?.orderInventories || []
|
|
1003
|
+
oi.forEach(itm => {
|
|
1004
|
+
let { sku, refCode } = itm.product
|
|
1005
|
+
let { packingType, packingSize, batchId, releaseQty, product, productDetail, uom } = itm
|
|
1006
|
+
|
|
1007
|
+
if (!sku && !refCode) {
|
|
1008
|
+
context.throw(404, `sku ${sku} or refCode ${refCode} not found`)
|
|
1009
|
+
}
|
|
1010
|
+
let existingIndex = acc.findIndex(
|
|
1011
|
+
itm =>
|
|
1012
|
+
itm?.sku == sku?.trim() &&
|
|
1013
|
+
itm?.refCode == refCode?.trim() &&
|
|
1014
|
+
itm?.packingType == packingType?.trim() &&
|
|
1015
|
+
itm?.packingSize == packingSize &&
|
|
1016
|
+
itm?.batchId == batchId?.trim() &&
|
|
1017
|
+
itm?.uom == uom?.trim()
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
if (existingIndex >= 0 && releaseQty > 0) {
|
|
1021
|
+
acc[existingIndex].releaseQty = acc[existingIndex].releaseQty + releaseQty
|
|
1022
|
+
acc[existingIndex].releaseUomValue =
|
|
1023
|
+
acc[existingIndex].releaseUomValue + releaseQty * (productDetail.uomValue <= 0 ? 1 : productDetail.uomValue)
|
|
1024
|
+
} else {
|
|
1025
|
+
let updateObj = {
|
|
1026
|
+
sku: sku?.trim(),
|
|
1027
|
+
packingType: packingType?.trim(),
|
|
1028
|
+
packingSize: packingSize,
|
|
1029
|
+
uom: uom?.trim(),
|
|
1030
|
+
releaseQty: releaseQty,
|
|
1031
|
+
releaseUomValue: releaseQty * (productDetail.uomValue <= 0 ? 1 : productDetail.uomValue),
|
|
1032
|
+
product,
|
|
1033
|
+
productDetail
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
if (refCode) updateObj['refCode'] = refCode.trim()
|
|
1037
|
+
updateObj['batchId'] = batchId ? batchId?.trim() : '-'
|
|
1038
|
+
|
|
1039
|
+
acc.push(updateObj)
|
|
1040
|
+
}
|
|
1041
|
+
})
|
|
1042
|
+
return acc
|
|
1043
|
+
}, [])
|
|
1044
|
+
|
|
1045
|
+
return { orderProducts, combinedItems }
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
throw error
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
async function assignToInventory(orderProducts: OrderProduct[], customerBizplace, warehouseDomain, context, tx) {
|
|
1052
|
+
const { client, domain, user } = context.state
|
|
1053
|
+
for (let opIdx = 0; opIdx < orderProducts.length; opIdx++) {
|
|
1054
|
+
let orderProduct = orderProducts[opIdx]
|
|
1055
|
+
|
|
1056
|
+
let assignedOrderInventories = []
|
|
1057
|
+
const pickingProductSetting: Setting = await tx.getRepository(Setting).findOne({
|
|
1058
|
+
where: { domain, name: 'rule-for-picking-product' }
|
|
1059
|
+
})
|
|
1060
|
+
|
|
1061
|
+
for (let oiIdx = 0; oiIdx < orderProducts[opIdx].orderInventories.length; oiIdx++) {
|
|
1062
|
+
const orderInventory = orderProduct.orderInventories[oiIdx]
|
|
1063
|
+
let sortings: any = []
|
|
1064
|
+
|
|
1065
|
+
switch (orderInventory.product.pickingStrategy) {
|
|
1066
|
+
case 'LIFO':
|
|
1067
|
+
sortings.push({ name: 'iv.created_at', desc: true })
|
|
1068
|
+
if (pickingProductSetting?.value) {
|
|
1069
|
+
for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
|
|
1070
|
+
sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
|
|
1071
|
+
}
|
|
1072
|
+
} else {
|
|
1073
|
+
sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false })
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
break
|
|
1077
|
+
case 'FEFO':
|
|
1078
|
+
sortings.push({ name: 'iv.expiration_date', desc: false }, { name: 'iv.created_at', desc: false })
|
|
1079
|
+
if (pickingProductSetting?.value) {
|
|
1080
|
+
for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
|
|
1081
|
+
sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
|
|
1082
|
+
}
|
|
1083
|
+
} else {
|
|
1084
|
+
sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false })
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
break
|
|
1088
|
+
case 'FMFO':
|
|
1089
|
+
sortings.push({ name: 'iv.manufacture_date', desc: false }, { name: 'iv.created_at', desc: false })
|
|
1090
|
+
if (pickingProductSetting?.value) {
|
|
1091
|
+
for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
|
|
1092
|
+
sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
|
|
1093
|
+
}
|
|
1094
|
+
} else {
|
|
1095
|
+
sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false })
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
break
|
|
1099
|
+
case 'LOCATION':
|
|
1100
|
+
if (pickingProductSetting?.value) {
|
|
1101
|
+
for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
|
|
1102
|
+
sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
|
|
1103
|
+
}
|
|
1104
|
+
} else {
|
|
1105
|
+
sortings.push({ name: 'loc.name', desc: false }, { name: 'iv.created_at', desc: false })
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
break
|
|
1109
|
+
//Every other case includes 'FIFO' will be applicable for this case
|
|
1110
|
+
default:
|
|
1111
|
+
sortings.push({ name: 'iv.created_at', desc: false })
|
|
1112
|
+
if (pickingProductSetting?.value) {
|
|
1113
|
+
for (const [key, value] of Object.entries(JSON.parse(pickingProductSetting.value))) {
|
|
1114
|
+
sortings.push({ name: `loc.${key}`, desc: value == 'DESC' ? true : false })
|
|
1115
|
+
}
|
|
1116
|
+
} else {
|
|
1117
|
+
sortings.push({ name: 'loc.name', desc: false })
|
|
1118
|
+
sortings.push({ name: 'iv.pallet_id', desc: false })
|
|
1119
|
+
}
|
|
1120
|
+
break
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
let queryFilters = []
|
|
1124
|
+
|
|
1125
|
+
let queryStrings = queryFilters.reduce(
|
|
1126
|
+
(acc, itm, idx, arr) => {
|
|
1127
|
+
acc.values.push(itm.filters)
|
|
1128
|
+
|
|
1129
|
+
switch (itm?.operator) {
|
|
1130
|
+
case 'notin':
|
|
1131
|
+
case 'in':
|
|
1132
|
+
acc.query.push(
|
|
1133
|
+
`${itm.query} ${itm.operator == 'notin' ? 'NOT IN' : 'IN'} (${itm.filtes
|
|
1134
|
+
.map((itm, idx) => {
|
|
1135
|
+
return `$${acc.values.length + 1}`
|
|
1136
|
+
})
|
|
1137
|
+
.join(',')})`
|
|
1138
|
+
)
|
|
1139
|
+
break
|
|
1140
|
+
|
|
1141
|
+
default:
|
|
1142
|
+
acc.query.push(`${itm.query} ${itm?.operator ? itm.operator : '='} $${acc.values.length + 1}`)
|
|
1143
|
+
break
|
|
1144
|
+
}
|
|
1145
|
+
acc.query.push(`${itm.query} ${itm?.operator ? itm.operator : '='} $${acc.values.length + 1}`)
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
query: [],
|
|
1149
|
+
values: []
|
|
1150
|
+
}
|
|
1151
|
+
)
|
|
1152
|
+
|
|
1153
|
+
let sortQuery = sortings
|
|
1154
|
+
.map(itm => {
|
|
1155
|
+
return `${itm.name} ${itm.desc ? 'DESC' : 'ASC'}`
|
|
1156
|
+
})
|
|
1157
|
+
.join(`,`)
|
|
1158
|
+
|
|
1159
|
+
let updatedInventories = await tx.getRepository(Inventory).query(
|
|
1160
|
+
`
|
|
1161
|
+
update inventories tgt set locked_qty = coalesce(locked_qty,0) + src.reserve_qty,
|
|
1162
|
+
locked_uom_value = coalesce(locked_uom_value,0) + src.reserve_uom_value,
|
|
1163
|
+
updated_at = NOW(),
|
|
1164
|
+
updater_id = $1
|
|
1165
|
+
from (
|
|
1166
|
+
select "id", "pallet_id","carton_id", "batch_id", "batch_id_ref", "unit", "uom", "packing_type", "packing_size", "manufacture_year",
|
|
1167
|
+
"reserve_qty", (("uom_value"/"qty") * "reserve_qty") as "reserve_uom_value" from (
|
|
1168
|
+
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",
|
|
1169
|
+
"release_qty", "release_uom_value", lock_amount,
|
|
1170
|
+
case when "lock_amount" < 0 then "available_qty" else "available_qty" - "lock_amount" end as "reserve_qty"
|
|
1171
|
+
from (
|
|
1172
|
+
select *,
|
|
1173
|
+
qty - locked_qty as available_qty,
|
|
1174
|
+
sum(qty - locked_qty - release_qty) over (order by sort_seq asc rows between unbounded preceding and current row) as lock_amount
|
|
1175
|
+
from (
|
|
1176
|
+
SELECT 0 as sort_seq, null as id, null as pallet_id, null as batch_id, null as batch_id_ref,
|
|
1177
|
+
null as unit, '${orderInventory.uom}' as uom, '${orderInventory.packingType}' as packing_type, '${
|
|
1178
|
+
orderInventory.packingSize
|
|
1179
|
+
}' as packing_size,
|
|
1180
|
+
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,
|
|
1181
|
+
'${orderInventory.releaseQty}' as release_qty, '${orderInventory.releaseUomValue}' as release_uom_value
|
|
1182
|
+
UNION
|
|
1183
|
+
(
|
|
1184
|
+
SELECT ROW_NUMBER() OVER(PARTITION BY iv.domain_id ORDER BY ${sortQuery}) as sort_seq,
|
|
1185
|
+
iv.id, iv.pallet_id, iv.batch_id, iv.batch_id_ref,
|
|
1186
|
+
iv.unit, iv.uom, iv.packing_type, iv.packing_size,
|
|
1187
|
+
iv.manufacture_year, iv.carton_id, iv.uom_value,
|
|
1188
|
+
coalesce(iv.locked_uom_value,0) as locked_uom_value, iv.qty, coalesce(iv.locked_qty,0) as locked_qty, iv.created_at,
|
|
1189
|
+
0 as release_qty, 0 as release_uom_value
|
|
1190
|
+
FROM "inventories" "iv"
|
|
1191
|
+
INNER JOIN "locations" "loc" ON "loc"."id"="iv"."location_id"
|
|
1192
|
+
WHERE case when coalesce("iv"."locked_qty",0) < 0 then 0 else ("iv"."qty" - coalesce("iv"."locked_qty",0)) end > 0 AND
|
|
1193
|
+
"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)
|
|
1194
|
+
${queryStrings.query.length > 0 ? `AND ${queryStrings.join(' AND ')}` : ''}
|
|
1195
|
+
ORDER BY ${sortQuery}
|
|
1196
|
+
)
|
|
1197
|
+
) dt1
|
|
1198
|
+
) dt2 where case when "lock_amount" < 0 then "available_qty" else "available_qty" - "lock_amount" end > 0
|
|
1199
|
+
) dt3 where sort_seq > 0
|
|
1200
|
+
) src where src.id = tgt.id
|
|
1201
|
+
returning
|
|
1202
|
+
tgt."id", tgt."qty", tgt."pallet_id", tgt."carton_id", tgt."batch_id", tgt."batch_id_ref", tgt."unit",
|
|
1203
|
+
tgt."uom", tgt."packing_type", tgt."packing_size", tgt."manufacture_year",
|
|
1204
|
+
tgt."locked_qty", tgt."uom_value", tgt."locked_uom_value", src."reserve_qty", src."reserve_uom_value";`,
|
|
1205
|
+
[
|
|
1206
|
+
user?.id || null,
|
|
1207
|
+
warehouseDomain.id,
|
|
1208
|
+
customerBizplace.id,
|
|
1209
|
+
orderInventory.packingType,
|
|
1210
|
+
orderInventory.packingSize,
|
|
1211
|
+
orderInventory.product.id,
|
|
1212
|
+
INVENTORY_STATUS.STORED,
|
|
1213
|
+
LOCATION_TYPE.QUARANTINE,
|
|
1214
|
+
LOCATION_TYPE.RESERVE,
|
|
1215
|
+
...queryStrings.values
|
|
1216
|
+
]
|
|
1217
|
+
)
|
|
1218
|
+
|
|
1219
|
+
let totalAssigned = updatedInventories[0].reduce((acc, inv) => {
|
|
1220
|
+
return acc + inv.reserve_qty
|
|
1221
|
+
}, 0)
|
|
1222
|
+
|
|
1223
|
+
if (orderInventory.releaseQty != totalAssigned) {
|
|
1224
|
+
throw new ValidationError({
|
|
1225
|
+
errorCode: 'INSUFFICIENT_STOCK',
|
|
1226
|
+
message: 'insufficient stock',
|
|
1227
|
+
detail: JSON.stringify({
|
|
1228
|
+
code: 'insufficient stock',
|
|
1229
|
+
sku: orderInventory.product.sku,
|
|
1230
|
+
packingType: orderInventory.packingType,
|
|
1231
|
+
packingSize: orderInventory.packingSize,
|
|
1232
|
+
uom: orderInventory.uom,
|
|
1233
|
+
qty: orderInventory.releaseQty,
|
|
1234
|
+
uom_value: orderInventory.releaseUomValue
|
|
1235
|
+
})
|
|
1236
|
+
})
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
updatedInventories[0].forEach(inv => {
|
|
1240
|
+
let oi = {
|
|
1241
|
+
...orderInventory,
|
|
1242
|
+
inventory: { id: inv.id },
|
|
1243
|
+
uom: inv.uom,
|
|
1244
|
+
packingType: inv.packing_type,
|
|
1245
|
+
batchId: inv.batch_id,
|
|
1246
|
+
releaseQty: inv.reserve_qty,
|
|
1247
|
+
releaseUomValue: inv.reserve_uom_value,
|
|
1248
|
+
pickedQty: 0,
|
|
1249
|
+
packedQty: 0,
|
|
1250
|
+
type: ORDER_TYPES.RELEASE_OF_GOODS,
|
|
1251
|
+
status: ORDER_INVENTORY_STATUS.PENDING_WORKSHEET,
|
|
1252
|
+
domain: { id: warehouseDomain.id },
|
|
1253
|
+
bizplace: { id: customerBizplace.id }
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
assignedOrderInventories.push(oi)
|
|
1257
|
+
})
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
orderProducts[opIdx].orderInventories = assignedOrderInventories
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
return orderProducts
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
async function createReleaseGood(
|
|
1267
|
+
releaseGood: any,
|
|
1268
|
+
orderProducts: OrderProduct[],
|
|
1269
|
+
bizplace: Bizplace,
|
|
1270
|
+
domain: Domain,
|
|
1271
|
+
context: any,
|
|
1272
|
+
tx?: EntityManager
|
|
1273
|
+
): Promise<ReleaseGood> {
|
|
1274
|
+
try {
|
|
1275
|
+
if (orderProducts.length <= 0) {
|
|
1276
|
+
throw new ValidationError({
|
|
1277
|
+
errorCode: 'ITEM_NOT_FOUND',
|
|
1278
|
+
message: 'no order items found',
|
|
1279
|
+
detail: JSON.stringify(releaseGood)
|
|
1280
|
+
})
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
let newReleaseGood: ReleaseGood = new ReleaseGood()
|
|
1284
|
+
|
|
1285
|
+
// find RO number rule setting
|
|
1286
|
+
const roNoSetting: Setting = await tx.getRepository(Setting).findOne({
|
|
1287
|
+
where: {
|
|
1288
|
+
domain,
|
|
1289
|
+
name: ORDER_NUMBER_SETTING_KEY.RO_NUMBER_RULE
|
|
1290
|
+
}
|
|
1291
|
+
})
|
|
1292
|
+
|
|
1293
|
+
newReleaseGood = {
|
|
1294
|
+
name: roNoSetting
|
|
1295
|
+
? await generateId({ domain, type: ORDER_NUMBER_RULE_TYPE.RO_NUMBER, seed: {} })
|
|
1296
|
+
: OrderNoGenerator.releaseGood(),
|
|
1297
|
+
domain: domain,
|
|
1298
|
+
bizplace: bizplace,
|
|
1299
|
+
collectionOrderNo: releaseGood.collectionOrderNo,
|
|
1300
|
+
courierOption: releaseGood.courierOption,
|
|
1301
|
+
exportOption: releaseGood.exportOption,
|
|
1302
|
+
ownTransport: releaseGood.ownTransport,
|
|
1303
|
+
packingOption: releaseGood.packingOption,
|
|
1304
|
+
marketplaceOrderStatus: releaseGood?.marketplaceOrderStatus || null,
|
|
1305
|
+
refNo: releaseGood.refNo,
|
|
1306
|
+
refNo2: releaseGood.refNo2 || null,
|
|
1307
|
+
refNo3: releaseGood.refNo3 || null,
|
|
1308
|
+
remark: releaseGood?.remark || null,
|
|
1309
|
+
releaseDate: releaseGood.releaseDate,
|
|
1310
|
+
truckNo: releaseGood.truckNo,
|
|
1311
|
+
type: releaseGood?.type ? releaseGood.type : 'b2b',
|
|
1312
|
+
status: ORDER_STATUS.PENDING_WORKSHEET,
|
|
1313
|
+
noOfItems: orderProducts.length,
|
|
1314
|
+
source: releaseGood?.source,
|
|
1315
|
+
paidAmount: releaseGood?.paidAmount,
|
|
1316
|
+
discountAmount: releaseGood?.discountAmount,
|
|
1317
|
+
taxAmount: releaseGood?.taxAmount,
|
|
1318
|
+
shippingFee: releaseGood?.shippingFee,
|
|
1319
|
+
acceptedAt: new Date()
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
let resultReleaseGood = await tx.getRepository(ReleaseGood).save(newReleaseGood)
|
|
1323
|
+
let combinedOrderInventories: OrderInventory[] = []
|
|
1324
|
+
let savedOrderProducts = await Promise.all(
|
|
1325
|
+
orderProducts.map(async orderProduct => {
|
|
1326
|
+
let orderInventories: OrderInventory[] = orderProduct.orderInventories
|
|
1327
|
+
orderProduct = Object.assign(new OrderProduct(), {
|
|
1328
|
+
...orderProduct,
|
|
1329
|
+
name: uuidv4(),
|
|
1330
|
+
domain: domain,
|
|
1331
|
+
bizplace: bizplace,
|
|
1332
|
+
releaseGood: resultReleaseGood,
|
|
1333
|
+
type: ORDER_TYPES.RELEASE_OF_GOODS,
|
|
1334
|
+
status: ORDER_PRODUCT_STATUS.ASSIGNED
|
|
1335
|
+
})
|
|
1336
|
+
let result = await tx.getRepository(OrderProduct).save(orderProduct)
|
|
1337
|
+
|
|
1338
|
+
for (let oiIdx = 0; oiIdx < orderInventories.length; oiIdx++) {
|
|
1339
|
+
let orderInventory = Object.assign(new OrderInventory(), {
|
|
1340
|
+
...orderInventories[oiIdx],
|
|
1341
|
+
name: uuidv4(),
|
|
1342
|
+
domain: domain,
|
|
1343
|
+
bizplace: bizplace,
|
|
1344
|
+
releaseGood: resultReleaseGood,
|
|
1345
|
+
type: ORDER_TYPES.RELEASE_OF_GOODS,
|
|
1346
|
+
status: ORDER_INVENTORY_STATUS.PENDING_WORKSHEET
|
|
1347
|
+
})
|
|
1348
|
+
await tx.getRepository(OrderInventory).save(orderInventory)
|
|
1349
|
+
|
|
1350
|
+
combinedOrderInventories.push({
|
|
1351
|
+
product: { name: orderInventory.product.name, sku: orderInventory.product.sku },
|
|
1352
|
+
batchId: orderInventory.batchId,
|
|
1353
|
+
packingType: orderInventory.packingType,
|
|
1354
|
+
packingSize: orderInventory.packingSize,
|
|
1355
|
+
releaseQty: orderInventory.releaseQty,
|
|
1356
|
+
releaseUomValue: orderInventory.releaseUomValue
|
|
1357
|
+
})
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
return result
|
|
1361
|
+
})
|
|
1362
|
+
)
|
|
1363
|
+
|
|
1364
|
+
newReleaseGood.orderProducts = savedOrderProducts
|
|
1365
|
+
newReleaseGood.orderInventories = combinedOrderInventories
|
|
1366
|
+
|
|
1367
|
+
return newReleaseGood
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
throw error
|
|
1370
|
+
}
|
|
1371
|
+
}
|