@things-factory/operato-wms 4.3.769 → 4.3.770

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 (42) hide show
  1. package/client/pages/components/list-view/product-list-view.js +28 -3
  2. package/client/pages/components/mobile-condition-of-goods-listing.js +342 -0
  3. package/client/pages/components/unloading-worksheet-information-popup.js +299 -0
  4. package/client/pages/constants/order.js +7 -0
  5. package/client/pages/inbound/condition-of-goods-popup.js +646 -0
  6. package/client/pages/inbound/putaway/putaway-product.js +35 -6
  7. package/client/pages/inbound/unload-product-landing-page.js +282 -0
  8. package/client/pages/inbound/unload-product.js +707 -175
  9. package/client/pages/inbound/unloaded-inventories-popup.js +3 -0
  10. package/client/pages/inventory/inventory-adjustment-approval.js +28 -1
  11. package/client/pages/inventory/inventory-adjustment.js +31 -1
  12. package/client/pages/inventory/inventory-by-product-detail.js +15 -0
  13. package/client/pages/inventory/inventory-history.js +18 -0
  14. package/client/pages/inventory/onhand-inventory.js +14 -0
  15. package/client/pages/order/release-order/b2b/b2b-order-list.js +99 -0
  16. package/client/pages/order/release-order/packing-label-history-popup.js +378 -0
  17. package/client/pages/order/release-order/preview-print-packing-list.js +533 -0
  18. package/client/pages/order/release-order/print-quantity-popup.js +118 -0
  19. package/client/pages/outbound/loading-v2/loading-execution-base.js +29 -0
  20. package/client/pages/outbound/loading-v2/loading-execution.js +579 -2
  21. package/client/pages/outbound/loading-v2/loading-package-info-popup.js +182 -0
  22. package/client/pages/outbound/loading-v2/loading.js +18 -0
  23. package/client/pages/report/inbound-order-details-report.js +26 -1
  24. package/client/route.js +17 -0
  25. package/dist-server/graphql/resolvers/outbound/find-loadable-release-good-by-bin.js +9 -1
  26. package/dist-server/graphql/resolvers/outbound/find-loadable-release-good-by-bin.js.map +1 -1
  27. package/dist-server/graphql/resolvers/outbound/loading-worksheet-v2.js +11 -5
  28. package/dist-server/graphql/resolvers/outbound/loading-worksheet-v2.js.map +1 -1
  29. package/dist-server/graphql/resolvers/reports/inbound-order-details-report.js +2 -1
  30. package/dist-server/graphql/resolvers/reports/inbound-order-details-report.js.map +1 -1
  31. package/dist-server/graphql/types/reports/inbound-order-details-report.js +1 -0
  32. package/dist-server/graphql/types/reports/inbound-order-details-report.js.map +1 -1
  33. package/package.json +15 -15
  34. package/server/graphql/resolvers/outbound/find-loadable-release-good-by-bin.ts +9 -1
  35. package/server/graphql/resolvers/outbound/loading-worksheet-v2.ts +13 -1
  36. package/server/graphql/resolvers/reports/inbound-order-details-report.ts +2 -1
  37. package/server/graphql/types/reports/inbound-order-details-report.ts +1 -0
  38. package/things-factory.config.js +8 -0
  39. package/translations/en.json +45 -1
  40. package/translations/ko.json +40 -0
  41. package/translations/ms.json +40 -0
  42. package/translations/zh.json +40 -0
