@things-factory/operato-wms 4.3.767 → 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 (46) hide show
  1. package/client/pages/bulk-operation/validate-gan-popup.js +2 -2
  2. package/client/pages/components/list-view/product-list-view.js +28 -3
  3. package/client/pages/components/mobile-condition-of-goods-listing.js +342 -0
  4. package/client/pages/components/unloading-worksheet-information-popup.js +299 -0
  5. package/client/pages/constants/order.js +7 -0
  6. package/client/pages/inbound/condition-of-goods-popup.js +646 -0
  7. package/client/pages/inbound/putaway/putaway-product.js +35 -6
  8. package/client/pages/inbound/unload-product-landing-page.js +282 -0
  9. package/client/pages/inbound/unload-product.js +707 -175
  10. package/client/pages/inbound/unloaded-inventories-popup.js +3 -0
  11. package/client/pages/inventory/inventory-adjustment-approval.js +28 -1
  12. package/client/pages/inventory/inventory-adjustment.js +77 -44
  13. package/client/pages/inventory/inventory-by-product-detail.js +15 -0
  14. package/client/pages/inventory/inventory-history.js +18 -0
  15. package/client/pages/inventory/onhand-inventory.js +14 -0
  16. package/client/pages/order/arrival-notice/create-arrival-notice.js +209 -206
  17. package/client/pages/order/arrival-notice/duplicate-arrival-notice.js +132 -129
  18. package/client/pages/order/arrival-notice/edit-product-popup.js +97 -92
  19. package/client/pages/order/release-order/b2b/b2b-order-list.js +99 -0
  20. package/client/pages/order/release-order/packing-label-history-popup.js +378 -0
  21. package/client/pages/order/release-order/preview-print-packing-list.js +533 -0
  22. package/client/pages/order/release-order/print-quantity-popup.js +118 -0
  23. package/client/pages/outbound/loading-v2/loading-execution-base.js +29 -0
  24. package/client/pages/outbound/loading-v2/loading-execution.js +579 -2
  25. package/client/pages/outbound/loading-v2/loading-package-info-popup.js +182 -0
  26. package/client/pages/outbound/loading-v2/loading.js +18 -0
  27. package/client/pages/report/inbound-order-details-report.js +26 -1
  28. package/client/route.js +17 -0
  29. package/dist-server/graphql/resolvers/outbound/find-loadable-release-good-by-bin.js +9 -1
  30. package/dist-server/graphql/resolvers/outbound/find-loadable-release-good-by-bin.js.map +1 -1
  31. package/dist-server/graphql/resolvers/outbound/loading-worksheet-v2.js +11 -5
  32. package/dist-server/graphql/resolvers/outbound/loading-worksheet-v2.js.map +1 -1
  33. package/dist-server/graphql/resolvers/reports/inbound-order-details-report.js +2 -1
  34. package/dist-server/graphql/resolvers/reports/inbound-order-details-report.js.map +1 -1
  35. package/dist-server/graphql/types/reports/inbound-order-details-report.js +1 -0
  36. package/dist-server/graphql/types/reports/inbound-order-details-report.js.map +1 -1
  37. package/package.json +15 -15
  38. package/server/graphql/resolvers/outbound/find-loadable-release-good-by-bin.ts +9 -1
  39. package/server/graphql/resolvers/outbound/loading-worksheet-v2.ts +13 -1
  40. package/server/graphql/resolvers/reports/inbound-order-details-report.ts +2 -1
  41. package/server/graphql/types/reports/inbound-order-details-report.ts +1 -0
  42. package/things-factory.config.js +8 -0
  43. package/translations/en.json +45 -1
  44. package/translations/ko.json +40 -0
  45. package/translations/ms.json +40 -0
  46. package/translations/zh.json +40 -0
@@ -2,6 +2,7 @@ import '@things-factory/form-ui'
2
2
  import '@things-factory/grist-ui'
3
3
  import '@things-factory/worksheet-ui'
4
4
  import '../batch-picking-popup'
5
+ import '../packing-label-history-popup'
5
6
  import '../../../outbound/generate-worksheet-popup'
6
7
  import '../../../outbound/select-operator-popup'
7
8
 
@@ -58,6 +59,11 @@ class B2bOrderList extends localize(i18next)(PageView) {
58
59
  }
59
60
  }
60
61
 
