@things-factory/operato-wms 3.6.3 → 3.6.8

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.
@@ -880,7 +880,7 @@ class InventoryAdjustment extends connect(store)(localize(i18next)(PageView)) {
880
880
  async fetchBizplaces(bizplace = []) {
881
881
  const response = await client.query({
882
882
  query: gql`
883
- query bizplaces($filters: [Filter]) {
883
+ query bizplaces($filters: [Filter!]) {
884
884
  bizplaces(filters: $filters) {
885
885
  items {
886
886
  id
@@ -1,3 +1,4 @@
1
+ import '@things-factory/biz-ui'
1
2
  import '@things-factory/form-ui'
2
3
  import '@things-factory/grist-ui'
3
4
  import '@things-factory/import-ui'
@@ -7,16 +8,33 @@ import { css, html } from 'lit-element'
7
8
 
8
9
  import { MultiColumnFormStyles } from '@things-factory/form-ui'
9
10
  import { i18next, localize } from '@things-factory/i18n-base'
11
+ import { openPopup } from '@things-factory/layout-base'
10
12
  import { client, CustomAlert, navigate, PageView } from '@things-factory/shell'
11
13
  import { CommonButtonStyles, ScrollbarStyles } from '@things-factory/styles'
12
- import { gqlBuilder, isMobileDevice } from '@things-factory/utils'
14
+ import { isMobileDevice } from '@things-factory/utils'
15
+
16
+ import { fetchLocationSortingRule } from '../../fetch-location-sorting-rule'
17
+ import { LOCATION_SORTING_RULE } from '../constants'
18
+
19
+ // import { openPopup } from '@things-factory/layout-base'
20
+ // import { fetchLocationSortingRule } from '../../fetch-location-sorting-rule'
21
+ // import { INVENTORY_STATUS, LOCATION_SORTING_RULE } from '../constants'
22
+ // import { fetchSettingRule } from '../../../fetch-setting-value'
23
+
24
+ const _ = require('lodash')
13
25
 
14
26
  class CreateCycleCount extends localize(i18next)(PageView) {
15
27
  static get properties() {
16
28
  return {
17
29
  _searchFields: Array,
18
30
  config: Object,
19
- data: Object
31
+ data: Object,
32
+ _bizplaceId: String,
33
+ _bizplaceName: String,
34
+ fullRecords: Object,
35
+ inventoryData: Object,
36
+ _locationSortingRules: Object,
37
+ _ccStrat: String
20
38
  }
21
39
  }
22
40
 
@@ -83,117 +101,318 @@ class CreateCycleCount extends localize(i18next)(PageView) {
83
101
  ]
84
102
  }
85
103
 
86
- render() {
87
- return html`
88
- <form id="input-form" name="cycleCount" class="multi-column-form">
89
- <fieldset>
90
- <legend>${i18next.t('title.cycle_count_information')}</legend>
91
- <label>${i18next.t('label.execute_date')}</label>
92
- <input name="executionDate" type="date" min="${this._getStdDate()}" required />
93
- </fieldset>
94
- <fieldset>
95
- <legend>${i18next.t('title.cycle_count_configuration')}</legend>
96
- <label>${i18next.t('label.no_of_random_item')}</label>
97
- <input name="limit" type="number" />
98
- </fieldset>
99
- </form>
104
+ get dataGrist() {
105
+ return this.shadowRoot.querySelector('data-grist')
106
+ }
100
107
 
101
- <h2>${i18next.t('label.company')}</h2>
102
- <search-form id="search-form" .fields=${this._searchFields} @submit=${() => this.dataGrist.fetch()}></search-form>
103
-
104
- <div class="grist-container">
105
- <div class="grist">
106
- <data-grist
107
- id="userBizplaces"
108
- .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
109
- .config=${this.config}
110
- .fetchHandler="${this._fetchPartners.bind(this)}"
111
- ></data-grist>
112
- </div>
113
- </div>
114
- `
108
+ get date() {
109
+ return this.shadowRoot.querySelector('input[name=executionDate]')
115
110
  }
116
111
 
117
- get context() {
118
- return {
119
- title: i18next.t('title.create_cycle_count'),
120
- actions: [
121
- {
122
- title: i18next.t('button.create'),
123
- action: this._createCycleCount.bind(this),
124
- ...CommonButtonStyles.submit
125
- }
126
- ],
127
- help: 'operato-wms/inventory-check/create-cycle-count'
128
- }
112
+ get limitField() {
113
+ return this.shadowRoot.querySelector('input[name=limit]')
129
114
  }
130
115
 
131
- Updated(changes) {
132
- if (this.active) {
133
- this.dataGrist.fetch()
134
- }
116
+ get cycleCountForm() {
117
+ return this.shadowRoot.querySelector('form[name=cycleCount]')
135
118
  }
136
119
 
137
- firstUpdated() {
138
- this.config = {
139
- list: {
140
- fields: ['name', 'description']
141
- },
142
- pagination: { pages: [10, 20, 50, 2500] },
120
+ get dataGristConfig() {
121
+ return {
122
+ pagination: { infinite: true },
143
123
  rows: {
144
- selectable: {
145
- multiple: false
146
- },
147
- appendable: false
124
+ selectable: { multiple: false },
125
+ classifier: (record, rowIndex) => {
126
+ return {
127
+ emphasized: record.isError
128
+ }
129
+ }
148
130
  },
131
+ list: { fields: ['inventory', 'product', 'location', 'releaseQty'] },
149
132
  columns: [
150
133
  { type: 'gutter', gutterName: 'sequence' },
151
- { type: 'gutter', gutterName: 'row-selector', multiple: false },
134
+ {
135
+ type: 'gutter',
136
+ gutterName: 'button',
137
+ icon: 'close',
138
+ handlers: {
139
+ click: (columns, data, column, record, rowIndex) => {
140
+ try {
141
+ const newData = data.records.filter((_, idx) => idx !== rowIndex)
142
+ this.inventoryData = { ...this.inventoryData, records: newData }
143
+ this.dataGrist.dirtyData.records = newData
144
+ this._updateInventoryGrist()
145
+ } catch (e) {
146
+ this._showToast(e)
147
+ }
148
+ }
149
+ }
150
+ },
151
+ {
152
+ type: 'object',
153
+ name: 'inventory',
154
+ header: i18next.t('field.inventory_list'),
155
+ imex: {
156
+ header: i18next.t('field.inventory_list'),
157
+ key: 'inventory.name',
158
+ width: 50,
159
+ type: 'array',
160
+ arrData: []
161
+ },
162
+ record: {
163
+ editable: !!this._bizplaceId,
164
+ options: {
165
+ queryName: 'inventoryProductGroup',
166
+ basicArgs: {
167
+ filters: [
168
+ { name: 'bizplace_id', operator: 'eq', value: this._bizplaceId },
169
+ { name: 'groupType', operator: 'noteq', value: 'BUNDLE' }
170
+ ]
171
+ },
172
+ nameField: 'batchId',
173
+ descriptionField: 'productName',
174
+ select: [
175
+ { name: 'productId', hidden: true },
176
+ { name: 'remainUomValue', hidden: true },
177
+ { name: 'uom', hidden: true },
178
+ { name: 'batchId', header: i18next.t('field.batch_id') },
179
+ { name: 'batchIdRef', header: i18next.t('field.batch_no_ref') },
180
+ {
181
+ name: 'productSKU',
182
+ header: i18next.t('field.sku'),
183
+ width: 150
184
+ },
185
+ {
186
+ name: 'productName',
187
+ header: i18next.t('field.product_info'),
188
+ width: 280
189
+ },
190
+ { name: 'packingType', header: i18next.t('field.packing_type') },
191
+ { name: 'packingSize', header: i18next.t('field.packing_size') },
192
+ {
193
+ name: 'remainQty',
194
+ header: i18next.t('field.remain_qty'),
195
+ ignoreCondition: true
196
+ },
197
+ {
198
+ name: 'remainUomValueWithUom',
199
+ header: i18next.t('field.remain_uom_value'),
200
+ ignoreCondition: true
201
+ },
202
+ {
203
+ name: 'groupType',
204
+ header: i18next.t('field.group_type'),
205
+ ignoreCondition: true
206
+ }
207
+ ],
208
+ list: {
209
+ fields: [
210
+ 'batchId',
211
+ 'batchIdRef',
212
+ 'productName',
213
+ 'packingType',
214
+ 'remainQty',
215
+ 'remainUomValue',
216
+ 'groupType'
217
+ ]
218
+ }
219
+ }
220
+ },
221
+ width: 250
222
+ },
152
223
  {
153
224
  type: 'string',
154
- name: 'name',
155
- header: i18next.t('field.name'),
156
- width: 300
225
+ name: 'productSKU',
226
+ header: i18next.t('field.sku'),
227
+ width: 100
228
+ },
229
+ {
230
+ type: 'object',
231
+ name: 'product',
232
+ header: i18next.t('field.product'),
233
+ width: 150
234
+ },
235
+ {
236
+ type: 'code',
237
+ name: 'packingType',
238
+ header: i18next.t('field.packing_type'),
239
+ record: {
240
+ codeName: 'PACKING_TYPES'
241
+ },
242
+ width: 150
243
+ },
244
+ {
245
+ type: 'number',
246
+ name: 'packingSize',
247
+ header: i18next.t('field.packing_size'),
248
+ width: 140
249
+ },
250
+ {
251
+ type: 'integer',
252
+ name: 'remainQty',
253
+ header: i18next.t('field.available_qty'),
254
+ width: 140
157
255
  },
158
256
  {
159
257
  type: 'string',
160
- name: 'description',
161
- header: i18next.t('field.description'),
162
- width: 300
258
+ name: 'remainUomValueWithUom',
259
+ header: i18next.t('field.available_uom_value'),
260
+ width: 140
163
261
  }
164
262
  ]
165
263
  }
166
-
167
- this._searchFields = [
168
- {
169
- label: i18next.t('label.company'),
170
- name: 'name',
171
- type: 'text',
172
- props: { searchOper: 'i_like' }
173
- }
174
- ]
175
264
  }
176
265
 
177
- get dataGrist() {
178
- return this.shadowRoot.querySelector('data-grist')
266
+ get context() {
267
+ return {
268
+ title: i18next.t('title.create_cycle_count'),
269
+ actions: [
270
+ {
271
+ title: i18next.t('button.create'),
272
+ action: this._createCycleCount.bind(this),
273
+ ...CommonButtonStyles.submit
274
+ }
275
+ ],
276
+ help: 'operato-wms/inventory-check/create-cycle-count'
277
+ }
179
278
  }
180
279
 
181
- get searchForm() {
182
- return this.shadowRoot.querySelector('search-form')
280
+ render() {
281
+ let dynamicFields = html``
282
+
283
+ switch (this._ccStrat) {
284
+ case 'random':
285
+ dynamicFields = html`
286
+ <form id="input-form" name="randomStratForm" class="multi-column-form">
287
+ <fieldset>
288
+ <label>${i18next.t('label.no_of_random_item')}</label>
289
+ <input name="limit" type="number" />
290
+ </fieldset>
291
+ </form>
292
+ `
293
+ break
294
+ case 'product':
295
+ dynamicFields = html`
296
+ <div class="grist-container">
297
+ <div class="grist">
298
+ <data-grist
299
+ id="inventory"
300
+ .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
301
+ .config=${this.inventoryGristConfig}
302
+ .data=${this.inventoryData}
303
+ @field-change="${this._gristFieldChange.bind(this)}"
304
+ ></data-grist>
305
+ </div>
306
+ </div>
307
+ `
308
+ default:
309
+ break
310
+ }
311
+
312
+ return html`
313
+ <form id="input-form" name="cycleCount" class="multi-column-form">
314
+ <fieldset>
315
+ <legend>${i18next.t('title.cycle_count_information')}</legend>
316
+ <label>${i18next.t('label.company')}</label>
317
+ <input type="hidden" name="bizplaceId" value="${this._bizplaceId}" />
318
+ <input
319
+ type="text"
320
+ name="bizplaceName"
321
+ value="${this._bizplaceName}"
322
+ readonly
323
+ @click="${this._openBizplacesSelector.bind(this)}"
324
+ />
325
+
326
+ <label>${i18next.t('label.execute_date')}</label>
327
+ <input name="executionDate" type="date" min="${this._getStdDate()}" required />
328
+ </fieldset>
329
+ <fieldset>
330
+ <legend>${i18next.t('title.cycle_count_configuration')}</legend>
331
+
332
+ <input
333
+ id="cc-by-random-product"
334
+ name="cc-strat"
335
+ type="radio"
336
+ value="random"
337
+ @change="${e => {
338
+ this._ccStrat = e.currentTarget.value
339
+ this.inventoryGristConfig = this.dataGristConfig
340
+ }}"
341
+ .checked="${this._ccStrat === 'random'}"
342
+ checked
343
+ />
344
+ <label for="cc-by-random-product">Random</label>
345
+
346
+ <input
347
+ id="cc-by-product"
348
+ name="cc-strat"
349
+ type="radio"
350
+ value="product"
351
+ @change="${e => {
352
+ this._ccStrat = e.currentTarget.value
353
+ this.inventoryGristConfig = this.dataGristConfig
354
+ }}"
355
+ .checked="${this._ccStrat === 'product'}"
356
+ checked
357
+ />
358
+ <label for="cc-by-product">By Product</label>
359
+ </fieldset>
360
+ </form>
361
+
362
+ ${dynamicFields}
363
+ `
364
+
365
+ /* <label>${i18next.t('label.no_of_random_item')}</label>
366
+ <input name="limit" type="number" />
367
+ <div class="grist-container">
368
+ <div class="grist">
369
+ <data-grist
370
+ id="inventory"
371
+ .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
372
+ .config=${this.inventoryGristConfig}
373
+ .data=${this.inventoryData}
374
+ .fetchHandler="${this.fetchHandler.bind(this)}"
375
+ @field-change="${this._gristFieldChange.bind(this)}"
376
+ ></data-grist>
377
+ </div>
378
+ </div> */
183
379
  }
184
380
 
185
- get date() {
186
- return this.shadowRoot.querySelector('input[name=executionDate]')
381
+ pageUpdated(changes) {
382
+ if (this.active) {
383
+ this._bizplaceName = this._bizplaceId && this._bizplaceName ? this._bizplaceName : ''
384
+ this._clearView()
385
+ }
187
386
  }
188
387
 
189
- get limitField() {
190
- return this.shadowRoot.querySelector('input[name=limit]')
388
+ constructor() {
389
+ super()
390
+ this.inventoryData = { ...this.inventoryData, records: [] }
191
391
  }
192
392
 
193
- get cycleCountForm() {
194
- return this.shadowRoot.querySelector('form[name=cycleCount]')
393
+ async pageInitialized() {
394
+ this._ccStrat = 'random'
395
+ this._locationSortingRules = await fetchLocationSortingRule(LOCATION_SORTING_RULE.CREATE_RELEASE_ORDER.value)
396
+ this.inventoryGristConfig = this.dataGristConfig
195
397
  }
196
398
 
399
+ // fetchHandler({ page, limit, sorters }) {
400
+ // if (this.fullRecords?.length > 0) {
401
+ // this.inventoryData = {
402
+ // total: this.fullRecords.length,
403
+ // records: _(this.fullRecords)
404
+ // .drop((page - 1) * limit)
405
+ // .take(limit)
406
+ // .value()
407
+ // }
408
+ // } else {
409
+ // this.inventoryData = {
410
+ // total: 0,
411
+ // records: []
412
+ // }
413
+ // }
414
+ // }
415
+
197
416
  async _fetchPartners({ page, limit, sorters = [] }) {
198
417
  const filters = await this.searchForm.getQueryFilters()
199
418
  const pagination = { page, limit }
@@ -244,13 +463,13 @@ class CreateCycleCount extends localize(i18next)(PageView) {
244
463
  })
245
464
 
246
465
  let limit
247
- if (this.limitField.value) limit = this.limitField.value
466
+ if (this?.limitField?.value) limit = this.limitField.value
248
467
  else limit = null
249
468
 
250
469
  const worksheetNo = await this.generateCycleCountWorksheet(
251
- this.dataGrist.selected[0].id,
470
+ this._bizplaceId,
252
471
  this.date.value,
253
- parseInt(limit)
472
+ limit ? parseInt(limit) : null
254
473
  )
255
474
 
256
475
  if (worksheetNo) {
@@ -270,49 +489,204 @@ class CreateCycleCount extends localize(i18next)(PageView) {
270
489
  }
271
490
  } catch (e) {
272
491
  this._showToast(e)
492
+
493
+ CustomAlert({
494
+ title: i18next.t('title.error'),
495
+ text: i18next.t('text.x_error', { state: { x: i18next.t('text.create_cycle_count_worksheet') } }),
496
+ confirmButton: { text: i18next.t('button.confirm') }
497
+ })
273
498
  }
274
499
  }
275
500
 
276
501
  _validate() {
277
- const _selectedInventory = this.dataGrist.selected
278
- if (_selectedInventory && _selectedInventory.length == 0) {
279
- throw new Error(i18next.t('text.there_no_selected'))
502
+ // const _selectedInventory = this.dataGrist.selected
503
+ // if (_selectedInventory && _selectedInventory.length == 0) {
504
+ // throw new Error(i18next.t('text.there_no_selected'))
505
+ // }
506
+
507
+ if (!this._bizplaceId || this?._bizplaceId == '') {
508
+ throw new Error(i18next.t('text.bizplace_is_not_selected'))
280
509
  }
281
510
 
282
511
  if (!this.cycleCountForm.checkValidity()) {
283
512
  throw new Error(i18next.t('text.cycle_count_date_invalid'))
284
513
  }
285
514
 
286
- if (this.limitField.value && this.limitField.value <= 0) {
287
- throw new Error(i18next.t('text.invalid_no_of_random_item_value'))
515
+ switch (this._ccStrat) {
516
+ case 'random':
517
+ if (this.limitField.value && this.limitField.value <= 0) {
518
+ throw new Error(i18next.t('text.invalid_no_of_random_item_value'))
519
+ }
520
+ break
521
+
522
+ case 'product':
523
+ if (this?.inventoryData?.records && this?.inventoryData?.records <= 0) {
524
+ throw new Error(i18next.t('text.nothing_selected'))
525
+ }
526
+ break
527
+ default:
528
+ break
288
529
  }
289
530
  }
290
531
 
291
532
  async generateCycleCountWorksheet(customerId, executionDate, limit) {
292
- const response = await client.query({
293
- query: gql`
294
- mutation {
295
- generateCycleCountWorksheet(${gqlBuilder.buildArgs({
296
- executionDate,
297
- customerId,
298
- limit
299
- })}) {
533
+ // const response = await client.query({
534
+ // query: gql`
535
+ // mutation {
536
+ // generateCycleCountWorksheet(${gqlBuilder.buildArgs({
537
+ // executionDate,
538
+ // customerId,
539
+ // limit
540
+ // })}) {
541
+ // name
542
+ // }
543
+ // }
544
+ // `
545
+ // })
546
+
547
+ let orderInventories =
548
+ this?.inventoryData?.records.map(itm => {
549
+ return {
550
+ batchId: itm.batchId,
551
+ batchIdRef: itm.batchIdRef,
552
+ packingSize: itm.packingSize,
553
+ packingType: itm.packingType,
554
+ productId: itm.productId
555
+ }
556
+ }) || []
557
+
558
+ const response = await client.mutate({
559
+ mutation: gql`
560
+ mutation generateCycleCountWorksheet(
561
+ $executionDate: String!
562
+ $customerId: String!
563
+ $orderInventories: [OrderInventoryPatch]
564
+ $limit: Int
565
+ ) {
566
+ generateCycleCountWorksheet(
567
+ executionDate: $executionDate
568
+ customerId: $customerId
569
+ orderInventories: $orderInventories
570
+ limit: $limit
571
+ ) {
300
572
  name
301
573
  }
302
574
  }
303
- `
575
+ `,
576
+ variables: { executionDate, customerId, orderInventories, limit }
304
577
  })
305
578
 
306
579
  if (!response.errors) {
307
- this.clearView()
580
+ this._clearView()
308
581
  return response.data.generateCycleCountWorksheet.name
309
582
  } else {
310
583
  return null
311
584
  }
312
585
  }
313
586
 
314
- clearView() {
315
- this.cycleCountForm.reset()
587
+ _clearView() {
588
+ // this.cycleCountForm.reset()
589
+ this._clearGristConditions()
590
+ }
591
+
592
+ _openBizplacesSelector() {
593
+ openPopup(
594
+ html`
595
+ <popup-partner-bizplaces-selector
596
+ @selected="${e => {
597
+ this._bizplaceId = e.detail.id
598
+ this._bizplaceName = e.detail.name
599
+ this.inventoryGristConfig = this.dataGristConfig
600
+ this.inventoryData = { records: [] }
601
+ this._clearGristConditions()
602
+ }}"
603
+ ></popup-partner-bizplaces-selector>
604
+ `,
605
+ {
606
+ backdrop: true,
607
+ size: 'large',
608
+ title: i18next.t('title.select_company')
609
+ }
610
+ )
611
+ }
612
+
613
+ _updateInventoryGrist(e) {
614
+ const _selectedInv = (this.dataGrist.dirtyData.records || []).map(record => {
615
+ return {
616
+ batchId: record?.inventory?.batchId,
617
+ packingType: record?.inventory?.packingType,
618
+ productId: record?.inventory?.productId,
619
+ groupType: record?.inventory?.groupType
620
+ }
621
+ })
622
+
623
+ this.inventoryGristConfig = {
624
+ ...this.inventoryGristConfig,
625
+ columns: this.inventoryGristConfig.columns.map(column => {
626
+ if (column.name === 'inventory') {
627
+ column.record.options.basicArgs = {
628
+ ...column.record.options.basicArgs,
629
+ filters: [...column.record.options.basicArgs.filters.filter(filter => filter.name !== 'batch_product')]
630
+ }
631
+
632
+ if (_selectedInv.length) {
633
+ column.record.options.basicArgs.filters.push({
634
+ name: 'batch_product',
635
+ value: _selectedInv,
636
+ operator: 'notin'
637
+ })
638
+ }
639
+ }
640
+
641
+ return column
642
+ })
643
+ }
644
+ }
645
+
646
+ _gristFieldChange(e) {
647
+ var { after, before, column, record, row } = e.detail
648
+ if (column?.name == 'inventory') {
649
+ this.inventoryData = {
650
+ ...this.inventoryData,
651
+ ...this.dataGrist.dirtyData,
652
+ records: this.dataGrist.dirtyData.records.map((record, idx) => {
653
+ // const factor = this._pickingStd === PICKING_STANDARD.SELECT_BY_PRODUCT.value ? e.detail.row : e.detail.record.id
654
+
655
+ let returnObj = {
656
+ ...record,
657
+ ...record.inventory,
658
+ name: record.name ? record.name : idx,
659
+ id: idx,
660
+ product: {
661
+ id: record?.inventory?.productId,
662
+ name: record?.inventory?.productName
663
+ }
664
+ }
665
+
666
+ return returnObj
667
+ })
668
+ }
669
+ }
670
+ this._updateInventoryGrist()
671
+ }
672
+
673
+ _clearGristConditions() {
674
+ if (this.inventoryGristConfig?.columns?.length) {
675
+ this.inventoryGristConfig = {
676
+ ...this.inventoryGristConfig,
677
+ columns: this.inventoryGristConfig.columns.map(column => {
678
+ if (column.name === 'inventory') {
679
+ column.record.options.basicArgs = {
680
+ filters: column.record.options.basicArgs.filters.filter(
681
+ filter => filter.name !== 'batch_product' && filter.name !== 'id'
682
+ )
683
+ }
684
+ }
685
+
686
+ return column
687
+ })
688
+ }
689
+ }
316
690
  }
317
691
 
318
692
  _getStdDate() {