@things-factory/biz-ui 4.3.701 → 4.3.705

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.
@@ -105,7 +105,7 @@ class ContactPointsPopup extends localize(i18next)(LitElement) {
105
105
  firstUpdated() {
106
106
  this.config = {
107
107
  list: {
108
- fields: ['companyName', 'accountNo', 'email', 'name', 'description']
108
+ fields: ['companyName', 'accountNo', 'email', 'name', 'description', 'country', 'releaseShelfLife']
109
109
  },
110
110
  pagination: { pages: [10, 20, 50, 100] },
111
111
  rows: {
@@ -154,6 +154,18 @@ class ContactPointsPopup extends localize(i18next)(LitElement) {
154
154
  header: i18next.t('field.billing_address'),
155
155
  width: 350
156
156
  },
157
+ {
158
+ type: 'string',
159
+ name: 'country',
160
+ header: i18next.t('field.country'),
161
+ width: 350
162
+ },
163
+ {
164
+ type: 'number',
165
+ name: 'releaseShelfLife',
166
+ header: i18next.t('field.release_shelf_life'),
167
+ width: 350
168
+ },
157
169
  {
158
170
  type: 'string',
159
171
  name: 'phone',
@@ -234,8 +246,10 @@ class ContactPointsPopup extends localize(i18next)(LitElement) {
234
246
  address
235
247
  address2
236
248
  postCode
249
+ country
237
250
  city
238
251
  state
252
+ releaseShelfLife
239
253
  billingAddress
240
254
  }
241
255
  total
@@ -51,6 +51,7 @@ export class ContactPointList extends localize(i18next)(PageView) {
51
51
  .config=${this.config}
52
52
  .data=${this.data}
53
53
  .fetchHandler="${this.fetchHandler.bind(this)}"
54
+ @field-change=${this._onFieldChange.bind(this)}
54
55
  ></data-grist>
55
56
  `
56
57
  }
@@ -81,12 +82,20 @@ export class ContactPointList extends localize(i18next)(PageView) {
81
82
  rows: this.config.rows,
82
83
  columns: [...this.config.columns.filter(column => column.imex !== undefined)]
83
84
  }
84
- openImportPopUp(records, config, async patches => {
85
+ // Normalize records to handle Excel object values (e.g., hyperlinks for email) and numeric fields
86
+ const normalizedRecords = records.map(record => {
87
+ let normalized = this._normalizeStringFields(record)
88
+ normalized = this._normalizeNumericFields(normalized)
89
+ return normalized
90
+ })
91
+ openImportPopUp(normalizedRecords, config, async patches => {
85
92
  try {
86
93
  patches = patches.map(patch => {
87
94
  if (!patch?.id) delete patch.id
88
-
89
- return patch
95
+ // Normalize both string fields (for hyperlinks) and numeric fields (for GraphQL types)
96
+ let normalized = this._normalizeStringFields(patch)
97
+ normalized = this._normalizeNumericFields(normalized)
98
+ return normalized
90
99
  })
91
100
  await this._validateImport(patches)
92
101
  await this._saveContactPoints(patches)
@@ -302,6 +311,14 @@ export class ContactPointList extends localize(i18next)(PageView) {
302
311
  header: i18next.t('field.contact_point_state'),
303
312
  width: 150
304
313
  },
314
+ {
315
+ type: 'string',
316
+ name: 'country',
317
+ record: { editable: true },
318
+ imex: { header: i18next.t('field.country'), key: 'country', width: 40, type: 'string' },
319
+ header: i18next.t('field.country'),
320
+ width: 150
321
+ },
305
322
  {
306
323
  type: 'string',
307
324
  name: 'billingAddress',
@@ -315,6 +332,25 @@ export class ContactPointList extends localize(i18next)(PageView) {
315
332
  header: i18next.t('field.contact_point_billing_address'),
316
333
  width: 300
317
334
  },
335
+ {
336
+ type: 'number',
337
+ name: 'releaseShelfLife',
338
+ record: {
339
+ editable: true,
340
+ validation: (after, before, record, column) => {
341
+ const validation = this._validateReleaseShelfLife(after, true)
342
+ return validation.valid
343
+ }
344
+ },
345
+ header: i18next.t('field.release_shelf_life'),
346
+ imex: {
347
+ header: i18next.t('field.release_shelf_life'),
348
+ key: 'releaseShelfLife',
349
+ width: 40,
350
+ type: 'number'
351
+ },
352
+ width: 150
353
+ },
318
354
  {
319
355
  name: 'type',
320
356
  record: { editable: true },
@@ -357,6 +393,131 @@ export class ContactPointList extends localize(i18next)(PageView) {
357
393
  }
358
394
  }
359
395
 
396
+ /**
397
+ * Validates releaseShelfLife value
398
+ * @param {any} value - The value to validate
399
+ * @param {boolean} allowEmpty - Whether to allow empty/null/undefined values (default: true)
400
+ * @returns {{valid: boolean, error?: string}} - Validation result with optional error message
401
+ */
402
+ _validateReleaseShelfLife(value, allowEmpty = true) {
403
+ // Allow empty/null values if allowed
404
+ if (allowEmpty && (value === null || value === undefined || value === '')) {
405
+ return { valid: true }
406
+ }
407
+
408
+ // If not allowed and empty, return error
409
+ if (!allowEmpty && (value === null || value === undefined || value === '')) {
410
+ return { valid: false, error: i18next.t('field.release_shelf_life') + ' is required' }
411
+ }
412
+
413
+ const numValue = Number(value)
414
+
415
+ // Check if it's a valid number
416
+ if (isNaN(numValue)) {
417
+ return { valid: false, error: i18next.t('field.release_shelf_life') + ' must be a valid number' }
418
+ }
419
+
420
+ // Check if it's negative
421
+ if (numValue < 0) {
422
+ return { valid: false, error: i18next.t('field.release_shelf_life') + ' cannot be negative' }
423
+ }
424
+
425
+ // Check if it's an integer (no decimals)
426
+ if (!Number.isInteger(numValue)) {
427
+ return { valid: false, error: i18next.t('field.release_shelf_life') + ' must be an integer (no decimal values)' }
428
+ }
429
+
430
+ return { valid: true }
431
+ }
432
+
433
+ _onFieldChange(e) {
434
+ const { after, before, column, record, row } = e.detail
435
+
436
+ // Handle releaseShelfLife validation (redundant with column validation, but kept for user feedback)
437
+ if (column.name === 'releaseShelfLife') {
438
+ const validation = this._validateReleaseShelfLife(after, true)
439
+ if (!validation.valid) {
440
+ // Revert to previous value
441
+ this.dataGrist._data.records[row][column.name] = before
442
+ this.data = {
443
+ ...this.dataGrist._data
444
+ }
445
+ // Show error message
446
+ this.showToast(validation.error)
447
+ }
448
+ }
449
+ }
450
+
451
+ _normalizeStringFields(record) {
452
+ // Normalize object values to strings (e.g., Excel hyperlinks for email fields)
453
+ // ExcelJS may return email cells as objects like { text: 'email@example.com', hyperlink: 'mailto:...' }
454
+ const normalized = { ...record }
455
+ const stringFields = [
456
+ 'email',
457
+ 'name',
458
+ 'companyName',
459
+ 'description',
460
+ 'fax',
461
+ 'phone',
462
+ 'accountNo',
463
+ 'address',
464
+ 'address2',
465
+ 'postCode',
466
+ 'city',
467
+ 'state',
468
+ 'country',
469
+ 'billingAddress',
470
+ 'type'
471
+ ]
472
+ stringFields.forEach(field => {
473
+ if (normalized[field] && typeof normalized[field] === 'object' && !Array.isArray(normalized[field])) {
474
+ // Handle ExcelJS hyperlink objects
475
+ if (normalized[field].text !== undefined) {
476
+ normalized[field] = normalized[field].text
477
+ } else {
478
+ // Fallback: try to stringify or use empty string
479
+ normalized[field] = normalized[field].toString ? normalized[field].toString() : ''
480
+ }
481
+ }
482
+ })
483
+ return normalized
484
+ }
485
+
486
+ _normalizeNumericFields(record) {
487
+ // Normalize numeric fields - convert string numbers to integers/floats
488
+ // GraphQL requires integers to be actual numbers, not strings
489
+ const normalized = { ...record }
490
+
491
+ // releaseShelfLife must be an integer
492
+ // Explicitly check for 0 as a valid value (both number 0 and string "0")
493
+ const releaseShelfLifeValue = normalized.releaseShelfLife
494
+
495
+ if (releaseShelfLifeValue === 0 || releaseShelfLifeValue === '0') {
496
+ // 0 is a valid value, ensure it's stored as integer 0
497
+ normalized.releaseShelfLife = 0
498
+ } else if (releaseShelfLifeValue !== null && releaseShelfLifeValue !== undefined && releaseShelfLifeValue !== '') {
499
+ if (typeof releaseShelfLifeValue === 'string') {
500
+ const numValue = Number(releaseShelfLifeValue)
501
+ if (!isNaN(numValue)) {
502
+ normalized.releaseShelfLife = Number.isInteger(numValue) ? numValue : Math.floor(numValue)
503
+ } else {
504
+ // Invalid number string, set to default 0
505
+ normalized.releaseShelfLife = 0
506
+ }
507
+ } else if (typeof releaseShelfLifeValue === 'number') {
508
+ // Ensure it's an integer
509
+ normalized.releaseShelfLife = Number.isInteger(releaseShelfLifeValue)
510
+ ? releaseShelfLifeValue
511
+ : Math.floor(releaseShelfLifeValue)
512
+ }
513
+ } else {
514
+ // Empty/null values should default to 0
515
+ normalized.releaseShelfLife = 0
516
+ }
517
+
518
+ return normalized
519
+ }
520
+
360
521
  async fetchPartnersBizplaces() {
361
522
  const response = await client.query({
362
523
  query: gql`
@@ -402,9 +563,11 @@ export class ContactPointList extends localize(i18next)(PageView) {
402
563
  state
403
564
  city
404
565
  postCode
566
+ country
405
567
  billingAddress
406
568
  description
407
569
  type
570
+ releaseShelfLife
408
571
  updatedAt
409
572
  bizplace {
410
573
  id
@@ -472,6 +635,19 @@ export class ContactPointList extends localize(i18next)(PageView) {
472
635
  if (!errors.find(err => err.type == 'customerName'))
473
636
  errors.push({ type: 'customerName', value: 'customer name is required' })
474
637
  }
638
+
639
+ // Validate releaseShelfLife: must be non-negative integer
640
+ const releaseShelfLifeValidation = this._validateReleaseShelfLife(itm.releaseShelfLife, true)
641
+ if (!releaseShelfLifeValidation.valid) {
642
+ itm.error = true
643
+ if (!errors.find(err => err.type == 'releaseShelfLife')) {
644
+ errors.push({
645
+ type: 'releaseShelfLife',
646
+ value: releaseShelfLifeValidation.error
647
+ })
648
+ }
649
+ }
650
+
475
651
  return itm
476
652
  })
477
653
 
@@ -562,6 +738,14 @@ export class ContactPointList extends localize(i18next)(PageView) {
562
738
  throw new Error(i18next.t('text.contact_type_does_not_exist'))
563
739
  }
564
740
 
741
+ // Validate releaseShelfLife: must be non-negative integer
742
+ const releaseShelfLifeValidation = this._validateReleaseShelfLife(item.releaseShelfLife, true)
743
+ if (!releaseShelfLifeValidation.valid) {
744
+ if (!errors.find(error => error.type == 'releaseShelfLife')) {
745
+ errors.push({ type: 'releaseShelfLife', value: releaseShelfLifeValidation.error })
746
+ }
747
+ }
748
+
565
749
  return item
566
750
  })
567
751
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/biz-ui",
3
- "version": "4.3.701",
3
+ "version": "4.3.705",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "client/index.js",
6
6
  "things-factory": true,
@@ -23,14 +23,14 @@
23
23
  "migration:create": "node ../../node_modules/typeorm/cli.js migration:create -d ./server/migrations"
24
24
  },
25
25
  "dependencies": {
26
- "@things-factory/biz-base": "^4.3.701",
27
- "@things-factory/code-base": "^4.3.695",
28
- "@things-factory/form-ui": "^4.3.695",
29
- "@things-factory/grist-ui": "^4.3.695",
30
- "@things-factory/i18n-base": "^4.3.695",
31
- "@things-factory/import-ui": "^4.3.695",
32
- "@things-factory/layout-base": "^4.3.695",
33
- "@things-factory/shell": "^4.3.695"
26
+ "@things-factory/biz-base": "^4.3.705",
27
+ "@things-factory/code-base": "^4.3.705",
28
+ "@things-factory/form-ui": "^4.3.705",
29
+ "@things-factory/grist-ui": "^4.3.705",
30
+ "@things-factory/i18n-base": "^4.3.705",
31
+ "@things-factory/import-ui": "^4.3.705",
32
+ "@things-factory/layout-base": "^4.3.705",
33
+ "@things-factory/shell": "^4.3.705"
34
34
  },
35
- "gitHead": "2cb46fe9d95770899e069846c590a3b24c847d9c"
35
+ "gitHead": "37ae3b066444a1094389a59b38414e5c2b75db20"
36
36
  }
@@ -5,12 +5,13 @@
5
5
  "field.billing_address": "billing address",
6
6
  "field.company_name": "company name",
7
7
  "field.other_company": "other company",
8
+ "field.release_shelf_life": "release shelf life",
8
9
  "title.contact_points": "contact points",
9
10
  "title.select_supplier": "select supplier",
10
11
  "text.bizplace_is_not_selected": "customer is not selected",
11
12
  "label.account_no": "account no",
12
13
  "text.invalid_form": "invalid form",
13
-
14
+
14
15
  "field.contact_point_customer_name": "Customer Name",
15
16
  "field.contact_point_company_name": "Company Name",
16
17
  "field.contact_point_other_company_name": "Other Company",
@@ -5,6 +5,7 @@
5
5
  "field.billing_address": "[jp]billing address",
6
6
  "field.company_name": "[jp]company name",
7
7
  "field.other_company": "[jp]other company",
8
+ "field.release_shelf_life": "[jp]release shelf life",
8
9
  "title.contact_points": "[jp]contact points",
9
10
  "title.select_supplier": "[jp]select supplier",
10
11
  "text.bizplace_is_not_selected": "[jp]customer is not selected",
@@ -5,6 +5,7 @@
5
5
  "field.billing_address": "[ko]billing address",
6
6
  "field.company_name": "[ko]company name",
7
7
  "field.other_company": "[ko]other company",
8
+ "field.release_shelf_life": "[ko]release shelf life",
8
9
  "title.contact_points": "[ko]contact points",
9
10
  "title.select_supplier": "[ko]select supplier",
10
11
  "text.bizplace_is_not_selected": "[ko]customer is not selected",
@@ -5,6 +5,7 @@
5
5
  "field.billing_address": "[ms]billing address",
6
6
  "field.company_name": "[ms]company name",
7
7
  "field.other_company": "[ms]other company",
8
+ "field.release_shelf_life": "[ms]release shelf life",
8
9
  "title.contact_points": "[ms]contact points",
9
10
  "title.select_supplier": "[ms]select supplier",
10
11
  "text.bizplace_is_not_selected": "[ms]customer is not selected",
@@ -5,6 +5,7 @@
5
5
  "field.billing_address": "[zh]billing address",
6
6
  "field.company_name": "[zh]company name",
7
7
  "field.other_company": "[zh]other company",
8
+ "field.release_shelf_life": "[zh]release shelf life",
8
9
  "title.contact_points": "[zh]contact points",
9
10
  "title.select_supplier": "[zh]select supplier",
10
11
  "text.bizplace_is_not_selected": "[zh]customer is not selected",