62
+ constructor() {
63
+ super()
64
+ this._hasSelectionListener = false
65
+ }
66
+
61
67
  render() {
62
68
  return html`
63
69
  <search-form id="search-form" .fields=${this._searchFields} @submit=${e => this.dataGrist.fetch()}></search-form>
@@ -73,6 +79,32 @@ class B2bOrderList extends localize(i18next)(PageView) {
73
79
  get context() {
74
80
  var actions = []
75
81
 
82
+ actions.push({
83
+ title: i18next.t('title.packing_label_history'),
84
+ action: this._openPackingLabelHistory.bind(this),
85
+ icon: 'history',
86
+ emphasis: {
87
+ raised: false,
88
+ outlined: true,
89
+ dense: false,
90
+ danger: false
91
+ }
92
+ })
93
+
94
+ if (this._shouldShowButtonForSelection()) {
95
+ actions.push({
96
+ title: i18next.t('title.print_packing_label'),
97
+ action: this._openPrintPackingList.bind(this),
98
+ icon: 'print',
99
+ emphasis: {
100
+ raised: false,
101
+ outlined: true,
102
+ dense: false,
103
+ danger: false
104
+ }
105
+ })
106
+ }
107
+
76
108
  if (this.isUserBelongsDomain) {
77
109
  if (this._betaSetting) {
78
110
  actions.push({
@@ -96,6 +128,7 @@ class B2bOrderList extends localize(i18next)(PageView) {
96
128
  ...CommonButtonStyles.add
97
129
  })
98
130
  }
131
+
99
132
  return {
100
133
  title: i18next.t('title.release_orders'),
101
134
  actions,
@@ -501,6 +534,13 @@ class B2bOrderList extends localize(i18next)(PageView) {
501
534
  }
502
535
  ]
503
536
  }
537
+
538
+ await this.updateComplete
539
+ if (this.dataGrist && !this._hasSelectionListener) {
540
+ this.dataGrist.addEventListener('select-record-change', () => this.updateContext())
541
+ this.dataGrist.addEventListener('select-all-change', () => this.updateContext())
542
+ this._hasSelectionListener = true
543
+ }
504
544
  }
505
545
 
506
546
  get searchForm() {
@@ -588,6 +628,10 @@ class B2bOrderList extends localize(i18next)(PageView) {
588
628
  id
589
629
  name
590
630
  }
631
+ activeWorksheet {
632
+ type
633
+ status
634
+ }
591
635
  }
592
636
  total
593
637
  }
@@ -812,6 +856,61 @@ class B2bOrderList extends localize(i18next)(PageView) {
812
856
  return { header: headerSetting, data: data }
813
857
  }
814
858
 
859
+ _shouldShowButtonForSelection() {
860
+ if (!this.dataGrist) return false
861
+
862
+ const selected = this.dataGrist.selected || []
863
+ if (selected.length === 0) return false
864
+
865
+ const allowedWorksheetTypes = ['LOADING', 'PROCESSING', 'PARTIAL_PROCESSING']
866
+ const allowedWorksheetStatuses = ['EXECUTING', 'PARTIAL_EXECUTING']
867
+
868
+ return selected.every(order => {
869
+ // Check if activeWorksheet exists
870
+ if (!order.activeWorksheet) return false
871
+
872
+ // Check if activeWorksheet.type is one of the allowed types
873
+ if (!allowedWorksheetTypes.includes(order.activeWorksheet.type)) return false
874
+
875
+ // Check if activeWorksheet.status is EXECUTING or PARTIAL_EXECUTING
876
+ return allowedWorksheetStatuses.includes(order.activeWorksheet.status)
877
+ })
878
+ }
879
+
880
+ _openPrintPackingList() {
881
+ try {
882
+ if (!this.dataGrist || !this.dataGrist.selected || this.dataGrist.selected.length === 0) {
883
+ throw new Error(i18next.t('text.please_select_one_or_more_order') || 'Please select one or more orders')
884
+ }
885
+
886
+ const selectedOrderNames = this.dataGrist.selected.map(order => order.name).filter(Boolean)
887
+ if (selectedOrderNames.length === 0) {
888
+ throw new Error(i18next.t('text.no_valid_orders_selected') || 'No valid orders selected')
889
+ }
890
+
891
+ const orderNoParam = selectedOrderNames.join(',')
892
+ navigate(`preview_print_packing_list?orderNo=${encodeURIComponent(orderNoParam)}`)
893
+ } catch (e) {
894
+ this._showToast({ type: 'error', message: e.message || i18next.t('text.failed_to_open_print_packing_label') })
895
+ }
896
+ }
897
+
898
+ _openPackingLabelHistory() {
899
+ try {
900
+ openPopup(
901
+ html` <packing-label-history-popup></packing-label-history-popup> `,
902
+ {
903
+ backdrop: true,
904
+ size: 'large',
905
+ title: i18next.t('title.packing_label_history'),
906
+ escapable: false
907
+ }
908
+ )
909
+ } catch (e) {
910
+ this._showToast({ type: 'error', message: e.message || i18next.t('text.failed_to_open_packing_label_history') })
911
+ }
912
+ }
913
+
815
914
  _showToast({ type, message }) {
816
915
  document.dispatchEvent(
817
916
  new CustomEvent('notify', {
@@ -0,0 +1,378 @@
1
+ import '@things-factory/form-ui'
2
+ import '@things-factory/grist-ui'
3
+ import './print-quantity-popup'
4
+
5
+ import gql from 'graphql-tag'
6
+ import { css, html, LitElement } from 'lit-element'
7
+
8
+ import { i18next, localize } from '@things-factory/i18n-base'
9
+ import { openPopup } from '@things-factory/layout-base'
10
+ import { client } from '@things-factory/shell'
11
+ import { ScrollbarStyles } from '@things-factory/styles'
12
+ import { isMobileDevice } from '@things-factory/utils'
13
+
14
+ import { showToast } from '../../../util'
15
+
16
+ class PackingLabelHistoryPopup extends localize(i18next)(LitElement) {
17
+ static get properties() {
18
+ return {
19
+ _searchFields: Array,
20
+ config: Object,
21
+ data: Object,
22
+ _packingLabelOptions: Array
23
+ }
24
+ }
25
+
26
+ static get styles() {
27
+ return [
28
+ ScrollbarStyles,
29
+ css`
30
+ :host {
31
+ display: flex;
32
+ flex-direction: column;
33
+ overflow: hidden;
34
+ background-color: white;
35
+ }
36
+
37
+ search-form {
38
+ overflow: visible;
39
+ }
40
+
41
+ .grist {
42
+ display: flex;
43
+ flex-direction: column;
44
+ flex: 1;
45
+ overflow-y: auto;
46
+ }
47
+
48
+ .grist-container {
49
+ overflow-y: hidden;
50
+ display: flex;
51
+ flex: 1;
52
+ }
53
+
54
+ data-grist {
55
+ overflow-y: hidden;
56
+ flex: 1;
57
+ }
58
+
59
+ .button-container {
60
+ padding: var(--button-container-padding);
61
+ margin: var(--button-container-margin);
62
+ text-align: var(--button-container-align);
63
+ background-color: var(--button-container-background);
64
+ height: var(--button-container-height);
65
+ }
66
+ `
67
+ ]
68
+ }
69
+
70
+ constructor() {
71
+ super()
72
+ this._packingLabelOptions = []
73
+ }
74
+
75
+ get searchForm() {
76
+ return this.shadowRoot.querySelector('search-form')
77
+ }
78
+
79
+ get dataGrist() {
80
+ return this.shadowRoot.querySelector('data-grist')
81
+ }
82
+
83
+ _initSearchFields() {
84
+ this._searchFields = [
85
+ {
86
+ label: i18next.t('field.order_no') || 'Order No.',
87
+ name: 'releaseGood.name',
88
+ type: 'text',
89
+ props: { searchOper: 'i_like' }
90
+ },
91
+ {
92
+ label: i18next.t('field.packing_label_no'),
93
+ name: 'displayNumber',
94
+ type: 'select',
95
+ options: this._packingLabelOptions,
96
+ props: { searchOper: 'eq' }
97
+ }
98
+ ]
99
+ }
100
+
101
+ _initConfig() {
102
+ this.config = {
103
+ rows: { selectable: { multiple: true }, appendable: false },
104
+ list: { fields: ['releaseGood', 'refNo', 'displayNumber', 'printedAt'] },
105
+ pagination: { limit: 50, pages: [20, 50, 100, 200] },
106
+ columns: [
107
+ { type: 'gutter', gutterName: 'sequence' },
108
+ { type: 'gutter', gutterName: 'row-selector', multiple: true },
109
+ {
110
+ type: 'gutter',
111
+ gutterName: 'button',
112
+ icon: 'reorder',
113
+ handlers: {
114
+ click: (_columns, _data, _column, record) => {}
115
+ }
116
+ },
117
+ {
118
+ type: 'object',
119
+ name: 'releaseGood',
120
+ header: i18next.t('field.order_no') || 'Release Orders',
121
+ sortable: true,
122
+ width: 180
123
+ },
124
+ {
125
+ type: 'string',
126
+ name: 'refNo',
127
+ header: i18next.t('field.ref_no') || 'Ref No.',
128
+ sortable: true,
129
+ width: 150
130
+ },
131
+ {
132
+ type: 'number',
133
+ name: 'displayNumber',
134
+ header: i18next.t('field.packing_label_no') || 'Packing Label No.',
135
+ sortable: true,
136
+ width: 120
137
+ },
138
+ {
139
+ type: 'datetime',
140
+ name: 'printedAt',
141
+ header: i18next.t('field.printed_at') || 'Printed At',
142
+ sortable: true,
143
+ width: 160
144
+ },
145
+ {
146
+ type: 'object',
147
+ name: 'printedBy',
148
+ header: i18next.t('field.printed_by') || 'Printed By',
149
+ sortable: true,
150
+ width: 160
151
+ }
152
+ ]
153
+ }
154
+ }
155
+
156
+ async firstUpdated() {
157
+ await this._fetchPackingLabelOptions()
158
+ this._initSearchFields()
159
+ this._initConfig()
160
+ }
161
+
162
+ async _fetchPackingLabelOptions() {
163
+ try {
164
+ const response = await client.query({
165
+ query: gql`
166
+ query {
167
+ maxPackingLabelCount
168
+ }
169
+ `
170
+ })
171
+
172
+ if (!response.errors) {
173
+ const maxCount = response.data.maxPackingLabelCount || 0
174
+ this._packingLabelOptions = [
175
+ { value: '', name: '' },
176
+ ...Array.from({ length: maxCount }, (_, i) => ({
177
+ value: String(i + 1),
178
+ name: String(i + 1)
179
+ }))
180
+ ]
181
+ }
182
+ } catch (e) {
183
+ console.error('Failed to fetch packing label options:', e)
184
+ this._packingLabelOptions = [{ value: '', name: '' }]
185
+ }
186
+ }
187
+
188
+ async fetchHandler({ page, limit, sorters = [] }) {
189
+ const filters = this.searchForm.queryFilters
190
+
191
+ const response = await client.query({
192
+ query: gql`
193
+ query printedLoadingPackagesHistory($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
194
+ printedLoadingPackagesHistory(filters: $filters, pagination: $pagination, sortings: $sortings) {
195
+ items {
196
+ id
197
+ name
198
+ displayNumber
199
+ releaseGood {
200
+ id
201
+ name
202
+ refNo
203
+ }
204
+ printedAt
205
+ printedBy {
206
+ id
207
+ name
208
+ description
209
+ }
210
+ }
211
+ total
212
+ }
213
+ }
214
+ `,
215
+ variables: {
216
+ filters,
217
+ pagination: { page, limit },
218
+ sortings: sorters
219
+ }
220
+ })
221
+
222
+ if (!response.errors) {
223
+ return {
224
+ total: response.data.printedLoadingPackagesHistory.total || 0,
225
+ records:
226
+ response.data.printedLoadingPackagesHistory.items.map(item => ({
227
+ ...item,
228
+ refNo: item.releaseGood?.refNo || ''
229
+ })) || []
230
+ }
231
+ }
232
+
233
+ return { total: 0, records: [] }
234
+ }
235
+
236
+ _handlePrint() {
237
+ try {
238
+ const selected = this.dataGrist.selected || []
239
+ if (selected.length === 0) {
240
+ showToast({
241
+ type: 'warning',
242
+ message: i18next.t('text.please_select_at_least_one_record') || 'Please select at least one record'
243
+ })
244
+ return
245
+ }
246
+
247
+ openPopup(
248
+ html`
249
+ <print-quantity-popup
250
+ @print="${e => {
251
+ const printQuantity = e.detail.printQuantity
252
+
253
+ // Validate print quantity
254
+ if (!printQuantity || printQuantity <= 0) {
255
+ showToast({
256
+ type: 'error',
257
+ message: i18next.t('text.print_qty_must_be_at_least_one') || 'Print quantity must be at least 1'
258
+ })
259
+ return
260
+ }
261
+
262
+ // Close popup and execute print
263
+ history.back()
264
+ this._executePrint(selected, printQuantity)
265
+ }}"
266
+ ></print-quantity-popup>
267
+ `,
268
+ {
269
+ backdrop: true,
270
+ size: 'small',
271
+ title: i18next.t('title.print_packing_label') || 'Print Packing Label'
272
+ }
273
+ )
274
+ } catch (e) {
275
+ showToast({ type: 'error', message: e.message || i18next.t('text.failed_to_print') })
276
+ }
277
+ }
278
+
279
+ async _executePrint(selected, printQuantity) {
280
+ const loadingPackageIds = selected.map(item => item.id).filter(Boolean)
281
+
282
+ if (loadingPackageIds.length === 0) {
283
+ showToast({ type: 'error', message: i18next.t('text.no_valid_packages_selected') || 'No valid packages selected' })
284
+ return
285
+ }
286
+
287
+ try {
288
+ const response = await fetch('/render_packing_label', {
289
+ method: 'POST',
290
+ credentials: 'include',
291
+ headers: { 'Content-Type': 'application/json' },
292
+ body: JSON.stringify({
293
+ loadingPackageIds,
294
+ printQuantity: Math.max(1, parseInt(printQuantity) || 1)
295
+ })
296
+ })
297
+
298
+ if (!response.ok) {
299
+ const errorText = await response.text()
300
+ throw new Error(errorText || `Failed to render packing label: ${response.statusText}`)
301
+ }
302
+
303
+ const renderedHtml = await response.text()
304
+ if (!renderedHtml) {
305
+ showToast({ type: 'error', message: i18next.t('text.failed_to_render_template') || 'Failed to render template' })
306
+ return
307
+ }
308
+
309
+ await this._printViaIframe(renderedHtml)
310
+ this.dataGrist.fetch()
311
+ } catch (error) {
312
+ console.error('Print error:', error)
313
+ showToast({ type: 'error', message: error.message || i18next.t('text.failed_to_print') || 'Failed to print' })
314
+ }
315
+ }
316
+
317
+ async _printViaIframe(htmlContent) {
318
+ return new Promise((resolve, reject) => {
319
+ const iframe = this._printIframe
320
+ if (!iframe) {
321
+ reject(new Error('Print iframe not found'))
322
+ return
323
+ }
324
+
325
+ iframe.onload = () => {
326
+ try {
327
+ iframe.contentWindow.focus()
328
+ iframe.contentWindow.print()
329
+ resolve()
330
+ } catch (e) {
331
+ reject(e)
332
+ }
333
+ }
334
+
335
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow.document
336
+ iframeDoc.open()
337
+ iframeDoc.write(htmlContent)
338
+ iframeDoc.close()
339
+ })
340
+ }
341
+
342
+ get _printIframe() {
343
+ return this.shadowRoot?.querySelector('#print-iframe')
344
+ }
345
+
346
+ _closePopup() {
347
+ history.back()
348
+ }
349
+
350
+ render() {
351
+ return html`
352
+ <search-form
353
+ id="search-form"
354
+ .fields=${this._searchFields}
355
+ @submit=${() => this.dataGrist.fetch()}
356
+ ></search-form>
357
+
358
+ <div class="grist-container">
359
+ <div class="grist">
360
+ <data-grist
361
+ .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
362
+ .config=${this.config}
363
+ .fetchHandler=${this.fetchHandler.bind(this)}
364
+ ></data-grist>
365
+ </div>
366
+ </div>
367
+
368
+ <div class="button-container">
369
+ <mwc-button @click=${this._closePopup}>${i18next.t('button.close')}</mwc-button>
370
+ <mwc-button raised @click=${this._handlePrint}>${i18next.t('button.print')}</mwc-button>
371
+ </div>
372
+
373
+ <iframe id="print-iframe" style="display: none;"></iframe>
374
+ `
375
+ }
376
+ }
377
+
378
+ window.customElements.define('packing-label-history-popup', PackingLabelHistoryPopup)