@things-factory/operato-hub 4.3.804 → 4.3.806

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 (29) hide show
  1. package/dist-server/routers/api/restful-apis/v1/utils/params.js +2 -0
  2. package/dist-server/routers/api/restful-apis/v1/utils/params.js.map +1 -1
  3. package/dist-server/routers/api/restful-apis/v1/warehouse/add-inbound-order.js +3 -1
  4. package/dist-server/routers/api/restful-apis/v1/warehouse/add-inbound-order.js.map +1 -1
  5. package/dist-server/routers/api/restful-apis/v1/warehouse/get-goods-received-note-list.js +1 -0
  6. package/dist-server/routers/api/restful-apis/v1/warehouse/get-goods-received-note-list.js.map +1 -1
  7. package/dist-server/routers/api/restful-apis/v1/warehouse/get-inbound-order-details.js +1 -0
  8. package/dist-server/routers/api/restful-apis/v1/warehouse/get-inbound-order-details.js.map +1 -1
  9. package/dist-server/routers/api/restful-apis/v1/warehouse/get-inbound-order-list.js +1 -0
  10. package/dist-server/routers/api/restful-apis/v1/warehouse/get-inbound-order-list.js.map +1 -1
  11. package/dist-server/routers/api/restful-apis/v1/warehouse/update-arrival-notice.js +24 -5
  12. package/dist-server/routers/api/restful-apis/v1/warehouse/update-arrival-notice.js.map +1 -1
  13. package/dist-server/routers/api/restful-apis/v1/warehouse/update-gan-to-arrived.js +1 -0
  14. package/dist-server/routers/api/restful-apis/v1/warehouse/update-gan-to-arrived.js.map +1 -1
  15. package/dist-server/routers/api/restful-apis/v1/warehouse/update-release-order-details.js +11 -2
  16. package/dist-server/routers/api/restful-apis/v1/warehouse/update-release-order-details.js.map +1 -1
  17. package/dist-server/routers/xilnex-router.js +149 -37
  18. package/dist-server/routers/xilnex-router.js.map +1 -1
  19. package/openapi/v1/outbound.yaml +14 -5
  20. package/package.json +6 -6
  21. package/server/routers/api/restful-apis/v1/utils/params.ts +2 -0
  22. package/server/routers/api/restful-apis/v1/warehouse/add-inbound-order.ts +3 -0
  23. package/server/routers/api/restful-apis/v1/warehouse/get-goods-received-note-list.ts +1 -0
  24. package/server/routers/api/restful-apis/v1/warehouse/get-inbound-order-details.ts +1 -0
  25. package/server/routers/api/restful-apis/v1/warehouse/get-inbound-order-list.ts +1 -0
  26. package/server/routers/api/restful-apis/v1/warehouse/update-arrival-notice.ts +28 -5
  27. package/server/routers/api/restful-apis/v1/warehouse/update-gan-to-arrived.ts +1 -0
  28. package/server/routers/api/restful-apis/v1/warehouse/update-release-order-details.ts +10 -2
  29. package/server/routers/xilnex-router.ts +169 -40
