@things-factory/worksheet-base 4.3.753 → 4.3.756

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 (31) hide show
  1. package/dist-server/controllers/outbound/picking-worksheet-controller.js +97 -27
  2. package/dist-server/controllers/outbound/picking-worksheet-controller.js.map +1 -1
  3. package/dist-server/controllers/outbound/sorting-worksheet-controller.js +117 -24
  4. package/dist-server/controllers/outbound/sorting-worksheet-controller.js.map +1 -1
  5. package/dist-server/controllers/render-ro-do.js +324 -96
  6. package/dist-server/controllers/render-ro-do.js.map +1 -1
  7. package/dist-server/graphql/resolvers/worksheet/find-sorting-release-orders-by-task-no.js +244 -123
  8. package/dist-server/graphql/resolvers/worksheet/find-sorting-release-orders-by-task-no.js.map +1 -1
  9. package/dist-server/graphql/resolvers/worksheet/index.js +1 -1
  10. package/dist-server/graphql/resolvers/worksheet/index.js.map +1 -1
  11. package/dist-server/graphql/resolvers/worksheet/loading/index.js +3 -1
  12. package/dist-server/graphql/resolvers/worksheet/loading/index.js.map +1 -1
  13. package/dist-server/graphql/resolvers/worksheet/loading/validate-qc-seals.js +79 -0
  14. package/dist-server/graphql/resolvers/worksheet/loading/validate-qc-seals.js.map +1 -0
  15. package/dist-server/graphql/types/worksheet/index.js +5 -1
  16. package/dist-server/graphql/types/worksheet/index.js.map +1 -1
  17. package/dist-server/graphql/types/worksheet/validate-qc-seals-result.js +12 -0
  18. package/dist-server/graphql/types/worksheet/validate-qc-seals-result.js.map +1 -0
  19. package/dist-server/graphql/types/worksheet/worksheet-info.js +1 -0
  20. package/dist-server/graphql/types/worksheet/worksheet-info.js.map +1 -1
  21. package/package.json +21 -21
  22. package/server/controllers/outbound/picking-worksheet-controller.ts +105 -31
  23. package/server/controllers/outbound/sorting-worksheet-controller.ts +137 -25
  24. package/server/controllers/render-ro-do.ts +378 -136
  25. package/server/graphql/resolvers/worksheet/find-sorting-release-orders-by-task-no.ts +305 -128
  26. package/server/graphql/resolvers/worksheet/index.ts +3 -2
  27. package/server/graphql/resolvers/worksheet/loading/index.ts +5 -0
  28. package/server/graphql/resolvers/worksheet/loading/validate-qc-seals.ts +91 -0
  29. package/server/graphql/types/worksheet/index.ts +5 -1
  30. package/server/graphql/types/worksheet/validate-qc-seals-result.ts +9 -0
  31. package/server/graphql/types/worksheet/worksheet-info.ts +1 -0
@@ -1,46 +1,27 @@
1
1
  import FormData from 'form-data'
2
2
  import fetch from 'node-fetch'
3
- import {
4
- EntityManager,
5
- getManager,
6
- getRepository
7
- } from 'typeorm'
3
+ import { EntityManager, getManager, getRepository, In } from 'typeorm'
8
4
 
9
- import {
10
- Attachment,
11
- STORAGE
12
- } from '@things-factory/attachment-base'
13
- import {
14
- Partner,
15
- User
16
- } from '@things-factory/auth-base'
17
- import {
18
- Bizplace,
19
- ContactPoint
20
- } from '@things-factory/biz-base'
5
+ import { Attachment, STORAGE } from '@things-factory/attachment-base'
6
+ import { Partner, User } from '@things-factory/auth-base'
7
+ import { Bizplace, ContactPoint } from '@things-factory/biz-base'
21
8
  import { config } from '@things-factory/env'
22
9
  import { ProductDetail } from '@things-factory/product-base'
23
10
  import {
24
11
  DeliveryOrder,
25
12
  ORDER_STATUS,
26
13
  OrderInventory,
27
- ReleaseGood, OrderProduct
14
+ OrderTote,
15
+ OrderToteItem,
16
+ OrderToteSeal,
17
+ ReleaseGood,
18
+ OrderProduct
28
19
  } from '@things-factory/sales-base'