@@ -0,0 +1,533 @@
1
+ import '@things-factory/grist-ui'
2
+ import '@things-factory/barcode-ui'
3
+
4
+ import gql from 'graphql-tag'
5
+ import { css, html } from 'lit-element'
6
+
7
+ import { MultiColumnFormStyles } from '@things-factory/form-ui'
8
+ import { i18next, localize } from '@things-factory/i18n-base'
9
+ import { client, PageView, gqlContext } from '@things-factory/shell'
10
+ import { CommonButtonStyles } from '@things-factory/styles'
11
+ import { isMobileDevice } from '@things-factory/utils'
12
+ import { openPopup } from '@things-factory/layout-base'
13
+
14
+ import { showToast } from '../../../util'
15
+ import './print-quantity-popup'
16
+
17
+ class PreviewPrintPackingList extends localize(i18next)(PageView) {
18
+ constructor() {
19
+ super()
20
+ this.orderNos = []
21
+ this.releaseOrders = []
22
+ this.loadingPackagesMap = {}
23
+ }
24
+
25
+ static get properties() {
26
+ return {
27
+ packingListConfig: Object,
28
+ orderNos: Array,
29
+ releaseOrders: Array,
30
+ loadingPackagesMap: Object
31
+ }
32
+ }
33
+
34
+ static get styles() {
35
+ return [
36
+ MultiColumnFormStyles,
37
+ css`
38
+ :host {
39
+ overflow-x: auto;
40
+ }
41
+ section {
42
+ display: flex;
43
+ flex-direction: column;
44
+ margin-bottom: 20px;
45
+ }
46
+ .form-container {
47
+ display: flex;
48
+ }
49
+ .form-container > form {
50
+ flex: 1;
51
+ }
52
+ barcode-tag {
53
+ width: 100px;
54
+ height: 100px;
55
+ margin: 10px;
56
+ }
57
+ .grist {
58
+ background-color: var(--main-section-background-color);
59
+ display: flex;
60
+ flex-direction: column;
61
+ flex: 1;
62
+ overflow-y: auto;
63
+ }
64
+ .grist-column {
65
+ flex: 1;
66
+ display: flex;
67
+ flex-direction: column;
68
+ overflow: auto;
69
+ }
70
+ data-grist {
71
+ overflow-y: hidden;
72
+ flex: 1;
73
+ }
74
+ h2 {
75
+ padding: var(--subtitle-padding);
76
+ font: var(--subtitle-font);
77
+ color: var(--subtitle-text-color);
78
+ border-bottom: var(--subtitle-border-bottom);
79
+ }
80
+ .grist h2 {
81
+ margin: var(--grist-title-margin);
82
+ border: var(--grist-title-border);
83
+ font: var(--grist-title-font);
84
+ color: var(--secondary-color);
85
+ text-transform: capitalize;
86
+ }
87
+
88
+ .grist h2 mwc-icon {
89
+ vertical-align: middle;
90
+ margin: var(--grist-title-icon-margin);
91
+ font-size: var(--grist-title-icon-size);
92
+ color: var(--grist-title-icon-color);
93
+ }
94
+
95
+ h2 + data-grist {
96
+ padding-top: var(--grist-title-with-grid-padding);
97
+ }
98
+
99
+ .print-iframe {
100
+ position: absolute;
101
+ left: -9999px;
102
+ top: -9999px;
103
+ width: 0;
104
+ height: 0;
105
+ border: none;
106
+ visibility: hidden;
107
+ }
108
+ `
109
+ ]
110
+ }
111
+
112
+ get context() {
113
+ var actions = [
114
+ {
115
+ title: i18next.t('button.back_to_list'),
116
+ action: () => history.back(),
117
+ ...CommonButtonStyles.back
118
+ },
119
+ {
120
+ title: i18next.t('button.print') || 'Print',
121
+ action: this._openPrintQuantityPopup.bind(this),
122
+ ...CommonButtonStyles.print,
123
+ emphasis: {
124
+ ...CommonButtonStyles.activate.emphasis
125
+ }
126
+ }
127
+ ]
128
+
129
+ return {
130
+ title: i18next.t('title.release_orders'),
131
+ actions,
132
+ }
133
+ }
134
+
135
+ _openPrintQuantityPopup() {
136
+ openPopup(
137
+ html`
138
+ <print-quantity-popup
139
+ @print="${e => {
140
+ const printQuantity = e.detail.printQuantity
141
+
142
+ // Validate print quantity
143
+ if (!printQuantity || printQuantity <= 0) {
144
+ showToast({
145
+ type: 'error',
146
+ message: i18next.t('text.print_qty_must_be_at_least_one') || 'Print quantity must be at least 1'
147
+ })
148
+ return
149
+ }
150
+
151
+ // Close popup and execute print
152
+ history.back()
153
+ this._handlePrint(printQuantity)
154
+ }}"
155
+ ></print-quantity-popup>
156
+ `,
157
+ {
158
+ backdrop: true,
159
+ size: 'small',
160
+ title: i18next.t('title.print_packing_label') || 'Print Packing Label'
161
+ }
162
+ )
163
+ }
164
+
165
+ async _handlePrint(printQuantity) {
166
+ if (!this.orderNos?.length) {
167
+ showToast({ type: 'warning', message: i18next.t('text.no_data_to_print') || 'No data to print' })
168
+ return
169
+ }
170
+
171
+ try {
172
+ const response = await fetch('/render_packing_label', {
173
+ method: 'POST',
174
+ credentials: 'include',
175
+ headers: { 'Content-Type': 'application/json' },
176
+ body: JSON.stringify({
177
+ releaseGoodNos: this.orderNos,
178
+ printQuantity: Math.max(1, parseInt(printQuantity) || 1)
179
+ })
180
+ })
181
+
182
+ if (!response.ok) {
183
+ const errorText = await response.text()
184
+ throw new Error(errorText || `Failed to render packing label: ${response.statusText}`)
185
+ }
186
+
187
+ const renderedHtml = await response.text()
188
+ if (!renderedHtml) {
189
+ showToast({ type: 'error', message: i18next.t('text.failed_to_render_template') || 'Failed to render template' })
190
+ return
191
+ }
192
+
193
+ await this._printViaIframe(renderedHtml)
194
+ } catch (error) {
195
+ console.error('Print error:', error)
196
+ showToast({ type: 'error', message: error.message || 'Print failed' })
197
+ }
198
+ }
199
+
200
+ async _printViaIframe(htmlContent) {
201
+ return new Promise((resolve, reject) => {
202
+ const iframe = this._printIframe
203
+ if (!iframe) {
204
+ reject(new Error('Print iframe not found'))
205
+ return
206
+ }
207
+
208
+ const handleLoad = () => {
209
+ iframe.removeEventListener('load', handleLoad)
210
+ try {
211
+ iframe.contentWindow.print()
212
+ resolve()
213
+ } catch (error) {
214
+ reject(error)
215
+ }
216
+ }
217
+
218
+ iframe.addEventListener('load', handleLoad)
219
+
220
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow.document
221
+ iframeDoc.open()
222
+ iframeDoc.write(htmlContent)
223
+ iframeDoc.close()
224
+ })
225
+ }
226
+
227
+ get _printIframe() {
228
+ return this.shadowRoot?.querySelector('#print-iframe')
229
+ }
230
+
231
+ get packingListGrist() {
232
+ return this.shadowRoot.querySelector('data-grist')
233
+ }
234
+
235
+ async pageInitialized(lifecycle) {
236
+ this.packingListConfig = {
237
+ pagination: { limit: 50, pages: [50, 100, 200, 500, 1000] },
238
+ rows: { appendable: false },
239
+ columns: [
240
+ { type: 'gutter', gutterName: 'sequence' },
241
+ {
242
+ type: 'string',
243
+ name: 'sku',
244
+ header: i18next.t('field.sku'),
245
+ width: 120
246
+ },
247
+ {
248
+ type: 'string',
249
+ name: 'productName',
250
+ header: i18next.t('field.name'),
251
+ width: 180
252
+ },
253
+ {
254
+ type: 'string',
255
+ name: 'packingType',
256
+ header: i18next.t('field.packing_type'),
257
+ width: 150
258
+ },
259
+ {
260
+ type: 'number',
261
+ name: 'packingSize',
262
+ header: i18next.t('field.packing_size'),
263
+ width: 140
264
+ },
265
+ {
266
+ type: 'number',
267
+ name: 'packedQty',
268
+ header: i18next.t('field.qty'),
269
+ width: 130
270
+ },
271
+ {
272
+ type: 'float',
273
+ name: 'uomValue',
274
+ header: i18next.t('field.uom_value'),
275
+ width: 150
276
+ },
277
+ {
278
+ type: 'string',
279
+ name: 'uom',
280
+ header: i18next.t('field.uom'),
281
+ width: 80
282
+ }
283
+ ]
284
+ }
285
+
286
+ // Also check for URL params in pageInitialized
287
+ await this.updateComplete
288
+ if (this.active && lifecycle?.params?.orderNo) {
289
+ this.orderNos = lifecycle.params.orderNo.split(',').filter(name => name.trim())
290
+ await this.fetchReleaseOrders()
291
+ }
292
+ }
293
+
294
+ async pageUpdated(changes, lifecycle) {
295
+ if (this.active && lifecycle?.params?.orderNo) {
296
+ const newOrderNos = lifecycle.params.orderNo.split(',').filter(name => name.trim())
297
+
298
+ // Check if orderNos changed - re-fetch if different
299
+ const currentOrderNos = this.orderNos || []
300
+ const hasChanged =
301
+ newOrderNos.length !== currentOrderNos.length ||
302
+ newOrderNos.some((orderNo, idx) => orderNo !== currentOrderNos[idx])
303
+
304
+ if (hasChanged) {
305
+ this.orderNos = newOrderNos
306
+ await this.fetchReleaseOrders()
307
+ }
308
+ }
309
+ }
310
+
311
+ async fetchReleaseOrders() {
312
+ if (!this.orderNos?.length) return
313
+
314
+ try {
315
+ const loadingPackagesResponse = await client.query({
316
+ query: gql`
317
+ query loadingPackagesByReleaseGoodNos($releaseGoodNos: [String!]!) {
318
+ loadingPackagesByReleaseGoodNos(releaseGoodNos: $releaseGoodNos) {
319
+ id
320
+ name
321
+ status
322
+ createdAt
323
+ releaseGood {
324
+ id
325
+ name
326
+ refNo
327
+ refNo2
328
+ refNo3
329
+ releaseDate
330
+ bizplace {
331
+ id
332
+ name
333
+ }
334
+ }
335
+ loadingPackageItems {
336
+ id
337
+ name
338
+ packedQty
339
+ orderProduct {
340
+ id
341
+ name
342
+ packingType
343
+ packingSize
344
+ uom
345
+ uomValue
346
+ product {
347
+ id
348
+ name
349
+ sku
350
+ }
351
+ }
352
+ productDetail {
353
+ id
354
+ packingType
355
+ packingSize
356
+ uom
357
+ uomValue
358
+ }
359
+ }
360
+ }
361
+ }
362
+ `,
363
+ variables: { releaseGoodNos: this.orderNos },
364
+ context: gqlContext()
365
+ })
366
+
367
+ if (!loadingPackagesResponse.errors && loadingPackagesResponse.data.loadingPackagesByReleaseGoodNos) {
368
+ // Extract unique release orders from loading packages
369
+ const releaseOrdersMap = new Map()
370
+ const loadingPackagesMap = {}
371
+
372
+ loadingPackagesResponse.data.loadingPackagesByReleaseGoodNos.forEach(pkg => {
373
+ const releaseGood = pkg.releaseGood
374
+ if (releaseGood && releaseGood.name) {
375
+ const orderNo = releaseGood.name
376
+
377
+ // Store release order details (will overwrite duplicates, but they should be the same)
378
+ if (!releaseOrdersMap.has(orderNo)) {
379
+ releaseOrdersMap.set(orderNo, releaseGood)
380
+ }
381
+
382
+ // Group loading packages by releaseGood name
383
+ if (!loadingPackagesMap[orderNo]) {
384
+ loadingPackagesMap[orderNo] = []
385
+ }
386
+ loadingPackagesMap[orderNo].push(pkg)
387
+ }
388
+ })
389
+
390
+ // Convert map to array for releaseOrders
391
+ this.releaseOrders = Array.from(releaseOrdersMap.values())
392
+
393
+ // Ensure all orderNos have an entry (even if empty)
394
+ this.orderNos.forEach(orderNo => {
395
+ if (!loadingPackagesMap[orderNo]) {
396
+ loadingPackagesMap[orderNo] = []
397
+ }
398
+ // If orderNo not in releaseOrders, add it with minimal data
399
+ if (!releaseOrdersMap.has(orderNo)) {
400
+ this.releaseOrders.push({
401
+ name: orderNo,
402
+ refNo: '',
403
+ refNo2: '',
404
+ refNo3: '',
405
+ releaseDate: '',
406
+ attentionTo: '',
407
+ phone1: '',
408
+ deliveryAddress1: '',
409
+ remark: '',
410
+ bizplace: { id: '', name: '' }
411
+ })
412
+ }
413
+ })
414
+
415
+ this.loadingPackagesMap = loadingPackagesMap
416
+ } else {
417
+ // Initialize empty map for all orders
418
+ this.releaseOrders = this.orderNos.map(orderNo => ({
419
+ name: orderNo,
420
+ refNo: '',
421
+ refNo2: '',
422
+ refNo3: '',
423
+ releaseDate: '',
424
+ bizplace: { id: '', name: '' }
425
+ }))
426
+ this.loadingPackagesMap = this.orderNos.reduce((acc, orderNo) => {
427
+ acc[orderNo] = []
428
+ return acc
429
+ }, {})
430
+ }
431
+ } catch (error) {
432
+ console.error('Error fetching loading packages:', error)
433
+ this.releaseOrders = this.orderNos.map(orderNo => ({
434
+ name: orderNo,
435
+ refNo: '',
436
+ refNo2: '',
437
+ refNo3: '',
438
+ releaseDate: '',
439
+ bizplace: { id: '', name: '' }
440
+ }))
441
+ this.loadingPackagesMap = this.orderNos.reduce((acc, orderNo) => {
442
+ acc[orderNo] = []
443
+ return acc
444
+ }, {})
445
+ }
446
+ }
447
+
448
+ _renderContent() {
449
+ const releaseOrders = this.releaseOrders || []
450
+ const loadingPackagesMap = this.loadingPackagesMap || {}
451
+
452
+ return releaseOrders.map(releaseOrder => {
453
+ const loadingPackages = loadingPackagesMap[releaseOrder.name] || []
454
+
455
+ if (loadingPackages.length == 0) { return html`` }
456
+
457
+ return html`
458
+ <section>
459
+ <div class="form-container">
460
+ <form name="releaseOrder" class="multi-column-form">
461
+ <fieldset>
462
+ <legend>${i18next.t('title.release_order_no')}: ${releaseOrder.name}</legend>
463
+
464
+ <label>${i18next.t('label.company')}</label>
465
+ <input name="bizplaceName" value="${releaseOrder?.bizplace?.name || ''}" readonly />
466
+
467
+ <label>${i18next.t('label.ref_no')}</label>
468
+ <input name="refNo" value="${releaseOrder?.refNo || ''}" readonly />
469
+
470
+ <label>${i18next.t('label.ref_no_2')}</label>
471
+ <input name="refNo2" value="${releaseOrder?.refNo2 || ''}" readonly />
472
+
473
+ <label>${i18next.t('label.ref_no_3')}</label>
474
+ <input name="refNo3" value="${releaseOrder?.refNo3 || ''}" readonly />
475
+
476
+ <label>${i18next.t('label.release_date')}</label>
477
+ <input name="releaseDate" type="date" value="${releaseOrder.releaseDate || ''}" readonly />
478
+ </fieldset>
479
+ </form>
480
+
481
+ <barcode-tag bcid="qrcode" .value=${releaseOrder.name} showLogo="true"></barcode-tag>
482
+ </div>
483
+
484
+ ${loadingPackages.length > 0
485
+ ? html`
486
+ ${loadingPackages.map((loadingPackage, packageIndex) => {
487
+ const packageItems = loadingPackage.loadingPackageItems || []
488
+ const itemsData = {
489
+ records: packageItems.map(item => ({
490
+ id: item.id,
491
+ sku: item.orderProduct?.product?.sku || '',
492
+ productName: item.orderProduct?.product?.name || item.orderProduct?.name || '',
493
+ packingType: item.productDetail?.packingType || item.orderProduct?.packingType || '',
494
+ packingSize: item.productDetail?.packingSize || item.orderProduct?.packingSize || null,
495
+ packedQty: item.packedQty || 0,
496
+ uomValue: item.productDetail?.uomValue || item.orderProduct?.uomValue || null,
497
+ uom: item.productDetail?.uom || item.orderProduct?.uom || ''
498
+ })),
499
+ total: packageItems.length
500
+ }
501
+
502
+ return html`
503
+ <div class="grist">
504
+ <h2>
505
+ <mwc-icon>inventory_2</mwc-icon>
506
+ ${i18next.t('title.packing_label')} ${packageIndex + 1}
507
+ </h2>
508
+ <data-grist
509
+ .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
510
+ .config=${this.packingListConfig}
511
+ .data=${itemsData}
512
+ ></data-grist>
513
+ </div>
514
+ `
515
+ })}
516
+ `
517
+ : html``}
518
+ </section>
519
+ `
520
+ })
521
+ }
522
+
523
+ render() {
524
+ const content = this._renderContent()
525
+
526
+ return html`
527
+ ${content}
528
+ <iframe id="print-iframe" class="print-iframe"></iframe>
529
+ `
530
+ }
531
+ }
532
+
533
+ window.customElements.define('preview-print-packing-list', PreviewPrintPackingList)
@@ -0,0 +1,118 @@
1
+ import { i18next, localize } from '@things-factory/i18n-base'
2
+ import { css, html, LitElement } from 'lit-element'
3
+
4
+ class PrintQuantityPopup extends localize(i18next)(LitElement) {
5
+ static get properties() {
6
+ return {
7
+ printQuantity: { type: Number }
8
+ }
9
+ }
10
+
11
+ static get styles() {
12
+ return [
13
+ css`
14
+ :host {
15
+ display: flex;
16
+ flex-direction: column;
17
+ overflow: hidden;
18
+ background-color: var(--main-section-background-color);
19
+ padding: 20px;
20
+ }
21
+ .content {
22
+ display: flex;
23
+ flex: 1;
24
+ align-items: center;
25
+ justify-content: center;
26
+ }
27
+ .input-row {
28
+ display: flex;
29
+ align-items: center;
30
+ gap: 12px;
31
+ width: 100%;
32
+ }
33
+ .input-row label {
34
+ font-size: 14px;
35
+ font-weight: 500;
36
+ white-space: nowrap;
37
+ }
38
+ .input-row input {
39
+ flex: 1;
40
+ padding: 8px 12px;
41
+ font-size: 14px;
42
+ border: 1px solid var(--secondary-color, #ccc);
43
+ border-radius: 4px;
44
+ }
45
+ .button-container {
46
+ display: flex;
47
+ gap: 10px;
48
+ justify-content: center;
49
+ margin-top: 20px;
50
+ }
51
+ `
52
+ ]
53
+ }
54
+
55
+ constructor() {
56
+ super()
57
+ this.printQuantity = 1
58
+ }
59
+
60
+ render() {
61
+ return html`
62
+ <div class="content">
63
+ <div class="input-row">
64
+ <label>${i18next.t('label.print_quantity')}</label>
65
+ <input
66
+ name="printQuantity"
67
+ type="number"
68
+ min="1"
69
+ .value="${this.printQuantity}"
70
+ @input="${e => {
71
+ this.printQuantity = parseInt(e.target.value)
72
+ }}"
73
+ />
74
+ </div>
75
+ </div>
76
+ <div class="button-container">
77
+ <mwc-button
78
+ outlined
79
+ @click="${() => {
80
+ this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))
81
+ history.back()
82
+ }}"
83
+ >
84
+ <mwc-icon slot="icon">close</mwc-icon>
85
+ ${i18next.t('button.close') || 'Close'}
86
+ </mwc-button>
87
+ <mwc-button
88
+ raised
89
+ @click="${() => {
90
+ this.dispatchEvent(
91
+ new CustomEvent('print', {
92
+ detail: { printQuantity: this.printQuantity },
93
+ bubbles: true,
94
+ composed: true
95
+ })
96
+ )
97
+ }}"
98
+ >
99
+ <mwc-icon slot="icon">print</mwc-icon>
100
+ ${i18next.t('button.print') || 'Print'}
101
+ </mwc-button>
102
+ </div>
103
+ `
104
+ }
105
+
106
+ get printQuantityInput() {
107
+ return this.shadowRoot?.querySelector('input[name=printQuantity]')
108
+ }
109
+
110
+ firstUpdated() {
111
+ if (this.printQuantityInput) {
112
+ this.printQuantityInput.focus()
113
+ this.printQuantityInput.select()
114
+ }
115
+ }
116
+ }
117
+
118
+ window.customElements.define('print-quantity-popup', PrintQuantityPopup)