@@ -42,7 +42,9 @@ const debug = require('debug')('things-factory:integration-accounting:xilnex-rou
42
42
  export const xilnexRouter = new Router()
43
43
 
44
44
  xilnexRouter.post('/webhook-xilnex-po', async (context, next) => {
45
- const originalContext = context
45
+ let accountWebhookOrder: any = null
46
+ let accountDomain: any = null
47
+
46
48
  try {
47
49
  await getConnection().transaction(async (tx: EntityManager) => {
48
50
  const { id, warehouseId } = context.query
@@ -52,7 +54,6 @@ xilnexRouter.post('/webhook-xilnex-po', async (context, next) => {
52
54
  logger.info(
53
55
  `xilnex-router[webhook-xilnex-po][raw-payload]: ${JSON.stringify({
54
56
  query: context.query,
55
- headers: header,
56
57
  body
57
58
  })}`
58
59
  )
@@ -82,6 +83,17 @@ xilnexRouter.post('/webhook-xilnex-po', async (context, next) => {
82
83
  context.throw(400, `${id} not found in Account`)
83
84
  }
84
85
 
86
+ accountDomain = account.domain
87
+
88
+ // Prepare webhook order record early for persistence
89
+ accountWebhookOrder = {
90
+ name: uuidv4(),
91
+ request: body,
92
+ platform: ACCOUNT_PLATFORM.XILNEX,
93
+ domain: account.domain,
94
+ status: ACCOUNT_SYNC_STATUS.FAILED
95
+ }
96
+
85
97
  const { webhookKey } = account
86
98
  const signature = header['xilnex-webhook-signature']
87
99
 
@@ -98,6 +110,7 @@ xilnexRouter.post('/webhook-xilnex-po', async (context, next) => {
98
110
  }
99
111
 
100
112
  if (!xilnex.isValidSignature(payload, webhookKey, signature)) {
113
+ logger.error(`xilnex-router[webhook-xilnex-po][signature-failed]: accountId=${id}, source=${sourceType}`)
101
114
  context.throw(400, `Signature validation failed`)
102
115
  }
103
116
 
@@ -115,16 +128,9 @@ xilnexRouter.post('/webhook-xilnex-po', async (context, next) => {
115
128
  context.throw(400, `${warehouseId} not found in warehouse domain`)
116
129
  }
117
130
 
118
- let accountWebhookOrder: any = {
119
- name: uuidv4(),
120
- request: body,
121
- platform: ACCOUNT_PLATFORM.XILNEX,
122
- domain: account.domain,
123
- status: ACCOUNT_SYNC_STATUS.FAILED
124
- }
125
-
126
131
  // Get order ID based on source type
127
132
  const orderId = sourceType === 'XilnexPortal' ? data?.purchaseOrder?.id : data?.id
133
+ logger.info(`xilnex-router[webhook-xilnex-po][processing]: orderId=${orderId}, source=${sourceType}`)
128
134
 
129
135
  if (orderId) {
130
136
  let orderInfo: any
@@ -189,8 +195,7 @@ xilnexRouter.post('/webhook-xilnex-po', async (context, next) => {
189
195
  `xilnex-router[webhook-xilnex-po]: Duplicate PO skipped, refNo ${orderInfo.orderId} already exists as ${existingPO.name}`
190
196
  )
191
197
  accountWebhookOrder.status = ACCOUNT_SYNC_STATUS.SUCCESS
192
- await tx.getRepository(AccountWebhookOrder).save(accountWebhookOrder)
193
- context.status = 200
198
+ accountWebhookOrder.response = `Duplicate PO skipped, already exists as ${existingPO.name}`
194
199
  return
195
200
  }
196
201
 
@@ -277,26 +282,64 @@ xilnexRouter.post('/webhook-xilnex-po', async (context, next) => {
277
282
  }
278
283
 
279
284
  accountWebhookOrder.status = ACCOUNT_SYNC_STATUS.SUCCESS
280
- await tx.getRepository(AccountWebhookOrder).save(accountWebhookOrder)
281
285
  }
282
286
  })
283
287
 
288
+ // Save webhook order outside transaction so it persists even if tx rolled back
289
+ if (accountWebhookOrder) {
290
+ await getRepository(AccountWebhookOrder).save(accountWebhookOrder)
291
+ }
292
+
284
293
  context.status = 200
285
294
  } catch (err) {
286
- logger.error(`xilnex-router[webhook-xilnex-po-original-context]: ${JSON.stringify(originalContext)}`)
287
- logger.error(`xilnex-router[webhook-xilnex-po]: ${err}`)
295
+ const errorMessage = err instanceof ValidationError
296
+ ? JSON.stringify({ errorCode: err.errorCode, message: err.message, detail: err.detail })
297
+ : err?.message || String(err)
298
+ logger.error(`xilnex-router[webhook-xilnex-po]: ${errorMessage}`)
299
+
300
+ // Persist failed webhook order record for troubleshooting
301
+ try {
302
+ if (accountWebhookOrder) {
303
+ accountWebhookOrder.status = ACCOUNT_SYNC_STATUS.FAILED
304
+ accountWebhookOrder.response = errorMessage
305
+ await getRepository(AccountWebhookOrder).save(accountWebhookOrder)
306
+ } else if (accountDomain) {
307
+ await getRepository(AccountWebhookOrder).save({
308
+ name: uuidv4(),
309
+ request: JSON.stringify(context.request.body),
310
+ response: errorMessage,
311
+ platform: ACCOUNT_PLATFORM.XILNEX,
312
+ domain: accountDomain,
313
+ status: ACCOUNT_SYNC_STATUS.FAILED
314
+ })
315
+ }
316
+ } catch (saveErr) {
317
+ logger.error(`xilnex-router[webhook-xilnex-po][failed-to-save-webhook-order]: ${saveErr?.message}`)
318
+ }
319
+
288
320
  context.status = 400
289
- context.throw(400, err)
321
+ context.body = { error: errorMessage }
290
322
  }
291
323
  })
292
324
 
293
325
  xilnexRouter.post('/webhook-xilnex-transfer-note', async (context, next) => {
294
- const originalContext = context
326
+ // Save webhook order outside the main transaction so it persists even on failure
327
+ let accountWebhookOrder: any = null
328
+ let accountDomain: any = null
329
+
295
330
  try {
296
331
  await getConnection().transaction(async (tx: EntityManager) => {
297
332
  const { id, warehouseId } = context.query
298
333
  const { body, header } = context.request
299
334
 
335
+ // Log raw webhook payload for debugging
336
+ logger.info(
337
+ `xilnex-router[webhook-xilnex-transfer-note][raw-payload]: ${JSON.stringify({
338
+ query: context.query,
339
+ body
340
+ })}`
341
+ )
342
+
300
343
  // Detect source type and parse data accordingly
301
344
  const sourceType = body?.Source || 'Classic'
302
345
  let data: any = {}
@@ -322,6 +365,17 @@ xilnexRouter.post('/webhook-xilnex-transfer-note', async (context, next) => {
322
365
  context.throw(400, `${id} not found in Account`)
323
366
  }
324
367
 
368
+ accountDomain = account.domain
369
+
370
+ // Prepare webhook order record early for persistence
371
+ accountWebhookOrder = {
372
+ name: uuidv4(),
373
+ request: body,
374
+ platform: ACCOUNT_PLATFORM.XILNEX,
375
+ domain: account.domain,
376
+ status: ACCOUNT_SYNC_STATUS.FAILED
377
+ }
378
+
325
379
  const { webhookKey } = account
326
380
  const signature = header['xilnex-webhook-signature']
327
381
 
@@ -338,6 +392,7 @@ xilnexRouter.post('/webhook-xilnex-transfer-note', async (context, next) => {
338
392
  }
339
393
 
340
394
  if (!xilnex.isValidSignature(payload, webhookKey, signature)) {
395
+ logger.error(`xilnex-router[webhook-xilnex-transfer-note][signature-failed]: accountId=${id}, source=${sourceType}`)
341
396
  context.throw(400, `Signature validation failed`)
342
397
  }
343
398
 
@@ -355,18 +410,11 @@ xilnexRouter.post('/webhook-xilnex-transfer-note', async (context, next) => {
355
410
  context.throw(400, `${warehouseId} not found in warehouse domain`)
356
411
  }
357
412
 
358
- let accountWebhookOrder: any = {
359
- name: uuidv4(),
360
- request: body,
361
- platform: ACCOUNT_PLATFORM.XILNEX,
362
- domain: account.domain,
363
- status: ACCOUNT_SYNC_STATUS.FAILED
364
- }
365
-
366
413
  let accountInfo: any = account?.accountInfo ? JSON.parse(account.accountInfo) : {}
367
414
 
368
415
  // Get order ID based on source type
369
416
  const orderId = sourceType === 'XilnexPortal' ? data?.transferNote?.id : data?.id
417
+ logger.info(`xilnex-router[webhook-xilnex-transfer-note][processing]: orderId=${orderId}, source=${sourceType}, accountOutlet=${accountInfo.outlet}`)
370
418
 
371
419
  if (orderId) {
372
420
  let orderInfo: any
@@ -419,6 +467,10 @@ xilnexRouter.post('/webhook-xilnex-transfer-note', async (context, next) => {
419
467
  orderItems = transferNote.orderItems
420
468
  }
421
469
 
470
+ logger.info(
471
+ `xilnex-router[webhook-xilnex-transfer-note][outlet-check]: orderId=${orderId}, from=${orderInfo.fromLocation}, to=${orderInfo.toLocation}, outlet=${accountInfo.outlet}, matchFrom=${orderInfo.fromLocation == accountInfo.outlet}, matchTo=${orderInfo.toLocation == accountInfo.outlet}`
472
+ )
473
+
422
474
  if (orderInfo.fromLocation == accountInfo.outlet || orderInfo.toLocation == accountInfo.outlet) {
423
475
  // VDC release
424
476
  if (accountInfo.outlet == orderInfo.fromLocation) {
@@ -597,6 +649,7 @@ xilnexRouter.post('/webhook-xilnex-transfer-note', async (context, next) => {
597
649
  status: 'INTRANSIT'
598
650
  }
599
651
  arrivalNotice = await tx.getRepository(ArrivalNotice).save(arrivalNotice)
652
+ logger.info(`xilnex-router[webhook-xilnex-transfer-note][arrival-notice-created]: orderId=${orderId}, ganNo=${arrivalNotice.name}, ganId=${arrivalNotice.id}`)
600
653
 
601
654
  let orderProducts: any[] = []
602
655
  await Promise.all(
@@ -647,27 +700,71 @@ xilnexRouter.post('/webhook-xilnex-transfer-note', async (context, next) => {
647
700
  )
648
701
  await tx.getRepository(OrderProduct).save(orderProducts)
649
702
  }
703
+ } else {
704
+ logger.info(`xilnex-router[webhook-xilnex-transfer-note][skipped-no-outlet-match]: orderId=${orderId}, outlet=${accountInfo.outlet}, from=${orderInfo.fromLocation}, to=${orderInfo.toLocation}`)
650
705
  }
706
+ } else {
707
+ logger.info(`xilnex-router[webhook-xilnex-transfer-note][skipped-no-order-id]: no orderId found in webhook payload`)
651
708
  }
652
709
 
653
710
  accountWebhookOrder.status = ACCOUNT_SYNC_STATUS.SUCCESS
654
- await tx.getRepository(AccountWebhookOrder).save(accountWebhookOrder)
655
711
  })
656
712
 
713
+ // Save webhook order outside transaction so it persists even if tx rolled back
714
+ if (accountWebhookOrder) {
715
+ await getRepository(AccountWebhookOrder).save(accountWebhookOrder)
716
+ }
717
+
657
718
  context.status = 200
658
719
  } catch (err) {
659
- logger.error(`xilnex-router[webhook-xilnex-transfer-note-original-context]: ${JSON.stringify(originalContext)}`)
660
- logger.error(`xilnex-router[webhook-xilnex-transfer-note]: ${err}`)
720
+ const errorMessage = err instanceof ValidationError
721
+ ? JSON.stringify({ errorCode: err.errorCode, message: err.message, detail: err.detail })
722
+ : err?.message || String(err)
723
+ logger.error(`xilnex-router[webhook-xilnex-transfer-note]: ${errorMessage}`)
724
+
725
+ // Persist failed webhook order record for troubleshooting
726
+ try {
727
+ if (accountWebhookOrder) {
728
+ accountWebhookOrder.status = ACCOUNT_SYNC_STATUS.FAILED
729
+ accountWebhookOrder.response = errorMessage
730
+ await getRepository(AccountWebhookOrder).save(accountWebhookOrder)
731
+ } else if (accountDomain) {
732
+ // Error happened before webhook order was prepared — save with minimal info
733
+ await getRepository(AccountWebhookOrder).save({
734
+ name: uuidv4(),
735
+ request: JSON.stringify(context.request.body),
736
+ response: errorMessage,
737
+ platform: ACCOUNT_PLATFORM.XILNEX,
738
+ domain: accountDomain,
739
+ status: ACCOUNT_SYNC_STATUS.FAILED
740
+ })
741
+ }
742
+ } catch (saveErr) {
743
+ logger.error(`xilnex-router[webhook-xilnex-transfer-note][failed-to-save-webhook-order]: ${saveErr?.message}`)
744
+ }
745
+
661
746
  context.status = 400
662
- context.throw(400, err)
747
+ context.body = { error: errorMessage }
663
748
  }
664
749
  })
665
750
 
666
751
  xilnexRouter.post('/webhook-xilnex-stock-request', async (context, next) => {
752
+ let accountWebhookOrder: any = null
753
+ let accountDomain: any = null
754
+
667
755
  try {
668
756
  await getConnection().transaction(async (tx: EntityManager) => {
669
757
  const { id, warehouseId } = context.query
670
758
  const { body, headers } = context.request
759
+
760
+ // Log raw webhook payload for debugging
761
+ logger.info(
762
+ `xilnex-router[webhook-xilnex-stock-request][raw-payload]: ${JSON.stringify({
763
+ query: context.query,
764
+ body
765
+ })}`
766
+ )
767
+
671
768
  const data = body?.Data ? JSON.parse(body.Data) : {}
672
769
 
673
770
  const account = await AccountingAPI.getAccounting(id)
@@ -675,6 +772,17 @@ xilnexRouter.post('/webhook-xilnex-stock-request', async (context, next) => {
675
772
  throw new Error(`${id} not found in Account`)
676
773
  }
677
774
 
775
+ accountDomain = account.domain
776
+
777
+ // Prepare webhook order record early for persistence
778
+ accountWebhookOrder = {
779
+ name: uuidv4(),
780
+ request: body,
781
+ platform: ACCOUNT_PLATFORM.XILNEX,
782
+ domain: account.domain,
783
+ status: ACCOUNT_SYNC_STATUS.FAILED
784
+ }
785
+
678
786
  const { webhookKey } = account
679
787
  const signature = headers['xilnex-webhook-signature']
680
788
 
@@ -683,6 +791,7 @@ xilnexRouter.post('/webhook-xilnex-stock-request', async (context, next) => {
683
791
  // webhook-xilnex-stock-request only supports Classic source (no Source field in body)
684
792
  // Use parsed body for Classic source
685
793
  if (!xilnex.isValidSignature(body, webhookKey, signature)) {
794
+ logger.error(`xilnex-router[webhook-xilnex-stock-request][signature-failed]: accountId=${id}`)
686
795
  context.throw(400, 'Signature validation failed')
687
796
  }
688
797
 
@@ -700,14 +809,6 @@ xilnexRouter.post('/webhook-xilnex-stock-request', async (context, next) => {
700
809
  context.throw(400, `${warehouseId} not found in warehouse domain`)
701
810
  }
702
811
 
703
- let accountWebhookOrder: any = {
704
- name: uuidv4(),
705
- request: body,
706
- platform: ACCOUNT_PLATFORM.XILNEX,
707
- domain: account.domain,
708
- status: ACCOUNT_SYNC_STATUS.FAILED
709
- }
710
-
711
812
  let accountInfo: any = account?.accountInfo ? JSON.parse(account.accountInfo) : {}
712
813
 
713
814
  if (data?.id) {
@@ -931,14 +1032,42 @@ xilnexRouter.post('/webhook-xilnex-stock-request', async (context, next) => {
931
1032
  }
932
1033
 
933
1034
  accountWebhookOrder.status = ACCOUNT_SYNC_STATUS.SUCCESS
934
- await tx.getRepository(AccountWebhookOrder).save(accountWebhookOrder)
935
1035
  })
936
1036
 
1037
+ // Save webhook order outside transaction so it persists even if tx rolled back
1038
+ if (accountWebhookOrder) {
1039
+ await getRepository(AccountWebhookOrder).save(accountWebhookOrder)
1040
+ }
1041
+
937
1042
  context.status = 200
938
1043
  } catch (err) {
939
- logger.error(`xilnex-router[webhook-xilnex-stock-request]: ${err}`)
1044
+ const errorMessage = err instanceof ValidationError
1045
+ ? JSON.stringify({ errorCode: err.errorCode, message: err.message, detail: err.detail })
1046
+ : err?.message || String(err)
1047
+ logger.error(`xilnex-router[webhook-xilnex-stock-request]: ${errorMessage}`)
1048
+
1049
+ // Persist failed webhook order record for troubleshooting
1050
+ try {
1051
+ if (accountWebhookOrder) {
1052
+ accountWebhookOrder.status = ACCOUNT_SYNC_STATUS.FAILED
1053
+ accountWebhookOrder.response = errorMessage
1054
+ await getRepository(AccountWebhookOrder).save(accountWebhookOrder)
1055
+ } else if (accountDomain) {
1056
+ await getRepository(AccountWebhookOrder).save({
1057
+ name: uuidv4(),
1058
+ request: JSON.stringify(context.request.body),
1059
+ response: errorMessage,
1060
+ platform: ACCOUNT_PLATFORM.XILNEX,
1061
+ domain: accountDomain,
1062
+ status: ACCOUNT_SYNC_STATUS.FAILED
1063
+ })
1064
+ }
1065
+ } catch (saveErr) {
1066
+ logger.error(`xilnex-router[webhook-xilnex-stock-request][failed-to-save-webhook-order]: ${saveErr?.message}`)
1067
+ }
1068
+
940
1069
  context.status = 400
941
- context.throw(400, err)
1070
+ context.body = { error: errorMessage }
942
1071
  }
943
1072
  })
944
1073