@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
@@ -353,8 +353,8 @@ class ValidateGanPopup extends localize(i18next)(LitElement) {
353
353
  containerNo: raw.containerNo,
354
354
  containerSize: raw.containerSize,
355
355
  sku: raw.sku,
356
- batchId: raw.batchId,
357
- batchIdRef: raw.batchIdRef,
356
+ batchId: raw.batchId ? raw.batchId.trim() : raw.batchId,
357
+ batchIdRef: raw.batchIdRef ? raw.batchIdRef.trim() : raw.batchIdRef,
358
358
  packingType: raw.packingType,
359
359
  packingSize: raw.packingSize,
360
360
  uom: raw.uom,
@@ -6,6 +6,9 @@ class ProductListView extends localize(i18next)(LitElement) {
6
6
  static get styles() {
7
7
  return [
8
8
  css`
9
+ :host {
10
+ --product-item-selected-bg: transparent;
11
+ }
9
12
  .flex-col {
10
13
  display: flex;
11
14
  flex-direction: column;
@@ -38,6 +41,10 @@ class ProductListView extends localize(i18next)(LitElement) {
38
41
  grid-gap: 3px;
39
42
  color: #3a5877;
40
43
  border-bottom: solid 0.5px #3a5877;
44
+ cursor: pointer;
45
+ }
46
+ .product-item[selected] {
47
+ background-color: var(--product-item-selected-bg);
41
48
  }
42
49
  .product-item-info {
43
50
  grid-column: span 8;
@@ -159,7 +166,8 @@ class ProductListView extends localize(i18next)(LitElement) {
159
166
  callback: Object,
160
167
  _show: Boolean,
161
168
  selectedItems: { type: Array },
162
- selectAll: { type: Boolean }
169
+ selectAll: { type: Boolean },
170
+ selectedRecordId: { type: String, attribute: false }
163
171
  }
164
172
  }
165
173
 
@@ -183,12 +191,23 @@ class ProductListView extends localize(i18next)(LitElement) {
183
191
  try {
184
192
  itm = flattenObject(itm, '.')
185
193
 
194
+ const isSelected = this.selectedRecordId && itm.id === this.selectedRecordId
195
+
186
196
  return html`
187
197
  <div
188
198
  class="product-item"
199
+ ?selected=${isSelected}
189
200
  style="grid-template-columns: repeat(${
190
201
  this.config.columnLeft.size + this.config.columnRight.size + (this.config?.selectable ? 1 : 0)
191
202
  }, 1fr) !important;"
203
+ @click=${e => {
204
+ if (e.target.closest('button') || e.target.closest('input') || e.target.closest('.action-box')) {
205
+ return
206
+ }
207
+ if (typeof this.config?.onRecordClick === 'function') {
208
+ this.config.onRecordClick(itm, e, index)
209
+ }
210
+ }}
192
211
  >
193
212
  ${
194
213
  this.config?.selectable
@@ -243,14 +262,20 @@ class ProductListView extends localize(i18next)(LitElement) {
243
262
  case 'editableValueBox':
244
263
  const data = itm[field.field]
245
264
  const header = field.header
265
+ const placeholder = field.placeholder || ''
266
+ const defaultValue = field.defaultValue !== undefined ? field.defaultValue : 0
246
267
  return html`
247
268
  <div class="flex-col mt-5px">
248
269
  ${header != null || header != undefined ? html` <label class="product-item-info-title">${header}</label> ` : ``}
249
270
  <div class="qty-box">
250
271
  <input
251
272
  class="input-field"
252
- value="${data ? data : 0}"
253
- @change=${e => this._handleValueChange(field.field, parseFloat(e.target.value), index)}
273
+ placeholder="${placeholder}"
274
+ .value="${data ? data : defaultValue}"
275
+ @change=${e => {
276
+ const val = e.target.value.trim()
277
+ this._handleValueChange(field.field, val === '' ? null : parseFloat(val), index)
278
+ }}
254
279
  />
255
280
  </div>
256
281
  </div>`
@@ -0,0 +1,342 @@
1
+ import { i18next, localize } from '@things-factory/i18n-base'
2
+ import { css, html, LitElement } from 'lit-element'
3
+ import { flattenObject } from '@things-factory/utils'
4
+ import '@material/mwc-icon'
5
+
6
+ class MobileConditionOfGoodsListing extends localize(i18next)(LitElement) {
7
+ static get properties() {
8
+ return {
9
+ config: Object,
10
+ fetchHandler: Function,
11
+ _data: Array,
12
+ _showSpinner: Boolean
13
+ }
14
+ }
15
+
16
+ constructor() {
17
+ super()
18
+ this._data = []
19
+ this._originalData = []
20
+ this._showSpinner = false
21
+ this._isExecuting = false // Flag to prevent fetching during confirmation
22
+ }
23
+
24
+ static get styles() {
25
+ return [
26
+ css`
27
+ :host {
28
+ display: flex;
29
+ flex-direction: column;
30
+ flex: 1;
31
+ overflow: hidden;
32
+ position: relative;
33
+ }
34
+
35
+ .list-container {
36
+ flex: 1;
37
+ overflow-y: auto;
38
+ }
39
+
40
+ .list-item {
41
+ padding: 12px;
42
+ color: #3a5877;
43
+ border-bottom: solid 0.5px #3a5877;
44
+ position: relative;
45
+ }
46
+
47
+ .list-item[dirty]::before {
48
+ content: '';
49
+ position: absolute;
50
+ left: 0;
51
+ top: 0;
52
+ width: 0;
53
+ height: 0;
54
+ border-top: 12px solid #4caf50;
55
+ border-right: 12px solid transparent;
56
+ }
57
+
58
+ .dirty-icon {
59
+ position: absolute;
60
+ left: 2px;
61
+ top: 2px;
62
+ color: white;
63
+ font-size: 12px;
64
+ z-index: 1;
65
+ }
66
+
67
+ .list-item:hover {
68
+ background-color: var(--grid-record-hover-background-color, #f0f0f0);
69
+ }
70
+
71
+ .list-item[selected] {
72
+ background-color: var(--grid-record-selected-background-color, #e3f2fd);
73
+ }
74
+
75
+ .field-row {
76
+ display: flex;
77
+ flex-direction: row;
78
+ align-items: center;
79
+ padding: 0px;
80
+ gap: 16px;
81
+ }
82
+
83
+ .field-label {
84
+ font-weight: bold;
85
+ text-transform: capitalize;
86
+ font-size: 14px;
87
+ min-width: 150px;
88
+ color: #3a5877;
89
+ }
90
+
91
+ .field-value {
92
+ font-size: 14px;
93
+ color: #3a5877;
94
+ flex: 1;
95
+ }
96
+
97
+ .field-col {
98
+ display: flex;
99
+ flex-direction: row;
100
+ align-items: center;
101
+ gap: 16px;
102
+ width: 100%;
103
+ }
104
+
105
+ .selection-box {
106
+ flex: 1;
107
+ max-width: 300px;
108
+ padding: 6px;
109
+ border: 1px solid #ccc;
110
+ border-radius: 4px;
111
+ font-size: 14px;
112
+ background-color: var(--theme-white-color);
113
+ color: #3a5877;
114
+ }
115
+
116
+ .selection-box:focus {
117
+ outline: none;
118
+ border-color: var(--primary-color);
119
+ }
120
+
121
+ oops-spinner {
122
+ display: none;
123
+ position: absolute;
124
+ left: 50%;
125
+ top: 50%;
126
+ transform: translate(-50%, -50%);
127
+ }
128
+
129
+ oops-spinner[show] {
130
+ display: block;
131
+ }
132
+
133
+ oops-note {
134
+ display: block;
135
+ position: absolute;
136
+ left: 50%;
137
+ top: 50%;
138
+ transform: translate(-50%, -50%);
139
+ }
140
+ `
141
+ ]
142
+ }
143
+
144
+ firstUpdated() {
145
+ this._fetchData()
146
+ }
147
+
148
+ updated(changedProperties) {
149
+ // Like data-grist: only update handler reference when fetchHandler changes, don't fetch
150
+ if (changedProperties.has('fetchHandler')) {
151
+ // Just update the reference, don't fetch automatically
152
+ // Fetch will be called explicitly via fetch() method when needed
153
+ }
154
+
155
+ // Only fetch when config changes (like data-grist does)
156
+ if (changedProperties.has('config') && !this._isExecuting && !this._showSpinner) {
157
+ this._fetchData()
158
+ }
159
+ }
160
+
161
+ async _fetchData() {
162
+ if (!this.fetchHandler || !this.config) return
163
+
164
+ // Don't fetch if we're currently executing (e.g., during confirmation)
165
+ if (this._isExecuting) return
166
+
167
+ this._showSpinner = true
168
+ this.requestUpdate()
169
+
170
+ try {
171
+ const result = await this.fetchHandler({
172
+ page: 1,
173
+ limit: 9999 // Fetch all records for mobile view
174
+ })
175
+
176
+ // Only update data if we're not executing (to prevent resetting during confirmation)
177
+ if (!this._isExecuting) {
178
+ this._data = result.records || []
179
+ this._originalData = this._data.map(record => ({ ...record }))
180
+ }
181
+ } catch (error) {
182
+ console.error('Error fetching data:', error)
183
+ if (!this._isExecuting) {
184
+ this._data = []
185
+ this._originalData = []
186
+ }
187
+ } finally {
188
+ // Only hide spinner if we're not executing (spinner is controlled externally during execution)
189
+ if (!this._isExecuting) {
190
+ this._showSpinner = false
191
+ this.requestUpdate()
192
+ }
193
+ }
194
+ }
195
+
196
+ _onSelectionChange(e, record, field) {
197
+ const newValue = e.target.value
198
+
199
+ // Update the record
200
+ const updatedRecord = {
201
+ ...record,
202
+ [field]: newValue
203
+ }
204
+
205
+ // Update in data array
206
+ const index = this._data.findIndex(r => r.id === record.id)
207
+ if (index !== -1) {
208
+ this._data[index] = updatedRecord
209
+ }
210
+
211
+ // Trigger re-render to show dirty indicator
212
+ this.requestUpdate()
213
+
214
+ // Dispatch change event
215
+ this.dispatchEvent(
216
+ new CustomEvent('field-change', {
217
+ bubbles: true,
218
+ composed: true,
219
+ detail: {
220
+ record: updatedRecord,
221
+ field: field,
222
+ value: newValue,
223
+ originalRecord: record
224
+ }
225
+ })
226
+ )
227
+ }
228
+
229
+ _isRecordDirty(record) {
230
+ const original = this._originalData.find(r => r.id === record.id)
231
+ if (!original) return false
232
+ return original.conditionOfGoods !== record.conditionOfGoods
233
+ }
234
+
235
+ _renderField(fieldConfig, record) {
236
+ const fieldType = fieldConfig.type || 'text'
237
+ const field = fieldConfig.field
238
+ const header = fieldConfig.header
239
+ const value = record[field]
240
+ const fieldColor = fieldConfig.color || '#3A5877'
241
+
242
+ switch (fieldType) {
243
+ case 'selectionBox':
244
+ const options = fieldConfig.options || []
245
+ const optionValueKey = fieldConfig.optionValueKey || 'value'
246
+ const optionLabelKey = fieldConfig.optionLabelKey || 'label'
247
+ const currentValue = value || ''
248
+
249
+ return html`
250
+ <div class="field-row">
251
+ <div class="field-label" style="color: ${fieldColor}">${header || ''}</div>
252
+ <select
253
+ class="selection-box"
254
+ .value="${String(currentValue)}"
255
+ @change="${e => this._onSelectionChange(e, record, field)}"
256
+ style="color: ${fieldColor}"
257
+ >
258
+ ${fieldConfig.placeholder
259
+ ? html`<option value="">${fieldConfig.placeholder}</option>`
260
+ : ''}
261
+ ${options.map(option => {
262
+ const optValue = typeof option === 'object' ? option[optionValueKey] : option
263
+ const optLabel = typeof option === 'object' ? option[optionLabelKey] : option
264
+ const isSelected = String(optValue) === String(currentValue)
265
+ return html`<option value="${optValue}" ?selected="${isSelected}">${optLabel}</option>`
266
+ })}
267
+ </select>
268
+ </div>
269
+ `
270
+ case 'text':
271
+ default:
272
+ return html`
273
+ <div class="field-row">
274
+ <div class="field-label" style="color: ${fieldColor}">${header || ''}</div>
275
+ <div class="field-value" style="color: ${fieldColor}">${value || '-'}</div>
276
+ </div>
277
+ `
278
+ }
279
+ }
280
+
281
+ render() {
282
+ if (!this.config || !this.fetchHandler) {
283
+ return html``
284
+ }
285
+
286
+ var oops = !this._showSpinner && (!this._data || this._data.length === 0)
287
+
288
+ return html`
289
+ ${oops
290
+ ? html` <oops-note icon="list" title="EMPTY LIST" description="There are no records to be shown"></oops-note> `
291
+ : html``}
292
+ <div class="list-container">
293
+ ${this._data.map(record => {
294
+ try {
295
+ const flattenedRecord = flattenObject(record, '.')
296
+ const isDirty = this._isRecordDirty(record)
297
+ return html`
298
+ <div class="list-item" ?dirty=${isDirty}>
299
+ ${isDirty ? html`<mwc-icon class="dirty-icon">done</mwc-icon>` : ''}
300
+ ${this.config.columns.map(columnConfig => this._renderField(columnConfig, flattenedRecord))}
301
+ </div>
302
+ `
303
+ } catch (e) {
304
+ console.error('Error rendering record:', e)
305
+ return html``
306
+ }
307
+ })}
308
+ </div>
309
+ <oops-spinner ?show=${this._showSpinner}></oops-spinner>
310
+ `
311
+ }
312
+
313
+ fetch() {
314
+ this._fetchData()
315
+ }
316
+
317
+ getRecords() {
318
+ return this._data
319
+ }
320
+
321
+ getChangedRecords() {
322
+ return this._data.filter(record => {
323
+ const original = this._originalData.find(r => r.id === record.id)
324
+ if (!original) return false
325
+ return original.conditionOfGoods !== record.conditionOfGoods
326
+ })
327
+ }
328
+
329
+ showSpinner() {
330
+ this._showSpinner = true
331
+ this._isExecuting = true // Prevent fetching during execution
332
+ this.requestUpdate()
333
+ }
334
+
335
+ hideSpinner() {
336
+ this._showSpinner = false
337
+ this._isExecuting = false
338
+ this.requestUpdate()
339
+ }
340
+ }
341
+
342
+ window.customElements.define('mobile-condition-of-goods-listing', MobileConditionOfGoodsListing)