@things-factory/worksheet-base 4.3.794 → 4.3.797

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.
@@ -26,51 +26,81 @@ export async function activateBatchPicking(
26
26
  ): Promise<Worksheet> {
27
27
  try {
28
28
  const worksheetController: PickingWorksheetController = new PickingWorksheetController(tx, domain, user)
29
- const ecommerceCtrl: EcommerceController = new EcommerceController(tx, domain, user)
30
29
  const worksheet = await worksheetController.activateBatchPicking(worksheetNo)
31
30
  const worksheetDetails = worksheet.worksheetDetails
32
31
  const companyDomain: Domain = worksheet?.bizplace.company.domain
33
32
 
34
- let orderInventories: any[] = worksheetDetails.map(wsd => wsd.targetInventory)
35
-
36
- // find for any existing marketplace store connections
37
- const marketplaceStores: MarketplaceStore[] = await tx.getRepository(MarketplaceStore).find({
38
- where: { domain: companyDomain, status: 'ACTIVE', isAutoUpdateStockQty: true },
39
- relations: ['marketplaceDistributors']
33
+ // Collect release good IDs from the controller result for post-commit processing
34
+ const orderInventories: any[] = worksheetDetails.map(wsd => wsd.targetInventory)
35
+ const releaseGoodIds: string[] = orderInventories.reduce((data, orderInventory) => {
36
+ if (orderInventory?.releaseGood?.id && !data.includes(orderInventory.releaseGood.id)) {
37
+ data.push(orderInventory.releaseGood.id)
38
+ }
39
+ return data
40
+ }, [])
41
+
42
+ // Schedule all marketplace/LMD side effects to run AFTER the transaction commits.
43
+ // This prevents deadlocks caused by nested transactions competing for locks
44
+ // held by the outer @transaction decorator.
45
+ setImmediate(() => {
46
+ processPostActivationSideEffects(
47
+ domain,
48
+ user,
49
+ companyDomain,
50
+ worksheetNo,
51
+ worksheetDetails,
52
+ releaseGoodIds
53
+ ).catch(error => {
54
+ logger.error(`activate-batch-picking[postActivation]: ${worksheetNo}: ${error}`)
55
+ })
40
56
  })
41
57
 
42
- const updateMarketplaceProductVariationStock = async (
43
- worksheetDetails,
44
- domain,
45
- user,
46
- marketplaceStores,
47
- companyDomain
48
- ) => {
58
+ return worksheet
59
+ } catch (error) {
60
+ logger.error(`activate-batch-picking[activateBatchPicking]: ${worksheetNo + ':' + error}`)
61
+ throw new Error('Something went wrong. Please contact support.')
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Handles marketplace stock sync, MMS order packages, and LMD parcel creation.
67
+ * Runs outside the main activation transaction to prevent deadlocks.
68
+ */
69
+ async function processPostActivationSideEffects(
70
+ domain: Domain,
71
+ user: User,
72
+ companyDomain: Domain,
73
+ worksheetNo: string,
74
+ worksheetDetails: any[],
75
+ releaseGoodIds: string[]
76
+ ) {
77
+ try {
78
+ // Find marketplace store connections
79
+ const marketplaceStores: MarketplaceStore[] = await getConnection()
80
+ .getRepository(MarketplaceStore)
81
+ .find({
82
+ where: { domain: companyDomain, status: 'ACTIVE', isAutoUpdateStockQty: true },
83
+ relations: ['marketplaceDistributors']
84
+ })
85
+
86
+ // Update marketplace product variation stock
87
+ if (marketplaceStores?.length && marketplaceStores.some(store => store.isAutoUpdateStockQty)) {
49
88
  try {
50
89
  await getConnection().transaction(async (tx2: EntityManager) => {
51
- let orderInventories: any[] = worksheetDetails.map(wsd => wsd.targetInventory)
90
+ const orderInventories: any[] = worksheetDetails.map(wsd => wsd.targetInventory)
52
91
  const ecommerceCtrl: EcommerceController = new EcommerceController(tx2, domain, user)
53
92
  await ecommerceCtrl.updateProductVariationStock(marketplaceStores, orderInventories, companyDomain)
54
93
  })
55
94
  } catch (error) {
56
- logger.error(`activate-batch-picking[activateBatchPicking]: ${worksheetNo + ':' + error}`)
95
+ logger.error(`activate-batch-picking[marketplaceStockUpdate]: ${worksheetNo}: ${error}`)
57
96
  }
58
97
  }
59
98
 
60
- if (marketplaceStores?.length && marketplaceStores.some(store => store.isAutoUpdateStockQty)) {
61
- updateMarketplaceProductVariationStock(worksheetDetails, domain, user, marketplaceStores, companyDomain)
62
- }
99
+ if (!releaseGoodIds.length) return
63
100
 
64
- try {
65
- let releaseGoods: ReleaseGood[] = []
66
- let releaseGoodIds: ReleaseGood[] = orderInventories.reduce((data, orderInventory) => {
67
- if (!data.find(x => x.id == orderInventory.releaseGood.id)) {
68
- data.push(orderInventory.releaseGood.id)
69
- }
70
- return data
71
- }, [])
72
-
73
- releaseGoods = await tx.getRepository(ReleaseGood).find({
101
+ // Load release goods and process marketplace order packages
102
+ await getConnection().transaction(async (tx: EntityManager) => {
103
+ const releaseGoods: ReleaseGood[] = await tx.getRepository(ReleaseGood).find({
74
104
  where: { id: In(releaseGoodIds) },
75
105
  relations: [
76
106
  'domain',
@@ -88,48 +118,43 @@ export async function activateBatchPicking(
88
118
  ]
89
119
  })
90
120
 
91
- if (releaseGoods?.length) {
92
- for (const releaseGood of releaseGoods) {
93
- const orderSource = releaseGood?.source
94
- const marketplaceOrder: MarketplaceOrder = await tx.getRepository(MarketplaceOrder).findOne({
95
- where: { orderNo: releaseGood.refNo, domain: companyDomain },
96
- relations: ['marketplaceStore']
97
- })
98
-
99
- switch (orderSource) {
100
- case ApplicationType.MMS:
101
- if (marketplaceStores?.length) {
102
- if (marketplaceOrder) {
103
- const marketplaceStore: MarketplaceStore = marketplaceOrder.marketplaceStore
104
- await ecommerceCtrl.createOrderPackage(
105
- tx,
106
- marketplaceOrder,
107
- companyDomain,
108
- marketplaceStore,
109
- releaseGood
110
- )
111
- }
112
- }
113
- break
114
- default:
115
- break
121
+ if (!releaseGoods?.length) return
122
+
123
+ // Batch load marketplace orders for MMS release goods (avoids N+1 queries)
124
+ const mmsReleaseGoods = releaseGoods.filter(rg => rg?.source === ApplicationType.MMS)
125
+
126
+ if (mmsReleaseGoods.length && marketplaceStores?.length) {
127
+ const refNos = mmsReleaseGoods.map(rg => rg.refNo).filter(Boolean)
128
+ const marketplaceOrders: MarketplaceOrder[] = refNos.length
129
+ ? await tx.getRepository(MarketplaceOrder).find({
130
+ where: { orderNo: In(refNos), domain: companyDomain },
131
+ relations: ['marketplaceStore']
132
+ })
133
+ : []
134
+
135
+ const ecommerceCtrl: EcommerceController = new EcommerceController(tx, domain, user)
136
+ for (const releaseGood of mmsReleaseGoods) {
137
+ try {
138
+ const marketplaceOrder = marketplaceOrders.find(mo => mo.orderNo === releaseGood.refNo)
139
+ if (marketplaceOrder) {
140
+ const marketplaceStore: MarketplaceStore = marketplaceOrder.marketplaceStore
141
+ await ecommerceCtrl.createOrderPackage(tx, marketplaceOrder, companyDomain, marketplaceStore, releaseGood)
142
+ }
143
+ } catch (error) {
144
+ logger.error(`activate-batch-picking[mmsOrderPackage]: ${worksheetNo}: ${error}`)
116
145
  }
117
146
  }
118
- // Process LMD orders
119
- processLmdOrders(releaseGoods, domain, companyDomain, user)
120
147
  }
121
- } catch (e) {
122
- logger.error(e)
123
- }
124
148
 
125
- return worksheet
149
+ // Process LMD orders
150
+ processLmdOrders(releaseGoods, domain, companyDomain, user)
151
+ })
126
152
  } catch (error) {
127
- logger.error(`activate-batch-picking[activateBatchPicking]: ${worksheetNo + ':' + error}`)
128
- throw new Error('Something went wrong. Please contact support.')
153
+ logger.error(`activate-batch-picking[postActivation]: ${worksheetNo}: ${error}`)
129
154
  }
130
155
  }
131
156
 
132
- //triger create parcel for lmd orders
157
+ // Trigger create parcel for LMD orders
133
158
  async function processLmdOrders(releaseGoods: ReleaseGood[], domain: Domain, companyDomain: Domain, user: User) {
134
159
  // Filter only those that need processing
135
160
  const validReleaseGoods = releaseGoods.filter(
@@ -106,6 +106,48 @@ export async function pickingWorksheet(domain: Domain, orderNo: String, location
106
106
  .andWhere('"T_INV"."status" != :t_invstatus', { t_invstatus: ORDER_INVENTORY_STATUS.CANCELLED })
107
107
  .getMany()
108
108
 
109
+ const inventoryIds = worksheetDetails
110
+ .map((wd: WorksheetDetail) => wd.targetInventory?.inventory?.id)
111
+ .filter((id): id is string => id != null)
112
+ const inventoryIdsWithMissing = new Set<string>()
113
+ if (inventoryIds.length > 0) {
114
+ const rows = await tx
115
+ .getRepository(InventoryChange)
116
+ .createQueryBuilder('ic')
117
+ .select('DISTINCT ic.inventory_id', 'inventoryId')
118
+ .where('ic.inventory_id IN (:...ids)', { ids: inventoryIds })
119
+ .andWhere('ic.status = :status', { status: 'PENDING' })
120
+ .andWhere('ic.transaction_type = :transactionType', { transactionType: 'MISSING' })
121
+ .getRawMany()
122
+ rows.forEach((r: { inventoryId: string }) => inventoryIdsWithMissing.add(r.inventoryId))
123
+ }
124
+
125
+ const worksheetDetailInfos = worksheetDetails.map((pickingWSD: WorksheetDetail) => {
126
+ const targetInventory: OrderInventory = pickingWSD.targetInventory
127
+ const inventory: Inventory = targetInventory.inventory
128
+ return {
129
+ name: pickingWSD.name,
130
+ palletId: inventory?.palletId,
131
+ cartonId: inventory?.cartonId,
132
+ batchId: inventory?.batchId,
133
+ batchIdRef: inventory?.batchIdRef,
134
+ product: inventory?.product,
135
+ qty: inventory?.qty,
136
+ binLocation: targetInventory?.binLocation || '',
137
+ releaseQty: targetInventory.releaseQty,
138
+ pickedQty: targetInventory.pickedQty,
139
+ status: pickingWSD.status,
140
+ description: pickingWSD.description,
141
+ targetName: targetInventory.name,
142
+ packingType: inventory?.packingType,
143
+ packingSize: inventory?.packingSize,
144
+ expirationDate: inventory?.expirationDate,
145
+ location: inventory?.location,
146
+ relatedOrderInv: targetInventory,
147
+ hasMissingInventoryChanges: inventory?.id ? inventoryIdsWithMissing.has(inventory.id) : false
148
+ }
149
+ })
150
+
109
151
  return {
110
152
  worksheetInfo: {
111
153
  worksheet,
@@ -123,40 +165,7 @@ export async function pickingWorksheet(domain: Domain, orderNo: String, location
123
165
  customerCompanyDomainId: releaseGood.bizplace?.company?.domain?.id,
124
166
  releaseGood
125
167
  },
126
- worksheetDetailInfos: worksheetDetails.map(async (pickingWSD: WorksheetDetail) => {
127
- const targetInventory: OrderInventory = pickingWSD.targetInventory
128
- const inventory: Inventory = targetInventory.inventory
129
-
130
- const inventoryChangesCount: number = await tx.getRepository(InventoryChange).count({
131
- where: {
132
- inventory: inventory.id,
133
- status: 'PENDING',
134
- transactionType: 'MISSING'
135
- }
136
- })
137
-
138
- return {
139
- name: pickingWSD.name,
140
- palletId: inventory?.palletId,
141
- cartonId: inventory?.cartonId,
142
- batchId: inventory?.batchId,
143
- batchIdRef: inventory?.batchIdRef,
144
- product: inventory?.product,
145
- qty: inventory?.qty,
146
- binLocation: targetInventory?.binLocation || '',
147
- releaseQty: targetInventory.releaseQty,
148
- pickedQty: targetInventory.pickedQty,
149
- status: pickingWSD.status,
150
- description: pickingWSD.description,
151
- targetName: targetInventory.name,
152
- packingType: inventory?.packingType,
153
- packingSize: inventory?.packingSize,
154
- expirationDate: inventory?.expirationDate,
155
- location: inventory?.location,
156
- relatedOrderInv: targetInventory,
157
- hasMissingInventoryChanges: inventoryChangesCount > 0 ? true : false
158
- }
159
- })
168
+ worksheetDetailInfos
160
169
  }
161
170
  }
162
171
  }