@things-factory/worksheet-base 4.3.795 → 4.3.798

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 (37) hide show
  1. package/dist-server/controllers/outbound/picking-worksheet-controller.js +32 -35
  2. package/dist-server/controllers/outbound/picking-worksheet-controller.js.map +1 -1
  3. package/dist-server/controllers/render-ro-do.js +2 -0
  4. package/dist-server/controllers/render-ro-do.js.map +1 -1
  5. package/dist-server/graphql/resolvers/worksheet/batch-picking-worksheet.js +48 -3
  6. package/dist-server/graphql/resolvers/worksheet/batch-picking-worksheet.js.map +1 -1
  7. package/dist-server/graphql/resolvers/worksheet/delivery-order-by-worksheet.js +2 -1
  8. package/dist-server/graphql/resolvers/worksheet/delivery-order-by-worksheet.js.map +1 -1
  9. package/dist-server/graphql/resolvers/worksheet/picking/activate-batch-picking.js +71 -47
  10. package/dist-server/graphql/resolvers/worksheet/picking/activate-batch-picking.js.map +1 -1
  11. package/dist-server/graphql/resolvers/worksheet/picking-worksheet.js +50 -33
  12. package/dist-server/graphql/resolvers/worksheet/picking-worksheet.js.map +1 -1
  13. package/dist-server/graphql/resolvers/worksheet/sorting-worksheet.js +12 -0
  14. package/dist-server/graphql/resolvers/worksheet/sorting-worksheet.js.map +1 -1
  15. package/dist-server/graphql/resolvers/worksheet/worksheets.js +9 -1
  16. package/dist-server/graphql/resolvers/worksheet/worksheets.js.map +1 -1
  17. package/dist-server/graphql/types/worksheet/contact-point-info.js +1 -0
  18. package/dist-server/graphql/types/worksheet/contact-point-info.js.map +1 -1
  19. package/dist-server/graphql/types/worksheet/worksheet-detail-info.js +1 -0
  20. package/dist-server/graphql/types/worksheet/worksheet-detail-info.js.map +1 -1
  21. package/dist-server/graphql/types/worksheet/worksheet-info.js +1 -0
  22. package/dist-server/graphql/types/worksheet/worksheet-info.js.map +1 -1
  23. package/dist-server/utils/worksheet-util.js +40 -1
  24. package/dist-server/utils/worksheet-util.js.map +1 -1
  25. package/package.json +13 -13
  26. package/server/controllers/outbound/picking-worksheet-controller.ts +35 -42
  27. package/server/controllers/render-ro-do.ts +2 -0
  28. package/server/graphql/resolvers/worksheet/batch-picking-worksheet.ts +52 -3
  29. package/server/graphql/resolvers/worksheet/delivery-order-by-worksheet.ts +2 -1
  30. package/server/graphql/resolvers/worksheet/picking/activate-batch-picking.ts +88 -63
  31. package/server/graphql/resolvers/worksheet/picking-worksheet.ts +54 -36
  32. package/server/graphql/resolvers/worksheet/sorting-worksheet.ts +18 -2
  33. package/server/graphql/resolvers/worksheet/worksheets.ts +13 -1
  34. package/server/graphql/types/worksheet/contact-point-info.ts +1 -0
  35. package/server/graphql/types/worksheet/worksheet-detail-info.ts +1 -0
  36. package/server/graphql/types/worksheet/worksheet-info.ts +1 -0
  37. package/server/utils/worksheet-util.ts +51 -0
@@ -125,7 +125,8 @@ export const deliveryOrderByWorksheetResolver = {
125
125
  fax: releaseOrderDeliverTo.fax || '',
126
126
  phone: releaseOrderDeliverTo.phone || '',
127
127
  contactName: releaseOrderDeliverTo.name || '',
128
- businessRestDay: releaseOrderDeliverTo.businessRestDay || ''
128
+ businessRestDay: releaseOrderDeliverTo.businessRestDay || '',
129
+ customerCode: releaseOrderDeliverTo.customerCode || ''
129
130
  }
130
131
  : null
131
132
  }
