@things-factory/operato-wms 4.3.752 → 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 (78) hide show
  1. package/client/pages/inbound/unload-product.js +30 -33
  2. package/client/pages/outbound/loading-product.js +2 -3
  3. package/client/pages/outbound/loading-v2/loading-execution-base.js +568 -56
  4. package/client/pages/outbound/loading-v2/loading-execution.js +258 -8
  5. package/client/pages/outbound/loading-v2/loading.js +82 -12
  6. package/client/pages/outbound/picking-product.js +11 -6
  7. package/client/pages/outbound/picking-v2/picking-execution-base.js +8 -8
  8. package/client/pages/outbound/picking-v2/picking-execution.js +75 -0
  9. package/client/pages/outbound/route-label-history-dialog.js +253 -0
  10. package/client/pages/outbound/route-label-popup.js +637 -131
  11. package/client/pages/outbound/show-ro-list-popup.js +8 -4
  12. package/client/pages/outbound/single-outbound-worksheet.js +55 -6
  13. package/client/pages/outbound/sorting-product.js +71 -25
  14. package/client/pages/outbound/zone-worksheet.js +62 -3
  15. package/dist-server/entities/index.js +9 -0
  16. package/dist-server/entities/index.js.map +1 -0
  17. package/dist-server/entities/route-label-sequence-counter.js +75 -0
  18. package/dist-server/entities/route-label-sequence-counter.js.map +1 -0
  19. package/dist-server/entities/route-label-sequence-log.js +74 -0
  20. package/dist-server/entities/route-label-sequence-log.js.map +1 -0
  21. package/dist-server/graphql/resolvers/index.js +3 -0
  22. package/dist-server/graphql/resolvers/index.js.map +1 -1
  23. package/dist-server/graphql/resolvers/outbound/complete-load.js +40 -3
  24. package/dist-server/graphql/resolvers/outbound/complete-load.js.map +1 -1
  25. package/dist-server/graphql/resolvers/outbound/find-loadable-release-good-by-bin.js +50 -11
  26. package/dist-server/graphql/resolvers/outbound/find-loadable-release-good-by-bin.js.map +1 -1
  27. package/dist-server/graphql/resolvers/outbound/find-loadable-release-good.js +90 -48
  28. package/dist-server/graphql/resolvers/outbound/find-loadable-release-good.js.map +1 -1
  29. package/dist-server/graphql/resolvers/outbound/loading-worksheet-v2.js +15 -7
  30. package/dist-server/graphql/resolvers/outbound/loading-worksheet-v2.js.map +1 -1
  31. package/dist-server/graphql/resolvers/outbound/sort-item.js +95 -16
  32. package/dist-server/graphql/resolvers/outbound/sort-item.js.map +1 -1
  33. package/dist-server/graphql/resolvers/route-label/index.js +7 -0
  34. package/dist-server/graphql/resolvers/route-label/index.js.map +1 -0
  35. package/dist-server/graphql/resolvers/route-label/route-label.js +220 -0
  36. package/dist-server/graphql/resolvers/route-label/route-label.js.map +1 -0
  37. package/dist-server/graphql/resolvers/zone-worksheet/index.js.map +1 -1
  38. package/dist-server/graphql/resolvers/zone-worksheet/zone-picking.js +123 -27
  39. package/dist-server/graphql/resolvers/zone-worksheet/zone-picking.js.map +1 -1
  40. package/dist-server/graphql/resolvers/zone-worksheet/zone-worksheet.js.map +1 -1
  41. package/dist-server/graphql/types/index.js +4 -0
  42. package/dist-server/graphql/types/index.js.map +1 -1
  43. package/dist-server/graphql/types/outbound/index.js +7 -1
  44. package/dist-server/graphql/types/outbound/index.js.map +1 -1
  45. package/dist-server/graphql/types/route-label/index.js +14 -0
  46. package/dist-server/graphql/types/route-label/index.js.map +1 -0
  47. package/dist-server/graphql/types/route-label/route-label-sequence.js +53 -0
  48. package/dist-server/graphql/types/route-label/route-label-sequence.js.map +1 -0
  49. package/dist-server/index.js +5 -0
  50. package/dist-server/index.js.map +1 -1
  51. package/dist-server/validators/setting-validators.js +519 -0
  52. package/dist-server/validators/setting-validators.js.map +1 -0
  53. package/helps/operato-wms/outbound/picking-product.en.md +156 -158
  54. package/package.json +50 -50
  55. package/server/entities/index.ts +6 -0
  56. package/server/entities/route-label-sequence-counter.ts +56 -0
  57. package/server/entities/route-label-sequence-log.ts +47 -0
  58. package/server/graphql/resolvers/index.ts +3 -0
  59. package/server/graphql/resolvers/outbound/complete-load.ts +42 -1
  60. package/server/graphql/resolvers/outbound/find-loadable-release-good-by-bin.ts +56 -13
  61. package/server/graphql/resolvers/outbound/find-loadable-release-good.ts +120 -57
  62. package/server/graphql/resolvers/outbound/loading-worksheet-v2.ts +9 -2
  63. package/server/graphql/resolvers/outbound/sort-item.ts +125 -21
  64. package/server/graphql/resolvers/route-label/index.ts +16 -0
  65. package/server/graphql/resolvers/route-label/route-label.ts +264 -0
  66. package/server/graphql/resolvers/zone-worksheet/index.ts +2 -2
  67. package/server/graphql/resolvers/zone-worksheet/zone-picking.ts +156 -39
  68. package/server/graphql/resolvers/zone-worksheet/zone-worksheet.ts +1 -2
  69. package/server/graphql/types/index.ts +4 -0
  70. package/server/graphql/types/outbound/index.ts +7 -1
  71. package/server/graphql/types/route-label/index.ts +18 -0
  72. package/server/graphql/types/route-label/route-label-sequence.ts +50 -0
  73. package/server/index.ts +7 -1
  74. package/server/validators/setting-validators.ts +668 -0
  75. package/translations/en.json +280 -252
  76. package/translations/ko.json +29 -1
  77. package/translations/ms.json +28 -0
  78. package/translations/zh.json +28 -0