29
20
  import { Domain } from '@things-factory/shell'
30
- import {
31
- Inventory,
32
- Pallet
33
- } from '@things-factory/warehouse-base'
21
+ import { Inventory, Pallet } from '@things-factory/warehouse-base'
34
22
 
35
- import {
36
- TEMPLATE_TYPE,
37
- WORKSHEET_STATUS,
38
- WORKSHEET_TYPE
39
- } from '../constants'
40
- import {
41
- Worksheet,
42
- WorksheetDetail
43
- } from '../entities'
23
+ import { TEMPLATE_TYPE, WORKSHEET_STATUS, WORKSHEET_TYPE } from '../constants'
24
+ import { Worksheet, WorksheetDetail } from '../entities'
44
25
  import { DateTimeConverter } from '../utils/datetime-util'
45
26
 
46
27
  const REPORT_API_URL = config.get('reportApiUrl', 'http://localhost:8888/rest/report/show_html')
@@ -166,6 +147,55 @@ export async function renderRODO({ doNo }, context: any) {
166
147
 
167
148
  const foundWSD: WorksheetDetail[] = await wdQb.getMany()
168
149
 
150
+ // Find order tote items linked to the order inventories in this DO
151
+ const foundOrderToteItems: OrderToteItem[] =
152
+ orderInvIds.length > 0
153
+ ? await getRepository(OrderToteItem).find({
154
+ where: {
155
+ domain,
156
+ orderInventory: In(orderInvIds)
157
+ },
158
+ relations: [
159
+ 'orderTote',
160
+ 'orderTote.tote',
161
+ 'orderTote.orderToteSeals',
162
+ 'orderInventory',
163
+ 'orderInventory.product'
164
+ ]
165
+ })
166
+ : []
167
+
168
+ // Create a map of orderInventoryId -> OrderToteItem[] for quick lookup (multiple items can be in same tote)
169
+ const toteItemMap = new Map<string, OrderToteItem[]>()
170
+ foundOrderToteItems.forEach(item => {
171
+ if (item.orderInventory?.id) {
172
+ const existing = toteItemMap.get(item.orderInventory.id) || []
173
+ existing.push(item)
174
+ toteItemMap.set(item.orderInventory.id, existing)
175
+ }
176
+ })
177
+
178
+ // Extract unique order totes from order tote items (eliminates separate query)
179
+ const uniqueOrderTotes: OrderTote[] = [
180
+ ...new Map(
181
+ foundOrderToteItems
182
+ .filter(item => item.orderTote?.id)
183
+ .map(item => [item.orderTote.id, item.orderTote])
184
+ ).values()
185
+ ]
186
+
187
+ // Create a map of tote name -> count for total tote qty calculation
188
+ const toteCountMap = new Map<string, number>()
189
+ uniqueOrderTotes.forEach(tote => {
190
+ const toteName = tote.tote?.name || tote.name
191
+ if (toteName) {
192
+ toteCountMap.set(toteName, (toteCountMap.get(toteName) || 0) + 1)
193
+ }
194
+ })
195
+
196
+ // Calculate total tote qty
197
+ const totalToteQty = uniqueOrderTotes.length
198
+
169
199
  const foundTemplate: Attachment = await getRepository(Attachment).findOne({
170
200
  where: { domain, category: TEMPLATE_TYPE.DO_TEMPLATE }
171
201
  })
@@ -196,8 +226,15 @@ export async function renderRODO({ doNo }, context: any) {
196
226
  }
197
227
 
198
228
  let productList: any[] = []
199
- productList = foundWSD
200
- .map((wsd: WorksheetDetail) => {
229
+
230
+ // Check if there are totes - if yes, group by tote; if no, use original logic
231
+ const hasTotes = uniqueOrderTotes.length > 0 && foundOrderToteItems.length > 0
232
+
233
+ if (hasTotes) {
234
+ // Group products by tote
235
+ const toteProductMap = new Map<string, any[]>()
236
+
237
+ foundWSD.forEach((wsd: WorksheetDetail) => {
201
238
  const targetInventory: OrderInventory = wsd.targetInventory
202
239
  const inventory: Inventory = targetInventory.inventory
203
240
  const productDetails: ProductDetail[] = inventory.product.productDetails
@@ -205,110 +242,281 @@ export async function renderRODO({ doNo }, context: any) {
205
242
  const matchedProductDetail: ProductDetail = productDetails.find(
206
243
  productDetail => productDetail.packingType === inventory.packingType
207
244
  )
208
- return {
209
- product_name:
210
- wsd.targetInventory.product.id === wsd.targetInventory.inventory.product.id
211
- ? `${inventory.product.name} (${inventory.product.description})`
212
- : `${wsd.targetInventory.product.name} (${wsd.targetInventory.product.description})`,
213
- product_desc:
214
- wsd.targetInventory.product.id === wsd.targetInventory.inventory.product.id
215
- ? `${inventory.product?.description || ''}`
216
- : `${wsd.targetInventory.product.description || ''}`,
217
- product_nameOnly:
218
- wsd.targetInventory.product.id === wsd.targetInventory.inventory.product.id
219
- ? `${inventory.product.name}`
220
- : `${wsd.targetInventory.product.name}`,
221
- product_sku:
245
+
246
+ // Get tote information for this order inventory
247
+ const toteItems = toteItemMap.get(targetInventory.id) || []
248
+
249
+ if (toteItems.length > 0) {
250
+ // Group by tote name
251
+ toteItems.forEach(toteItem => {
252
+ const toteName = toteItem.orderTote?.tote?.name || toteItem.orderTote?.name || ''
253
+
254
+ if (!toteProductMap.has(toteName)) {
255
+ toteProductMap.set(toteName, [])
256
+ }
257
+
258
+ const productSku =
259
+ wsd.targetInventory.product.id === wsd.targetInventory.inventory.product.id
260
+ ? inventory.product.sku
261
+ : wsd.targetInventory.product.sku
262
+ const productName =
263
+ wsd.targetInventory.product.id === wsd.targetInventory.inventory.product.id
264
+ ? `${inventory.product.name} (${inventory.product.description})`
265
+ : `${wsd.targetInventory.product.name} (${wsd.targetInventory.product.description})`
266
+
267
+ toteProductMap.get(toteName).push({
268
+ tote_name: toteName,
269
+ product_sku: productSku,
270
+ product_name: productName,
271
+ product_qty: toteItem.qty || targetInventory.releaseQty,
272
+ product_type: inventory.packingType,
273
+ targetInventoryId: targetInventory.id,
274
+ wsd: wsd,
275
+ inventory: inventory,
276
+ productDetails: productDetails,
277
+ orderProduct: orderProduct,
278
+ matchedProductDetail: matchedProductDetail
279
+ })
280
+ })
281
+ } else {
282
+ // No tote assigned - add to a special "NO_TOTE" group
283
+ const toteName = 'NO_TOTE'
284
+ if (!toteProductMap.has(toteName)) {
285
+ toteProductMap.set(toteName, [])
286
+ }
287
+
288
+ const productSku =
222
289
  wsd.targetInventory.product.id === wsd.targetInventory.inventory.product.id
223
- ? `${inventory.product.sku}`
224
- : `${wsd.targetInventory.product.sku}`,
225
- product_type: inventory.packingType,
226
- product_size: matchedProductDetail ? matchedProductDetail.packingSize : inventory.packingSize,
227
- product_batch: inventory.batchId,
228
- product_volume: matchedProductDetail ? matchedProductDetail.volume : 0,
229
- product_batch_ref: inventory.batchIdRef,
230
- product_qty: targetInventory.releaseQty,
231
- product_weight: targetInventory.releaseWeight,
232
- product_gross_weight: inventory.product.grossWeight,
233
- product_uom_value: targetInventory.releaseUomValue,
234
- product_uom: inventory.uom,
235
- product_brand_sku:
290
+ ? inventory.product.sku
291
+ : wsd.targetInventory.product.sku
292
+ const productName =
236
293
  wsd.targetInventory.product.id === wsd.targetInventory.inventory.product.id
237
- ? `${inventory.product?.brandSku || ''}`
238
- : `${wsd.targetInventory.product?.brandSku || ''}`,
239
- remark: targetInventory.remark,
240
- inventory_remark: inventory.remark,
241
- cross_docking: targetInventory.crossDocking,
242
- pallet: inventory?.reusablePallet && inventory?.reusablePallet?.name ? inventory.reusablePallet.name : '',
243
- avgSellingPrice:
244
- orderProduct?.sellingPrice > 0 && orderProduct?.releaseQty > 0
245
- ? orderProduct?.sellingPrice / orderProduct?.releaseQty
246
- : 0,
247
- expiry_date: inventory?.expirationDate ? inventory?.expirationDate : ''
294
+ ? `${inventory.product.name} (${inventory.product.description})`
295
+ : `${wsd.targetInventory.product.name} (${wsd.targetInventory.product.description})`
296
+
297
+ toteProductMap.get(toteName).push({
298
+ tote_name: '',
299
+ product_sku: productSku,
300
+ product_name: productName,
301
+ product_qty: targetInventory.releaseQty,
302
+ product_type: inventory.packingType,
303
+ targetInventoryId: targetInventory.id,
304
+ wsd: wsd,
305
+ inventory: inventory,
306
+ productDetails: productDetails,
307
+ orderProduct: orderProduct,
308
+ matchedProductDetail: matchedProductDetail
309
+ })
248
310
  }
249
311
  })
250
- .reduce((newItem, item) => {
251
- var foundItem = newItem.find(
252
- newItem =>
253
- newItem.product_sku === item.product_sku &&
254
- newItem.product_name === item.product_name &&
255
- newItem.product_type === item.product_type &&
256
- newItem.product_batch === item.product_batch &&
257
- newItem.product_batch_ref === item.product_batch_ref &&
258
- newItem.cross_docking === item.cross_docking &&
259
- newItem.pallet === item.pallet
260
- )
261
- if (!foundItem) {
262
- foundItem = {
263
- product_sku: item.product_sku,
264
- product_name: item.product_name,
265
- product_type: item.product_type,
266
- product_size: item.product_size,
267
- product_batch: item.product_batch,
268
- product_batch_ref: item.product_batch_ref,
269
- product_qty: item.product_qty,
270
- product_weight: item.product_weight,
271
- product_gross_weight: item.product_gross_weight,
272
- product_uom_value: item.product_uom_value,
273
- product_uom: item.product_uom,
274
- product_desc: item.product_desc,
275
- product_nameOnly: item.product_nameOnly,
276
- product_brand_sku: item.product_brand_sku,
277
- remark: item.remark,
278
- inventory_remark: item.inventory_remark,
279
- expiry_date: item.expiry_date,
280
- palletQty: 1,
281
- cross_docking: item.cross_docking,
282
- pallet: item.pallet,
283
- avgSellingPrice: item.avgSellingPrice,
284
- product_volume: item.product_volume,
312
+
313
+ // Process each tote group
314
+ toteProductMap.forEach((products, toteName) => {
315
+ // Group products within each tote by sku, name, type
316
+ const groupedProducts = new Map<string, any>()
317
+
318
+ products.forEach(product => {
319
+ const key = `${product.product_sku}_${product.product_name}_${product.product_type}`
320
+
321
+ if (!groupedProducts.has(key)) {
322
+ groupedProducts.set(key, {
323
+ tote_name: product.tote_name,
324
+ product_sku: product.product_sku,
325
+ product_name: product.product_name,
326
+ product_qty: 0,
327
+ product_type: product.product_type,
328
+ no_of_tote: 0, // Count of items of this product in this tote
329
+ wsd: product.wsd,
330
+ inventory: product.inventory,
331
+ productDetails: product.productDetails,
332
+ orderProduct: product.orderProduct,
333
+ matchedProductDetail: product.matchedProductDetail
334
+ })
285
335
  }
286
336
 
287
- newItem.push(foundItem)
288
- return newItem
289
- } else {
290
- return newItem.map(ni => {
291
- if (
292
- ni.product_sku === item.product_sku &&
293
- ni.product_name === item.product_name &&
294
- ni.product_batch === item.product_batch &&
295
- ni.product_batch_ref === item.product_batch_ref &&
296
- ni.cross_docking === item.cross_docking &&
297
- ni.pallet === item.pallet
298
- ) {
299
- return {
300
- ...ni,
301
- palletQty: ni.palletQty + 1,
302
- product_qty: ni.product_qty + item.product_qty,
303
- product_weight: ni.product_weight + item.product_weight,
304
- product_uom_value: ni.product_uom_value + item.product_uom_value
305
- }
306
- } else {
307
- return ni
308
- }
337
+ const existing = groupedProducts.get(key)
338
+ existing.product_qty += product.product_qty
339
+ existing.no_of_tote += 1 // Increment count for each occurrence in this tote
340
+ })
341
+
342
+ // Add grouped products to productList
343
+ groupedProducts.forEach(product => {
344
+ const wsd = product.wsd
345
+ const inventory = product.inventory
346
+ const productDetails = product.productDetails
347
+ const orderProduct = product.orderProduct
348
+ const matchedProductDetail = product.matchedProductDetail
349
+ const targetInventory = wsd.targetInventory
350
+
351
+ productList.push({
352
+ tote_name: product.tote_name,
353
+ product_sku: product.product_sku,
354
+ product_name: product.product_name,
355
+ product_qty: product.product_qty,
356
+ product_type: product.product_type,
357
+ no_of_tote: product.no_of_tote,
358
+ product_desc:
359
+ wsd.targetInventory.product.id === wsd.targetInventory.inventory.product.id
360
+ ? `${inventory.product?.description || ''}`
361
+ : `${wsd.targetInventory.product.description || ''}`,
362
+ product_nameOnly:
363
+ wsd.targetInventory.product.id === wsd.targetInventory.inventory.product.id
364
+ ? `${inventory.product.name}`
365
+ : `${wsd.targetInventory.product.name}`,
366
+ product_size: matchedProductDetail ? matchedProductDetail.packingSize : inventory.packingSize,
367
+ product_batch: inventory.batchId,
368
+ product_volume: matchedProductDetail ? matchedProductDetail.volume : 0,
369
+ product_batch_ref: inventory.batchIdRef,
370
+ product_weight: targetInventory.releaseWeight,
371
+ product_gross_weight: inventory.product.grossWeight,
372
+ product_uom_value: targetInventory.releaseUomValue,
373
+ product_uom: inventory.uom,
374
+ product_brand_sku:
375
+ wsd.targetInventory.product.id === wsd.targetInventory.inventory.product.id
376
+ ? `${inventory.product?.brandSku || ''}`
377
+ : `${wsd.targetInventory.product?.brandSku || ''}`,
378
+ remark: targetInventory.remark,
379
+ inventory_remark: inventory.remark,
380
+ cross_docking: targetInventory.crossDocking,
381
+ pallet: inventory?.reusablePallet && inventory?.reusablePallet?.name ? inventory.reusablePallet.name : '',
382
+ avgSellingPrice:
383
+ orderProduct?.sellingPrice > 0 && orderProduct?.releaseQty > 0
384
+ ? orderProduct?.sellingPrice / orderProduct?.releaseQty
385
+ : 0,
386
+ expiry_date: inventory?.expirationDate ? inventory?.expirationDate : ''
309
387
  })
388
+ })
389
+ })
390
+ } else {
391
+ // Original logic when no totes
392
+ productList = foundWSD
393
+ .map((wsd: WorksheetDetail) => {
394
+ const targetInventory: OrderInventory = wsd.targetInventory
395
+ const inventory: Inventory = targetInventory.inventory
396
+ const productDetails: ProductDetail[] = inventory.product.productDetails
397
+ const orderProduct: OrderProduct = targetInventory.orderProduct
398
+ const matchedProductDetail: ProductDetail = productDetails.find(
399
+ productDetail => productDetail.packingType === inventory.packingType
400
+ )
401
+ return {
402
+ product_name:
403
+ wsd.targetInventory.product.id === wsd.targetInventory.inventory.product.id
404
+ ? `${inventory.product.name} (${inventory.product.description})`
405
+ : `${wsd.targetInventory.product.name} (${wsd.targetInventory.product.description})`,
406
+ product_desc:
407
+ wsd.targetInventory.product.id === wsd.targetInventory.inventory.product.id
408
+ ? `${inventory.product?.description || ''}`
409
+ : `${wsd.targetInventory.product.description || ''}`,
410
+ product_nameOnly:
411
+ wsd.targetInventory.product.id === wsd.targetInventory.inventory.product.id
412
+ ? `${inventory.product.name}`
413
+ : `${wsd.targetInventory.product.name}`,
414
+ product_sku:
415
+ wsd.targetInventory.product.id === wsd.targetInventory.inventory.product.id
416
+ ? `${inventory.product.sku}`
417
+ : `${wsd.targetInventory.product.sku}`,
418
+ product_type: inventory.packingType,
419
+ product_size: matchedProductDetail ? matchedProductDetail.packingSize : inventory.packingSize,
420
+ product_batch: inventory.batchId,
421
+ product_volume: matchedProductDetail ? matchedProductDetail.volume : 0,
422
+ product_batch_ref: inventory.batchIdRef,
423
+ product_qty: targetInventory.releaseQty,
424
+ product_weight: targetInventory.releaseWeight,
425
+ product_gross_weight: inventory.product.grossWeight,
426
+ product_uom_value: targetInventory.releaseUomValue,
427
+ product_uom: inventory.uom,
428
+ product_brand_sku:
429
+ wsd.targetInventory.product.id === wsd.targetInventory.inventory.product.id
430
+ ? `${inventory.product?.brandSku || ''}`
431
+ : `${wsd.targetInventory.product?.brandSku || ''}`,
432
+ remark: targetInventory.remark,
433
+ inventory_remark: inventory.remark,
434
+ cross_docking: targetInventory.crossDocking,
435
+ pallet: inventory?.reusablePallet && inventory?.reusablePallet?.name ? inventory.reusablePallet.name : '',
436
+ avgSellingPrice:
437
+ orderProduct?.sellingPrice > 0 && orderProduct?.releaseQty > 0
438
+ ? orderProduct?.sellingPrice / orderProduct?.releaseQty
439
+ : 0,
440
+ expiry_date: inventory?.expirationDate ? inventory?.expirationDate : ''
441
+ }
442
+ })
443
+ .reduce((newItem, item) => {
444
+ var foundItem = newItem.find(
445
+ newItem =>
446
+ newItem.product_sku === item.product_sku &&
447
+ newItem.product_name === item.product_name &&
448
+ newItem.product_type === item.product_type &&
449
+ newItem.product_batch === item.product_batch &&
450
+ newItem.product_batch_ref === item.product_batch_ref &&
451
+ newItem.cross_docking === item.cross_docking &&
452
+ newItem.pallet === item.pallet
453
+ )
454
+ if (!foundItem) {
455
+ foundItem = {
456
+ product_sku: item.product_sku,
457
+ product_name: item.product_name,
458
+ product_type: item.product_type,
459
+ product_size: item.product_size,
460
+ product_batch: item.product_batch,
461
+ product_batch_ref: item.product_batch_ref,
462
+ product_qty: item.product_qty,
463
+ product_weight: item.product_weight,
464
+ product_gross_weight: item.product_gross_weight,
465
+ product_uom_value: item.product_uom_value,
466
+ product_uom: item.product_uom,
467
+ product_desc: item.product_desc,
468
+ product_nameOnly: item.product_nameOnly,
469
+ product_brand_sku: item.product_brand_sku,
470
+ remark: item.remark,
471
+ inventory_remark: item.inventory_remark,
472
+ expiry_date: item.expiry_date,
473
+ palletQty: 1,
474
+ cross_docking: item.cross_docking,
475
+ pallet: item.pallet,
476
+ avgSellingPrice: item.avgSellingPrice,
477
+ product_volume: item.product_volume
478
+ }
479
+
480
+ newItem.push(foundItem)
481
+ return newItem
482
+ } else {
483
+ return newItem.map(ni => {
484
+ if (
485
+ ni.product_sku === item.product_sku &&
486
+ ni.product_name === item.product_name &&
487
+ ni.product_batch === item.product_batch &&
488
+ ni.product_batch_ref === item.product_batch_ref &&
489
+ ni.cross_docking === item.cross_docking &&
490
+ ni.pallet === item.pallet
491
+ ) {
492
+ return {
493
+ ...ni,
494
+ palletQty: ni.palletQty + 1,
495
+ product_qty: ni.product_qty + item.product_qty,
496
+ product_weight: ni.product_weight + item.product_weight,
497
+ product_uom_value: ni.product_uom_value + item.product_uom_value
498
+ }
499
+ } else {
500
+ return ni
501
+ }
502
+ })
503
+ }
504
+ }, [])
505
+ }
506
+
507
+ // Sort productList by tote_name in ascending order if totes exist
508
+ if (hasTotes) {
509
+ productList.sort((a, b) => {
510
+ const toteNameA = (a.tote_name || '').trim()
511
+ const toteNameB = (b.tote_name || '').trim()
512
+
513
+ // Sort by tote_name first (ascending), then by product_sku (ascending)
514
+ if (toteNameA !== toteNameB) {
515
+ return toteNameA.localeCompare(toteNameB, undefined, { numeric: true, sensitivity: 'base' })
310
516
  }
311
- }, [])
517
+ return (a.product_sku || '').localeCompare(b.product_sku || '', undefined, { numeric: true, sensitivity: 'base' })
518
+ })
519
+ }
312
520
 
313
521
  const data = {
314
522
  logo_url: logo,
@@ -440,7 +648,7 @@ export async function renderRODO({ doNo }, context: any) {
440
648
  product_uom_value: `${Math.round(prod.product_uom_value * 100) / 100} ${prod.product_uom}`,
441
649
  selling_price:
442
650
  prod?.avgSellingPrice > 0 ? parseFloat((prod?.avgSellingPrice * prod.product_qty).toFixed(2)) : 0,
443
- product_volume: (Math.round(prod.product_volume * prod.product_qty * 10000)/10000).toFixed(4),
651
+ product_volume: (Math.round(prod.product_volume * prod.product_qty * 10000) / 10000).toFixed(4),
444
652
  remark: prod?.remark
445
653
  ? prod.remark
446
654
  : prod.cross_docking
@@ -453,16 +661,50 @@ export async function renderRODO({ doNo }, context: any) {
453
661
  inventory_remark: prod?.inventory_remark ? prod.inventory_remark : '',
454
662
  batch_id_ref: prod.product_batch_ref,
455
663
  product_serial_number:
456
- foundInventoryItem.length === 0 || !foundInventoryItem.some(item => item.sku === prod.product_sku) ? ''
457
- : foundInventoryItem.filter(item => item.sku === prod.product_sku)
458
- .map((item: any, idx) => item.serial_number)
459
- .join(', '),
460
- }
664
+ foundInventoryItem.length === 0 || !foundInventoryItem.some(item => item.sku === prod.product_sku)
665
+ ? ''
666
+ : foundInventoryItem
667
+ .filter(item => item.sku === prod.product_sku)
668
+ .map((item: any, idx) => item.serial_number)
669
+ .join(', ')
670
+ }
461
671
  }),
462
672
  serialNumber:
463
673
  foundInventoryItem.map((item: any, idx) => {
464
674
  return { ...item, delivery_to: foundDO.to }
465
- }) || ''
675
+ }) || '',
676
+ order_tote_items: foundOrderToteItems.map(item => {
677
+ // Get product information from orderInventory or orderProduct
678
+ const productFromInventory = item.orderInventory?.product || item.orderInventory?.inventory?.product
679
+ const product = productFromInventory
680
+
681
+ // Get packingType from orderInventory.inventory or item itself
682
+ const packingType = item.orderInventory?.inventory?.packingType
683
+
684
+ // Get tote information
685
+ const toteName = item.orderTote?.tote?.name || item.orderTote?.name || ''
686
+
687
+ // Get seals for this tote and join by comma
688
+ const seals = item.orderTote?.orderToteSeals || []
689
+ const sealNames = seals.map((seal: OrderToteSeal) => seal.name).filter(Boolean)
690
+ const sealsString = sealNames.length > 0 ? sealNames.join(', ') : null
691
+
692
+ return {
693
+ id: item.id,
694
+ name: item.name,
695
+ tote_id: item.orderTote?.id,
696
+ tote_name: toteName,
697
+ order_tote_id: item.orderTote?.id,
698
+ product_qty: item.qty,
699
+ product_sku: product?.sku,
700
+ product_brand_sku: product?.brandSku,
701
+ product_name: product?.name,
702
+ product_type: packingType,
703
+ closed_date: item.orderTote?.closedAt,
704
+ seals: sealsString
705
+ }
706
+ }),
707
+ total_tote_qty: totalToteQty
466
708
  } //.. make data from do
467
709
  const formData = new FormData()
468
710