@things-factory/operato-wms 3.7.6 → 3.7.7

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 (27) hide show
  1. package/client/pages/components/select-product-popup.js +5 -1
  2. package/client/pages/constants/order.js +10 -0
  3. package/client/pages/inventory-check/inspecting-product.js +9 -8
  4. package/client/pages/manifest/create-loading-manifest.js +314 -0
  5. package/client/pages/manifest/dispatcher-verification-popup.js +86 -0
  6. package/client/pages/manifest/loading-manifest-detail.js +345 -0
  7. package/client/pages/manifest/loading-manifest-list.js +305 -0
  8. package/client/pages/order/goods-received-note/received-note-list.js +138 -6
  9. package/client/pages/order/release-order/b2b/b2b-order-list.js +1 -1
  10. package/client/pages/order/release-order/b2b/b2b-order-requests.js +1 -1
  11. package/client/pages/order/release-order/b2c/b2c-order-requests.js +111 -14
  12. package/client/pages/order/reverse-kitting-order/reverse-kitting-order-detail.js +115 -50
  13. package/client/pages/order/reverse-kitting-order/reverse-kitting-order-list.js +1 -6
  14. package/client/pages/order/reverse-kitting-order/reverse-kitting-pallet-label-popup.js +239 -0
  15. package/client/pages/order/transport-order/delivery-order-list.js +47 -3
  16. package/client/pages/outbound/picking-product.js +200 -66
  17. package/client/pages/outbound/serial-number-popup.js +280 -0
  18. package/client/pages/vas/reverse-kitting/execute-reverse-kitting.js +30 -12
  19. package/client/pages/vas/reverse-kitting/popup-combination-sets.js +75 -5
  20. package/client/pages/vas/reverse-kitting/popup-reverse-kitting-template.js +113 -18
  21. package/client/route.js +12 -0
  22. package/package.json +10 -10
  23. package/things-factory.config.js +12 -0
  24. package/translations/en.json +42 -6
  25. package/translations/ko.json +35 -1
  26. package/translations/ms.json +36 -1
  27. package/translations/zh.json +35 -1
@@ -174,7 +174,11 @@ class SelectProductPopup extends localize(i18next)(LitElement) {
174
174
  }
175
175
 
