@things-factory/sales-base 4.0.24 → 4.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist-server/controllers/ecommerce/ecommerce-controller.js +7 -4
  2. package/dist-server/controllers/ecommerce/ecommerce-controller.js.map +1 -1
  3. package/dist-server/controllers/ecommerce/sellercraft-controller.js +59 -0
  4. package/dist-server/controllers/ecommerce/sellercraft-controller.js.map +1 -1
  5. package/dist-server/controllers/order-controller.js +40 -1
  6. package/dist-server/controllers/order-controller.js.map +1 -1
  7. package/dist-server/service/arrival-notice/arrival-notice-mutation.js +180 -0
  8. package/dist-server/service/arrival-notice/arrival-notice-mutation.js.map +1 -1
  9. package/dist-server/service/arrival-notice/arrival-notice-query.js +193 -1
  10. package/dist-server/service/arrival-notice/arrival-notice-query.js.map +1 -1
  11. package/dist-server/service/arrival-notice/arrival-notice-types.js +160 -2
  12. package/dist-server/service/arrival-notice/arrival-notice-types.js.map +1 -1
  13. package/dist-server/service/delivery-order/delivery-order-types.js +1 -1
  14. package/dist-server/service/delivery-order/delivery-order-types.js.map +1 -1
  15. package/dist-server/service/delivery-order/delivery-order.js +1 -1
  16. package/dist-server/service/delivery-order/delivery-order.js.map +1 -1
  17. package/dist-server/service/goods-receival-note/goods-receival-note.js +5 -0
  18. package/dist-server/service/goods-receival-note/goods-receival-note.js.map +1 -1
  19. package/dist-server/service/job-sheet/job-sheet-mutation.js +1 -0
  20. package/dist-server/service/job-sheet/job-sheet-mutation.js.map +1 -1
  21. package/dist-server/service/job-sheet/job-sheet-query.js +2 -0
  22. package/dist-server/service/job-sheet/job-sheet-query.js.map +1 -1
  23. package/dist-server/service/order-inventory/order-inventory.js +22 -1
  24. package/dist-server/service/order-inventory/order-inventory.js.map +1 -1
  25. package/dist-server/service/purchase-order/purchase-order.js +10 -1
  26. package/dist-server/service/purchase-order/purchase-order.js.map +1 -1
  27. package/dist-server/service/release-good/release-good-mutation.js +219 -212
  28. package/dist-server/service/release-good/release-good-mutation.js.map +1 -1
  29. package/dist-server/service/release-good/release-good-query.js +135 -99
  30. package/dist-server/service/release-good/release-good-query.js.map +1 -1
  31. package/dist-server/service/release-good/release-good-types.js +38 -2
  32. package/dist-server/service/release-good/release-good-types.js.map +1 -1
  33. package/dist-server/service/release-good/release-good.js +63 -1
  34. package/dist-server/service/release-good/release-good.js.map +1 -1
  35. package/dist-server/service/return-order/return-order-mutation.js +1 -1
  36. package/dist-server/service/return-order/return-order-mutation.js.map +1 -1
  37. package/dist-server/utils/inventory-util.js +89 -1
  38. package/dist-server/utils/inventory-util.js.map +1 -1
  39. package/package.json +12 -12
  40. package/server/controllers/ecommerce/ecommerce-controller.ts +15 -6
  41. package/server/controllers/ecommerce/sellercraft-controller.ts +77 -1
  42. package/server/controllers/order-controller.ts +55 -2
  43. package/server/service/arrival-notice/arrival-notice-mutation.ts +237 -1
  44. package/server/service/arrival-notice/arrival-notice-query.ts +214 -4
  45. package/server/service/arrival-notice/arrival-notice-types.ts +120 -1
  46. package/server/service/delivery-order/delivery-order-types.ts +1 -1
  47. package/server/service/delivery-order/delivery-order.ts +1 -1
  48. package/server/service/goods-receival-note/goods-receival-note.ts +4 -0
  49. package/server/service/job-sheet/job-sheet-mutation.ts +1 -0
  50. package/server/service/job-sheet/job-sheet-query.ts +3 -1
  51. package/server/service/order-inventory/order-inventory.ts +17 -1
  52. package/server/service/purchase-order/purchase-order.ts +8 -1
  53. package/server/service/release-good/release-good-mutation.ts +280 -283
  54. package/server/service/release-good/release-good-query.ts +158 -115
  55. package/server/service/release-good/release-good-types.ts +30 -2
  56. package/server/service/release-good/release-good.ts +46 -0
  57. package/server/service/return-order/return-order-mutation.ts +1 -1
  58. package/server/utils/index.ts +1 -1
  59. package/server/utils/inventory-util.ts +129 -1