@@ -1,6 +1,7 @@
1
1
  import '@things-factory/barcode-ui'
2
2
  import '@things-factory/grist-ui'
3
3
  import { css, html } from 'lit-element'
4
+ import { gql } from 'graphql-tag'
4
5
  import { MultiColumnFormStyles, SingleColumnFormStyles } from '@things-factory/form-ui'
5
6
  import { WORKSHEET_STATUS } from '../../constants'
6
7
  import { i18next } from '@things-factory/i18n-base'
@@ -8,11 +9,13 @@ import { isMobileDevice } from '@things-factory/utils'
8
9
  import { LoadingExecutionBase } from './loading-execution-base'
9
10
  import '../../components/list-view/product-list-view'
10
11
  import { fetchSettingRule } from '../../../fetch-setting-value'
12
+ import { client, gqlContext } from '@things-factory/shell'
11
13
 
12
14
  // Constants
13
15
  const SETTINGS = {
14
16
  QC_SKU: 'qc-sku',
15
- DISABLE_QC: 'disable-QC-in-loading'
17
+ DISABLE_QC: 'disable-QC-in-loading',
18
+ ENABLE_TOTE_SCANNING: 'enable-tote-scanning'
16
19
  }
17
20
 
18
21
  const TAB_TYPES = {
@@ -24,6 +27,16 @@ const TAB_TYPES = {
24
27
  const DEFAULT_TABS = [TAB_TYPES.QC, TAB_TYPES.LOADING, TAB_TYPES.LOADED]
25
28
  const LOADING_TABS = [TAB_TYPES.LOADING, TAB_TYPES.LOADED]
26
29
 
30
+ const VALIDATE_QC_SEALS = gql`
31
+ query validateQcSeals($releaseGoodNo: String!) {
32
+ validateQcSeals(releaseGoodNo: $releaseGoodNo) {
33
+ valid
34
+ error
35
+ minimumSealNumber
36
+ }
37
+ }
38
+ `
39
+
27
40
  // List View Configurations
28
41
  const QC_LIST_CONFIG = {
29
42
  selectable: true,
@@ -121,13 +134,14 @@ const LOADING_LIST_CONFIG = {
121
134
  }
122
135
  }
123
136
 
124
-
125
137
  export class LoadingExecution extends LoadingExecutionBase {
126
138
  static get properties() {
127
139
  return {
128
140
  ...super.properties,
129
141
  _useQcSku: { type: Boolean },
130
- _currentReleaseGood: { type: Object }
142
+ _currentReleaseGood: { type: Object },
143
+ _tabCounts: { type: Object },
144
+ _qcBannerState: { type: Object }
131
145
  }
132
146
  }
133
147
 
@@ -255,6 +269,52 @@ export class LoadingExecution extends LoadingExecutionBase {
255
269
  align-items: center;
256
270
  padding: 0 12px 8px;
257
271
  }
272
+ .loading-banner {
273
+ background-color: #fff3cd;
274
+ border: 1px solid #ffc107;
275
+ border-radius: 4px;
276
+ padding: 12px 16px;
277
+ margin: 10px;
278
+ display: flex;
279
+ justify-content: space-between;
280
+ align-items: center;
281
+ color: #856404;
282
+ }
283
+ .loading-banner button {
284
+ background-color: #007e7f;
285
+ color: white;
286
+ border: none;
287
+ padding: 6px 12px;
288
+ border-radius: 4px;
289
+ cursor: pointer;
290
+ font-weight: bold;
291
+ }
292
+ .loading-banner button:hover {
293
+ background-color: #005f60;
294
+ }
295
+ .qc-banner {
296
+ background-color: #fff3cd;
297
+ border: 1px solid #ffc107;
298
+ border-radius: 4px;
299
+ padding: 12px 16px;
300
+ margin: 10px;
301
+ display: flex;
302
+ justify-content: space-between;
303
+ align-items: center;
304
+ color: #856404;
305
+ }
306
+ .qc-banner button {
307
+ background-color: #007e7f;
308
+ color: white;
309
+ border: none;
310
+ padding: 6px 12px;
311
+ border-radius: 4px;
312
+ cursor: pointer;
313
+ font-weight: bold;
314
+ }
315
+ .qc-banner button:hover {
316
+ background-color: #005f60;
317
+ }
258
318
  `
259
319
  ]
260
320
  }
@@ -264,6 +324,7 @@ export class LoadingExecution extends LoadingExecutionBase {
264
324
  this._executionData = { ...this._executionData, ownCollection: false }
265
325
  this._useQcSku = false
266
326
  this._currentReleaseGood = null
327
+ this._qcBannerState = { show: false, needsSealing: false, minimumSealNumber: 0 }
267
328
  this._deliveryOrderConfig = {
268
329
  columnLeft: {
269
330
  size: 8,
@@ -306,6 +367,12 @@ export class LoadingExecution extends LoadingExecutionBase {
306
367
  startedAt: this.worksheet?.startedAt
307
368
  }
308
369
  }
370
+ // Update banner state when QC or loading data changes
371
+ if (changedProperties.has('qcData') || changedProperties.has('loadingData')) {
372
+ if (this._updateQcBannerState) {
373
+ this._updateQcBannerState()
374
+ }
375
+ }
309
376
  }
310
377
 
311
378
  combineSameProducts(data) {
@@ -331,16 +398,33 @@ export class LoadingExecution extends LoadingExecutionBase {
331
398
  const disableQCSetting = await fetchSettingRule(SETTINGS.DISABLE_QC)
332
399
  this._disableQC = disableQCSetting === true || disableQCSetting === 'true'
333
400
  this._useQcSku = await fetchSettingRule(SETTINGS.QC_SKU)
334
-
335
- // Set initial active tab based on settings
336
- const activeTab = !this._disableQC ? TAB_TYPES.QC : TAB_TYPES.LOADING
401
+ this._enableToteScanningValue = parseInt((await fetchSettingRule(SETTINGS.ENABLE_TOTE_SCANNING)) || '0', 10)
402
+ this._enableToteScanning = this._enableToteScanningValue >= 1
403
+ this.toteNo = ''
404
+
405
+ // UX-safe tab selection: Auto-switch to LOADED if LOADING is empty but LOADED has items
406
+ let activeTab = !this._disableQC ? TAB_TYPES.QC : TAB_TYPES.LOADING
407
+ if (this._tabCounts && activeTab === TAB_TYPES.LOADING) {
408
+ const loadingCounts = this._tabCounts.LOADING || { total: 0, pending: 0 }
409
+ const loadedCounts = this._tabCounts.LOADED || { total: 0, pending: 0 }
410
+ if (loadingCounts.total === 0 && loadedCounts.total > 0) {
411
+ activeTab = TAB_TYPES.LOADED
412
+ }
413
+ }
337
414
  this.state = { ...this.state, activeTab }
338
415
 
339
416
  // Process worksheet details
340
417
  const processedDetails = this._useQcSku ? this.combineSameProducts(this.worksheetDetails) : this.worksheetDetails
341
418
 
419
+ // Check if any order inventory has refWorksheetId (when data comes from props)
420
+ if (this.worksheetDetails && this.worksheetDetails.length > 0) {
421
+ this._hasRefWorksheetId = this.worksheetDetails.some(
422
+ item => item?.relatedOrderInv?.refWorksheetId != null && item?.relatedOrderInv?.refWorksheetId !== ''
423
+ )
424
+ }
425
+
342
426
  // Filter data for different tabs
343
- this.qcData = processedDetails.filter(data => data?.sortedQty != data?.pickedQty && data.status !== 'LOADED') || []
427
+ this.qcData = processedDetails.filter(data => data?.sortedQty < data?.pickedQty && data.status !== 'LOADED') || []
344
428
  this.loadingData =
345
429
  processedDetails.filter(data => data?.sortedQty > 0 && data.status != WORKSHEET_STATUS.DONE.value) || []
346
430
  this.loadedData = this.deliveryOrdersInfo || []
@@ -352,7 +436,11 @@ export class LoadingExecution extends LoadingExecutionBase {
352
436
  worksheetId: this.worksheet?.worksheet?.id
353
437
  }
354
438
 
439
+ // Check seal status after resetting view (needed for Tote button visibility)
440
+ await this._checkAndUpdateSealStatus()
355
441
  this.getButton()
442
+ // Update QC banner state after resetting view
443
+ await this._updateQcBannerState()
356
444
  }
357
445
 
358
446
  get qcListItemConfig() {
@@ -367,6 +455,106 @@ export class LoadingExecution extends LoadingExecutionBase {
367
455
  return this._deliveryOrderConfig
368
456
  }
369
457
 
458
+ /**
459
+ * Determines if the "Go to LOADED" banner should be shown.
460
+ * Banner appears when:
461
+ * - Current tab is LOADING
462
+ * - LOADING tab has no items (loadingData.length === 0) - uses SAME source as Total SKU
463
+ * - LOADED tab has items (loadedData.deliveryOrders.length > 0)
464
+ *
465
+ * Uses the same data source as Total SKU display to ensure consistency.
466
+ */
467
+ _shouldShowLoadingBanner() {
468
+ // Banner only shows on LOADING tab
469
+ if (this.state?.activeTab !== TAB_TYPES.LOADING) {
470
+ return false
471
+ }
472
+
473
+ // Use the SAME source as Total SKU display: this.loadingData?.length
474
+ const loadingItemCount = this.loadingData?.length || 0
475
+ const loadedItemCount = this.loadedData?.deliveryOrders?.length || 0
476
+
477
+ // Show banner when LOADING is empty but LOADED has items
478
+ return loadingItemCount === 0 && loadedItemCount > 0
479
+ }
480
+
481
+ /**
482
+ * Checks if totes need to be sealed by calling the validation query
483
+ * Returns: { needsSealing: boolean, error?: string, minimumSealNumber?: number }
484
+ */
485
+ async _checkToteSealStatus() {
486
+ if (!this._enableToteScanning) {
487
+ return { needsSealing: false, minimumSealNumber: 0 }
488
+ }
489
+
490
+ const orderNo = this._currentReleaseGood?.releaseGood?.name || this.worksheet?.releaseGood?.name
491
+ if (!orderNo) {
492
+ return { needsSealing: false, minimumSealNumber: 0 }
493
+ }
494
+
495
+ try {
496
+ const validationResponse = await client.query({
497
+ query: VALIDATE_QC_SEALS,
498
+ variables: { releaseGoodNo: orderNo },
499
+ context: gqlContext()
500
+ })
501
+
502
+ if (!validationResponse.errors && validationResponse.data?.validateQcSeals) {
503
+ const { valid, error, minimumSealNumber } = validationResponse.data.validateQcSeals
504
+ const minSealNum = minimumSealNumber || 0
505
+ // If minimumSealNumber is 0, sealing is not required even if valid is false
506
+ const needsSealing = !valid && minSealNum > 0
507
+ return { needsSealing, error, minimumSealNumber: minSealNum }
508
+ }
509
+ } catch (e) {
510
+ // If validation fails, assume sealing is needed
511
+ return { needsSealing: true, error: e.message || 'Seal validation failed', minimumSealNumber: 0 }
512
+ }
513
+
514
+ return { needsSealing: false, minimumSealNumber: 0 }
515
+ }
516
+
517
+ /**
518
+ * Updates the QC banner state based on current data
519
+ */
520
+ async _updateQcBannerState() {
521
+ // Banner only shows on QC tab
522
+ if (this.state?.activeTab !== TAB_TYPES.QC) {
523
+ this._qcBannerState = { show: false, needsSealing: false, minimumSealNumber: 0 }
524
+ return
525
+ }
526
+
527
+ // Use the SAME source as Total SKU display: this.qcData?.length
528
+ const qcItemCount = this.qcData?.length || 0
529
+ const loadingItemCount = this.loadingData?.length || 0
530
+
531
+ // Show banner when QC is empty but LOADING has items
532
+ const shouldShow = qcItemCount === 0 && loadingItemCount > 0
533
+
534
+ if (!shouldShow) {
535
+ this._qcBannerState = { show: false, needsSealing: false, minimumSealNumber: 0 }
536
+ return
537
+ }
538
+
539
+ // Check if totes need sealing
540
+ const sealStatus = await this._checkToteSealStatus()
541
+
542
+ this._qcBannerState = {
543
+ show: true,
544
+ needsSealing: sealStatus.needsSealing,
545
+ error: sealStatus.error,
546
+ minimumSealNumber: sealStatus.minimumSealNumber || 0
547
+ }
548
+ this.requestUpdate()
549
+ }
550
+
551
+ /**
552
+ * Determines if the QC banner should be shown (synchronous getter for render)
553
+ */
554
+ get _shouldShowQcBanner() {
555
+ return this._qcBannerState?.show || false
556
+ }
557
+
370
558
  render() {
371
559
  if (!this.state) {
372
560
  this.resetView()
@@ -388,6 +576,12 @@ export class LoadingExecution extends LoadingExecutionBase {
388
576
  ${this.state?.activeTab === TAB_TYPES.QC
389
577
  ? html`
390
578
  <!-- QC Tab Inputs -->
579
+ ${this._enableToteScanning && this._disableQC === true
580
+ ? html`
581
+ <label class="font-bold">${i18next.t('field.tote')}</label>
582
+ <input name="toteNo" custom-input disabled .value="${this.toteNo || ''}" />
583
+ `
584
+ : ''}
391
585
  <label class="font-bold">${i18next.t('label.product_barcode')}</label>
392
586
  <barcode-scanable-input
393
587
  name="productBarcode"
@@ -447,7 +641,14 @@ export class LoadingExecution extends LoadingExecutionBase {
447
641
  }}
448
642
  >
449
643
  <label class="text-14 font-bold order-no"
450
- >Order No.: ${this._currentReleaseGood?.releaseGood?.name || this.worksheet?.binLocationName || ''}</label
644
+ >Order No.:
645
+ ${this._currentReleaseGood?.releaseGood?.name || this.worksheet?.releaseGood?.name
646
+ ? this._currentReleaseGood?.releaseGood?.name || this.worksheet?.releaseGood?.name
647
+ : this.worksheet?.isReleaseGoodScan
648
+ ? this.worksheet?.releaseGood?.name
649
+ : this._useQcSku
650
+ ? this.worksheet?.binLocationName
651
+ : ''}</label
451
652
  >
452
653
  <mwc-icon style="color:#007E7F;">info</mwc-icon>
453
654
  </button>
@@ -473,6 +674,55 @@ export class LoadingExecution extends LoadingExecutionBase {
473
674
  >
474
675
  </div>
475
676
 
677
+ ${this._shouldShowLoadingBanner()
678
+ ? html`
679
+ <div class="loading-banner">
680
+ <span
681
+ >${i18next.t('text.all_items_loaded_proceed_to_loaded', {
682
+ defaultValue: 'All items loaded. Proceed to LOADED to complete dispatch.'
683
+ })}</span
684
+ >
685
+ <button @click=${async e => await this.openTab(e, TAB_TYPES.LOADED)}>
686
+ ${i18next.t('button.go_to_loaded', { defaultValue: 'Go to LOADED' })}
687
+ </button>
688
+ </div>
689
+ `
690
+ : ''}
691
+ ${this._shouldShowQcBanner
692
+ ? html`
693
+ <div class="qc-banner">
694
+ <span
695
+ >${this._qcBannerState?.needsSealing
696
+ ? this._qcBannerState?.error ||
697
+ (this._qcBannerState?.minimumSealNumber > 0
698
+ ? i18next.t('text.tote_needs_to_be_sealed_with_count', {
699
+ defaultValue: `Tote needs to be sealed before proceeding to LOADING. Minimum ${this._qcBannerState.minimumSealNumber} seal(s) required.`,
700
+ count: this._qcBannerState.minimumSealNumber
701
+ })
702
+ : i18next.t('text.tote_needs_to_be_sealed', {
703
+ defaultValue: 'Tote needs to be sealed before proceeding to LOADING.'
704
+ }))
705
+ : i18next.t('text.all_items_qc_complete_proceed_to_loading', {
706
+ defaultValue: 'All items QC complete. Proceed to LOADING to continue.'
707
+ })}</span
708
+ >
709
+ <button
710
+ @click=${async e => {
711
+ if (this._qcBannerState?.needsSealing) {
712
+ await this.openTotePopup()
713
+ } else {
714
+ await this.openTab(e, TAB_TYPES.LOADING)
715
+ }
716
+ }}
717
+ >
718
+ ${this._qcBannerState?.needsSealing
719
+ ? i18next.t('button.seal_tote', { defaultValue: 'Seal Tote' })
720
+ : i18next.t('button.go_to_loading', { defaultValue: 'Go to LOADING' })}
721
+ </button>
722
+ </div>
723
+ `
724
+ : ''}
725
+
476
726
  <div id="qc" class="tab-content ${this.state?.activeTab === TAB_TYPES.QC ? 'active' : ''}">
477
727
  <product-list-view id="qc" .config=${this.qcListItemConfig} .data=${this.qcData || []}></product-list-view>
478
728
  </div>
@@ -54,6 +54,18 @@ const FIND_LOADABLE_RELEASE_GOOD = gql`
54
54
  total
55
55
  pending
56
56
  }
57
+ tabCounts {
58
+ LOADING {
59
+ total
60
+ completed
61
+ pending
62
+ }
63
+ LOADED {
64
+ total
65
+ completed
66
+ pending
67
+ }
68
+ }
57
69
  }
58
70
  }
59
71
  `
@@ -71,6 +83,7 @@ const FIND_LOADABLE_RELEASE_GOOD_BY_BIN = gql`
71
83
  bizplaceName
72
84
  startedAt
73
85
  binLocationName
86
+ isReleaseGoodScan
74
87
  releaseGood {
75
88
  id
76
89
  name
@@ -79,6 +92,9 @@ const FIND_LOADABLE_RELEASE_GOOD_BY_BIN = gql`
79
92
  attentionTo
80
93
  deliveryAddress1
81
94
  deliveryAddress2
95
+ refNo
96
+ refNo2
97
+ refNo3
82
98
  }
83
99
  }
84
100
  worksheetDetailInfos {
@@ -101,6 +117,7 @@ const FIND_LOADABLE_RELEASE_GOOD_BY_BIN = gql`
101
117
  id
102
118
  batchId
103
119
  packingType
120
+ refWorksheetId
104
121
  product {
105
122
  id
106
123
  sku
@@ -161,6 +178,7 @@ const LOADING_WORKSHEET_V2 = gql`
161
178
  id
162
179
  batchId
163
180
  packingType
181
+ refWorksheetId
164
182
  product {
165
183
  id
166
184
  sku
@@ -244,6 +262,7 @@ const GET_LOADING_TASK = gql`
244
262
  id
245
263
  batchId
246
264
  packingType
265
+ refWorksheetId
247
266
  product {
248
267
  id
249
268
  sku
@@ -265,7 +284,8 @@ class LoadingProductV2 extends connect(store)(localize(i18next)(PageView)) {
265
284
  _data: Object,
266
285
  _context: Object,
267
286
  _qcSku: Boolean,
268
- _disableQC: Boolean
287
+ _disableQC: Boolean,
288
+ _tabCounts: Object
269
289
  }
270
290
  }
271
291
 
@@ -375,6 +395,7 @@ class LoadingProductV2 extends connect(store)(localize(i18next)(PageView)) {
375
395
  .worksheetDetails=${this._data?.worksheetDetailInfos}
376
396
  .deliveryOrdersInfo=${this._data?.deliveryOrderInfoList}
377
397
  ._useQcSku=${this._qcSku}
398
+ ._tabCounts=${this._tabCounts}
378
399
  ></loading-execution>`
379
400
  } else {
380
401
  return html` <div class="${isMobileDevice() ? 'single-column-form' : 'multi-column-form'}">
@@ -423,7 +444,12 @@ class LoadingProductV2 extends connect(store)(localize(i18next)(PageView)) {
423
444
 
424
445
  async fetchOrder(orderNo) {
425
446
  try {
426
- if (!this._qcSku) {
447
+ // Use FIND_LOADABLE_RELEASE_GOOD if:
448
+ // 1. qc-sku is false, OR
449
+ // 2. qc-sku is true but disable-QC-in-loading is true (QC tab is hidden, so use order number scanning)
450
+ const shouldUseOrderNumberQuery = !this._qcSku || this._disableQC === true || this._disableQC === 'true'
451
+
452
+ if (shouldUseOrderNumberQuery) {
427
453
  const defaultTabName = this._disableQC === true || this._disableQC === 'true' ? 'LOADING' : 'QC'
428
454
 
429
455
  const response = await client.query({
@@ -432,7 +458,8 @@ class LoadingProductV2 extends connect(store)(localize(i18next)(PageView)) {
432
458
  orderNo,
433
459
  tabName: defaultTabName
434
460
  },
435
- context: gqlContext()
461
+ context: gqlContext(),
462
+ fetchPolicy: 'network-only' // Always fetch fresh data to show updated item counts
436
463
  })
437
464
 
438
465
  if (!response.errors) {
@@ -445,6 +472,7 @@ class LoadingProductV2 extends connect(store)(localize(i18next)(PageView)) {
445
472
  }
446
473
  }
447
474
  } else {
475
+ // Use FIND_LOADABLE_RELEASE_GOOD_BY_BIN when qc-sku is true and QC is enabled
448
476
  const response = await client.query({
449
477
  query: FIND_LOADABLE_RELEASE_GOOD_BY_BIN,
450
478
  variables: { orderNo },
@@ -485,8 +513,17 @@ class LoadingProductV2 extends connect(store)(localize(i18next)(PageView)) {
485
513
  <show-ro-list-popup
486
514
  .releaseOrders=${releaseGoods}
487
515
  .sorting=${false}
488
- @ro-selected-loading=${e => {
489
- this.fetchWorksheet(e.detail.ro?.name)
516
+ @ro-selected-loading=${async e => {
517
+ const selectedRO = e.detail.ro
518
+ // Store tabCounts for passing to loading-execution component
519
+ if (selectedRO?.tabCounts) {
520
+ this._tabCounts = selectedRO.tabCounts
521
+ }
522
+ // Ensure settings are loaded before fetching worksheet to use correct filter
523
+ if (this._disableQC === undefined || this._disableQC === null) {
524
+ await this.initializeSettings()
525
+ }
526
+ await this.fetchWorksheet(selectedRO?.name)
490
527
  }}
491
528
  ></show-ro-list-popup>
492
529
  `,
@@ -500,15 +537,48 @@ class LoadingProductV2 extends connect(store)(localize(i18next)(PageView)) {
500
537
 
501
538
  async fetchWorksheet(orderNo) {
502
539
  try {
503
- const filter = this._disableQC === true || this._disableQC === 'true' ? FILTER_TYPES.LOADING : FILTER_TYPES.QC
540
+ // Ensure settings are loaded before determining filter
541
+ // fetchSettingRule returns boolean true/false, so check for boolean true
542
+ const disableQC = this._disableQC === true
543
+
544
+ // Fetch BOTH QC and LOADING data so the banner can properly determine state
545
+ // This ensures loadingData is available even when QC is complete
546
+ const [qcResponse, loadingResponse] = await Promise.all([
547
+ client.query({
548
+ query: LOADING_WORKSHEET_V2,
549
+ variables: { orderNo, filter: FILTER_TYPES.QC },
550
+ fetchPolicy: 'network-only'
551
+ }),
552
+ client.query({
553
+ query: LOADING_WORKSHEET_V2,
554
+ variables: { orderNo, filter: FILTER_TYPES.LOADING },
555
+ fetchPolicy: 'network-only'
556
+ })
557
+ ])
504
558
 
505
- const response = await client.query({
506
- query: LOADING_WORKSHEET_V2,
507
- variables: { orderNo, filter }
508
- })
559
+ if (!qcResponse.errors && !loadingResponse.errors) {
560
+ const qcData = qcResponse.data?.loadingWorksheetv2
561
+ const loadingData = loadingResponse.data?.loadingWorksheetv2
509
562
 
510
- if (!response.errors) {
511
- let items = response.data?.loadingWorksheetv2
563
+ // Combine worksheetDetailInfos from both queries, removing duplicates by id
564
+ const qcDetails = qcData?.worksheetDetailInfos || []
565
+ const loadingDetails = loadingData?.worksheetDetailInfos || []
566
+ const detailsMap = new Map()
567
+
568
+ // Add QC details first
569
+ qcDetails.forEach(detail => {
570
+ if (detail?.id) detailsMap.set(detail.id, detail)
571
+ })
572
+ // Add loading details (will overwrite duplicates)
573
+ loadingDetails.forEach(detail => {
574
+ if (detail?.id) detailsMap.set(detail.id, detail)
575
+ })
576
+
577
+ // Use worksheetInfo from whichever has it (prefer QC, fallback to loading)
578
+ let items = {
579
+ worksheetInfo: qcData?.worksheetInfo || loadingData?.worksheetInfo,
580
+ worksheetDetailInfos: Array.from(detailsMap.values())
581
+ }
512
582
  items.deliveryOrderInfoList = await this.fetchDeliveryOrders(orderNo)
513
583
  this._data = items
514
584
  }
@@ -70,7 +70,8 @@ class PickingProduct extends connect(store)(localize(i18next)(PageView)) {
70
70
  toteNo: String,
71
71
  _pageSettings: Object,
72
72
  _isReplenishment: Boolean,
73
- _hasVas: Boolean
73
+ _hasVas: Boolean,
74
+ _enableToteScanningValue: Number
74
75
  }
75
76
  }
76
77
 
@@ -148,10 +149,11 @@ class PickingProduct extends connect(store)(localize(i18next)(PageView)) {
148
149
  })
149
150
  }
150
151
 
152
+ // Tote button only appears when enable-tote-scanning is exactly 1 (not 2 or higher)
151
153
  if (
152
154
  this.refPickingType === PICKING_TYPES.PICKING.value &&
153
155
  this.releaseGoodNo &&
154
- this._enableToteScanning &&
156
+ this._enableToteScanningValue === 1 &&
155
157
  !this._isReplenishment
156
158
  ) {
157
159
  actions.push({
@@ -328,6 +330,7 @@ class PickingProduct extends connect(store)(localize(i18next)(PageView)) {
328
330
  this._isLastUnit = false
329
331
  this._isReplenishment = false
330
332
  this._hasVas = false
333
+ this._enableToteScanningValue = 0
331
334
  }
332
335
 
333
336
  render() {
@@ -1076,7 +1079,7 @@ class PickingProduct extends connect(store)(localize(i18next)(PageView)) {
1076
1079
  binLocation:
1077
1080
  this._hasBinPicking && (!this.toteNo || this.tote == '') ? this.binLocationInput.value : null,
1078
1081
  serialNumber: this._isRequireSerialNumberScanningOutbound ? this.serialInput.value : null,
1079
- toteNo: this._enableToteScanning && this.toteNo !== '' ? this.toteNo : null
1082
+ toteNo: this._enableToteScanningValue >= 1 && this.toteNo !== '' ? this.toteNo : null
1080
1083
  }
1081
1084
  })
1082
1085
 
@@ -1167,7 +1170,7 @@ class PickingProduct extends connect(store)(localize(i18next)(PageView)) {
1167
1170
  serialNumber: this._isRequireSerialNumberScanningOutbound ? this.serialInput.value : null,
1168
1171
  binLocation:
1169
1172
  this._hasBinPicking && (!this.toteNo || this.tote == '') ? this.binLocationInput.value : null,
1170
- toteNo: this._enableToteScanning && this.toteNo !== '' ? this.toteNo : null,
1173
+ toteNo: this._enableToteScanningValue >= 1 && this.toteNo !== '' ? this.toteNo : null,
1171
1174
  pickingQty: qty
1172
1175
  }
1173
1176
  })
@@ -2306,7 +2309,7 @@ class PickingProduct extends connect(store)(localize(i18next)(PageView)) {
2306
2309
  const settingsMap = new Map(pageSettings.settings.map(setting => [setting.name, setting.value]))
2307
2310
 
2308
2311
  this._enableProductScanning = settingsMap.get('enable-product-scanning') === 'true'
2309
- this._enableToteScanning = settingsMap.get('enable-tote-scanning') === 'true'
2312
+ this._enableToteScanningValue = parseInt(settingsMap.get('enable-tote-scanning') || '0', 10)
2310
2313
  this._enableInputQty = settingsMap.get('enable-input-qty')
2311
2314
  this._qrProcessing = settingsMap.get('qr-code-scanning')
2312
2315
 
@@ -2351,10 +2354,12 @@ class PickingProduct extends connect(store)(localize(i18next)(PageView)) {
2351
2354
  const settingsMap = new Map(pageSettings.settings.map(setting => [setting.name, setting.value]))
2352
2355
 
2353
2356
  this._enableProductScanning = settingsMap.get('enable-product-scanning') === 'true'
2354
- this._enableToteScanning = settingsMap.get('enable-tote-scanning') === 'true'
2357
+ this._enableToteScanningValue = parseInt(settingsMap.get('enable-tote-scanning') || '0', 10)
2355
2358
  this._enableInputQty = settingsMap.get('enable-input-qty')
2356
2359
  this._qrProcessing = settingsMap.get('qr-code-scanning')
2357
2360
 
2361
+ // Update context after settings are loaded to ensure tote button visibility is correct
2362
+ this.updateContext()
2358
2363
  this.focusOnInput()
2359
2364
  }
2360
2365
 
@@ -272,14 +272,14 @@ export class PickingExecutionBase extends LitElement {
272
272
  this.qtyInput.value = ''
273
273
  this._focusOnCartonNoInput()
274
274
 
275
- //fetch data
276
- let newData = await this.fetchWorksheet(
277
- this._executionData.worksheetNo,
278
- this.parent._context.locationSortingRules,
279
- 'PICKING'
280
- )
281
-
282
- this.pickingData = newData.worksheetDetailInfos
275
+ //fetch data for both tabs to ensure banner condition is accurate
276
+ const [pickingData, pickedData] = await Promise.all([
277
+ this.fetchWorksheet(this._executionData.worksheetNo, this.parent._context.locationSortingRules, 'PICKING'),
278
+ this.fetchWorksheet(this._executionData.worksheetNo, this.parent._context.locationSortingRules, 'PICKED')
279
+ ])
280
+
281
+ this.pickingData = pickingData.worksheetDetailInfos
282
+ this.pickedData = pickedData.worksheetDetailInfos
283
283
  }
284
284
  } catch (e) {
285
285
  showToast(e)