@things-factory/operato-wms 4.3.761 → 4.3.764
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.
- package/client/pages/outbound/loading-v2/loading-execution-base.js +40 -1
- package/client/pages/outbound/picking-product.js +34 -0
- package/client/pages/outbound/sorting-product.js +30 -0
- package/client/pages/outbound/tote-popup.js +226 -36
- package/config.development.js +105 -88
- package/dist-server/graphql/resolvers/outbound/index.js +2 -1
- package/dist-server/graphql/resolvers/outbound/index.js.map +1 -1
- package/dist-server/graphql/resolvers/outbound/unsort-item.js +65 -0
- package/dist-server/graphql/resolvers/outbound/unsort-item.js.map +1 -0
- package/dist-server/graphql/resolvers/reports/tote-loading-qc-report.js +19 -10
- package/dist-server/graphql/resolvers/reports/tote-loading-qc-report.js.map +1 -1
- package/dist-server/graphql/types/outbound/index.js +4 -0
- package/dist-server/graphql/types/outbound/index.js.map +1 -1
- package/package.json +50 -50
- package/server/graphql/resolvers/outbound/index.ts +2 -0
- package/server/graphql/resolvers/outbound/unsort-item.ts +78 -0
- package/server/graphql/resolvers/reports/tote-loading-qc-report.ts +34 -11
- package/server/graphql/types/outbound/index.ts +4 -0
- package/translations/en.json +8 -0
- package/translations/ko.json +7 -0
- package/translations/ms.json +7 -0
- package/translations/zh.json +7 -0
|
@@ -448,19 +448,58 @@ export class LoadingExecutionBase extends LitElement {
|
|
|
448
448
|
throw new Error(i18next.t('text.no_release_order_selected'))
|
|
449
449
|
}
|
|
450
450
|
|
|
451
|
+
// Query for minimum seal number setting
|
|
452
|
+
let minimumSealNumber = 0
|
|
453
|
+
try {
|
|
454
|
+
const validationResponse = await client.query({
|
|
455
|
+
query: VALIDATE_QC_SEALS,
|
|
456
|
+
variables: { releaseGoodNo: orderNo },
|
|
457
|
+
context: gqlContext()
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
if (!validationResponse.errors && validationResponse.data?.validateQcSeals) {
|
|
461
|
+
minimumSealNumber = validationResponse.data.validateQcSeals.minimumSealNumber || 0
|
|
462
|
+
}
|
|
463
|
+
} catch (err) {
|
|
464
|
+
// If query fails, default to 0 (don't block tote scanning)
|
|
465
|
+
console.warn('Failed to fetch minimum seal number:', err)
|
|
466
|
+
}
|
|
467
|
+
|
|
451
468
|
openPopup(
|
|
452
469
|
html`
|
|
453
470
|
<tote-popup
|
|
454
471
|
.orderNo="${orderNo}"
|
|
455
472
|
.toteNo="${this.toteNo}"
|
|
456
473
|
.bizplace="${bizplace}"
|
|
474
|
+
.minimumSealNumber="${minimumSealNumber}"
|
|
457
475
|
@completed="${async e => {
|
|
458
476
|
this.toteNo = e.detail
|
|
459
477
|
// Check seal status after tote is sealed (may have changed)
|
|
460
478
|
await this._checkAndUpdateSealStatus()
|
|
479
|
+
|
|
480
|
+
// Refresh data for both QC and LOADING tabs to reflect any undone items
|
|
481
|
+
// Use the same logic as validateSorting to determine which order/bin to refresh
|
|
482
|
+
const orderNoForRefresh = this.worksheet?.isReleaseGoodScan
|
|
483
|
+
? this.worksheet?.releaseGood?.name
|
|
484
|
+
: this._useQcSku
|
|
485
|
+
? this.worksheet?.binLocationName
|
|
486
|
+
: this.worksheet?.releaseGood?.name
|
|
487
|
+
const useQcSkuForRefresh = this._useQcSku && !this.worksheet?.isReleaseGoodScan
|
|
488
|
+
|
|
489
|
+
const [qcData, loadingData] = await Promise.all([
|
|
490
|
+
this.fetchData(orderNoForRefresh, 'qc', useQcSkuForRefresh),
|
|
491
|
+
this.fetchData(orderNoForRefresh, 'loading', useQcSkuForRefresh)
|
|
492
|
+
])
|
|
493
|
+
|
|
494
|
+
// QC tab: items that still need QC (sortedQty < pickedQty)
|
|
495
|
+
this.qcData = qcData.filter(data => data?.sortedQty < data?.pickedQty) || []
|
|
496
|
+
// Loading tab: items that have completed QC (sortedQty >= pickedQty) and are not yet loaded
|
|
497
|
+
this.loadingData =
|
|
498
|
+
loadingData.filter(data => data?.sortedQty >= data?.pickedQty && data.status != WORKSHEET_STATUS.DONE.value) || []
|
|
499
|
+
|
|
461
500
|
// Update button visibility after sealing (Tote button may need to be hidden if sealing is complete)
|
|
462
501
|
this.getButton()
|
|
463
|
-
// Update QC banner state after
|
|
502
|
+
// Update QC banner state after data is refreshed
|
|
464
503
|
if (this._updateQcBannerState) {
|
|
465
504
|
await this._updateQcBannerState()
|
|
466
505
|
}
|
|
@@ -33,6 +33,17 @@ import {
|
|
|
33
33
|
} from '../constants'
|
|
34
34
|
|
|
35
35
|
const PICKING_TYPES = { PICKING, BATCH_PICKING }
|
|
36
|
+
|
|
37
|
+
const VALIDATE_QC_SEALS = gql`
|
|
38
|
+
query validateQcSeals($releaseGoodNo: String!) {
|
|
39
|
+
validateQcSeals(releaseGoodNo: $releaseGoodNo) {
|
|
40
|
+
valid
|
|
41
|
+
error
|
|
42
|
+
minimumSealNumber
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
`
|
|
46
|
+
|
|
36
47
|
class PickingProduct extends connect(store)(localize(i18next)(PageView)) {
|
|
37
48
|
static get properties() {
|
|
38
49
|
return {
|
|
@@ -976,14 +987,37 @@ class PickingProduct extends connect(store)(localize(i18next)(PageView)) {
|
|
|
976
987
|
|
|
977
988
|
async openTotePopup() {
|
|
978
989
|
await sleep(1010)
|
|
990
|
+
|
|
991
|
+
// Query for minimum seal number setting
|
|
992
|
+
let minimumSealNumber = 0
|
|
993
|
+
try {
|
|
994
|
+
const validationResponse = await client.query({
|
|
995
|
+
query: VALIDATE_QC_SEALS,
|
|
996
|
+
variables: { releaseGoodNo: this.releaseGoodNo },
|
|
997
|
+
context: gqlContext()
|
|
998
|
+
})
|
|
999
|
+
|
|
1000
|
+
if (!validationResponse.errors && validationResponse.data?.validateQcSeals) {
|
|
1001
|
+
minimumSealNumber = validationResponse.data.validateQcSeals.minimumSealNumber || 0
|
|
1002
|
+
}
|
|
1003
|
+
} catch (err) {
|
|
1004
|
+
// If query fails, default to 0 (don't block tote scanning)
|
|
1005
|
+
console.warn('Failed to fetch minimum seal number:', err)
|
|
1006
|
+
}
|
|
1007
|
+
|
|
979
1008
|
openPopup(
|
|
980
1009
|
html`
|
|
981
1010
|
<tote-popup
|
|
982
1011
|
.orderNo="${this.releaseGoodNo}"
|
|
983
1012
|
.toteNo="${this.toteNo}"
|
|
984
1013
|
.bizplace="${this._bizplaceName}"
|
|
1014
|
+
.minimumSealNumber="${minimumSealNumber}"
|
|
985
1015
|
@completed="${async e => {
|
|
986
1016
|
this.toteNo = e.detail
|
|
1017
|
+
// Refresh the item list to reflect any undone items
|
|
1018
|
+
await this.fetchInventories(
|
|
1019
|
+
this.refPickingType === PICKING_TYPES.PICKING.value ? this.releaseGoodNo : this.taskNo
|
|
1020
|
+
)
|
|
987
1021
|
}}"
|
|
988
1022
|
></tote-popup>
|
|
989
1023
|
`,
|
|
@@ -21,6 +21,16 @@ import { fetchSettingRule } from '../../fetch-setting-value'
|
|
|
21
21
|
import { WORKSHEET_STATUS } from '../constants'
|
|
22
22
|
import { decodeQR, fetchPageSettings, isValidHttpUrl } from '../../util'
|
|
23
23
|
|
|
24
|
+
const VALIDATE_QC_SEALS = gql`
|
|
25
|
+
query validateQcSeals($releaseGoodNo: String!) {
|
|
26
|
+
validateQcSeals(releaseGoodNo: $releaseGoodNo) {
|
|
27
|
+
valid
|
|
28
|
+
error
|
|
29
|
+
minimumSealNumber
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
`
|
|
33
|
+
|
|
24
34
|
class SortingProduct extends connect(store)(localize(i18next)(PageView)) {
|
|
25
35
|
static get properties() {
|
|
26
36
|
return {
|
|
@@ -515,14 +525,34 @@ class SortingProduct extends connect(store)(localize(i18next)(PageView)) {
|
|
|
515
525
|
|
|
516
526
|
async _openTotePopup() {
|
|
517
527
|
await sleep(1010)
|
|
528
|
+
|
|
529
|
+
// Query for minimum seal number setting
|
|
530
|
+
let minimumSealNumber = 0
|
|
531
|
+
try {
|
|
532
|
+
const validationResponse = await client.query({
|
|
533
|
+
query: VALIDATE_QC_SEALS,
|
|
534
|
+
variables: { releaseGoodNo: this._selectedReleaseGood }
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
if (!validationResponse.errors && validationResponse.data?.validateQcSeals) {
|
|
538
|
+
minimumSealNumber = validationResponse.data.validateQcSeals.minimumSealNumber || 0
|
|
539
|
+
}
|
|
540
|
+
} catch (err) {
|
|
541
|
+
// If query fails, default to 0 (don't block tote scanning)
|
|
542
|
+
console.warn('Failed to fetch minimum seal number:', err)
|
|
543
|
+
}
|
|
544
|
+
|
|
518
545
|
openPopup(
|
|
519
546
|
html`
|
|
520
547
|
<tote-popup
|
|
521
548
|
.orderNo="${this._selectedReleaseGood}"
|
|
522
549
|
.toteNo="${this.toteNo}"
|
|
523
550
|
.bizplace="${this.bizplaceName}"
|
|
551
|
+
.minimumSealNumber="${minimumSealNumber}"
|
|
524
552
|
@completed="${async e => {
|
|
525
553
|
this.toteNo = e.detail
|
|
554
|
+
// Refresh the item list to reflect any undone items
|
|
555
|
+
await this._fetchItemList(this._selectedReleaseGood)
|
|
526
556
|
}}"
|
|
527
557
|
></tote-popup>
|
|
528
558
|
`,
|
|
@@ -6,7 +6,7 @@ import { css, html, LitElement } from 'lit-element'
|
|
|
6
6
|
|
|
7
7
|
import { MultiColumnFormStyles, SingleColumnFormStyles } from '@things-factory/form-ui'
|
|
8
8
|
import { i18next, localize } from '@things-factory/i18n-base'
|
|
9
|
-
import { client } from '@things-factory/shell'
|
|
9
|
+
import { client, CustomAlert } from '@things-factory/shell'
|
|
10
10
|
import { ScrollbarStyles } from '@things-factory/styles'
|
|
11
11
|
import { isMobileDevice } from '@things-factory/utils'
|
|
12
12
|
import { TOTE_STATUS } from '../constants'
|
|
@@ -20,6 +20,7 @@ class TotePopup extends localize(i18next)(LitElement) {
|
|
|
20
20
|
data: Object,
|
|
21
21
|
orderNo: String,
|
|
22
22
|
toteNo: String,
|
|
23
|
+
minimumSealNumber: Number,
|
|
23
24
|
_totalSeal: Number,
|
|
24
25
|
_selectedToteNo: String,
|
|
25
26
|
_toteNoList: Object,
|
|
@@ -76,11 +77,26 @@ class TotePopup extends localize(i18next)(LitElement) {
|
|
|
76
77
|
.break {
|
|
77
78
|
word-wrap: break-word;
|
|
78
79
|
}
|
|
80
|
+
.mobile-hint {
|
|
81
|
+
padding: 10px;
|
|
82
|
+
background-color: var(--primary-light-color, #f0f0f0);
|
|
83
|
+
border-left: 4px solid var(--primary-color, #2196f3);
|
|
84
|
+
margin: 10px;
|
|
85
|
+
font-size: 0.9em;
|
|
86
|
+
}
|
|
87
|
+
.refresh-button {
|
|
88
|
+
margin: 10px;
|
|
89
|
+
display: flex;
|
|
90
|
+
justify-content: center;
|
|
91
|
+
}
|
|
79
92
|
`
|
|
80
93
|
]
|
|
81
94
|
}
|
|
82
95
|
|
|
83
96
|
render() {
|
|
97
|
+
const shouldShowSealSection = this._shouldShowSealSection()
|
|
98
|
+
const sealInputDisabled = !this.toteNo || this.toteNo === ''
|
|
99
|
+
|
|
84
100
|
return html`
|
|
85
101
|
<form class="multi-column-form">
|
|
86
102
|
<fieldset>
|
|
@@ -121,34 +137,46 @@ class TotePopup extends localize(i18next)(LitElement) {
|
|
|
121
137
|
</fieldset>
|
|
122
138
|
</form>
|
|
123
139
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
140
|
+
${shouldShowSealSection
|
|
141
|
+
? html`
|
|
142
|
+
<form class="multi-column-form">
|
|
143
|
+
<fieldset>
|
|
144
|
+
<label>${i18next.t('field.seal_no')}</label>
|
|
145
|
+
<barcode-scanable-input
|
|
146
|
+
name="sealNo"
|
|
147
|
+
custom-input
|
|
148
|
+
?disabled="${sealInputDisabled}"
|
|
149
|
+
@keypress="${e => {
|
|
150
|
+
if (e.keyCode === 13) {
|
|
151
|
+
e.preventDefault()
|
|
152
|
+
this.sealNo = this.sealNoInput.value
|
|
153
|
+
if (this.toteNo) {
|
|
154
|
+
this._sealTote()
|
|
155
|
+
} else {
|
|
156
|
+
this._showToast({ message: i18next.t('text.tote_is_not_scanned') })
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}}"
|
|
160
|
+
></barcode-scanable-input>
|
|
161
|
+
</fieldset>
|
|
162
|
+
|
|
163
|
+
<label>${i18next.t('field.scanned')} (${this._totalSeal || 0})</label>
|
|
164
|
+
<select name="sealNoList">
|
|
165
|
+
<option value="">-- ${i18next.t('text.seal_number')} --</option>
|
|
166
|
+
${(this._sealNoList?.orderToteSeals || []).map(
|
|
167
|
+
list => html` <option value="${list.name}">${list.name}</option> `
|
|
168
|
+
)}
|
|
169
|
+
</select>
|
|
170
|
+
</form>
|
|
171
|
+
`
|
|
172
|
+
: ''}
|
|
173
|
+
${isMobileDevice() && this._selectedToteNo?.length > 0
|
|
174
|
+
? html`
|
|
175
|
+
<div class="mobile-hint">
|
|
176
|
+
${i18next.t('text.viewing_items_in_tote')}: <strong>${this._selectedToteNo[0].name}</strong>
|
|
177
|
+
</div>
|
|
178
|
+
`
|
|
179
|
+
: ''}
|
|
152
180
|
|
|
153
181
|
<div class="grist-container">
|
|
154
182
|
<div class="grist">
|
|
@@ -161,9 +189,35 @@ class TotePopup extends localize(i18next)(LitElement) {
|
|
|
161
189
|
</div>
|
|
162
190
|
</div>
|
|
163
191
|
|
|
192
|
+
${isMobileDevice()
|
|
193
|
+
? html`
|
|
194
|
+
<div class="refresh-button">
|
|
195
|
+
<mwc-button
|
|
196
|
+
icon="refresh"
|
|
197
|
+
@click=${async () => {
|
|
198
|
+
if (this.grist) {
|
|
199
|
+
await this.grist.fetch()
|
|
200
|
+
this._showToast({ type: 'info', message: i18next.t('text.list_refreshed') })
|
|
201
|
+
}
|
|
202
|
+
}}
|
|
203
|
+
label="${i18next.t('button.refresh')}"
|
|
204
|
+
></mwc-button>
|
|
205
|
+
</div>
|
|
206
|
+
`
|
|
207
|
+
: ''}
|
|
208
|
+
|
|
164
209
|
<div class="button-container">
|
|
210
|
+
${this._shouldShowUndoButton()
|
|
211
|
+
? html`
|
|
212
|
+
<mwc-button
|
|
213
|
+
@click=${this._undoSelectedItems.bind(this)}
|
|
214
|
+
raised
|
|
215
|
+
style="--mdc-theme-primary: var(--status-warning-color, #ff9800); margin-right: 10px;"
|
|
216
|
+
label="${i18next.t('button.undo')}"
|
|
217
|
+
></mwc-button>
|
|
218
|
+
`
|
|
219
|
+
: ''}
|
|
165
220
|
<mwc-button
|
|
166
|
-
danger
|
|
167
221
|
@click=${() => {
|
|
168
222
|
this.dispatchEvent(
|
|
169
223
|
new CustomEvent('completed', {
|
|
@@ -188,11 +242,25 @@ class TotePopup extends localize(i18next)(LitElement) {
|
|
|
188
242
|
}
|
|
189
243
|
|
|
190
244
|
get sealNoInput() {
|
|
191
|
-
return this.shadowRoot.querySelector('barcode-scanable-input[name=sealNo]')
|
|
245
|
+
return this.shadowRoot.querySelector('barcode-scanable-input[name=sealNo]')?.shadowRoot?.querySelector('input')
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
_shouldShowSealSection() {
|
|
249
|
+
// Only show seal section if minimumSealNumber is greater than 0
|
|
250
|
+
return (this.minimumSealNumber || 0) > 0
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
_shouldShowUndoButton() {
|
|
254
|
+
// Show undo button only if selected tote is not sealed
|
|
255
|
+
const selectedTote = this._selectedToteNo?.[0]
|
|
256
|
+
return selectedTote && !selectedTote?.closedDate
|
|
192
257
|
}
|
|
193
258
|
|
|
194
259
|
_focusOnSealNoInput() {
|
|
195
|
-
|
|
260
|
+
const sealInput = this.sealNoInput
|
|
261
|
+
if (sealInput) {
|
|
262
|
+
setTimeout(() => sealInput.focus(), 50)
|
|
263
|
+
}
|
|
196
264
|
}
|
|
197
265
|
|
|
198
266
|
_focusOnToteNoInput() {
|
|
@@ -201,28 +269,36 @@ class TotePopup extends localize(i18next)(LitElement) {
|
|
|
201
269
|
|
|
202
270
|
async firstUpdated() {
|
|
203
271
|
this.config = {
|
|
272
|
+
list: { fields: ['sku', 'packingType', 'pickedQty'] },
|
|
204
273
|
pagination: { pages: [10, 20, 50, 100] },
|
|
205
274
|
rows: {
|
|
206
|
-
appendable: false
|
|
275
|
+
appendable: false,
|
|
276
|
+
selectable: {
|
|
277
|
+
multiple: true
|
|
278
|
+
}
|
|
207
279
|
},
|
|
208
280
|
columns: [
|
|
209
281
|
{ type: 'gutter', gutterName: 'sequence' },
|
|
282
|
+
{ type: 'gutter', gutterName: 'row-selector', multiple: true },
|
|
210
283
|
{
|
|
211
284
|
type: 'string',
|
|
212
285
|
name: 'sku',
|
|
213
286
|
header: i18next.t('field.sku'),
|
|
287
|
+
label: true,
|
|
214
288
|
width: 180
|
|
215
289
|
},
|
|
216
290
|
{
|
|
217
291
|
type: 'string',
|
|
218
292
|
name: 'packingType',
|
|
219
293
|
header: i18next.t('field.packing_type'),
|
|
294
|
+
label: true,
|
|
220
295
|
width: 180
|
|
221
296
|
},
|
|
222
297
|
{
|
|
223
298
|
type: 'string',
|
|
224
299
|
name: 'pickedQty',
|
|
225
300
|
header: i18next.t('field.picked_qty'),
|
|
301
|
+
label: true,
|
|
226
302
|
width: 180
|
|
227
303
|
}
|
|
228
304
|
]
|
|
@@ -269,7 +345,8 @@ class TotePopup extends localize(i18next)(LitElement) {
|
|
|
269
345
|
toteNo: this._selectedToteNo[0].name,
|
|
270
346
|
sortings,
|
|
271
347
|
orderNo: this.orderNo
|
|
272
|
-
}
|
|
348
|
+
},
|
|
349
|
+
fetchPolicy: 'no-cache'
|
|
273
350
|
})
|
|
274
351
|
|
|
275
352
|
if (!response.errors) {
|
|
@@ -279,7 +356,10 @@ class TotePopup extends localize(i18next)(LitElement) {
|
|
|
279
356
|
}
|
|
280
357
|
|
|
281
358
|
if (this.toteNo && this.toteNo !== '') {
|
|
282
|
-
|
|
359
|
+
// Only focus on seal input if seal section is visible
|
|
360
|
+
if (this._shouldShowSealSection()) {
|
|
361
|
+
this._focusOnSealNoInput()
|
|
362
|
+
}
|
|
283
363
|
} else {
|
|
284
364
|
this._focusOnToteNoInput()
|
|
285
365
|
}
|
|
@@ -306,6 +386,21 @@ class TotePopup extends localize(i18next)(LitElement) {
|
|
|
306
386
|
|
|
307
387
|
if (result) {
|
|
308
388
|
await this._checkToteAvailability()
|
|
389
|
+
|
|
390
|
+
// After successful tote scan, refresh the tote list and auto-select the scanned tote
|
|
391
|
+
if (this.toteNo && this.toteNo !== '') {
|
|
392
|
+
await this._fetchToteList()
|
|
393
|
+
|
|
394
|
+
// Auto-select the scanned tote for PDA/mobile users
|
|
395
|
+
const scannedTote = this._toteNoList?.find(tote => tote.name === this.toteNo)
|
|
396
|
+
if (scannedTote) {
|
|
397
|
+
this._selectedToteNo = [scannedTote]
|
|
398
|
+
// Trigger grist to fetch items for the selected tote
|
|
399
|
+
if (this.grist) {
|
|
400
|
+
await this.grist.fetch()
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
309
404
|
} else {
|
|
310
405
|
this.toteNoInput.value = ''
|
|
311
406
|
this.toteNo = ''
|
|
@@ -315,7 +410,10 @@ class TotePopup extends localize(i18next)(LitElement) {
|
|
|
315
410
|
}
|
|
316
411
|
|
|
317
412
|
if (this.toteNo && this.toteNo !== '') {
|
|
318
|
-
|
|
413
|
+
// Only focus on seal input if seal section is visible
|
|
414
|
+
if (this._shouldShowSealSection()) {
|
|
415
|
+
this._focusOnSealNoInput()
|
|
416
|
+
}
|
|
319
417
|
} else {
|
|
320
418
|
this._focusOnToteNoInput()
|
|
321
419
|
}
|
|
@@ -420,6 +518,7 @@ class TotePopup extends localize(i18next)(LitElement) {
|
|
|
420
518
|
items {
|
|
421
519
|
id
|
|
422
520
|
name
|
|
521
|
+
closedDate
|
|
423
522
|
orderToteSeals {
|
|
424
523
|
id
|
|
425
524
|
name
|
|
@@ -446,6 +545,97 @@ class TotePopup extends localize(i18next)(LitElement) {
|
|
|
446
545
|
}
|
|
447
546
|
}
|
|
448
547
|
|
|
548
|
+
async _undoSelectedItems() {
|
|
549
|
+
// Get selected rows from grist
|
|
550
|
+
const selectedRecords = this.grist.selected
|
|
551
|
+
|
|
552
|
+
if (!selectedRecords || selectedRecords.length === 0) {
|
|
553
|
+
await CustomAlert({
|
|
554
|
+
type: 'info',
|
|
555
|
+
title: i18next.t('text.nothing_selected'),
|
|
556
|
+
text: i18next.t('text.please_select_items_to_undo')
|
|
557
|
+
})
|
|
558
|
+
return
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Confirm before deleting
|
|
562
|
+
const itemCount = selectedRecords.length
|
|
563
|
+
const itemText =
|
|
564
|
+
itemCount > 1
|
|
565
|
+
? `${itemCount} items`
|
|
566
|
+
: `${selectedRecords[0].sku} (${selectedRecords[0].pickedQty} ${selectedRecords[0].packingType})`
|
|
567
|
+
|
|
568
|
+
const answer = await CustomAlert({
|
|
569
|
+
type: 'warning',
|
|
570
|
+
title: i18next.t('button.undo'),
|
|
571
|
+
text: i18next.t('text.are_you_sure_to_remove_x_from_tote', { x: itemText }),
|
|
572
|
+
confirmButton: {
|
|
573
|
+
text: i18next.t('button.confirm'),
|
|
574
|
+
color: '#ff9800'
|
|
575
|
+
},
|
|
576
|
+
cancelButton: {
|
|
577
|
+
text: i18next.t('button.cancel'),
|
|
578
|
+
color: '#cfcfcf'
|
|
579
|
+
}
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
if (!answer.value) return
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
// Undo all selected items
|
|
586
|
+
for (const record of selectedRecords) {
|
|
587
|
+
await this._unsortItem(record.id, false) // Don't show individual toasts
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Show single success message for all items
|
|
591
|
+
this._showToast({
|
|
592
|
+
type: 'info',
|
|
593
|
+
message:
|
|
594
|
+
itemCount > 1
|
|
595
|
+
? i18next.t('text.items_removed_from_tote', { count: itemCount })
|
|
596
|
+
: i18next.t('text.item_removed_from_tote')
|
|
597
|
+
})
|
|
598
|
+
|
|
599
|
+
// Refresh once after all items are removed
|
|
600
|
+
await this._fetchToteList()
|
|
601
|
+
await this.grist.fetch()
|
|
602
|
+
} catch (e) {
|
|
603
|
+
this._showToast(e)
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async _unsortItem(orderToteItemId, showToast = true) {
|
|
608
|
+
try {
|
|
609
|
+
const response = await client.mutate({
|
|
610
|
+
mutation: gql`
|
|
611
|
+
mutation unsortItem($orderToteItemId: String!, $reason: String) {
|
|
612
|
+
unsortItem(orderToteItemId: $orderToteItemId, reason: $reason)
|
|
613
|
+
}
|
|
614
|
+
`,
|
|
615
|
+
variables: {
|
|
616
|
+
orderToteItemId,
|
|
617
|
+
reason: 'Removed by user from tote popup'
|
|
618
|
+
}
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
if (!response.errors) {
|
|
622
|
+
if (showToast) {
|
|
623
|
+
this._showToast({
|
|
624
|
+
type: 'info',
|
|
625
|
+
message: i18next.t('text.item_removed_from_tote')
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
// Refresh the tote list and grist
|
|
629
|
+
await this._fetchToteList()
|
|
630
|
+
await this.grist.fetch()
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
} catch (e) {
|
|
634
|
+
this._showToast(e)
|
|
635
|
+
throw e // Re-throw to stop batch processing
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
449
639
|
_showToast({ type, message }) {
|
|
450
640
|
document.dispatchEvent(
|
|
451
641
|
new CustomEvent('notify', {
|