@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
@@ -4,25 +4,28 @@ import '../components/popup-note'
4
4
  import '../components/popup-reusable-pallet'
5
5
  import './palletizing-pallets-popup'
6
6
  import '../components/worksheet-information-popup'
7
+ import '../components/unloading-worksheet-information-popup'
7
8
  import './unloaded-inventories-popup'
8
9
  import './undo-unload-popup'
10
+ import './condition-of-goods-popup'
9
11
  import './select-product-batch'
10
12
  import './serial-number-list-popup'
13
+ import '../components/list-view/product-list-view'
11
14
 
12
15
  import gql from 'graphql-tag'
13
16
  import { css, html } from 'lit-element'
14
17
  import orderBy from 'lodash-es/orderBy'
15
18
  import { connect } from 'pwa-helpers/connect-mixin.js'
16
19
 
17
- import { MultiColumnFormStyles } from '@things-factory/form-ui'
20
+ import { MultiColumnFormStyles, SingleColumnFormStyles } from '@things-factory/form-ui'
18
21
  import { i18next, localize } from '@things-factory/i18n-base'
19
22
  import { openPopup } from '@things-factory/layout-base'
20
- import { client, CustomAlert, PageView, store } from '@things-factory/shell'
23
+ import { client, CustomAlert, navigate, PageView, store } from '@things-factory/shell'
21
24
  import { CommonButtonStyles } from '@things-factory/styles'
22
25
  import { isMobileDevice, sleep } from '@things-factory/utils'
23
26
 
24
27
  import { fetchSettingRule } from '../../fetch-setting-value'
25
- import { ARRIVAL_NOTICE, RETURN_ORDER, VAS_TYPE } from '../constants'
28
+ import { ARRIVAL_NOTICE, RETURN_ORDER, VAS_TYPE, CONDITION_OF_GOODS } from '../constants'
26
29
 
27
30
  const AVAIL_ORDER_TYPES = { ARRIVAL_NOTICE, RETURN_ORDER }
28
31
  class UnloadProduct extends connect(store)(localize(i18next)(PageView)) {
@@ -60,13 +63,15 @@ class UnloadProduct extends connect(store)(localize(i18next)(PageView)) {
60
63
  _pageSettings: Object,
61
64
  _productBarcode: String,
62
65
  _productBarcodeScanned: String,
63
- _packingTypeScanned: String
66
+ _packingTypeScanned: String,
67
+ _conditionOfGoods: String
64
68
  }
65
69
  }
66
70
 
67
71
  static get styles() {
68
72
  return [
69
73
  MultiColumnFormStyles,
74
+ SingleColumnFormStyles,
70
75
  css`
71
76
  data-grist {
72
77
  overflow: auto;
@@ -93,6 +98,135 @@ class UnloadProduct extends connect(store)(localize(i18next)(PageView)) {
93
98
  color: var(--grist-title-icon-color);
94
99
  cursor: pointer;
95
100
  }
101
+
102
+ /* Mobile form styles - matching landing page design */
103
+ .mobile-form {
104
+ display: flex;
105
+ flex-direction: column;
106
+ gap: 10px;
107
+ padding: 8px 0;
108
+ }
109
+ .mobile-form fieldset {
110
+ display: flex;
111
+ flex-direction: column;
112
+ align-items: flex-start !important;
113
+ border: none;
114
+ margin: 0;
115
+ padding: 0;
116
+ width: 100%;
117
+ box-sizing: border-box;
118
+ }
119
+ .mobile-form label {
120
+ display: block;
121
+ grid-column: unset !important;
122
+ text-align: left !important;
123
+ align-self: flex-start !important;
124
+ margin-bottom: 4px;
125
+ color: #333;
126
+ font-weight: bold;
127
+ font-size: 14px;
128
+ width: 100%;
129
+ text-transform: capitalize;
130
+ }
131
+ .mobile-form select,
132
+ .mobile-form input[type="text"],
133
+ .mobile-form input[type="date"] {
134
+ grid-column: unset !important;
135
+ width: 100%;
136
+ max-width: 100%;
137
+ height: 24px;
138
+ min-height: 24px;
139
+ border: 1px solid rgba(0, 0, 0, 0.2);
140
+ border-radius: 4px;
141
+ padding: 4px 8px;
142
+ font-size: 12px;
143
+ background-color: white;
144
+ box-sizing: border-box;
145
+ }
146
+ .mobile-form input[type="number"] {
147
+ grid-column: unset !important;
148
+ width: 100%;
149
+ max-width: 100%;
150
+ height: 24px;
151
+ min-height: 24px;
152
+ border: 1px solid rgba(0, 0, 0, 0.2);
153
+ border-radius: 4px;
154
+ padding: 4px 8px;
155
+ padding-right: 24px;
156
+ font-size: 12px;
157
+ background-color: white;
158
+ box-sizing: border-box;
159
+ -moz-appearance: textfield;
160
+ }
161
+ .mobile-form input[type="number"]::-webkit-inner-spin-button,
162
+ .mobile-form input[type="number"]::-webkit-outer-spin-button {
163
+ opacity: 1;
164
+ height: 16px;
165
+ }
166
+ .mobile-form select:focus,
167
+ .mobile-form input:focus {
168
+ outline: none;
169
+ border-color: #017E7F;
170
+ }
171
+ .mobile-form [custom-input] {
172
+ grid-column: unset !important;
173
+ width: 100%;
174
+ box-sizing: border-box;
175
+ border: var(--input-field-border, 1px solid rgba(0, 0, 0, 0.2));
176
+ border-radius: var(--input-field-border-radius, 4px);
177
+ }
178
+ .mobile-form barcode-scanable-input {
179
+ width: 100%;
180
+ max-width: 100%;
181
+ box-sizing: border-box;
182
+ min-width: 0;
183
+ height: 24px;
184
+ min-height: 24px;
185
+ }
186
+ .mobile-form input[disabled] {
187
+ background-color: #f5f5f5;
188
+ cursor: not-allowed;
189
+ }
190
+ .mobile-form .side-by-side-row {
191
+ display: flex;
192
+ flex-direction: row;
193
+ gap: 10px;
194
+ }
195
+ .mobile-form .side-by-side-row fieldset {
196
+ flex: 1;
197
+ min-width: 0;
198
+ }
199
+ /* End of mobile form styles - matching landing page design */
200
+
201
+ .order-no {
202
+ display: -webkit-box;
203
+ -webkit-line-clamp: 1;
204
+ -webkit-box-orient: vertical;
205
+ overflow: hidden;
206
+ }
207
+ .order-info {
208
+ border: none;
209
+ background: none;
210
+ display: flex;
211
+ justify-content: space-between;
212
+ align-items: center;
213
+ padding: 8px;
214
+ color: #017E7F;
215
+ font-weight: bold;
216
+ }
217
+ .hr-line {
218
+ border: none;
219
+ border-top: 1px solid #017E7F;
220
+ margin: 0px;
221
+ }
222
+ product-list-view {
223
+ display: flex;
224
+ flex-direction: column;
225
+ flex: 1;
226
+ min-height: 0;
227
+ overflow-y: auto;
228
+ --product-item-active-bg: #F0FFFA;
229
+ }
96
230
  `
97
231
  ]
98
232
  }