176
176
  async fetchProducts({ page, limit, sorters = [] }) {
177
- const filters = [...this.searchForm.queryFilters, { name: 'bizplace_id', operator: 'eq', value: this.bizplaceId }]
177
+ const filters = [
178
+ ...this.searchForm.queryFilters,
179
+ { name: 'bizplace_id', operator: 'eq', value: this.bizplaceId },
180
+ { name: 'create_arrival_notice', operator: 'eq', value: true }
181
+ ]
178
182
  const pagination = { page, limit }
179
183
  const sortings = sorters
180
184
 
@@ -62,6 +62,16 @@ export const ORDER_STATUS = {
62
62
  READY_TO_SEND: { name: 'order_status_ready_to_send', value: 'READY_TO_SEND' }
63
63
  }
64
64
 
65
+ export const MANIFEST_STATUS = {
66
+ OPEN: { name: 'open', value: 'OPEN' },
67
+ CLOSED: { name: 'closed', value: 'CLOSED' }
68
+ }
69
+
70
+ export const DISPATCHMENT_STATUS = {
71
+ READY_TO_DISPATCH: { name: 'ready_to_dispatch', value: 'READY_TO_DISPATCH' },
72
+ DISPATCHED: { name: 'dispatched', value: 'DISPATCHED' }
73
+ }
74
+
65
75
  export const ORDER_PRODUCT_STATUS = {
66
76
  ADDED: { name: 'added', value: 'ADDED' },
67
77
  READY_TO_APPROVED: { name: 'ready_to_approve', value: 'READY_TO_APPROVED' },
@@ -233,12 +233,7 @@ class InspectingProduct extends connect(store)(localize(i18next)(PageView)) {
233
233
  : html` <barcode-scanable-input name="palletId" custom-input></barcode-scanable-input>`}
234
234
 
235
235
  <label>${i18next.t('label.inspected_batch_no')}</label>
236
- <input
237
- name="inspectedBatchNo"
238
- .value="${this._hideCCColumnSetting
239
- ? this.selectedInventory?.inspectedBatchNo || ''
240
- : this.selectedInventory?.inspectedBatchNo || this.selectedInventory?.batchId || ''}"
241
- />
236
+ <input name="inspectedBatchNo" .value="${this.selectedInventory?.batchId || ''}" />
242
237
 
243
238
  <label>${i18next.t('label.inspected_qty')}</label>
244
239
  <input
@@ -434,6 +429,7 @@ class InspectingProduct extends connect(store)(localize(i18next)(PageView)) {
434
429
  { type: 'gutter', gutterName: 'sequence' },
435
430
  { type: 'boolean', name: 'completed', header: i18next.t('button.completed'), width: 80 },
436
431
  { type: 'string', name: 'palletId', header: i18next.t('label.lot_id'), width: 160 },
432
+ { type: 'string', name: 'batchId', header: i18next.t('label.batch_id'), width: 120 },
437
433
  {
438
434
  type: 'string',
439
435
  name: 'orderInventoryStatus',
@@ -1169,8 +1165,7 @@ class InspectingProduct extends connect(store)(localize(i18next)(PageView)) {
1169
1165
  if (!response.errors) {
1170
1166
  await this.fetchCycleCountWorksheet()
1171
1167
  this.renewInventoryGrist()
1172
- this.inspectedQtyInput.value = ''
1173
- this.locationInput.value = ''
1168
+ this.resetInput()
1174
1169
  }
1175
1170
  } catch (e) {
1176
1171
  this.showToast(e)
@@ -1245,6 +1240,12 @@ class InspectingProduct extends connect(store)(localize(i18next)(PageView)) {
1245
1240
  }
1246
1241
  }
1247
1242
 
1243
+ resetInput() {
1244
+ this.inspectedQtyInput.value = ''
1245
+ this.locationInput.value = ''
1246
+ this.inspectedUomValueInput.value = ''
1247
+ }
1248
+
1248
1249
  renewInventoryGrist() {
1249
1250
  this.selectedLocation = this.selectedLocation?.id
1250
1251
  ? this.formattedLocations.find(loc => loc.id === this.selectedLocation.id)
@@ -0,0 +1,314 @@
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, navigate, PageView } from '@things-factory/shell'
10
+ import { CommonButtonStyles } from '@things-factory/styles'
11
+ import { isMobileDevice } from '@things-factory/utils'
12
+
13
+ class CreateLoadingManifest extends localize(i18next)(PageView) {
14
+ static get styles() {
15
+ return [
16
+ MultiColumnFormStyles,
17
+ css`
18
+ :host {
19
+ display: flex;
20
+ flex-direction: column;
21
+ overflow-x: auto;
22
+ }
23
+ .container {
24
+ flex: 1;
25
+ display: flex;
26
+ overflow-y: auto;
27
+ min-height: 50vh;
28
+ }
29
+ .grist {
30
+ background-color: var(--main-section-background-color);
31
+ display: flex;
32
+ flex-direction: column;
33
+ flex: 1;
34
+ overflow-y: auto;
35
+ }
36
+ data-grist {
37
+ overflow-y: hidden;
38
+ flex: 1;
39
+ }
40
+ h2 {
41
+ padding: var(--subtitle-padding);
42
+ font: var(--subtitle-font);
43
+ color: var(--subtitle-text-color);
44
+ border-bottom: var(--subtitle-border-bottom);
45
+ }
46
+ .grist h2 {
47
+ margin: var(--grist-title-margin);
48
+ border: var(--grist-title-border);
49
+ font: var(--grist-title-font);
50
+ color: var(--secondary-color);
51
+ }
52
+
53
+ .grist h2 mwc-icon {
54
+ vertical-align: middle;
55
+ margin: var(--grist-title-icon-margin);
56
+ font-size: var(--grist-title-icon-size);
57
+ color: var(--grist-title-icon-color);
58
+ }
59
+
60
+ h2 + data-grist {
61
+ padding-top: var(--grist-title-with-grid-padding);
62
+ }
63
+ `
64
+ ]
65
+ }
66
+
67
+ static get properties() {
68
+ return {
69
+ shippingData: Object,
70
+ shippingGristConfig: Object
71
+ }
72
+ }
73
+
74
+ get context() {
75
+ return {
76
+ title: i18next.t('title.manifest'),
77
+ actions: [
78
+ {
79
+ title: i18next.t('button.submit'),
80
+ action: this._generateLoadingManifest.bind(this),
81
+ ...CommonButtonStyles.submit
82
+ }
83
+ ]
84
+ }
85
+ }
86
+
87
+ render() {
88
+ return html`
89
+ <form name="manifest" class="multi-column-form" autocomplete="off">
90
+ <fieldset>
91
+ <legend>${i18next.t('title.create_loading_manifest')}</legend>
92
+ <label>${i18next.t('label.scan_tracking_no')}</label>
93
+ <barcode-scanable-input
94
+ name="trackingNo"
95
+ custom-input
96
+ @keypress="${e => {
97
+ if (e.keyCode === 13) {
98
+ e.preventDefault()
99
+ if (this.trackingNoInput.value) {
100
+ this._fetchManifestItems(this.trackingNoInput.value)
101
+ }
102
+ }
103
+ }}"
104
+ ></barcode-scanable-input>
105
+ </fieldset>
106
+ </form>
107
+
108
+ <div class="container">
109
+ <div class="grist">
110
+ <h2><mwc-icon>list_alt</mwc-icon>${i18next.t('title.manifest_items')}</h2>
111
+
112
+ <data-grist
113
+ .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
114
+ .config=${this.shippingGristConfig}
115
+ .data=${this.shippingData}
116
+ ></data-grist>
117
+ </div>
118
+ </div>
119
+ `
120
+ }
121
+
122
+ constructor() {
123
+ super()
124
+ this.shippingData = { records: [] }
125
+ }
126
+
127
+ get shippingGrist() {
128
+ return this.shadowRoot.querySelector('data-grist')
129
+ }
130
+
131
+ get manifestForm() {
132
+ return this.shadowRoot.querySelector('form[name=manifest]')
133
+ }
134
+
135
+ get trackingNoInput() {
136
+ return this.shadowRoot.querySelector('barcode-scanable-input[name=trackingNo]')?.shadowRoot.querySelector('input')
137
+ }
138
+
139
+ pageUpdated(changes, lifecycle, before) {
140
+ if (this.active) {
141
+ this._focusOnTrackingNoInput()
142
+ }
143
+ }
144
+
145
+ pageInitialized() {
146
+ this.shippingGristConfig = {
147
+ list: { fields: ['refNo', 'trackingNo', 'transporter'] },
148
+ pagination: { infinite: true },
149
+ columns: [
150
+ { type: 'gutter', gutterName: 'sequence' },
151
+ {
152
+ type: 'gutter',
153
+ gutterName: 'button',
154
+ icon: 'close',
155
+ handlers: {
156
+ click: (columns, data, column, record, rowIndex) => {
157
+ const newData = data.records.filter((_, idx) => idx !== rowIndex)
158
+ this.shippingData = { ...this.shippingData, records: newData }
159
+ this.shippingGrist.dirtyData.records = newData
160
+ }
161
+ }
162
+ },
163
+ {
164
+ type: 'string',
165
+ name: 'refNo',
166
+ label: true,
167
+ header: i18next.t('field.order_no'),
168
+ width: 200
169
+ },
170
+ {
171
+ type: 'string',
172
+ name: 'trackingNo',
173
+ label: true,
174
+ header: i18next.t('field.tracking_no'),
175
+ width: 200
176
+ },
177
+ {
178
+ type: 'string',
179
+ name: 'transporter',
180
+ label: true,
181
+ header: i18next.t('field.shipping_provider'),
182
+ width: 200
183
+ }
184
+ ]
185
+ }
186
+ }
187
+
188
+ async _fetchManifestItems() {
189
+ try {
190
+ this._validateTrackingNo()
191
+
192
+ const filters = [
193
+ { name: 'trackingNo', operator: 'eq', value: this.trackingNoInput.value.trim() },
194
+ { name: 'manifest_id', operator: 'is_null', value: '' },
195
+ { name: 'status', operator: 'in', value: ['PACKING', 'DONE'] }
196
+ ]
197
+ const response = await client.query({
198
+ query: gql`
199
+ query releaseGoods($filters: [Filter], $sortings: [Sorting]) {
200
+ releaseGoods(filters: $filters, sortings: $sortings) {
201
+ items {
202
+ id
203
+ name
204
+ trackingNo
205
+ refNo
206
+ transporter
207
+ }
208
+ }
209
+ }
210
+ `,
211
+ variables: { filters, sortings: [] }
212
+ })
213
+
214
+ if (!response.errors) {
215
+ const releaseGood = (response.data.releaseGoods.items || []).map(record => {
216
+ return {
217
+ ...record,
218
+ orderNo: record?.refNo
219
+ }
220
+ })[0]
221
+
222
+ if (releaseGood && !this.shippingData.records.map(record => record.refNo).includes(releaseGood.refNo)) {
223
+ let manifestItems = this.shippingData.records || []
224
+ manifestItems.push({ ...releaseGood })
225
+ this.shippingData = { records: manifestItems }
226
+ } else {
227
+ this._showToast({ message: i18next.t('text.tracking_no_already_exist_in_manifest_list') })
228
+ }
229
+
230
+ this._clearTrackingNoInput()
231
+ }
232
+ } catch (e) {
233
+ this._showToast(e)
234
+ }
235
+ }
236
+
237
+ async _generateLoadingManifest() {
238
+ try {
239
+ this._validateManifestSubmission()
240
+
241
+ let manifest = {}
242
+ manifest.manifestItems = await this._getManifestItems()
243
+
244
+ const response = await client.mutate({
245
+ mutation: gql`
246
+ mutation generateManifest($manifest: NewManifest!) {
247
+ generateManifest(manifest: $manifest)
248
+ }
249
+ `,
250
+ variables: { manifest }
251
+ })
252
+
253
+ if (!response.errors) {
254
+ this._showToast({ message: i18next.t('text.manifest_has_been_generated') })
255
+ this._clearPage()
256
+ navigate('loading_manifest_list')
257
+ }
258
+ } catch (e) {
259
+ this._showToast(e)
260
+ }
261
+ }
262
+
263
+ _focusOnTrackingNoInput() {
264
+ setTimeout(() => this.trackingNoInput.focus(), 50)
265
+ }
266
+
267
+ _getManifestItems() {
268
+ return this.shippingGrist.data.records.map(record => {
269
+ return {
270
+ refNo: record.refNo,
271
+ trackingNo: record.trackingNo,
272
+ transporter: record.transporter
273
+ }
274
+ })
275
+ }
276
+
277
+ _validateManifestSubmission() {
278
+ if (!this.shippingGrist.data.records?.length) {
279
+ this._focusOnTrackingNoInput()
280
+ throw new Error(i18next.t('text.manifest_items_is_empty'))
281
+ }
282
+ }
283
+
284
+ _validateTrackingNo() {
285
+ if (!this.trackingNoInput?.value) {
286
+ this._focusOnTrackingNoInput()
287
+ throw new Error(i18next.t('text.tracking_no_is_empty'))
288
+ }
289
+ }
290
+
291
+ _clearTrackingNoInput() {
292
+ this.trackingNoInput.value = ''
293
+ this._focusOnTrackingNoInput()
294
+ }
295
+
296
+ _clearPage() {
297
+ this._clearTrackingNoInput()
298
+ this.shippingData = { ...this.shippingData, records: [] }
299
+ }
300
+
301
+ _showToast({ type, message }) {
302
+ document.dispatchEvent(
303
+ new CustomEvent('notify', {
304
+ detail: {
305
+ type,
306
+ message,
307
+ option: { timer: 1000 }
308
+ }
309
+ })
310
+ )
311
+ }
312
+ }
313
+
314
+ customElements.define('create-loading-manifest', CreateLoadingManifest)
@@ -0,0 +1,86 @@
1
+ import '@things-factory/grist-ui'
2
+
3
+ import { css, html, LitElement } from 'lit-element'
4
+
5
+ import { SingleColumnFormStyles } from '@things-factory/form-ui'
6
+ import { i18next, localize } from '@things-factory/i18n-base'
7
+
8
+ class DispatcherVerificationPopup extends localize(i18next)(LitElement) {
9
+ static get styles() {
10
+ return [
11
+ SingleColumnFormStyles,
12
+ css`
13
+ :host {
14
+ padding: 10px;
15
+ display: flex;
16
+ flex-direction: column;
17
+ overflow-x: overlay;
18
+ background-color: var(--main-section-background-color);
19
+ }
20
+ .button-container {
21
+ padding: var(--button-container-padding);
22
+ margin: var(--button-container-margin);
23
+ text-align: var(--button-container-align);
24
+ background-color: var(--button-container-background);
25
+ height: var(--button-container-height);
26
+ }
27
+ `
28
+ ]
29
+ }
30
+
31
+ get dispatchmentForm() {
32
+ return this.shadowRoot.querySelector('form[name=dispatchmentForm]')
33
+ }
34
+
35
+ render() {
36
+ return html`
37
+ <form name="dispatchmentForm" class="single-column-form">
38
+ <fieldset>
39
+ <label>${i18next.t('label.dispatcher_name')}</label>
40
+ <input name="dispatcherName" required />
41
+
42
+ <label>${i18next.t('label.dispatcher_identity')}</label>
43
+ <input name="dispatcherIdentity" required />
44
+ </fieldset>
45
+ </form>
46
+ <div class="button-container">
47
+ <mwc-button
48
+ @click="${this._updateManifestDispatchement.bind(this)}"
49
+ label="${`${i18next.t('button.confirm')}`}"
50
+ raised
51
+ ></mwc-button>
52
+ </div>
53
+ `
54
+ }
55
+
56
+ _updateManifestDispatchement() {
57
+ this._validateDispatchmentUpdate()
58
+ let dispatchment = this._serializeForm(this.dispatchmentForm)
59
+ this.dispatchEvent(
60
+ new CustomEvent('dispatch', {
61
+ detail: {
62
+ dispatcherName: dispatchment.dispatcherName,
63
+ dispatcherIdentity: dispatchment.dispatcherIdentity
64
+ }
65
+ })
66
+ )
67
+ history.back()
68
+ }
69
+
70
+ _serializeForm(form) {
71
+ let obj = {}
72
+ Array.from(form.querySelectorAll('input')).forEach(field => {
73
+ if (!field.hasAttribute('hidden') && field.value) {
74
+ obj[field.name] = field.type === 'checkbox' ? field.checked : field.value
75
+ }
76
+ })
77
+
78
+ return obj
79
+ }
80
+
81
+ _validateDispatchmentUpdate() {
82
+ this.dispatchmentForm.checkValidity()
83
+ }
84
+ }
85
+
86
+ window.customElements.define('dispatcher-verification-popup', DispatcherVerificationPopup)