@@ -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(
@@ -7,12 +7,13 @@ import {
7
7
  Replenishment,
8
8
  ShippingOrder
9
9
  } from '@things-factory/sales-base'
10
+ import { ProductDetail } from '@things-factory/product-base'
10
11
  import { Domain } from '@things-factory/shell'
11
12
  import { Inventory, InventoryChange, Location } from '@things-factory/warehouse-base'
12
13
 
13
14
  import { WORKSHEET_STATUS, WORKSHEET_TYPE } from '../../../constants'
14
15
  import { WorksheetDetail } from '../../../entities'
15
- import { fetchExecutingWorksheet } from '../../../utils'
16
+ import { computeConversionString, fetchExecutingWorksheet } from '../../../utils'
16
17
 
17
18
  export const pickingWorksheetResolver = {
18
19
  async pickingWorksheet(_: any, { orderNo, locationSortingRules }, context: any) {
@@ -86,6 +87,8 @@ export async function pickingWorksheet(domain: Domain, orderNo: String, location
86
87
  .leftJoinAndSelect('T_INV.inventory', 'INV')
87
88
  .leftJoinAndSelect('T_INV.product', 'PROD')
88
89
  .leftJoinAndSelect('T_INV.productDetail', 'PROD_DET')
90
+ .leftJoinAndSelect('PROD_DET.childProductDetail', 'CHILD_PROD_DET')
91
+ .leftJoinAndSelect('PROD_DET.parentProductDetails', 'PARENT_PROD_DETS')
89
92
  .leftJoinAndSelect('INV.location', 'LOC')
90
93
 
91
94
  if (locationSortingRules?.length > 0) {
@@ -106,6 +109,50 @@ export async function pickingWorksheet(domain: Domain, orderNo: String, location
106
109
  .andWhere('"T_INV"."status" != :t_invstatus', { t_invstatus: ORDER_INVENTORY_STATUS.CANCELLED })
107
110
  .getMany()
108
111
 
112
+ const inventoryIds = worksheetDetails
113
+ .map((wd: WorksheetDetail) => wd.targetInventory?.inventory?.id)
114
+ .filter((id): id is string => id != null)
115
+ const inventoryIdsWithMissing = new Set<string>()
116
+ if (inventoryIds.length > 0) {
117
+ const rows = await tx
118
+ .getRepository(InventoryChange)
119
+ .createQueryBuilder('ic')
120
+ .select('DISTINCT ic.inventory_id', 'inventoryId')
121
+ .where('ic.inventory_id IN (:...ids)', { ids: inventoryIds })
122
+ .andWhere('ic.status = :status', { status: 'PENDING' })
123
+ .andWhere('ic.transaction_type = :transactionType', { transactionType: 'MISSING' })
124
+ .getRawMany()
125
+ rows.forEach((r: { inventoryId: string }) => inventoryIdsWithMissing.add(r.inventoryId))
126
+ }
127
+
128
+ const worksheetDetailInfos = worksheetDetails.map((pickingWSD: WorksheetDetail) => {
129
+ const targetInventory: OrderInventory = pickingWSD.targetInventory
130
+ const inventory: Inventory = targetInventory.inventory
131
+ const productDetail: ProductDetail = targetInventory.productDetail as ProductDetail
132
+ return {
133
+ name: pickingWSD.name,
134
+ palletId: inventory?.palletId,
135
+ cartonId: inventory?.cartonId,
136
+ batchId: inventory?.batchId,
137
+ batchIdRef: inventory?.batchIdRef,
138
+ product: inventory?.product,
139
+ qty: inventory?.qty,
140
+ binLocation: targetInventory?.binLocation || '',
141
+ releaseQty: targetInventory.releaseQty,
142
+ pickedQty: targetInventory.pickedQty,
143
+ status: pickingWSD.status,
144
+ description: pickingWSD.description,
145
+ targetName: targetInventory.name,
146
+ packingType: inventory?.packingType,
147
+ packingSize: inventory?.packingSize,
148
+ expirationDate: inventory?.expirationDate,
149
+ location: inventory?.location,
150
+ relatedOrderInv: targetInventory,
151
+ hasMissingInventoryChanges: inventory?.id ? inventoryIdsWithMissing.has(inventory.id) : false,
152
+ conversion: computeConversionString(targetInventory.releaseQty, productDetail)
153
+ }
154
+ })
155
+
109
156
  return {
110
157
  worksheetInfo: {
111
158
  worksheet,
@@ -123,40 +170,7 @@ export async function pickingWorksheet(domain: Domain, orderNo: String, location
123
170
  customerCompanyDomainId: releaseGood.bizplace?.company?.domain?.id,
124
171
  releaseGood
125
172
  },
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
- })
173
+ worksheetDetailInfos
160
174
  }
161
175
  }
162
176
  }
@@ -183,6 +197,8 @@ async function replenishmentWorksheet(orderNo: String, tx, domain, locationSorti
183
197
  .leftJoinAndSelect('T_INV.inventory', 'INV')
184
198
  .leftJoinAndSelect('T_INV.product', 'PROD')
185
199
  .leftJoinAndSelect('T_INV.productDetail', 'PROD_DET')
200
+ .leftJoinAndSelect('PROD_DET.childProductDetail', 'CHILD_PROD_DET')
201
+ .leftJoinAndSelect('PROD_DET.parentProductDetails', 'PARENT_PROD_DETS')
186
202
  .leftJoinAndSelect('INV.location', 'LOC')
187
203
 
188
204
  if (locationSortingRules?.length > 0) {
@@ -210,6 +226,7 @@ async function replenishmentWorksheet(orderNo: String, tx, domain, locationSorti
210
226
  worksheetDetailInfos: worksheetDetails.map(async (pickingWSD: WorksheetDetail) => {
211
227
  const targetInventory: OrderInventory = pickingWSD.targetInventory
212
228
  const inventory: Inventory = targetInventory.inventory
229
+ const productDetail: ProductDetail = targetInventory.productDetail as ProductDetail
213
230
 
214
231
  return {
215
232
  name: pickingWSD.name,
@@ -229,7 +246,8 @@ async function replenishmentWorksheet(orderNo: String, tx, domain, locationSorti
229
246
  packingSize: inventory?.packingSize,
230
247
  expirationDate: inventory?.expirationDate,
231
248
  location: inventory?.location,
232
- relatedOrderInv: targetInventory
249
+ relatedOrderInv: targetInventory,
250
+ conversion: computeConversionString(targetInventory.releaseQty, productDetail)
233
251
  }
234
252
  })
235
253
  }
@@ -3,7 +3,7 @@ import {
3
3
  SelectQueryBuilder
4
4
  } from 'typeorm'
5
5
 
6
- import { ProductBarcode } from '@things-factory/product-base'
6
+ import { ProductBarcode, ProductDetail } from '@things-factory/product-base'
7
7
  import {
8
8
  ORDER_INVENTORY_STATUS,
9
9
  ORDER_STATUS,
@@ -13,7 +13,7 @@ import {
13
13
  import { Domain } from '@things-factory/shell'
14
14
 
15
15
  import { WORKSHEET_TYPE } from '../../../constants'
16
- import { fetchExecutingBatchWorksheet } from '../../../utils'
16
+ import { computeConversionString, fetchExecutingBatchWorksheet } from '../../../utils'
17
17
 
18
18
  export const sortingWorksheetResolver = {
19
19
  async sortingWorksheet(_: any, { releaseGoodNo, taskNo }, context: any) {
@@ -66,6 +66,19 @@ export const sortingWorksheetResolver = {
66
66
  .orderBy('PROD.sku', 'ASC')
67
67
 
68
68
  const items: any[] = await qb.getRawMany()
69
+
70
+ // Collect unique productDetail IDs to fetch with relationships
71
+ const productDetailIds = [...new Set(items.map(item => item.productDetailId).filter(Boolean))]
72
+ const productDetailsMap = new Map<string, ProductDetail>()
73
+
74
+ if (productDetailIds.length > 0) {
75
+ const productDetails: ProductDetail[] = await getRepository(ProductDetail).find({
76
+ where: productDetailIds.map(id => ({ id })),
77
+ relations: ['childProductDetail', 'parentProductDetails']
78
+ })
79
+ productDetails.forEach(pd => productDetailsMap.set(pd.id, pd))
80
+ }
81
+
69
82
  let results = {
70
83
  worksheetInfo: {
71
84
  bizplaceName: releaseGood.bizplace.name,
@@ -79,6 +92,8 @@ export const sortingWorksheetResolver = {
79
92
  },
80
93
  worksheetDetailInfos: await Promise.all(
81
94
  items.map(async (item: any) => {
95
+ const productDetail = productDetailsMap.get(item.productDetailId)
96
+
82
97
  return {
83
98
  batchId: item?.batchId,
84
99
  productId: item?.productId,
@@ -96,6 +111,7 @@ export const sortingWorksheetResolver = {
96
111
  packingType: item?.packingType,
97
112
  packingSize: item?.packingSize,
98
113
  binRemarks: item?.binRemarks,
114
+ conversion: computeConversionString(item.releaseQty, productDetail),
99
115
  releaseGood
100
116
  }
101
117
  })
@@ -118,6 +118,7 @@ export const worksheetsResolver = {
118
118
  const releaseGoodPriorityDeliveryParam = params.filters.find(
119
119
  param => param.name === 'releaseGoodPriorityDelivery'
120
120
  )
121
+ const releaseGoodRtmParam = params.filters.find(param => param.name === 'releaseGoodRtm')
121
122
 
122
123
  if (
123
124
  releaseGoodParam ||
@@ -125,7 +126,8 @@ export const worksheetsResolver = {
125
126
  releaseGoodCrossDockingParam ||
126
127
  releaseGoodCourierOptionParam ||
127
128
  releaseGoodPackingOptionParam ||
128
- releaseGoodPriorityDeliveryParam
129
+ releaseGoodPriorityDeliveryParam ||
130
+ releaseGoodRtmParam
129
131
  ) {
130
132
  // let arrFilters = []
131
133
  if (releaseGoodParam) {
@@ -176,6 +178,13 @@ export const worksheetsResolver = {
176
178
  )
177
179
  }
178
180
 
181
+ if (releaseGoodRtmParam) {
182
+ params.filters.splice(
183
+ params.filters.findIndex(item => item.name == 'releaseGoodRtm'),
184
+ 1
185
+ )
186
+ }
187
+
179
188
  // const foundReleaseGoods: ReleaseGood[] = await getRepository(ReleaseGood).find({
180
189
  // ...convertListParams({ filters: arrFilters }, domain.id)
181
190
  // })
@@ -555,6 +564,9 @@ export const worksheetsResolver = {
555
564
  priorityDelivery: releaseGoodPriorityDeliveryParam.value
556
565
  })
557
566
  }
567
+ if (releaseGoodRtmParam) {
568
+ qb.andWhere(`releaseGood.rtm = :rtm`, { rtm: releaseGoodRtmParam.value })
569
+ }
558
570
 
559
571
  ////Add sorting conditions
560
572
  const arrChildSortData = ['bizplace', 'arrivalNotice', 'releaseGood', 'returnOrder', 'inventoryCheck']
@@ -9,5 +9,6 @@ export const ContactPointInfo = gql`
9
9
  phone: String
10
10
  contactName: String
11
11
  businessRestDay: String
12
+ customerCode: String
12
13
  }
13
14
  `
@@ -75,6 +75,7 @@ export const WorksheetDetailInfo = gql`
75
75
  toteName: String
76
76
  completed: Boolean
77
77
  packingTypeSize: String
78
+ conversion: String
78
79
  reusablePalletName: String
79
80
  expirationPeriod: Int
80
81
  productDetail: ProductDetail
@@ -34,6 +34,7 @@ export const WorksheetInfo = gql`
34
34
  refNo: String
35
35
  refNo2: String
36
36
  refNo3: String
37
+ rtm: String
37
38
  looseItem: String
38
39
  orderVas: [WorksheetDetail]
39
40
  orderPackage: OrderPackage
@@ -20,6 +20,13 @@ import { Domain } from '@things-factory/shell'
20
20
  import { WORKSHEET_STATUS } from '../constants'
21
21
  import { Worksheet } from '../entities'
22
22
 
23
+ export interface PackingConversionDetail {
24
+ packingType?: string
25
+ packingSize?: number
26
+ childProductDetail?: PackingConversionDetail | null
27
+ parentProductDetails?: PackingConversionDetail[] | null
28
+ }
29
+
23
30
  export async function fetchExecutingWorksheet(
24
31
  domain: Domain,
25
32
  bizplace: Bizplace,
@@ -101,3 +108,47 @@ export async function fetchExecutingBatchWorksheet(
101
108
  throw new Error(`Current worksheet status (${worksheet.status}) is not proper to execute it.`)
102
109
  }
103
110
  }
111
+
112
+ export function computeConversionString(releaseQty: number, productDetail: PackingConversionDetail | null): string {
113
+ if (!productDetail || !releaseQty || releaseQty <= 0) {
114
+ return ''
115
+ }
116
+
117
+ const currentPackingType = productDetail.packingType
118
+ const childProductDetail = productDetail.childProductDetail
119
+ const parentProductDetails = productDetail.parentProductDetails
120
+
121
+ // Priority 1: If has child → convert to child terms (downward)
122
+ if (childProductDetail) {
123
+ const packingSize = productDetail.packingSize || 1
124
+ const childPackingType = childProductDetail.packingType
125
+
126
+ if (packingSize > 1) {
127
+ const totalChildUnits = releaseQty * packingSize
128
+ return `${totalChildUnits} ${childPackingType}`
129
+ }
130
+ }
131
+
132
+ // Priority 2: If has parent (no child) → convert to parent terms (upward)
133
+ // If multiple parents, select the one with lowest packingSize
134
+ if (parentProductDetails && parentProductDetails.length > 0) {
135
+ const parent = parentProductDetails.reduce((min, p) => ((p.packingSize || 1) < (min.packingSize || 1) ? p : min))
136
+ const parentPackingSize = parent.packingSize || 1
137
+ const parentPackingType = parent.packingType
138
+
139
+ if (parentPackingSize > 1) {
140
+ const fullParents = Math.floor(releaseQty / parentPackingSize)
141
+ const remainder = releaseQty % parentPackingSize
142
+
143
+ if (fullParents > 0 && remainder > 0) {
144
+ return `${fullParents} ${parentPackingType}, ${remainder} ${currentPackingType}`
145
+ } else if (fullParents > 0) {
146
+ return `${fullParents} ${parentPackingType}`
147
+ } else if (remainder > 0) {
148
+ return `${remainder} ${currentPackingType}`
149
+ }
150
+ }
151
+ }
152
+
153
+ return ''
154
+ }