@@ -159,10 +293,21 @@ class UnloadProduct extends connect(store)(localize(i18next)(PageView)) {
159
293
  }
160
294
 
161
295
  if (this._unloadedInventories && this._unloadedInventories.length > 0) {
296
+ actions.push(
297
+ {
298
+ title: i18next.t('button.undo'),
299
+ action: this._openUndoUnloadPopup.bind(this),
300
+ ...CommonButtonStyles.undo
301
+ }
302
+ )
303
+ }
304
+
305
+ if (this._worksheetInfo) {
162
306
  actions.push({
163
- title: i18next.t('button.undo'),
164
- action: this._openUndoUnloadPopup.bind(this),
165
- ...CommonButtonStyles.undo
307
+ title: i18next.t('button.condition'),
308
+ action: this._openConditionPopup.bind(this),
309
+ ...CommonButtonStyles.undo,
310
+ icon: 'search'
166
311
  })
167
312
  }
168
313
 
@@ -232,182 +377,239 @@ class UnloadProduct extends connect(store)(localize(i18next)(PageView)) {
232
377
  ?.shadowRoot?.querySelector('input')
233
378
  }
234
379
 
235
- render() {
236
- return html`
237
- <form id="info-form" class="multi-column-form">
238
- <fieldset>
239
- <label>${i18next.t('label.order_type')}</label>
240
- <select name="orderType" @change="${this.orderTypeChangeHandler.bind(this)}">
241
- ${Object.keys(AVAIL_ORDER_TYPES).map(key =>
242
- this.refOrderType === AVAIL_ORDER_TYPES[key].value
243
- ? html`
244
- <option value="${AVAIL_ORDER_TYPES[key].value}" selected>
245
- ${i18next.t(`label.${AVAIL_ORDER_TYPES[key].name}`)}
246
- </option>
247
- `
248
- : html`
249
- <option value="${AVAIL_ORDER_TYPES[key].value}">
250
- ${i18next.t(`label.${AVAIL_ORDER_TYPES[key].name}`)}
251
- </option>
252
- `
253
- )}
254
- </select>
380
+ get unloadingMobileListConfig() {
381
+ return {
382
+ selectable: false,
383
+ onRecordClick: (record, e, index) => {
384
+ if (record && record.batchId) {
385
+ const unflattenedRecord = this._unflattenData(record)
386
+ this._selectedOrderProduct = unflattenedRecord
387
+ this._isRequiredCheckExpiry = unflattenedRecord.product?.isRequiredCheckExpiry
388
+ this._isRequiredCheckManufactureDate = unflattenedRecord.product?.isRequiredCheckManufactureDate
389
+ this._isRequireSerialNumberScanningInbound = unflattenedRecord.product?.isRequireSerialNumberScanningInbound
390
+ this._isQtyChecked = false
391
+
392
+ this.inputForm.reset()
393
+
394
+ if (this._isRequiredCheckManufactureDate && unflattenedRecord?.manufactureDate && this.refOrderType === AVAIL_ORDER_TYPES.ARRIVAL_NOTICE.value) {
395
+ setTimeout(() => {
396
+ if (this.manufactureDateInput) {
397
+ this.manufactureDateInput.value = this._formatDateForInput(unflattenedRecord.manufactureDate)
398
+ }
399
+ }, 0)
400
+ }
255
401
 
256
- <label>${i18next.t('label.order_no')}</label>
257
- <barcode-scanable-input
258
- name="orderNo"
259
- custom-input
260
- @keypress="${e => {
261
- if (e.keyCode === 13) {
262
- e.preventDefault()
263
- if (this.orderNoInput.value) {
264
- this._clearView()
265
- this._scanning = false
266
- this._fetchProducts(this.orderNoInput.value)
267
- this.orderNoInput.value = ''
402
+ if (this._isRequiredCheckManufactureDate && unflattenedRecord?.inventory?.manufactureDate && this.refOrderType === AVAIL_ORDER_TYPES.RETURN_ORDER.value) {
403
+ setTimeout(() => {
404
+ if (this.manufactureDateInput) {
405
+ this.manufactureDateInput.value = this._formatDateForInput(unflattenedRecord.inventory.manufactureDate)
406
+ }
407
+ }, 0)
408
+ }
409
+
410
+ if (this._enableCartonLabel) {
411
+ if (this._enableProductScanning) {
412
+ this.productBarcodeInput.readOnly = false
413
+ this.productBarcodeInput.value = ''
414
+ }
415
+ if (unflattenedRecord.product?.isRequireSerialNumberScanningInbound) {
416
+ this.serialNumberInput.value = ''
417
+ }
418
+ this.cartonInput.value = ''
419
+ this._focusOnCartonInput()
420
+ } else {
421
+ this.lotInput.value = ''
422
+ this._focusOnLotInput()
423
+ }
424
+ }
425
+ },
426
+ columnLeft: {
427
+ size: 9,
428
+ fields: [
429
+ {
430
+ type: 'row',
431
+ content: [{ header: 'SKU No.', field: 'sku', type: 'string' }]
432
+ },
433
+ {
434
+ type: 'row',
435
+ content: [
436
+ {
437
+ header: 'Product Name',
438
+ field: 'product.name',
439
+ type: 'string',
440
+ style: '-webkit-line-clamp: 2;'
441
+ }
442
+ ]
443
+ },
444
+ {
445
+ type: 'row',
446
+ content: [
447
+ { header: 'Packing Type', field: 'packingType', type: 'string' },
448
+ { header: 'Packing Size', field: 'packingSize', type: 'string' }
449
+ ]
450
+ },
451
+ {
452
+ type: 'row',
453
+ content: [
454
+ { header: 'Mfg Date', field: 'displayManufactureDate', type: 'string' },
455
+ { header: 'Batch No', field: 'batchId', type: 'string' }
456
+ ]
457
+ },
458
+ {
459
+ type: 'row',
460
+ content: [{ header: 'Issue Note', field: 'issue', type: 'string' }]
461
+ }
462
+ ]
463
+ },
464
+ columnRight: {
465
+ size: 3,
466
+ fields: [
467
+ {
468
+ type: 'valueBox',
469
+ content: { header: 'Lot Qty.', field: 'actualPalletQty', field2: 'palletQty', type: 'string' }
470
+ },
471
+ {
472
+ type: 'valueBox',
473
+ content: { header: 'Pack Qty.', field: 'actualPackQty', field2: 'packQty', type: 'string' }
474
+ },
475
+ {
476
+ type: 'actionBox',
477
+ content: [
478
+ {
479
+ header: 'View',
480
+ callback: (item, e) => {
481
+ if (!this._unloadedInventories?.length) return
482
+
483
+ const { sku, batchId, packingType, packingSize } = item
484
+ openPopup(
485
+ html`
486
+ <unloaded-inventories-popup
487
+ .unloadedInventories="${this._unloadedInventories.filter(
488
+ inventory =>
489
+ inventory.batchId == batchId &&
490
+ inventory?.product.sku == sku &&
491
+ inventory.packingType == packingType &&
492
+ inventory.packingSize == packingSize
493
+ )}"
494
+ ></unloaded-inventories-popup>
495
+ `,
496
+ {
497
+ backdrop: true,
498
+ size: 'large',
499
+ title: i18next.t('title.unloaded_inventories')
500
+ }
501
+ )
502
+ },
503
+ btnClass: 'btn-success',
504
+ condition: (item) => {
505
+ return this._unloadedInventories?.length > 0
268
506
  }
269
507
  }
270
- }}"
271
- ></barcode-scanable-input>
272
- </fieldset>
273
- </form>
508
+ ]
509
+ }
510
+ ]
511
+ }
512
+ }
513
+ }
274
514
 
275
- <form id="input-form" class="multi-column-form">
276
- <fieldset>
277
- ${this._enableCartonLabel
278
- ? html`<label>${i18next.t('label.carton_id')}</label>
279
- <barcode-scanable-input
280
- name="cartonId"
281
- .value=${this._cartonId}
282
- without-enter
283
- custom-input
284
- ?scan-only=${this._pageSettings?.cameraScanOnly}
285
- @keypress="${async e => {
286
- if (e.keyCode === 13) {
287
- e.preventDefault()
288
- await this._transactionHandler(e)
289
- }
290
- }}"
291
- ></barcode-scanable-input>`
292
- : html`<label>${i18next.t('label.lot_id')}</label>
293
- <barcode-scanable-input
294
- name="lotId"
295
- .value=${this._palletId}
296
- without-enter
297
- custom-input
298
- ?scan-only=${this._pageSettings?.cameraScanOnly}
299
- @keypress="${async e => {
300
- if (e.keyCode === 13) {
301
- e.preventDefault()
302
- await this._transactionHandler(e)
303
- }
304
- }}"
305
- ></barcode-scanable-input>`}
306
- ${this._enableProductScanning
307
- ? html`
308
- <label>${i18next.t('label.product_barcode')}</label>
515
+ render() {
516
+ const isMobileDeviceCheck = isMobileDevice()
517
+
518
+ return html`
519
+ ${!isMobileDeviceCheck
520
+ ? html`
521
+ <form id="info-form" class="multi-column-form">
522
+ <fieldset>
523
+ <label>${i18next.t('label.order_type')}</label>
524
+ <select name="orderType" @change="${this.orderTypeChangeHandler.bind(this)}">
525
+ ${Object.keys(AVAIL_ORDER_TYPES).map(key =>
526
+ this.refOrderType === AVAIL_ORDER_TYPES[key].value
527
+ ? html`
528
+ <option value="${AVAIL_ORDER_TYPES[key].value}" selected>
529
+ ${i18next.t(`label.${AVAIL_ORDER_TYPES[key].name}`)}
530
+ </option>
531
+ `
532
+ : html`
533
+ <option value="${AVAIL_ORDER_TYPES[key].value}">
534
+ ${i18next.t(`label.${AVAIL_ORDER_TYPES[key].name}`)}
535
+ </option>
536
+ `
537
+ )}
538
+ </select>
539
+
540
+ <label>${i18next.t('label.order_no')}</label>
309
541
  <barcode-scanable-input
310
- name="productBarcode"
311
- .value=${this._productBarcode}
312
- without-enter
542
+ name="orderNo"
313
543
  custom-input
314
- ?scan-only=${this._pageSettings?.cameraScanOnly}
315
- @keypress="${async e => {
544
+ @keypress="${e => {
316
545
  if (e.keyCode === 13) {
317
546
  e.preventDefault()
318
- await this._transactionHandler(e)
547
+ if (this.orderNoInput.value) {
548
+ this._clearView()
549
+ this._scanning = false
550
+ this._fetchProducts(this.orderNoInput.value)
551
+ this.orderNoInput.value = ''
552
+ }
319
553
  }
320
554
  }}"