@@ -1,9 +1,12 @@
1
+ import { EntityManager, EntitySchema, FindOneOptions } from 'typeorm'
2
+
1
3
  import { Role, User } from '@things-factory/auth-base'
2
4
  import { Bizplace, getDomainUsers } from '@things-factory/biz-base'
3
5
  import { sendNotification } from '@things-factory/notification'
6
+ import { ProductDetail } from '@things-factory/product-base'
4
7
  import { Domain } from '@things-factory/shell'
5
8
  import { Inventory } from '@things-factory/warehouse-base'
6
- import { EntityManager, EntitySchema, FindOneOptions } from 'typeorm'
9
+
7
10
  import {
8
11
  ArrivalNotice,
9
12
  DeliveryOrder,
@@ -48,7 +51,9 @@ export type NotificationMsgInterface = {
48
51
  export class OrderController {
49
52
  public readonly ERROR_MSG = {
50
53
  FIND: {
51
- NO_RESULT: (condition: any) => `There's no results matched with condition ${condition}`
54
+ NO_RESULT: (condition: any) => `There's no results matched with condition ${condition}`,
55
+ NO_CHILD_RESULT: (condition: any) => `There's no child result matched with condition ${condition}`,
56
+ NOT_MATCH: (source: any, target: any) => `Unable to find matching ${target} using ${source}`
52
57
  },
53
58
  CREATE: {
54
59
  ID_EXISTS: 'Target has ID already',
@@ -217,6 +222,54 @@ export class OrderController {
217
222
  return await this.trxMgr.getRepository(Inventory).save(inventory)
218
223
  }
219
224
 
225
+ async getChildPackingSize(
226
+ productDetails: ProductDetail[],
227
+ defaultProductDetail: ProductDetail,
228
+ unmatchingProductDetail: ProductDetail
229
+ ): Promise<number> {
230
+ let hasChildRelation: boolean = Boolean(unmatchingProductDetail?.childProductDetail)
231
+ let hasMatchingChild: boolean
232
+ let packingSize: number = 1
233
+ let currentChildProductDetail: ProductDetail
234
+
235
+ if (hasChildRelation) {
236
+ currentChildProductDetail = productDetails.find(
237
+ (productDetail: ProductDetail) => productDetail.id === unmatchingProductDetail.childProductDetail.id
238
+ )
239
+
240
+ if (!currentChildProductDetail) {
241
+ throw new Error(this.ERROR_MSG.FIND.NOT_MATCH('packing type', `GTIN (${unmatchingProductDetail.gtin})`))
242
+ }
243
+
244
+ hasMatchingChild = Boolean(defaultProductDetail.id === currentChildProductDetail.id)
245
+ if (hasMatchingChild) {
246
+ packingSize = unmatchingProductDetail.packingSize
247
+ } else {
248
+ packingSize = unmatchingProductDetail.packingSize * packingSize
249
+ }
250
+ } else {
251
+ throw new Error(this.ERROR_MSG.FIND.NO_CHILD_RESULT(`${unmatchingProductDetail.gtin}`))
252
+ }
253
+
254
+ while (hasChildRelation && !hasMatchingChild) {
255
+ if (hasMatchingChild) {
256
+ packingSize = currentChildProductDetail.packingSize * packingSize // 12 x 10
257
+ } else if (!hasMatchingChild && hasChildRelation) {
258
+ packingSize = currentChildProductDetail.packingSize * packingSize // 12 x 10
259
+ currentChildProductDetail = productDetails.find(
260
+ (productDetail: ProductDetail) => productDetail.id === currentChildProductDetail.childProductDetail.id
261
+ )
262
+
263
+ hasChildRelation = Boolean(currentChildProductDetail?.childProductDetail)
264
+ hasMatchingChild = Boolean(defaultProductDetail.id === currentChildProductDetail.id)
265
+ } else if (!hasChildRelation && !hasMatchingChild) {
266
+ throw new Error(this.ERROR_MSG.FIND.NO_RESULT(unmatchingProductDetail.gtin))
267
+ }
268
+ }
269
+
270
+ return packingSize
271
+ }
272
+
220
273
  /**
221
274
  * @summary set common stamp like domain, creator, updater
222
275
  * @description Set common stamp to passed record
@@ -1,6 +1,6 @@
1
1
  import { FileUpload, GraphQLUpload } from 'graphql-upload'
2
2
  import { Arg, Ctx, Directive, Mutation, Resolver } from 'type-graphql'
3
- import { EntityManager, getRepository, In, Repository } from 'typeorm'
3
+ import { EntityManager, getConnection, getRepository, In, Repository } from 'typeorm'
4
4
 
5
5
  import { Attachment, createAttachments } from '@things-factory/attachment-base'
6
6
  import { Role, User } from '@things-factory/auth-base'
@@ -38,10 +38,13 @@ import {
38
38
  ORDER_VAS_STATUS
39
39
  } from '../../constants'
40
40
  import { ATTACHMENT_TYPE } from '../../constants/attachment-type'
41
+ import { ValidationError } from '../../errors'
41
42
  import { OrderNoGenerator } from '../../utils/order-no-generator'
42
43
  import { generateJobSheet } from '../job-sheet/job-sheet-mutation'
43
44
  import { confirmReleaseGood, deleteReleaseGood, rejectReleaseGood } from '../release-good/release-good-mutation'
45
+ import { validateBulkArrivalNoticesFunction } from './'
44
46
  import { ArrivalNotice } from './arrival-notice'
47
+ import { ArrivalNoticeList, RawArrivalNotice } from './arrival-notice-types'
45
48
 
46
49
  const debug = require('debug')('things-factory:operato-wms:addArrivalNotice')
47
50
 
@@ -230,6 +233,139 @@ export class ArrivalNoticeMutation {
230
233
  await tx.getRepository(ArrivalNotice).update(existingArrivalNotice.id, arrivalNotice)
231
234
  return await tx.getRepository(ArrivalNotice).findOne(existingArrivalNotice.id)
232
235
  }
236
+
237
+ @Directive('@privilege(category: "order_customer", privilege: "mutation")')
238
+ @Directive('@transaction')
239
+ @Mutation(returns => ArrivalNoticeList)
240
+ async bulkGenerateArrivalNotices(
241
+ @Ctx() context: any,
242
+ @Arg('rawArrivalNotices', type => [NewArrivalNotice], { nullable: true }) rawArrivalNotices: NewArrivalNotice[],
243
+ @Arg('bizplaceId', type => String) bizplaceId: string
244
+ ): Promise<ArrivalNoticeList> {
245
+ const { domain, user, tx }: { domain: Domain; user: User; tx: EntityManager } = context.state
246
+ let createdArrivalNoticeIds: string[] = [],
247
+ createdOrderProductIds: string[] = [],
248
+ errorsCaught: any[] = [],
249
+ finalArrivalNoticeList: ArrivalNotice[] = []
250
+
251
+ const foundBizplace: Bizplace = await tx.getRepository(Bizplace).findOne(bizplaceId)
252
+
253
+ if (!foundBizplace) throw new Error('company not found')
254
+
255
+ // to check whether the raw data are valid
256
+ const validatedArrivalNotices: RawArrivalNotice[] = await validateBulkArrivalNoticesFunction(
257
+ rawArrivalNotices,
258
+ bizplaceId,
259
+ tx
260
+ )
261
+
262
+ // check for custom GAN order no setting
263
+ const ganNoSetting: Setting = await tx.getRepository(Setting).findOne({
264
+ where: {
265
+ domain,
266
+ name: ORDER_NUMBER_SETTING_KEY.GAN_NUMBER_RULE
267
+ }
268
+ })
269
+
270
+ // extract rawArrivalNotices data into normal ArrivalNotice format
271
+ let arrivalNotices: Partial<ArrivalNotice[]> = extractArrivalNotices(validatedArrivalNotices)
272
+
273
+ for (let i = 0, l = arrivalNotices.length; i < l; i++) {
274
+ try {
275
+ await getConnection().transaction(async childTx => {
276
+ // check for duplication
277
+ const duplicatedArrivalNotice: ArrivalNotice = await childTx.getRepository(ArrivalNotice).findOne({
278
+ where: {
279
+ refNo: arrivalNotices[i].refNo,
280
+ refNo2: arrivalNotices[i].refNo2,
281
+ refNo3: arrivalNotices[i].refNo3,
282
+ etaDate: arrivalNotices[i].etaDate,
283
+ truckNo: arrivalNotices[i].truckNo,
284
+ containerNo: arrivalNotices[i].containerNo,
285
+ containerSize: arrivalNotices[i].containerSize,
286
+ looseItem: arrivalNotices[i].looseItem,
287
+ importCargo: arrivalNotices[i].importCargo
288
+ }
289
+ })
290
+
291
+ if (duplicatedArrivalNotice) {
292
+ throw new Error(`duplicated with ${duplicatedArrivalNotice.name}`)
293
+ }
294
+
295
+ // save new arrival notice
296
+ const createdArrivalNotice: ArrivalNotice = await childTx.getRepository(ArrivalNotice).save({
297
+ ...arrivalNotices[i],
298
+ name: ganNoSetting
299
+ ? await generateId({ domain, type: ORDER_NUMBER_RULE_TYPE.GAN_NUMBER, seed: {} })
300
+ : OrderNoGenerator.arrivalNotice(),
301
+ domain,
302
+ bizplace: foundBizplace,
303
+ status: ORDER_STATUS.PENDING,
304
+ creator: user,
305
+ updater: user
306
+ })
307
+
308
+ // save new order products
309
+ let createdOrderProducts: OrderProduct[] = await Promise.all(
310
+ arrivalNotices[i].orderProducts.map(async (op: OrderProduct) => {
311
+ return {
312
+ ...op,
313
+ domain,
314
+ bizplace: foundBizplace,
315
+ name: OrderNoGenerator.orderProduct(),
316
+ // product: await childTx.getRepository(Product).findOne(op.product.id),
317
+ status: ORDER_PRODUCT_STATUS.PENDING,
318
+ arrivalNotice: createdArrivalNotice,
319
+ creator: user
320
+ }
321
+ })
322
+ )
323
+ createdOrderProducts = await childTx.getRepository(OrderProduct).save(createdOrderProducts)
324
+
325
+ // generate new jobsheet
326
+ await generateJobSheet(domain, user, createdArrivalNotice, childTx)
327
+
328
+ // push arrivalNotice and orderProducts IDs if successfully saved
329
+ createdArrivalNoticeIds.push(createdArrivalNotice.id)
330
+ createdOrderProductIds.push(...createdOrderProducts.map(op => op.id))
331
+ })
332
+ } catch (error) {
333
+ // collect the arrivalNotice details and its error
334
+ let rawArrivalNotices: RawArrivalNotice[] = formRawArrivalNotices(arrivalNotices[i], error.message)
335
+ errorsCaught.push(...rawArrivalNotices)
336
+ }
337
+ }
338
+
339
+ if (createdArrivalNoticeIds.length && createdOrderProductIds.length) {
340
+ await getConnection().transaction(async childTx => {
341
+ // update arrivalNotices status to PENDING_RECEIVE
342
+ await childTx
343
+ .getRepository(ArrivalNotice)
344
+ .update({ id: In(createdArrivalNoticeIds) }, { status: ORDER_STATUS.PENDING_RECEIVE, updater: user })
345
+
346
+ // update orderProducts status to PENDING_RECEIVE
347
+ await childTx
348
+ .getRepository(OrderProduct)
349
+ .update({ id: In(createdOrderProductIds) }, { status: ORDER_PRODUCT_STATUS.PENDING_RECEIVE, updater: user })
350
+
351
+ finalArrivalNoticeList = await childTx.getRepository(ArrivalNotice).find({
352
+ where: { id: In(createdArrivalNoticeIds) },
353
+ relations: ['orderProducts', 'orderProducts.product', 'bizplace']
354
+ })
355
+ })
356
+ }
357
+
358
+ if (errorsCaught.length) {
359
+ // should return this error list in the grist so that user know which gan need to amend
360
+ throw new ValidationError({
361
+ ...ValidationError.ERROR_CODES.INVALID_DATA_FOUND,
362
+ detail: { data: JSON.stringify(errorsCaught) }
363
+ })
364
+ }
365
+
366
+ // return arrivalNotices with its orderProducts list
367
+ return { items: finalArrivalNoticeList, total: 0 }
368
+ }
233
369
  }
234
370
 
235
371
  export async function deleteArrivalNotice(
@@ -780,3 +916,103 @@ export async function editArrivalNoticeProducts(
780
916
  debug('gan-updated-order-products', updatedOrderProducts)
781
917
  await orderProductRepo.save(updatedOrderProducts)
782
918
  }
919
+
920
+ function extractArrivalNotices(rawArrivalNotices): Partial<ArrivalNotice[]> {
921
+ return rawArrivalNotices.reduce((arrivalNotices, raw) => {
922
+ // discard the item that has no productId or productDetailId
923
+ if (!raw.productId || !raw.productDetailId) return
924
+
925
+ const idx: number = arrivalNotices.findIndex(an => {
926
+ // consider these attributes if they are exist in "item"
927
+ const comparison = [
928
+ 'refNo2',
929
+ 'refNo3',
930
+ 'truckNo',
931
+ 'ownTransport',
932
+ 'container',
933
+ 'containerNo',
934
+ 'containerSize',
935
+ 'looseItem',
936
+ 'importCargo'
937
+ ]
938
+
939
+ let a: any = {},
940
+ b: any = {}
941
+
942
+ comparison.forEach(cc => {
943
+ if (raw[cc] !== null) {
944
+ a[cc] = raw[cc]
945
+ b[cc] = an[cc]
946
+ }
947
+ })
948
+
949
+ a = JSON.stringify(Object.fromEntries(Object.entries(a).sort()))
950
+ b = JSON.stringify(Object.fromEntries(Object.entries(b).sort()))
951
+
952
+ return an.refNo == raw.refNo && an.etaDate == raw.etaDate && a === b
953
+ })
954
+
955
+ const product: Product = Object.assign(new Product(), { id: raw.productId, sku: raw.sku })
956
+ const orderProduct: Partial<OrderProduct> = {
957
+ product,
958
+ batchId: raw.batchId,
959
+ packingType: raw.packingType,
960
+ packingSize: raw.packingSize,
961
+ packQty: raw.packQty,
962
+ uomValue: raw.uomValue,
963
+ uom: raw.uom,
964
+ totalUomValue: raw.totalUomValue,
965
+ palletQty: raw.palletQty,
966
+ unitPrice: raw.unitPrice,
967
+ manufactureYear: raw.manufactureYear
968
+ }
969
+
970
+ if (idx >= 0) {
971
+ const duplicateSkuIdx: number = arrivalNotices[idx].orderProducts.findIndex(
972
+ op =>
973
+ op.sku === raw.sku &&
974
+ op.packingType === raw.packingType &&
975
+ op.packingSize === raw.packingSize &&
976
+ op.batchId === raw.batchId
977
+ )
978
+
979
+ if (duplicateSkuIdx >= 0) arrivalNotices[idx].orderProducts[duplicateSkuIdx].packQty += raw.packQty
980
+ else arrivalNotices[idx].orderProducts.push(orderProduct)
981
+ } else {
982
+ arrivalNotices.push({
983
+ refNo: raw.refNo,
984
+ refNo2: raw.refNo2,
985
+ refNo3: raw.refNo3,
986
+ etaDate: raw.etaDate,
987
+ truckNo: raw.truckNo,
988
+ ownTransport: raw.ownTransport,
989
+ container: raw.container,
990
+ containerNo: raw.containerNo,
991
+ containerSize: raw.containerSize,
992
+ importCargo: raw.importCargo,
993
+ looseItem: raw.looseItem,
994
+ orderProducts: [orderProduct]
995
+ })
996
+ }
997
+
998
+ return arrivalNotices
999
+ }, [])
1000
+ }
1001
+
1002
+ function formRawArrivalNotices(arrivalNotice, errorMessage) {
1003
+ let rawArrivalNotices = []
1004
+ for (let i = 0, l = arrivalNotice.orderProducts.length; i < l; i++) {
1005
+ let rawArrivalNotice = {
1006
+ ...arrivalNotice,
1007
+ ...arrivalNotice.orderProducts[i],
1008
+ sku: arrivalNotice.orderProducts[i].product.sku,
1009
+ remark: errorMessage
1010
+ }
1011
+
1012
+ delete rawArrivalNotice.orderProducts
1013
+ delete rawArrivalNotice.product
1014
+
1015
+ rawArrivalNotices.push(rawArrivalNotice)
1016
+ }
1017
+ return rawArrivalNotices
1018
+ }
@@ -1,13 +1,13 @@
1
1
  import { Arg, Args, Ctx, Directive, FieldResolver, Query, Resolver, Root } from 'type-graphql'
2
- import { getRepository, In, SelectQueryBuilder } from 'typeorm'
2
+ import { EntityManager, getRepository, In, SelectQueryBuilder } from 'typeorm'
3
3
 
4
4
  import { Attachment } from '@things-factory/attachment-base'
5
- import { User } from '@things-factory/auth-base'
6
- import { Bizplace, getPermittedBizplaceIds } from '@things-factory/biz-base'
5
+ import { checkUserBelongsDomain, User } from '@things-factory/auth-base'
6
+ import { Bizplace, getCompanyBizplace, getPermittedBizplaceIds } from '@things-factory/biz-base'
7
7
  import { ProductDetail } from '@things-factory/product-base'
8
8
  import { buildQuery, Domain, ListParam } from '@things-factory/shell'
9
9
 
10
- import { ArrivalNoticeList } from '../'
10
+ import { ArrivalNoticeList, NewArrivalNotice, RawArrivalNotice } from '../'
11
11
  import { ATTACHMENT_TYPE, ORDER_STATUS } from '../../constants'
12
12
  import { OrderInventory, OrderProduct } from '../../service'
13
13
  import { ArrivalNotice } from './arrival-notice'
@@ -34,6 +34,7 @@ export class ArrivalNoticeQuery {
34
34
  relations: [
35
35
  'domain',
36
36
  'bizplace',
37
+ 'bizplace.domain',
37
38
  'releaseGood',
38
39
  'purchaseOrder',
39
40
  'purchaseOrder.bufferLocation',
@@ -117,6 +118,17 @@ export class ArrivalNoticeQuery {
117
118
  async arrivalNotices(@Ctx() context: any, @Args() params: ListParam): Promise<ArrivalNoticeList> {
118
119
  const { domain, user }: { domain: Domain; user: User } = context.state
119
120
 
121
+ if (await checkUserBelongsDomain(domain, user)) {
122
+ if (!params.filters.some(e => e.name === 'status') && !params.filters.some(e => e.name === 'name')) {
123
+ params.filters.push({
124
+ name: 'status',
125
+ operator: 'notin',
126
+ value: [ORDER_STATUS.PENDING, ORDER_STATUS.EDITING],
127
+ relation: false
128
+ })
129
+ }
130
+ }
131
+
120
132
  if (!params.filters.find((filter: any) => filter.name === 'bizplace')) {
121
133
  params.filters.push({
122
134
  name: 'bizplaceId',
@@ -287,6 +299,17 @@ export class ArrivalNoticeQuery {
287
299
  }
288
300
  }
289
301
 
302
+ @Directive('@transaction')
303
+ @Query(returns => [RawArrivalNotice])
304
+ async validateBulkArrivalNotices(
305
+ @Ctx() context: any,
306
+ @Arg('rawArrivalNotices', type => [NewArrivalNotice], { nullable: true }) rawArrivalNotices: NewArrivalNotice[],
307
+ @Arg('bizplaceId', type => String) bizplaceId: string
308
+ ): Promise<RawArrivalNotice[]> {
309
+ const tx: EntityManager = context.state.tx
310
+ return await validateBulkArrivalNoticesFunction(rawArrivalNotices, bizplaceId, tx)
311
+ }
312
+
290
313
  @FieldResolver(type => Domain)
291
314
  async domain(@Root() arrivalNotice: ArrivalNotice): Promise<Domain> {
292
315
  return await getRepository(Domain).findOne(arrivalNotice.domainId)
@@ -302,3 +325,190 @@ export class ArrivalNoticeQuery {
302
325
  return await getRepository(User).findOne(arrivalNotice.updaterId)
303
326
  }
304
327
  }
328
+
329
+ export async function validateBulkArrivalNoticesFunction(
330
+ rawArrivalNotices: NewArrivalNotice[],
331
+ bizplaceId: string,
332
+ trxMgr: EntityManager
333
+ ): Promise<RawArrivalNotice[]> {
334
+ const companyBizplace: Bizplace = await getCompanyBizplace(null, null, bizplaceId)
335
+
336
+ const json_oi = JSON.stringify(
337
+ rawArrivalNotices.map(raw => {
338
+ return {
339
+ ref_no: raw.refNo || null,
340
+ ref_no_2: raw.refNo2 || null,
341
+ ref_no_3: raw.refNo3 || null,
342
+ eta_date: raw.etaDate || null,
343
+ truck_no: raw.truckNo || null,
344
+ own_transport: !!raw.truckNo || false,
345
+ loose_item: raw.looseItem || false,
346
+ import_cargo: raw.importCargo || false,
347
+ container: (!!raw.containerNo && !!raw.containerSize) || false,
348
+ container_no: raw.containerNo || null,
349
+ container_size: raw.containerSize || null,
350
+ sku: raw.sku || null,
351
+ batch_id: raw.batchId || null,
352
+ packing_type: raw.packingType || null,
353
+ packing_size: raw.packingSize || null,
354
+ uom: raw.uom || null,
355
+ pack_qty: raw.packQty || null,
356
+ pallet_qty: raw.palletQty || null,
357
+ unit_price: raw.unitPrice || null,
358
+ manufacture_year: raw.manufactureYear || null
359
+ }
360
+ })
361
+ )
362
+
363
+ await trxMgr.query(
364
+ `
365
+ CREATE TEMP TABLE temp_order_products(
366
+ ref_no VARCHAR(150),
367
+ ref_no_2 VARCHAR(150),
368
+ ref_no_3 VARCHAR(150),
369
+ eta_date VARCHAR(24),
370
+ truck_no VARCHAR(10),
371
+ own_transport BOOLEAN,
372
+ container BOOLEAN,
373
+ container_no VARCHAR(50),
374
+ container_size VARCHAR(24),
375
+ import_cargo BOOLEAN,
376
+ loose_item BOOLEAN,
377
+ sku VARCHAR(150),
378
+ batch_id VARCHAR(100),
379
+ packing_type VARCHAR(50),
380
+ packing_size INT,
381
+ uom VARCHAR(10),
382
+ pack_qty INT,
383
+ pallet_qty INT,
384
+ unit_price FLOAT8,
385
+ manufacture_year INT4
386
+ );
387
+ `
388
+ )
389
+
390
+ await trxMgr.query(
391
+ `
392
+ INSERT INTO temp_order_products
393
+ SELECT
394
+ js.ref_no,
395
+ js.ref_no_2,
396
+ js.ref_no_3,
397
+ js.eta_date,
398
+ js.truck_no,
399
+ js.own_transport,
400
+ js.container,
401
+ js.container_no,
402
+ js.container_size,
403
+ js.import_cargo,
404
+ js.loose_item,
405
+ js.sku,
406
+ js.batch_id,
407
+ js.packing_type,
408
+ js.packing_size,
409
+ js.uom,
410
+ js.pack_qty,
411
+ js.pallet_qty,
412
+ js.unit_price,
413
+ js.manufacture_year
414
+ FROM
415
+ JSON_POPULATE_RECORDSET(NULL :: temp_order_products, $1) js;
416
+ `,
417
+ [json_oi]
418
+ )
419
+
420
+ let validatedItems = await trxMgr.query(
421
+ `
422
+ SELECT
423
+ tp.ref_no,
424
+ tp.ref_no_2,
425
+ tp.ref_no_3,
426
+ tp.eta_date,
427
+ tp.truck_no,
428
+ tp.own_transport,
429
+ tp.container,
430
+ tp.container_no,
431
+ tp.container_size,
432
+ tp.import_cargo,
433
+ tp.loose_item,
434
+ pr.id AS product_id,
435
+ pd.id AS product_detail_id,
436
+ pr.sku AS sku,
437
+ CASE WHEN pr.description NOT IN (NULL, '', '-') THEN CONCAT(pr.name, '(', pr.description, ')') ELSE pr.name END AS product_info,
438
+ tp.batch_id,
439
+ pd.packing_type,
440
+ pd.packing_size,
441
+ tp.pack_qty,
442
+ pd.uom_value,
443
+ pd.uom,
444
+ CONCAT(COALESCE(ROUND(pd.uom_value :: numeric, 2), 0) * tp.pack_qty, ' ', pd.uom) AS total_uom_value,
445
+ tp.pallet_qty,
446
+ tp.unit_price,
447
+ tp.manufacture_year,
448
+ CASE WHEN an.id NOTNULL THEN CONCAT('duplicated with ', an.name) ELSE CASE WHEN pr.id ISNULL OR pd.id ISNULL OR tp.sku ISNULL THEN 'product not found'
449
+ ELSE CASE WHEN tp.pack_qty <= 0 THEN 'pack qty must be integer'
450
+ ELSE CASE WHEN tp.batch_id ISNULL THEN 'batch no. is required'
451
+ ELSE CASE WHEN tp.ref_no ISNULL OR tp.ref_no = '' THEN 'ref no. is required'
452
+ ELSE CASE WHEN tp.eta_date ISNULL OR tp.eta_date = '' THEN 'eta date is required'
453
+ ELSE CASE WHEN tp.eta_date::date < now()::date THEN 'backdate is not allowed'
454
+ ELSE CASE WHEN tp.container_no NOTNULL AND tp.container_size ISNULL OR tp.container_no ISNULL AND tp.container_size NOTNULL THEN 'incomplete container information'
455
+ ELSE '' END END END END END END END END AS remark
456
+ FROM
457
+ temp_order_products tp
458
+ LEFT JOIN arrival_notices an ON tp.ref_no = an.ref_no
459
+ AND CASE WHEN tp.ref_no_2 NOTNULL THEN tp.ref_no_2 = an.ref_no_2 ELSE 1 = 1 END
460
+ AND CASE WHEN tp.ref_no_3 NOTNULL THEN tp.ref_no_3 = an.ref_no_3 ELSE 1 = 1 END
461
+ AND CASE WHEN tp.truck_no NOTNULL THEN tp.truck_no = an.truck_no ELSE 1 = 1 END
462
+ AND CASE WHEN tp.own_transport NOTNULL THEN tp.own_transport = an.own_transport ELSE 1 = 1 END
463
+ AND CASE WHEN tp.container NOTNULL THEN tp.container = an.container ELSE 1 = 1 END
464
+ AND CASE WHEN tp.container_no NOTNULL THEN tp.container_no = an.container_no ELSE 1 = 1 END
465
+ AND CASE WHEN tp.container_size NOTNULL THEN tp.container_size = an.container_size ELSE 1 = 1 END
466
+ AND CASE WHEN tp.loose_item NOTNULL THEN tp.loose_item = an.loose_item ELSE 1 = 1 END
467
+ AND CASE WHEN tp.import_cargo NOTNULL THEN tp.import_cargo = an.import_cargo ELSE 1 = 1 END
468
+ LEFT JOIN products pr ON pr.sku = tp.sku
469
+ LEFT JOIN product_details pd ON pr.id = pd.product_id
470
+ AND CASE WHEN tp.packing_type NOTNULL
471
+ AND tp.packing_size NOTNULL
472
+ AND tp.uom NOTNULL THEN pd.packing_type = tp.packing_type
473
+ AND pd.packing_size = tp.packing_size
474
+ AND pd.uom = tp.uom ELSE pd.is_default = TRUE END
475
+ WHERE
476
+ pr.bizplace_id = $1
477
+
478
+ `,
479
+ [companyBizplace.id]
480
+ )
481
+
482
+ await trxMgr.query('DROP TABLE temp_order_products')
483
+
484
+ return validatedItems.map(item => {
485
+ return {
486
+ refNo: item.ref_no,
487
+ refNo2: item.ref_no_2,
488
+ refNo3: item.ref_no_3,
489
+ etaDate: item.eta_date,
490
+ truckNo: item.truck_no,
491
+ ownTransport: !!item.own_transport,
492
+ container: item.container,
493
+ containerNo: item.container_no,
494
+ containerSize: item.container_size,
495
+ importCargo: !!item.import_cargo,
496
+ looseItem: !!item.loose_item,
497
+ productId: item.product_id,
498
+ productDetailId: item.product_detail_id,
499
+ sku: item.sku,
500
+ productInfo: item.product_info,
501
+ batchId: item.batch_id,
502
+ packingType: item.packing_type,
503
+ packingSize: item.packing_size,
504
+ uom: item.uom,
505
+ packQty: item.pack_qty,
506
+ uomValue: item.uom_value,
507
+ totalUomValue: item.total_uom_value,
508
+ palletQty: item.pallet_qty,
509
+ unitPrice: item.unit_price,
510
+ manufactureYear: item.manufacture_year,
511
+ remark: item.remark
512
+ }
513
+ })
514
+ }