@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.
- package/client/pages/inbound/unload-product.js +30 -33
- package/client/pages/outbound/loading-product.js +2 -3
- package/client/pages/outbound/loading-v2/loading-execution-base.js +568 -56
- package/client/pages/outbound/loading-v2/loading-execution.js +258 -8
- package/client/pages/outbound/loading-v2/loading.js +82 -12
- package/client/pages/outbound/picking-product.js +11 -6
- package/client/pages/outbound/picking-v2/picking-execution-base.js +8 -8
- package/client/pages/outbound/picking-v2/picking-execution.js +75 -0
- package/client/pages/outbound/route-label-history-dialog.js +253 -0
- package/client/pages/outbound/route-label-popup.js +637 -131
- package/client/pages/outbound/show-ro-list-popup.js +8 -4
- package/client/pages/outbound/single-outbound-worksheet.js +55 -6
- package/client/pages/outbound/sorting-product.js +71 -25
- package/client/pages/outbound/zone-worksheet.js +62 -3
- package/dist-server/entities/index.js +9 -0
- package/dist-server/entities/index.js.map +1 -0
- package/dist-server/entities/route-label-sequence-counter.js +75 -0
- package/dist-server/entities/route-label-sequence-counter.js.map +1 -0
- package/dist-server/entities/route-label-sequence-log.js +74 -0
- package/dist-server/entities/route-label-sequence-log.js.map +1 -0
- package/dist-server/graphql/resolvers/index.js +3 -0
- package/dist-server/graphql/resolvers/index.js.map +1 -1
- package/dist-server/graphql/resolvers/outbound/complete-load.js +40 -3
- package/dist-server/graphql/resolvers/outbound/complete-load.js.map +1 -1
- package/dist-server/graphql/resolvers/outbound/find-loadable-release-good-by-bin.js +50 -11
- package/dist-server/graphql/resolvers/outbound/find-loadable-release-good-by-bin.js.map +1 -1
- package/dist-server/graphql/resolvers/outbound/find-loadable-release-good.js +90 -48
- package/dist-server/graphql/resolvers/outbound/find-loadable-release-good.js.map +1 -1
- package/dist-server/graphql/resolvers/outbound/loading-worksheet-v2.js +15 -7
- package/dist-server/graphql/resolvers/outbound/loading-worksheet-v2.js.map +1 -1
- package/dist-server/graphql/resolvers/outbound/sort-item.js +95 -16
- package/dist-server/graphql/resolvers/outbound/sort-item.js.map +1 -1
- package/dist-server/graphql/resolvers/route-label/index.js +7 -0
- package/dist-server/graphql/resolvers/route-label/index.js.map +1 -0
- package/dist-server/graphql/resolvers/route-label/route-label.js +220 -0
- package/dist-server/graphql/resolvers/route-label/route-label.js.map +1 -0
- package/dist-server/graphql/resolvers/zone-worksheet/index.js.map +1 -1
- package/dist-server/graphql/resolvers/zone-worksheet/zone-picking.js +123 -27
- package/dist-server/graphql/resolvers/zone-worksheet/zone-picking.js.map +1 -1
- package/dist-server/graphql/resolvers/zone-worksheet/zone-worksheet.js.map +1 -1
- package/dist-server/graphql/types/index.js +4 -0
- package/dist-server/graphql/types/index.js.map +1 -1
- package/dist-server/graphql/types/outbound/index.js +7 -1
- package/dist-server/graphql/types/outbound/index.js.map +1 -1
- package/dist-server/graphql/types/route-label/index.js +14 -0
- package/dist-server/graphql/types/route-label/index.js.map +1 -0
- package/dist-server/graphql/types/route-label/route-label-sequence.js +53 -0
- package/dist-server/graphql/types/route-label/route-label-sequence.js.map +1 -0
- package/dist-server/index.js +5 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/validators/setting-validators.js +519 -0
- package/dist-server/validators/setting-validators.js.map +1 -0
- package/helps/operato-wms/outbound/picking-product.en.md +156 -158
- package/package.json +50 -50
- package/server/entities/index.ts +6 -0
- package/server/entities/route-label-sequence-counter.ts +56 -0
- package/server/entities/route-label-sequence-log.ts +47 -0
- package/server/graphql/resolvers/index.ts +3 -0
- package/server/graphql/resolvers/outbound/complete-load.ts +42 -1
- package/server/graphql/resolvers/outbound/find-loadable-release-good-by-bin.ts +56 -13
- package/server/graphql/resolvers/outbound/find-loadable-release-good.ts +120 -57
- package/server/graphql/resolvers/outbound/loading-worksheet-v2.ts +9 -2
- package/server/graphql/resolvers/outbound/sort-item.ts +125 -21
- package/server/graphql/resolvers/route-label/index.ts +16 -0
- package/server/graphql/resolvers/route-label/route-label.ts +264 -0
- package/server/graphql/resolvers/zone-worksheet/index.ts +2 -2
- package/server/graphql/resolvers/zone-worksheet/zone-picking.ts +156 -39
- package/server/graphql/resolvers/zone-worksheet/zone-worksheet.ts +1 -2
- package/server/graphql/types/index.ts +4 -0
- package/server/graphql/types/outbound/index.ts +7 -1
- package/server/graphql/types/route-label/index.ts +18 -0
- package/server/graphql/types/route-label/route-label-sequence.ts +50 -0
- package/server/index.ts +7 -1
- package/server/validators/setting-validators.ts +668 -0
- package/translations/en.json +280 -252
- package/translations/ko.json +29 -1
- package/translations/ms.json +28 -0
- package/translations/zh.json +28 -0
|
@@ -142,6 +142,29 @@ export class PickingExecution extends PickingExecutionBase {
|
|
|
142
142
|
background: none;
|
|
143
143
|
color: #f3f2f7;
|
|
144
144
|
}
|
|
145
|
+
.picking-banner {
|
|
146
|
+
background-color: #fff3cd;
|
|
147
|
+
border: 1px solid #ffc107;
|
|
148
|
+
border-radius: 4px;
|
|
149
|
+
padding: 12px 16px;
|
|
150
|
+
margin: 10px;
|
|
151
|
+
display: flex;
|
|
152
|
+
justify-content: space-between;
|
|
153
|
+
align-items: center;
|
|
154
|
+
color: #856404;
|
|
155
|
+
}
|
|
156
|
+
.picking-banner button {
|
|
157
|
+
background-color: #007e7f;
|
|
158
|
+
color: white;
|
|
159
|
+
border: none;
|
|
160
|
+
padding: 6px 12px;
|
|
161
|
+
border-radius: 4px;
|
|
162
|
+
cursor: pointer;
|
|
163
|
+
font-weight: bold;
|
|
164
|
+
}
|
|
165
|
+
.picking-banner button:hover {
|
|
166
|
+
background-color: #005f60;
|
|
167
|
+
}
|
|
145
168
|
`
|
|
146
169
|
]
|
|
147
170
|
}
|
|
@@ -212,6 +235,41 @@ export class PickingExecution extends PickingExecutionBase {
|
|
|
212
235
|
}
|
|
213
236
|
}
|
|
214
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Determines if the "Go to PICKED" banner should be shown.
|
|
240
|
+
* Banner appears when:
|
|
241
|
+
* - Current tab is PICKING
|
|
242
|
+
* - PICKING tab has no items (pickingData.length === 0) - uses SAME source as Total SKU
|
|
243
|
+
* - PICKED tab has items (pickedData.length > 0)
|
|
244
|
+
*
|
|
245
|
+
* Uses the same data source as Total SKU display to ensure consistency.
|
|
246
|
+
*/
|
|
247
|
+
_shouldShowPickingBanner() {
|
|
248
|
+
// Banner only shows on PICKING tab
|
|
249
|
+
if (this.state?.activeTab !== 'PICKING') {
|
|
250
|
+
return false
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Use the SAME source as Total SKU display: this.pickingData?.length
|
|
254
|
+
const pickingItemCount = this.pickingData?.length || 0
|
|
255
|
+
const pickedItemCount = this.pickedData?.length || 0
|
|
256
|
+
|
|
257
|
+
// Show banner when PICKING is empty but PICKED has items
|
|
258
|
+
const shouldShow = pickingItemCount === 0 && pickedItemCount > 0
|
|
259
|
+
|
|
260
|
+
// Console assert: catch mismatch if Total SKU > 0 but banner shows
|
|
261
|
+
if (shouldShow && pickingItemCount > 0) {
|
|
262
|
+
console.error('BANNER MISMATCH: Banner showing but Total SKU > 0', {
|
|
263
|
+
pickingItemCount,
|
|
264
|
+
pickedItemCount,
|
|
265
|
+
pickingDataLength: this.pickingData?.length,
|
|
266
|
+
pickedDataLength: this.pickedData?.length
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return shouldShow
|
|
271
|
+
}
|
|
272
|
+
|
|
215
273
|
get pickedListItemConfig() {
|
|
216
274
|
return {
|
|
217
275
|
columnLeft: {
|
|
@@ -371,6 +429,23 @@ export class PickingExecution extends PickingExecutionBase {
|
|
|
371
429
|
</div>
|
|
372
430
|
<!-- Tab Info Section End -->
|
|
373
431
|
|
|
432
|
+
${
|
|
433
|
+
this._shouldShowPickingBanner()
|
|
434
|
+
? html`
|
|
435
|
+
<div class="picking-banner">
|
|
436
|
+
<span
|
|
437
|
+
>${i18next.t('text.all_items_picked_proceed_to_picked', {
|
|
438
|
+
defaultValue: 'All items picked. Proceed to PICKED to complete picking.'
|
|
439
|
+
})}</span
|
|
440
|
+
>
|
|
441
|
+
<button @click=${async e => await this.openTab(e, 'PICKED')}>
|
|
442
|
+
${i18next.t('button.go_to_picked', { defaultValue: 'Go to PICKED' })}
|
|
443
|
+
</button>
|
|
444
|
+
</div>
|
|
445
|
+
`
|
|
446
|
+
: ''
|
|
447
|
+
}
|
|
448
|
+
|
|
374
449
|
<!-- Picking Item Section -->
|
|
375
450
|
<div id="picking" class="tab-content ${this.state?.activeTab == 'PICKING' ? 'active' : ''}">
|
|
376
451
|
<product-list-view .config=${this.pickingListItemConfig} .data=${this.pickingData}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import '@things-factory/grist-ui'
|
|
2
|
+
import '@material/mwc-button'
|
|
3
|
+
|
|
4
|
+
import { css, html, LitElement } from 'lit-element'
|
|
5
|
+
|
|
6
|
+
import { MultiColumnFormStyles, SingleColumnFormStyles } from '@things-factory/form-ui'
|
|
7
|
+
import { i18next, localize } from '@things-factory/i18n-base'
|
|
8
|
+
|
|
9
|
+
class RouteLabelHistoryDialog extends localize(i18next)(LitElement) {
|
|
10
|
+
static get styles() {
|
|
11
|
+
return [
|
|
12
|
+
MultiColumnFormStyles,
|
|
13
|
+
SingleColumnFormStyles,
|
|
14
|
+
css`
|
|
15
|
+
:host {
|
|
16
|
+
padding: 10px;
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
overflow: hidden;
|
|
20
|
+
background-color: var(--main-section-background-color);
|
|
21
|
+
height: 100%;
|
|
22
|
+
max-height: calc(100vh - 200px);
|
|
23
|
+
}
|
|
24
|
+
.history-dialog-content {
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
gap: 12px;
|
|
28
|
+
flex: 1;
|
|
29
|
+
min-height: 0;
|
|
30
|
+
overflow: hidden;
|
|
31
|
+
}
|
|
32
|
+
.history-grist {
|
|
33
|
+
flex: 1;
|
|
34
|
+
min-height: 0;
|
|
35
|
+
border: 1px solid var(--form-border-color);
|
|
36
|
+
background: var(--main-section-background-color);
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
}
|
|
39
|
+
.history-grist data-grist {
|
|
40
|
+
height: 100%;
|
|
41
|
+
overflow-y: auto;
|
|
42
|
+
}
|
|
43
|
+
.button-container {
|
|
44
|
+
padding: 10px 0 12px 0;
|
|
45
|
+
text-align: center;
|
|
46
|
+
flex-shrink: 0;
|
|
47
|
+
}
|
|
48
|
+
`
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static get properties() {
|
|
53
|
+
return {
|
|
54
|
+
target: Object,
|
|
55
|
+
logs: Array,
|
|
56
|
+
appendSeqField: String,
|
|
57
|
+
routeLabel: Object,
|
|
58
|
+
onReprint: Function
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
constructor() {
|
|
63
|
+
super()
|
|
64
|
+
this.logs = []
|
|
65
|
+
this._historyReprintSeq = ''
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get historyGrist() {
|
|
69
|
+
return this.shadowRoot.querySelector('#history-grist')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_showToast({ type, message }) {
|
|
73
|
+
document.dispatchEvent(
|
|
74
|
+
new CustomEvent('notify', {
|
|
75
|
+
detail: {
|
|
76
|
+
type,
|
|
77
|
+
message
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_validateSequence(seq) {
|
|
84
|
+
if (!seq || !seq.trim()) {
|
|
85
|
+
return { valid: false, error: '' }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const seqTrimmed = seq.trim()
|
|
89
|
+
|
|
90
|
+
// Check for decimal points in the input
|
|
91
|
+
// Split by comma first to check each part separately
|
|
92
|
+
const parts = seqTrimmed.split(',')
|
|
93
|
+
for (const part of parts) {
|
|
94
|
+
if (!part) continue
|
|
95
|
+
const trimmedPart = part.trim()
|
|
96
|
+
// Check if the part contains a decimal point
|
|
97
|
+
if (trimmedPart.includes('.')) {
|
|
98
|
+
return {
|
|
99
|
+
valid: false,
|
|
100
|
+
error: i18next.t('text.print_qty_must_be_integer')
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Check if it's a range (contains '-')
|
|
104
|
+
if (trimmedPart.includes('-')) {
|
|
105
|
+
const rangeParts = trimmedPart.split('-')
|
|
106
|
+
for (const rangePart of rangeParts) {
|
|
107
|
+
const trimmedRangePart = rangePart.trim()
|
|
108
|
+
if (trimmedRangePart.includes('.')) {
|
|
109
|
+
return {
|
|
110
|
+
valid: false,
|
|
111
|
+
error: i18next.t('text.print_qty_must_be_integer')
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Parse the input to support ranges (e.g., "1-2") and specific numbers (e.g., "1" or "1,3,5-7")
|
|
119
|
+
const parseSeqInput = input => {
|
|
120
|
+
if (!input || typeof input !== 'string') return []
|
|
121
|
+
const cleaned = input.replace(/\s/g, '')
|
|
122
|
+
if (!cleaned) return []
|
|
123
|
+
const parts = cleaned.split(',')
|
|
124
|
+
const numbers = new Set()
|
|
125
|
+
for (const part of parts) {
|
|
126
|
+
if (!part) continue
|
|
127
|
+
const range = part.split('-')
|
|
128
|
+
if (range.length === 1) {
|
|
129
|
+
const n = parseInt(range[0], 10)
|
|
130
|
+
if (Number.isFinite(n) && n > 0) numbers.add(n)
|
|
131
|
+
} else if (range.length === 2) {
|
|
132
|
+
const a = parseInt(range[0], 10)
|
|
133
|
+
const b = parseInt(range[1], 10)
|
|
134
|
+
if (Number.isFinite(a) && Number.isFinite(b) && a > 0 && b > 0) {
|
|
135
|
+
const from = Math.min(a, b)
|
|
136
|
+
const to = Math.max(a, b)
|
|
137
|
+
for (let i = from; i <= to; i++) numbers.add(i)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return Array.from(numbers).sort((x, y) => x - y)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const parsedSeqs = parseSeqInput(seqTrimmed)
|
|
145
|
+
if (parsedSeqs.length === 0) {
|
|
146
|
+
return { valid: false, error: i18next.t('text.invalid_sequence_number') }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check if all parsed sequences fall within any of the history ranges
|
|
150
|
+
const allValid = parsedSeqs.every(seqNum => {
|
|
151
|
+
return (this.logs || []).some(log => {
|
|
152
|
+
const from = parseInt(log.from, 10)
|
|
153
|
+
const to = parseInt(log.to, 10)
|
|
154
|
+
return !isNaN(from) && !isNaN(to) && seqNum >= from && seqNum <= to
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
if (!allValid) {
|
|
159
|
+
const ranges = (this.logs || []).map(log => `${log.from}-${log.to}`).join(', ')
|
|
160
|
+
return {
|
|
161
|
+
valid: false,
|
|
162
|
+
error: i18next.t('text.sequence_out_of_range', { ranges })
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { valid: true, error: '' }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
render() {
|
|
170
|
+
return html`
|
|
171
|
+
<div class="history-dialog-content">
|
|
172
|
+
<form class="multi-column-form">
|
|
173
|
+
<label>${i18next.t('field.ro')}</label>
|
|
174
|
+
<input disabled .value="${this.target?.name || ''}" />
|
|
175
|
+
|
|
176
|
+
<label>${i18next.t('field.sequence_reference')}</label>
|
|
177
|
+
<input
|
|
178
|
+
disabled
|
|
179
|
+
.value="${this.appendSeqField === 'refNo'
|
|
180
|
+
? i18next.t('field.ref_no')
|
|
181
|
+
: this.appendSeqField === 'refNo2'
|
|
182
|
+
? i18next.t('field.ref_no_2')
|
|
183
|
+
: i18next.t('field.ref_no_3')}"
|
|
184
|
+
/>
|
|
185
|
+
|
|
186
|
+
<label>${i18next.t('field.reprint_seq')}</label>
|
|
187
|
+
<input
|
|
188
|
+
placeholder="${i18next.t('text.reprint_seq_placeholder')}"
|
|
189
|
+
.value="${this._historyReprintSeq || ''}"
|
|
190
|
+
@input="${e => {
|
|
191
|
+
this._historyReprintSeq = e.currentTarget.value
|
|
192
|
+
}}"
|
|
193
|
+
/>
|
|
194
|
+
</form>
|
|
195
|
+
|
|
196
|
+
<div class="history-grist">
|
|
197
|
+
<data-grist
|
|
198
|
+
id="history-grist"
|
|
199
|
+
.mode=${'GRID'}
|
|
200
|
+
.config=${{
|
|
201
|
+
pagination: { infinite: true },
|
|
202
|
+
rows: { appendable: false, selectable: false },
|
|
203
|
+
list: { fields: ['range', 'printedAt', 'creatorName'] },
|
|
204
|
+
columns: [
|
|
205
|
+
{ type: 'gutter', gutterName: 'sequence' },
|
|
206
|
+
{ type: 'string', name: 'range', header: i18next.t('field.range'), width: 160 },
|
|
207
|
+
{ type: 'string', name: 'printedAt', header: i18next.t('field.printed_at'), width: 190 },
|
|
208
|
+
{ type: 'string', name: 'creatorName', header: i18next.t('field.last_printed_by'), width: 140 }
|
|
209
|
+
]
|
|
210
|
+
}}
|
|
211
|
+
.data=${{ records: this.logs || [], total: (this.logs || []).length }}
|
|
212
|
+
></data-grist>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<div class="button-container">
|
|
217
|
+
<mwc-button
|
|
218
|
+
raised
|
|
219
|
+
danger
|
|
220
|
+
@click="${async () => {
|
|
221
|
+
if (this.onReprint) {
|
|
222
|
+
const reprintSeq = (this._historyReprintSeq || '').trim()
|
|
223
|
+
if (!reprintSeq) {
|
|
224
|
+
// Show toast message - handled in _reprintSelectedFromHistory
|
|
225
|
+
await this.onReprint(reprintSeq)
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Validate sequence before reprinting
|
|
230
|
+
const validation = this._validateSequence(reprintSeq)
|
|
231
|
+
if (!validation.valid) {
|
|
232
|
+
if (validation.error) {
|
|
233
|
+
this._showToast({
|
|
234
|
+
type: 'error',
|
|
235
|
+
message: validation.error
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
await this.onReprint(reprintSeq)
|
|
242
|
+
}
|
|
243
|
+
}}"
|
|
244
|
+
>
|
|
245
|
+
${i18next.t('button.reprint')}
|
|
246
|
+
</mwc-button>
|
|
247
|
+
<mwc-button @click="${() => history.back()}">${i18next.t('button.cancel')}</mwc-button>
|
|
248
|
+
</div>
|
|
249
|
+
`
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
window.customElements.define('route-label-history-dialog', RouteLabelHistoryDialog)
|