321
555
  ></barcode-scanable-input>
322
- <label>${i18next.t('label.packing_type')}</label>
323
- <input name="packingType" type="text" value="${this._packingTypeScanned}" disabled="true" />
324
- `
325
- : ''}
326
- ${this._enableInputQty >= 0 && this._enableInputQty < 4 && !this._isRequireSerialNumberScanningInbound
327
- ? html`
328
- <label>${i18next.t('label.actual_qty')}</label>
329
- <input
330
- name="qty"
331
- type="number"
332
- min="1"
333
- @keypress="${async e => {
334
- if (e.keyCode === 13) {
335
- e.preventDefault()
336
- await this._transactionHandler(e)
337
- }
338
- }}"
339
- />
340
- `
341
- : ''}
342
- ${this._isRequiredCheckManufactureDate
343
- ? html`
344
- <label>${i18next.t('label.manufacture_date')}</label>
345
- <input
346
- name="manufactureDate"
347
- type="date"
348
- @keypress="${async e => {
349
- if (e.keyCode === 13) {
350
- e.preventDefault()
351
- await this._transactionHandler(e)
352
- }
353
- }}"
354
- />
355
- `
356
- : ''}
357
- ${this._isRequiredCheckExpiry
358
- ? html`
359
- <label>${i18next.t('label.expiry_date')}</label>
360
- <input
361
- name="expDate"
362
- type="date"
363
- @keypress="${async e => {
364
- if (e.keyCode === 13) {
365
- e.preventDefault()
366
- await this._transactionHandler(e)
367
- }
368
- }}"
369
- />
370
- `
371
- : ''}
372
- </fieldset>
373
- ${this._isRequireSerialNumberScanningInbound
374
- ? html`<fieldset>
375
- <label>${i18next.t('label.serial_number')}</label>
376
- <barcode-scanable-input
377
- name="serialNumber"
378
- without-enter
379
- custom-input
380
- ?scan-only=${this._pageSettings?.cameraScanOnly}
381
- @keypress="${async e => {
382
- if (e.keyCode === 13) {
383
- e.preventDefault()
384
- await this._transactionHandler(e)
385
- }
386
- }}"
387
- ></barcode-scanable-input>
388
- </fieldset>`
389
- : ''}
390
- </form>
391
-
392
- <data-grist
393
- .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
394
- .config=${this.orderProductConfig}
395
- .data=${this.orderProductData}
396
- >
397
- <div slot="headroom" id="form-section">
398
- <form id="info-form" class="multi-column-form">
399
- <fieldset>
400
- <legend>
401
- ${this._orderNo}
402
- ${this._orderNo
403
- ? html`(${this._bizplaceName})
404
- <mwc-icon @click="${this._openWorksheetInformation.bind(this)}">info</mwc-icon>`
405
- : ''}
406
- </legend>
407
- </fieldset>
556
+ </fieldset>
557
+ </form>
558
+
559
+ <form id="input-form" class="multi-column-form">
560
+ ${this._renderFormFields(false)}
561
+ </form>
562
+
563
+ <data-grist
564
+ .mode=${'GRID'}
565
+ .config=${this.orderProductConfig}
566
+ .data=${this.orderProductData}
567
+ >
568
+ <div slot="headroom" id="form-section">
569
+ <form id="info-form" class="multi-column-form">
570
+ <fieldset>
571
+ <legend>
572
+ ${this._orderNo}
573
+ ${this._orderNo
574
+ ? html`(${this._bizplaceName})
575
+ <mwc-icon @click="${this._openWorksheetInformation.bind(this)}">info</mwc-icon>`
576
+ : ''}
577
+ </legend>
578
+ </fieldset>
579
+ </form>
580
+ </div>
581
+ </data-grist>
582
+ `
583
+ : html`
584
+ <form id="input-form" class="mobile-form">
585
+ ${this._renderFormFields(true)}
408
586
  </form>
409
- </div>
410
- </data-grist>
587
+
588
+ ${this._orderNo ?
589
+ html`
590
+ <button
591
+ class="order-info text-center"
592
+ ?disabled=${!Boolean(this._orderNo)}
593
+ @click=${this._openWorksheetInformation.bind(this)}
594
+ >
595
+ <label class="text-14 font-bold order-no"
596
+ >GAN No.: ${this._orderNo || ''}</label
597
+ >
598
+ <mwc-icon style="color:#007E7F;">info</mwc-icon>
599
+ </button>
600
+ ` : html``}
601
+
602
+ <hr class="hr-line">
603
+
604
+ <product-list-view
605
+ id="unloading"
606
+ style="--product-item-selected-bg: #F0FFFA;"
607
+ .config=${this.unloadingMobileListConfig}
608
+ .data=${this.orderProductData?.records || []}
609
+ .selectedRecordId=${this._selectedOrderProduct?.id || null}
610
+ ></product-list-view>
611
+ `
612
+ }
411
613
  `
412
614
  }
413
615
 
@@ -431,6 +633,7 @@ class UnloadProduct extends connect(store)(localize(i18next)(PageView)) {
431
633
  this.orderProductData = { records: [] }
432
634
  this.orderVasData = { records: [] }
433
635
  this.reusablePalletIdData = { records: [] }
636
+ this._conditionOfGoods = CONDITION_OF_GOODS.GOOD.value
434
637
  }
435
638
 
436
639
  updated(changedProps) {
@@ -444,9 +647,54 @@ class UnloadProduct extends connect(store)(localize(i18next)(PageView)) {
444
647
  }
445
648
  }
446
649
 
447
- pageUpdated() {
650
+ pageUpdated(changes, lifecycle, before) {
448
651
  if (this.active) {
449
- this._focusOnOrderNoField()
652
+ // Mobile: Check if we have params from landing page and auto-load
653
+ if (isMobileDevice() && lifecycle?.params) {
654
+ const orderType = lifecycle.params.orderType
655
+ const orderNo = lifecycle.params.orderNo
656
+
657
+ if (orderType && orderNo) {
658
+ this.refOrderType = orderType
659
+ this._clearView()
660
+ this._scanning = false
661
+ this._fetchProducts(orderNo)
662
+
663
+ requestAnimationFrame(() => {
664
+ const currentState = history.state || {}
665
+ // Only create overlay if it doesn't exist yet
666
+ if (!currentState.overlay) {
667
+ const beforeOverlay = currentState.overlay
668
+ const beforeSequence = beforeOverlay?.sequence || 0
669
+ const afterSequence = beforeSequence + 1
670
+
671
+ const newState = {
672
+ ...currentState,
673
+ overlay: {
674
+ name: 'unloading-page',
675
+ sequence: afterSequence,
676
+ escapable: true
677
+ }
678
+ }
679
+
680
+ // Replace current state with overlay (preserves URL)
681
+ history.replaceState(newState, '', window.location.href)
682
+
683
+ // Trigger popstate to update toolbar
684
+ window.dispatchEvent(
685
+ new PopStateEvent('popstate', {
686
+ state: newState
687
+ })
688
+ )
689
+ }
690
+ })
691
+ }
692
+ } else if (isMobileDevice() && !this._worksheetInfo && !lifecycle?.params) {
693
+ // Mobile: If no order loaded and no params, redirect to landing page
694
+ navigate('unloading_landing')
695
+ } else {
696
+ this._focusOnOrderNoField()
697
+ }
450
698
  }
451
699
  }
452
700
 
@@ -1092,6 +1340,7 @@ class UnloadProduct extends connect(store)(localize(i18next)(PageView)) {
1092
1340
  inventoryItems {
1093
1341
  serialNumber
1094
1342
  }
1343
+ conditionOfGoods
1095
1344
  }
1096
1345
  }
1097
1346
  `,
