@things-factory/worksheet-base 4.3.753 → 4.3.755

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 +35 -1
  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 +49 -3
  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
 
@@ -20,7 +20,9 @@ export const findSortingReleaseOrdersByTaskNoResolver = {
20
20
  const { domain, tx, user }: { domain: Domain; tx: EntityManager; user: User } = context.state
21
21
 
22
22
  let selectedReleaseGood = null
23
- let ws = await tx.getRepository(WorksheetEntity).createQueryBuilder('ws')
23
+ let ws = await tx
24
+ .getRepository(WorksheetEntity)
25
+ .createQueryBuilder('ws')
24
26
  .addSelect('rg.name', 'release_good_name')
25
27
  .addSelect('rg.status', 'release_good_status')
26
28
  .innerJoin('worksheet_details', 'wd', `ws.id = wd.worksheet_id`)
@@ -36,7 +38,7 @@ export const findSortingReleaseOrdersByTaskNoResolver = {
36
38
 
37
39
  if (ws) {
38
40
  if (ws.release_good_status == 'LOADING' || ws.release_good_status == 'DONE') {
39
- throw new Error(`Release Good already sorted`);
41
+ throw new Error(`Release Good already sorted`)
40
42
  }
41
43
 
42
44
  selectedReleaseGood = ws.release_good_name
@@ -60,7 +62,11 @@ export const findSortingReleaseOrdersByTaskNoResolver = {
60
62
  .createQueryBuilder('orderInventory')
61
63
 
62
64
  qb.innerJoinAndSelect('orderInventory.releaseGood', 'releaseGood')
63
- .innerJoinAndSelect('worksheets', 'ws', `orderInventory.ref_worksheet_id = ws.id AND ws.type = 'BATCH_PICKING'`)
65
+ .innerJoinAndSelect(
66
+ 'worksheets',
67
+ 'ws',
68
+ `orderInventory.ref_worksheet_id = ws.id AND ws.type = 'BATCH_PICKING'`
69
+ )
64
70
  .innerJoinAndSelect('worksheets', 'ws2', `ws2.task_no = ws.task_no AND ws2.type = 'SORTING'`)
65
71
  .innerJoinAndSelect('releaseGood.bizplace', 'bizplace')
66
72
  .innerJoinAndSelect('bizplace.domain', 'domain')
@@ -155,6 +161,46 @@ export const findSortingReleaseOrdersByTaskNoResolver = {
155
161
 
156
162
  const releaseGoods: ReleaseGoodEntity[] = await qb.getRawMany()
157
163
 
164
+ if (releaseGoods.length > 0) {
165
+ // Get counts for all release goods in a single query (avoids N+1 problem)
166
+ const releaseGoodIds = releaseGoods.map(rg => rg.id)
167
+ const countsResult = await tx
168
+ .getRepository(OrderInventoryEntity)
169
+ .createQueryBuilder('orderInventory')
170
+ .innerJoin(WorksheetDetailEntity, 'wsd', 'orderInventory.id = wsd.target_inventory_id')
171
+ .innerJoin(WorksheetEntity, 'ws', 'wsd.worksheet_id = ws.id')
172
+ .select('orderInventory.release_good_id', 'releaseGoodId')
173
+ .addSelect('COUNT(*)', 'total')
174
+ .addSelect(
175
+ 'SUM(CASE WHEN orderInventory.sortedQty = orderInventory.releaseQty THEN 1 ELSE 0 END)',
176
+ 'completed'
177
+ )
178
+ .where('orderInventory.release_good_id IN (:...releaseGoodIds)', { releaseGoodIds })
179
+ .andWhere('orderInventory.domain_id = :domainId', { domainId: domain.id })
180
+ .andWhere('ws.taskNo = :taskNo', { taskNo: taskNo })
181
+ .andWhere('ws.type = :worksheetType', { worksheetType: WORKSHEET_TYPE.SORTING })
182
+ .andWhere('ws.status = :worksheetStatus', { worksheetStatus: WORKSHEET_STATUS.EXECUTING })
183
+ .groupBy('orderInventory.release_good_id')
184
+ .getRawMany()
185
+
186
+ // Build a map for quick lookup
187
+ const countsById = new Map(
188
+ countsResult.map(c => [
189
+ c.releaseGoodId,
190
+ { total: parseInt(c.total) || 0, completed: parseInt(c.completed) || 0 }
191
+ ])
192
+ )
193
+
194
+ // Assign itemCounts to each release good
195
+ for (const releaseGood of releaseGoods) {
196
+ const counts = countsById.get(releaseGood.id) || { total: 0, completed: 0 }
197
+ releaseGood['itemCounts'] = {
198
+ total: counts.total,
199
+ pending: counts.total - counts.completed
200
+ }
201
+ }
202
+ }
203
+
158
204
  return { releaseGoods, taskNo, selectedReleaseGood }
159
205
  }
160
206
  }
@@ -24,7 +24,7 @@ import { havingVasResolver } from './having-vas'
24
24
  import { Mutations as InspectMutations } from './inspecting'
25
25
  import { inventoriesByPalletResolver } from './inventories-by-pallet'
26
26
  import { loadedInventories } from './loaded-inventories'
27
- import { Mutations as LoadingMutations } from './loading'
27
+ import { Mutations as LoadingMutations, Query as LoadingQuery } from './loading'
28
28
  import { loadingWorksheetResolver } from './loading-worksheet'
29
29
  import { notTallyTargetInventoriesResolver } from './not-tally-target-inventories'
30
30
  import { Mutations as PackingMutations } from './packing'
@@ -115,7 +115,8 @@ export const Query = {
115
115
  ...findSortingReleaseOrdersByTaskNoResolver,
116
116
  ...findReleaseOrdersByTaskNoResolver,
117
117
  ...fetchDeliveryOrderROResolver,
118
- ...putawayReplenishmentWorksheetResolver
118
+ ...putawayReplenishmentWorksheetResolver,
119
+ ...LoadingQuery
119
120
  }
120
121
 
121
122
  export const Mutation = {