@@ -1141,6 +1390,10 @@ class UnloadProduct extends connect(store)(localize(i18next)(PageView)) {
1141
1390
  }
1142
1391
  }
1143
1392
 
1393
+ conditionOfGoodsChangeHandler(e) {
1394
+ this._conditionOfGoods = e.target.value
1395
+ }
1396
+
1144
1397
  _resetInput() {
1145
1398
  if (this._selectedOrderProduct?.isRequiredCheckExpiry && !this._isRequireSerialNumberScanningInbound)
1146
1399
  this.expDateInput.value = null
@@ -1268,6 +1521,7 @@ class UnloadProduct extends connect(store)(localize(i18next)(PageView)) {
1268
1521
  cartonId: this._enableCartonLabel ? this.cartonInput.value.trim() : null,
1269
1522
  expirationDate: this._isRequiredCheckExpiry ? this.expDateInput.value : null,
1270
1523
  manufactureDate: this._getManufactureDateValue(),
1524
+ conditionOfGoods: this._conditionOfGoods || CONDITION_OF_GOODS.GOOD.value
1271
1525
  }
1272
1526
  }
1273
1527
  })
@@ -1326,6 +1580,7 @@ class UnloadProduct extends connect(store)(localize(i18next)(PageView)) {
1326
1580
  cartonId: this._enableCartonLabel ? this.cartonInput.value.trim() : null,
1327
1581
  expirationDate: this._isRequiredCheckExpiry ? this.expDateInput.value : null,
1328
1582
  manufactureDate: this._getManufactureDateValue(),
1583
+ conditionOfGoods: this._conditionOfGoods || CONDITION_OF_GOODS.GOOD.value
1329
1584
  }
1330
1585
  }
1331
1586
  })
@@ -1421,7 +1676,8 @@ class UnloadProduct extends connect(store)(localize(i18next)(PageView)) {
1421
1676
  qty: qty, // Use the validated qty
1422
1677
  expirationDate: this._isRequiredCheckExpiry ? this.expDateInput.value : null,
1423
1678
  manufactureDate: this._getManufactureDateValue(),
1424
- reusablePallet: this._usingReusablePallet ? this._reusablePallet : null
1679
+ reusablePallet: this._usingReusablePallet ? this._reusablePallet : null,
1680
+ conditionOfGoods: this._conditionOfGoods || CONDITION_OF_GOODS.GOOD.value
1425
1681
  },
1426
1682
  productBarcode: this._enableProductScanning
1427
1683
  ? this._productBarcodeScanned
@@ -1994,10 +2250,42 @@ class UnloadProduct extends connect(store)(localize(i18next)(PageView)) {
1994
2250
  )
1995
2251
  }
1996
2252
 
2253
+ async _openConditionPopup() {
2254
+ if (!this.orderProductData || !this.orderProductData.records || this.orderProductData.records.length === 0) {
2255
+ this._showToast({ message: i18next.t('text.no_products_found'), type: 'warning' })
2256
+ return
2257
+ }
2258
+
2259
+ const worksheetDetailNames = this.orderProductData.records.map(record => record.name)
2260
+
2261
+ await sleep(1010)
2262
+ openPopup(
2263
+ html`
2264
+ <condition-of-goods-popup
2265
+ .unloadedInventories="${this._unloadedInventories}"
2266
+ .worksheetDetailNames="${worksheetDetailNames}"
2267
+ @condition-updated="${async e => {
2268
+ this._showToast({ message: i18next.t('text.info_update_successfully') })
2269
+ await this._fetchProducts(this._orderNo)
2270
+ }}"
2271
+ ></condition-of-goods-popup>
2272
+ `,
2273
+ {
2274
+ backdrop: true,
2275
+ size: 'large',
2276
+ title: i18next.t('title.condition_of_goods')
2277
+ }
2278
+ )
2279
+ }
2280
+
1997
2281
  _openWorksheetInformation() {
1998
2282
  openPopup(
1999
2283
  html`
2000
- <worksheet-information-popup .worksheetInformation="${this._worksheetInfo}"></worksheet-information-popup>
2284
+ <unloading-worksheet-information-popup
2285
+ .worksheetInformation="${this._worksheetInfo}"
2286
+ .unloadedInventories="${this._unloadedInventories || []}"
2287
+ .orderProductData="${this.orderProductData?.records || []}"
2288
+ ></unloading-worksheet-information-popup>
2001
2289
  `,
2002
2290
  {
2003
2291
  backdrop: true,
@@ -2031,6 +2319,31 @@ class UnloadProduct extends connect(store)(localize(i18next)(PageView)) {
2031
2319
  )
2032
2320
  }
2033
2321
 
2322
+ _unflattenData(flattenedData) {
2323
+ if (!flattenedData || typeof flattenedData !== 'object') return flattenedData
2324
+
2325
+ const unflattened = {}
2326
+ const nested = {}
2327
+
2328
+ Object.keys(flattenedData).forEach(key => {
2329
+ if (key.includes('.')) {
2330
+ const [rootKey, ...rest] = key.split('.')
2331
+ const restKeys = rest.join('.')
2332
+
2333
+ if (!nested[rootKey]) nested[rootKey] = {}
2334
+ nested[rootKey][restKeys] = flattenedData[key]
2335
+ } else {
2336
+ unflattened[key] = flattenedData[key]
2337
+ }
2338
+ })
2339
+
2340
+ Object.keys(nested).forEach(key => {
2341
+ unflattened[key] = this._unflattenData(nested[key])
2342
+ })
2343
+
2344
+ return unflattened
2345
+ }
2346
+
2034
2347
  async _openSerialNumberList() {
2035
2348
  await sleep(1010)
2036
2349
  openPopup(
@@ -2165,6 +2478,225 @@ class UnloadProduct extends connect(store)(localize(i18next)(PageView)) {
2165
2478
  this.orderNoInput.select()
2166
2479
  }
2167
2480
 
2481
+ _getFieldSequence(isMobile = false) {
2482
+ const conditions = {
2483
+ carton: this._enableCartonLabel,
2484
+ product: this._enableProductScanning,
2485
+ qty: this._enableInputQty >= 0 && this._enableInputQty < 4 && !this._isRequireSerialNumberScanningInbound,
2486
+ mfg: this._isRequiredCheckManufactureDate,
2487
+ exp: this._isRequiredCheckExpiry,
2488
+ serial: this._isRequireSerialNumberScanningInbound
2489
+ }
2490
+
2491
+ const conditionKey = [
2492
+ conditions.carton ? 'carton' : 'lot',
2493
+ conditions.product ? 'product' : '',
2494
+ conditions.qty ? 'qty' : '',
2495
+ conditions.mfg ? 'mfg' : '',
2496
+ conditions.exp ? 'exp' : '',
2497
+ conditions.serial ? 'serial' : ''
2498
+ ].filter(Boolean).join('_')
2499
+
2500
+ if (isMobile) {
2501
+ const getConditionRow = () =>
2502
+ conditions.product ? ['conditionOfGoods', 'packingType'] : ['conditionOfGoods']
2503
+
2504
+ // Predefined sequences for mobile (supports nested arrays for side-by-side)
2505
+ const sequences = {
2506
+ 'carton_product': ['cartonId', 'productBarcode', getConditionRow()],
2507
+ 'carton_product_qty': ['cartonId', 'productBarcode', getConditionRow(), 'qty'],
2508
+ 'carton_product_exp_serial': ['cartonId', 'productBarcode', ['expDate', 'serialNumber'], getConditionRow()],
2509
+ 'lot_qty_mfg_exp': [['lotId', 'qty'], ['manufactureDate', 'expiryDate'], getConditionRow()],
2510
+ 'carton_product_serial': ['cartonId', 'productBarcode', 'serialNumber', getConditionRow()],
2511
+ 'carton_product_mfg_exp': ['cartonId', 'productBarcode', ['manufactureDate', 'expiryDate'], getConditionRow()],
2512
+ 'carton_product_mfg_exp_serial': ['cartonId', ['productBarcode', 'serialNumber'], ['manufactureDate', 'expiryDate'], getConditionRow()],
2513
+ 'lot_qty_exp': ['lotId', ['expiryDate', 'qty'], getConditionRow()],
2514
+ 'carton_product_exp': ['cartonId', 'productBarcode', 'expiryDate', getConditionRow()],
2515
+ 'carton_product_qty_exp': ['cartonId', 'productBarcode', ['expiryDate', 'qty'], getConditionRow()],
2516
+ 'carton_product_qty_mfg_exp': ['cartonId', ['productBarcode', 'qty'], ['manufactureDate', 'expiryDate'], getConditionRow()],
2517
+ 'lot_qty': [['lotId', 'qty'], getConditionRow()],
2518
+ 'carton_product_qty_mfg': ['cartonId', 'productBarcode', ['qty', 'manufactureDate'], getConditionRow()],
2519
+ }
2520
+
2521
+ return sequences[conditionKey] || this._buildDynamicSequence(conditions, getConditionRow, isMobile)
2522
+ } else {
2523
+ // Web sequences (simple flat arrays, you can add specific combos here later)
2524
+ const sequences = {
2525
+ 'carton_product': ['cartonId', 'productBarcode', 'packingType', 'conditionOfGoods'],
2526
+ 'carton_product_qty': ['cartonId', 'productBarcode', 'packingType', 'conditionOfGoods', 'qty'],
2527
+ 'carton_product_exp_serial': ['cartonId', 'productBarcode', 'packingType', 'conditionOfGoods', 'expiryDate', 'serialNumber'],
2528
+ 'lotId_qty_mfg_exp': ['lotId', 'conditionOfGoods', 'packingType', 'manufactureDate', 'expiryDate', 'qty'],
2529
+ 'carton_product_sn': ['cartonId', 'productBarcode', 'packingType', 'conditionOfGoods', 'serialNumber'],
2530
+ 'carton_product_mfg_exp': ['cartonId', 'productBarcode', 'packingType', 'conditionOfGoods', 'manufactureDate', 'expiryDate'],
2531
+ 'carton_product_mfg_exp_serial': ['cartonId', 'productBarcode', 'packingType', 'conditionOfGoods', 'manufactureDate', 'expiryDate', 'serialNumber'],
2532
+ 'lotId_qty_exp': ['lotId', 'conditionOfGoods', 'packingType', 'expiryDate', 'qty'],
2533
+ 'carton_product_exp': ['cartonId', 'productBarcode', 'packingType', 'conditionOfGoods', 'expiryDate'],
2534
+ 'carton_product_qty_exp': ['cartonId', 'productBarcode', 'packingType', 'conditionOfGoods', 'expiryDate', 'qty'],
2535
+ 'carton_product_qty_mfg_exp': ['cartonId', 'productBarcode', 'packingType', 'conditionOfGoods', 'manufactureDate', 'expiryDate', 'qty'],
2536
+ 'lotId_qty': ['lotId', 'conditionOfGoods', 'packingType', 'qty'],
2537
+ }
2538
+
2539
+ return sequences[conditionKey] || this._buildDynamicSequence(conditions, null, isMobile)
2540
+ }
2541
+ }
2542
+
2543
+ _buildDynamicSequence(conditions, getConditionRow, isMobile = false) {
2544
+ if (isMobile) {
2545
+ const fields = [
2546
+ conditions.carton ? 'cartonId' : 'lotId',
2547
+ conditions.product && 'productBarcode',
2548
+ conditions.qty && 'qty',
2549
+ conditions.mfg && 'manufactureDate',
2550
+ conditions.exp && 'expiryDate',
2551
+ conditions.serial && 'serialNumber'
2552
+ ].filter(Boolean)
2553
+
2554
+ return [...fields, getConditionRow()]
2555
+ } else {
2556
+ const fields = [
2557
+ conditions.carton ? 'cartonId' : 'lotId',
2558
+ conditions.product && 'productBarcode',
2559
+ conditions.product && 'packingType',
2560
+ conditions.qty && 'qty',
2561
+ conditions.mfg && 'manufactureDate',
2562
+ conditions.exp && 'expiryDate',
2563
+ conditions.serial && 'serialNumber',
2564
+ 'conditionOfGoods'
2565
+ ].filter(Boolean)
2566
+
2567
+ return fields
2568
+ }
2569
+ }
2570
+
2571
+ _renderFormFields(isMobile = false) {
2572
+ const sequence = this._getFieldSequence(isMobile)
2573
+ const keypressHandler = async e => {
2574
+ if (e.keyCode === 13) {
2575
+ e.preventDefault()
2576
+ await this._transactionHandler(e)
2577
+ }
2578
+ }
2579
+
2580
+ if (isMobile) {
2581
+ return sequence.map(item => {
2582
+ if (Array.isArray(item)) {
2583
+ return html`
2584
+ <fieldset class="side-by-side-row">
2585
+ ${item.map(fieldName => html`<fieldset>${this._renderFieldContent(fieldName, keypressHandler)}</fieldset>`)}
2586
+ </fieldset>
2587
+ `
2588
+ }
2589
+ return html`<fieldset>${this._renderFieldContent(item, keypressHandler)}</fieldset>`
2590
+ })
2591
+ } else {
2592
+ return html`
2593
+ <fieldset>
2594
+ ${sequence.map(fieldName => this._renderFieldContent(fieldName, keypressHandler))}
2595
+ </fieldset>
2596
+ `
2597
+ }
2598
+ }
2599
+
2600
+ _renderFieldContent(fieldName, keypressHandler) {
2601
+ switch (fieldName) {
2602
+ case 'cartonId':
2603
+ return html`
2604
+ <label>${i18next.t('label.carton_id')}</label>
2605
+ <barcode-scanable-input
2606
+ name="cartonId"
2607
+ .value=${this._cartonId}
2608
+ without-enter
2609
+ custom-input
2610
+ ?scan-only=${this._pageSettings?.cameraScanOnly}
2611
+ @keypress="${keypressHandler}"
2612
+ ></barcode-scanable-input>
2613
+ `
2614
+ case 'lotId':
2615
+ return html`
2616
+ <label>${i18next.t('label.lot_id')}</label>
2617
+ <barcode-scanable-input
2618
+ name="lotId"
2619
+ .value=${this._palletId}
2620
+ without-enter
2621
+ custom-input
2622
+ ?scan-only=${this._pageSettings?.cameraScanOnly}
2623
+ @keypress="${keypressHandler}"
2624
+ ></barcode-scanable-input>
2625
+ `
2626
+ case 'productBarcode':
2627
+ return html`
2628
+ <label>${i18next.t('label.product_barcode')}</label>
2629
+ <barcode-scanable-input
2630
+ name="productBarcode"
2631
+ .value=${this._productBarcode}
2632
+ without-enter
2633
+ custom-input
2634
+ ?scan-only=${this._pageSettings?.cameraScanOnly}
2635
+ @keypress="${keypressHandler}"
2636
+ ></barcode-scanable-input>
2637
+ `
2638
+ case 'packingType':
2639
+ return html`
2640
+ <label>${i18next.t('label.packing_type')}</label>
2641
+ <input name="packingType" type="text" value="${this._packingTypeScanned}" disabled="true" />
2642
+ `
2643
+ case 'qty':
2644
+ return html`
2645
+ <label>${i18next.t('label.actual_qty')}</label>
2646
+ <input
2647
+ name="qty"
2648
+ type="number"
2649
+ min="1"
2650
+ @keypress="${keypressHandler}"
2651
+ />
2652
+ `
2653
+ case 'manufactureDate':
2654
+ return html`
2655
+ <label>${i18next.t('label.manufacture_date')}</label>
2656
+ <input
2657
+ name="manufactureDate"
2658
+ type="date"
2659
+ @keypress="${keypressHandler}"
2660
+ />
2661
+ `
2662
+ case 'expiryDate':
2663
+ return html`
2664
+ <label>${i18next.t('label.expiry_date')}</label>
2665
+ <input
2666
+ name="expDate"
2667
+ type="date"
2668
+ @keypress="${keypressHandler}"
2669
+ />
2670
+ `
2671
+ case 'serialNumber':
2672
+ return html`
2673
+ <label>${i18next.t('label.serial_number')}</label>
2674
+ <barcode-scanable-input
2675
+ name="serialNumber"
2676
+ without-enter
2677
+ custom-input
2678
+ ?scan-only=${this._pageSettings?.cameraScanOnly}
2679
+ @keypress="${keypressHandler}"
2680
+ ></barcode-scanable-input>
2681
+ `
2682
+ case 'conditionOfGoods':
2683
+ return html`
2684
+ <label>${i18next.t('label.condition')}</label>
2685
+ <select name="conditionOfGoods" .value="${this._conditionOfGoods || CONDITION_OF_GOODS.GOOD.value}" @change="${this.conditionOfGoodsChangeHandler.bind(this)}">
2686
+ ${Object.keys(CONDITION_OF_GOODS).map(key =>
2687
+ html`
2688
+ <option value="${CONDITION_OF_GOODS[key].value}" ?selected="${this._conditionOfGoods === CONDITION_OF_GOODS[key].value}">
2689
+ ${i18next.t(`label.${CONDITION_OF_GOODS[key].name}`)}
2690
+ </option>
2691
+ `
2692
+ )}
2693
+ </select>
2694
+ `
2695
+ default:
2696
+ return html``
2697
+ }
2698
+ }
2699
+
2168
2700
  async _exportableData() {
2169
2701
  try {
2170
2702
  let records = this.orderProductData.records