diff --git a/packages/nc-gui/components/smartsheet/Form.vue b/packages/nc-gui/components/smartsheet/Form.vue index 30b7da12c7..19a9482c63 100644 --- a/packages/nc-gui/components/smartsheet/Form.vue +++ b/packages/nc-gui/components/smartsheet/Form.vue @@ -33,7 +33,7 @@ provide(IsGalleryInj, ref(false)) // todo: generate hideCols based on default values const hiddenCols = ['created_at', 'updated_at'] -const hiddenColTypes = [UITypes.Rollup, UITypes.Lookup, UITypes.Formula, UITypes.QrCode, UITypes.SpecificDBType] +const hiddenColTypes = [UITypes.Rollup, UITypes.Lookup, UITypes.Formula, UITypes.QrCode, UITypes.Barcode, UITypes.SpecificDBType] const state = useGlobal() @@ -229,7 +229,9 @@ async function addAllColumns() { } function shouldSkipColumn(col: Record) { - return isDbRequired(col) || !!col.required || (!!col.rqd && !col.cdf) || col.uidt === UITypes.QrCode + return ( + isDbRequired(col) || !!col.required || (!!col.rqd && !col.cdf) || col.uidt === UITypes.QrCode || col.uidt === UITypes.Barcode + ) } async function removeAllColumns() { diff --git a/packages/nc-gui/components/smartsheet/VirtualCell.vue b/packages/nc-gui/components/smartsheet/VirtualCell.vue index dad0b33311..4ec2045752 100644 --- a/packages/nc-gui/components/smartsheet/VirtualCell.vue +++ b/packages/nc-gui/components/smartsheet/VirtualCell.vue @@ -7,6 +7,7 @@ import { IsFormInj, RowInj, inject, + isBarcode, isBt, isCount, isFormula, @@ -59,6 +60,7 @@ function onNavigate(dir: NavigateDir, e: KeyboardEvent) { + diff --git a/packages/nc-gui/components/smartsheet/column/BarcodeOptions.vue b/packages/nc-gui/components/smartsheet/column/BarcodeOptions.vue new file mode 100644 index 0000000000..1ef0bcc5c3 --- /dev/null +++ b/packages/nc-gui/components/smartsheet/column/BarcodeOptions.vue @@ -0,0 +1,120 @@ + + + diff --git a/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue b/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue index cfe70bae07..8211e94759 100644 --- a/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue +++ b/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue @@ -171,6 +171,7 @@ useEventListener('keydown', (e: KeyboardEvent) => { + diff --git a/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue b/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue index 2721028f22..1dea646550 100644 --- a/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue +++ b/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue @@ -26,7 +26,7 @@ const props = defineProps<{ const emit = defineEmits(['update:value']) -const uiTypesNotSupportedInFormulas = [UITypes.QrCode] +const uiTypesNotSupportedInFormulas = [UITypes.QrCode, UITypes.Barcode] const vModel = useVModel(props, 'value', emit) diff --git a/packages/nc-gui/components/smartsheet/column/QrCodeOptions.vue b/packages/nc-gui/components/smartsheet/column/QrCodeOptions.vue index ecab893a65..172829fa26 100644 --- a/packages/nc-gui/components/smartsheet/column/QrCodeOptions.vue +++ b/packages/nc-gui/components/smartsheet/column/QrCodeOptions.vue @@ -1,6 +1,6 @@ + + diff --git a/packages/nc-gui/components/virtual-cell/barcode/JsBarcodeWrapper.vue b/packages/nc-gui/components/virtual-cell/barcode/JsBarcodeWrapper.vue new file mode 100644 index 0000000000..50e999286e --- /dev/null +++ b/packages/nc-gui/components/virtual-cell/barcode/JsBarcodeWrapper.vue @@ -0,0 +1,39 @@ + + + diff --git a/packages/nc-gui/composables/useSharedView.ts b/packages/nc-gui/composables/useSharedView.ts index cb0c5ea8c5..ac56e40d97 100644 --- a/packages/nc-gui/composables/useSharedView.ts +++ b/packages/nc-gui/composables/useSharedView.ts @@ -42,6 +42,7 @@ export function useSharedView() { f.uidt !== UITypes.Rollup && f.uidt !== UITypes.Lookup && f.uidt !== UITypes.Formula && + f.uidt !== UITypes.Barcode && f.uidt !== UITypes.QrCode, ) .sort((a: Record, b: Record) => a.order - b.order) diff --git a/packages/nc-gui/composables/useViewData.ts b/packages/nc-gui/composables/useViewData.ts index aa3c30391e..643d23100a 100644 --- a/packages/nc-gui/composables/useViewData.ts +++ b/packages/nc-gui/composables/useViewData.ts @@ -292,6 +292,7 @@ export function useViewData( if ( col.uidt === UITypes.Formula || col.uidt === UITypes.QrCode || + col.uidt === UITypes.Barcode || col.uidt === UITypes.Rollup || col.au || col.cdf?.includes(' on update ') diff --git a/packages/nc-gui/lang/de.json b/packages/nc-gui/lang/de.json index 7304e360f8..3e9ff3f6c3 100644 --- a/packages/nc-gui/lang/de.json +++ b/packages/nc-gui/lang/de.json @@ -498,7 +498,8 @@ "warning": { "nonEditableFields": { "computedFieldUnableToClear": "Warning: Computed field - unable to clear text", - "qrFieldsCannotBeDirectlyChanged": "Warning: QR fields cannot be directly changed." + "qrFieldsCannotBeDirectlyChanged": "Warning: QR fields cannot be directly changed.", + "barcodeFieldsCannotBeDirectlyChanged": "Warning: Barcode fields cannot be directly changed." } }, "info": { diff --git a/packages/nc-gui/lang/en.json b/packages/nc-gui/lang/en.json index b4b2c181b0..0db7d6d4ba 100644 --- a/packages/nc-gui/lang/en.json +++ b/packages/nc-gui/lang/en.json @@ -248,7 +248,10 @@ "sqlOutput": "SQL Output", "addOption": "Add option", "qrCodeValueColumn": "Column with QR code value", + "barcodeValueColumn": "Column with Barcode value", + "barcodeFormat": "Barcode format", "qrCodeValueTooLong": "Too many characters for a QR code", + "barcodeValueTooLong": "Too many characters for a barcode", "aggregateFunction": "Aggregate function", "dbCreateIfNotExists": "Database : create if not exists", "clientKey": "Client Key", @@ -496,6 +499,9 @@ }, "msg": { "warning": { + "barcode": { + "renderError": "Barcode error - please check compatibility between input and barcode type" + }, "nonEditableFields": { "computedFieldUnableToClear": "Warning: Computed field - unable to clear text", "qrFieldsCannotBeDirectlyChanged": "Warning: QR fields cannot be directly changed." diff --git a/packages/nc-gui/package-lock.json b/packages/nc-gui/package-lock.json index f0711461de..5e189e06c3 100644 --- a/packages/nc-gui/package-lock.json +++ b/packages/nc-gui/package-lock.json @@ -23,6 +23,7 @@ "dayjs": "^1.11.3", "file-saver": "^2.0.5", "httpsnippet": "^2.0.0", + "jsbarcode": "^3.11.5", "jsep": "^1.3.6", "just-clone": "^6.1.1", "jwt-decode": "^3.1.2", @@ -10336,6 +10337,66 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbarcode": { + "version": "3.11.5", + "resolved": "https://registry.npmjs.org/jsbarcode/-/jsbarcode-3.11.5.tgz", + "integrity": "sha512-zv3KsH51zD00I/LrFzFSM6dst7rDn0vIMzaiZFL7qusTjPZiPtxg3zxetp0RR7obmjTw4f6NyGgbdkBCgZUIrA==", + "bin": { + "auto.js": "bin/barcodes/CODE128/auto.js", + "Barcode.js": "bin/barcodes/Barcode.js", + "barcodes": "bin/barcodes", + "canvas.js": "bin/renderers/canvas.js", + "checksums.js": "bin/barcodes/MSI/checksums.js", + "codabar": "bin/barcodes/codabar", + "CODE128": "bin/barcodes/CODE128", + "CODE128_AUTO.js": "bin/barcodes/CODE128/CODE128_AUTO.js", + "CODE128.js": "bin/barcodes/CODE128/CODE128.js", + "CODE128A.js": "bin/barcodes/CODE128/CODE128A.js", + "CODE128B.js": "bin/barcodes/CODE128/CODE128B.js", + "CODE128C.js": "bin/barcodes/CODE128/CODE128C.js", + "CODE39": "bin/barcodes/CODE39", + "constants.js": "bin/barcodes/ITF/constants.js", + "defaults.js": "bin/options/defaults.js", + "EAN_UPC": "bin/barcodes/EAN_UPC", + "EAN.js": "bin/barcodes/EAN_UPC/EAN.js", + "EAN13.js": "bin/barcodes/EAN_UPC/EAN13.js", + "EAN2.js": "bin/barcodes/EAN_UPC/EAN2.js", + "EAN5.js": "bin/barcodes/EAN_UPC/EAN5.js", + "EAN8.js": "bin/barcodes/EAN_UPC/EAN8.js", + "encoder.js": "bin/barcodes/EAN_UPC/encoder.js", + "ErrorHandler.js": "bin/exceptions/ErrorHandler.js", + "exceptions": "bin/exceptions", + "exceptions.js": "bin/exceptions/exceptions.js", + "fixOptions.js": "bin/help/fixOptions.js", + "GenericBarcode": "bin/barcodes/GenericBarcode", + "getOptionsFromElement.js": "bin/help/getOptionsFromElement.js", + "getRenderProperties.js": "bin/help/getRenderProperties.js", + "help": "bin/help", + "index.js": "bin/renderers/index.js", + "index.tmp.js": "bin/barcodes/index.tmp.js", + "ITF": "bin/barcodes/ITF", + "ITF.js": "bin/barcodes/ITF/ITF.js", + "ITF14.js": "bin/barcodes/ITF/ITF14.js", + "JsBarcode.js": "bin/JsBarcode.js", + "linearizeEncodings.js": "bin/help/linearizeEncodings.js", + "merge.js": "bin/help/merge.js", + "MSI": "bin/barcodes/MSI", + "MSI.js": "bin/barcodes/MSI/MSI.js", + "MSI10.js": "bin/barcodes/MSI/MSI10.js", + "MSI1010.js": "bin/barcodes/MSI/MSI1010.js", + "MSI11.js": "bin/barcodes/MSI/MSI11.js", + "MSI1110.js": "bin/barcodes/MSI/MSI1110.js", + "object.js": "bin/renderers/object.js", + "options": "bin/options", + "optionsFromStrings.js": "bin/help/optionsFromStrings.js", + "pharmacode": "bin/barcodes/pharmacode", + "renderers": "bin/renderers", + "shared.js": "bin/renderers/shared.js", + "svg.js": "bin/renderers/svg.js", + "UPC.js": "bin/barcodes/EAN_UPC/UPC.js", + "UPCE.js": "bin/barcodes/EAN_UPC/UPCE.js" + } + }, "node_modules/jsdom": { "version": "20.0.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", @@ -25172,6 +25233,11 @@ "argparse": "^2.0.1" } }, + "jsbarcode": { + "version": "3.11.5", + "resolved": "https://registry.npmjs.org/jsbarcode/-/jsbarcode-3.11.5.tgz", + "integrity": "sha512-zv3KsH51zD00I/LrFzFSM6dst7rDn0vIMzaiZFL7qusTjPZiPtxg3zxetp0RR7obmjTw4f6NyGgbdkBCgZUIrA==" + }, "jsdom": { "version": "20.0.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", diff --git a/packages/nc-gui/package.json b/packages/nc-gui/package.json index 927f80f41f..49a6c5a9a0 100644 --- a/packages/nc-gui/package.json +++ b/packages/nc-gui/package.json @@ -46,6 +46,7 @@ "dayjs": "^1.11.3", "file-saver": "^2.0.5", "httpsnippet": "^2.0.0", + "jsbarcode": "^3.11.5", "jsep": "^1.3.6", "just-clone": "^6.1.1", "jwt-decode": "^3.1.2", diff --git a/packages/nc-gui/utils/columnUtils.ts b/packages/nc-gui/utils/columnUtils.ts index 54569a8cea..eebd3514a5 100644 --- a/packages/nc-gui/utils/columnUtils.ts +++ b/packages/nc-gui/utils/columnUtils.ts @@ -2,6 +2,7 @@ import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk' import { RelationTypes, UITypes } from 'nocodb-sdk' import LinkVariant from '~icons/mdi/link-variant' import QrCodeScan from '~icons/mdi/qrcode-scan' +import BarcodeScan from '~icons/mdi/barcode-scan' import FormatColorText from '~icons/mdi/format-color-text' import TextSubject from '~icons/mdi/text-subject' import JSONIcon from '~icons/mdi/code-json' @@ -131,6 +132,11 @@ const uiTypes = [ icon: QrCodeScan, virtual: 1, }, + { + name: UITypes.Barcode, + icon: BarcodeScan, + virtual: 1, + }, { name: UITypes.Geometry, icon: RulerSquareCompass, diff --git a/packages/nc-gui/utils/virtualCell.ts b/packages/nc-gui/utils/virtualCell.ts index c5f705a0ee..92ab9eb7cb 100644 --- a/packages/nc-gui/utils/virtualCell.ts +++ b/packages/nc-gui/utils/virtualCell.ts @@ -17,4 +17,5 @@ export const isLookup = (column: ColumnType) => column.uidt === UITypes.Lookup export const isRollup = (column: ColumnType) => column.uidt === UITypes.Rollup export const isFormula = (column: ColumnType) => column.uidt === UITypes.Formula export const isQrCode = (column: ColumnType) => column.uidt === UITypes.QrCode +export const isBarcode = (column: ColumnType) => column.uidt === UITypes.Barcode export const isCount = (column: ColumnType) => column.uidt === UITypes.Count diff --git a/packages/noco-docs/content/en/setup-and-usages/column-types.md b/packages/noco-docs/content/en/setup-and-usages/column-types.md index afeeaa55c6..97f44dd51d 100644 --- a/packages/noco-docs/content/en/setup-and-usages/column-types.md +++ b/packages/noco-docs/content/en/setup-and-usages/column-types.md @@ -33,6 +33,7 @@ menuTitle: 'Column Types' |[Rating](#rating)| Rating | |[Formula](#formula)| Formula based generated column | |[QR Code](#qr-code)| QR Code visualization of another referenced column | +|[Barcode](#barcode)| Barcode visualization of another referenced column | | [Count](#count) | | |[DateTime](#datetime)| Date & Time selector | |[CreateTime](#createtime)| | @@ -279,6 +280,18 @@ Encodes the value of a reference column as QR code. The following column types a * Email Since it's a virtual column, the cell content (QR code) cannot be changed directly. + +### Barcode + +Encodes the value of a reference column as Barcode. Supported barcode formats: CODE128, EAN, EAN-13, EAN-8, EAN-5, EAN-2, UPC (A), CODE39, ITF-14, MSI, Pharmacode, Codabar. The following column types are supported for the for reference column: +* Formula +* Single Line Text +* Long Text +* Phone Number +* URL +* Email + +Since it's a virtual column, the cell content (Barcode) cannot be changed directly. ### Count #### Available Database Types diff --git a/packages/nocodb-sdk/src/lib/UITypes.ts b/packages/nocodb-sdk/src/lib/UITypes.ts index e8d069bf81..efef013b65 100644 --- a/packages/nocodb-sdk/src/lib/UITypes.ts +++ b/packages/nocodb-sdk/src/lib/UITypes.ts @@ -52,6 +52,7 @@ export function isVirtualCol( UITypes.LinkToAnotherRecord, UITypes.Formula, UITypes.QrCode, + UITypes.Barcode, UITypes.Rollup, UITypes.Lookup, // UITypes.Count, diff --git a/packages/nocodb-sdk/src/lib/columnRules/QrCodeRules.ts b/packages/nocodb-sdk/src/lib/columnRules/QrAndBarcodeRules.ts similarity index 78% rename from packages/nocodb-sdk/src/lib/columnRules/QrCodeRules.ts rename to packages/nocodb-sdk/src/lib/columnRules/QrAndBarcodeRules.ts index bf2e1f77e1..e6f05c322e 100644 --- a/packages/nocodb-sdk/src/lib/columnRules/QrCodeRules.ts +++ b/packages/nocodb-sdk/src/lib/columnRules/QrAndBarcodeRules.ts @@ -1,6 +1,6 @@ import UITypes from '../UITypes'; -export const AllowedColumnTypesForQrCode = [ +export const AllowedColumnTypesForQrAndBarcodes = [ UITypes.Formula, UITypes.SingleLineText, UITypes.LongText, diff --git a/packages/nocodb-sdk/src/lib/columnRules/index.ts b/packages/nocodb-sdk/src/lib/columnRules/index.ts index 30acec12c9..4f08acd8b3 100644 --- a/packages/nocodb-sdk/src/lib/columnRules/index.ts +++ b/packages/nocodb-sdk/src/lib/columnRules/index.ts @@ -1 +1 @@ -export * from './QrCodeRules'; +export * from './QrAndBarcodeRules'; diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts index d789d893a0..1ea5d068b1 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts @@ -45,6 +45,7 @@ import { customAlphabet } from 'nanoid'; import DOMPurify from 'isomorphic-dompurify'; import { sanitize, unsanitize } from './helpers/sanitize'; import QrCodeColumn from '../../../../models/QrCodeColumn'; +import BarcodeColumn from '../../../../models/BarcodeColumn'; const GROUP_COL = '__nc_group_id'; @@ -100,7 +101,7 @@ class BaseModelSqlv2 { qb.where(_wherePk(this.model.primaryKeys, id)); - let data = (await this.execAndParse(qb))?.[0]; + const data = (await this.execAndParse(qb))?.[0]; if (data) { const proto = await this.getProto(); @@ -158,7 +159,7 @@ class BaseModelSqlv2 { qb.orderBy(this.model.primaryKey.column_name); } - let data = await qb.first(); + const data = await qb.first(); if (data) { const proto = await this.getProto(); @@ -252,7 +253,7 @@ class BaseModelSqlv2 { if (!ignoreViewFilterAndSort) applyPaginate(qb, rest); const proto = await this.getProto(); - let data = await this.execAndParse(qb); + const data = await this.execAndParse(qb); return data?.map((d) => { d.__proto__ = proto; @@ -320,7 +321,8 @@ class BaseModelSqlv2 { as: 'count', }).first(); const res = (await this.dbDriver.raw(unsanitize(qb.toQuery()))) as any; - return ((this.isPg || this.isSnowflake) ? res.rows[0] : res[0][0] ?? res[0]).count; + return (this.isPg || this.isSnowflake ? res.rows[0] : res[0][0] ?? res[0]) + .count; } // todo: add support for sortArrJson and filterArrJson @@ -423,7 +425,7 @@ class BaseModelSqlv2 { .as('list') ); - let children = await this.execAndParse(childQb, childTable); + const children = await this.execAndParse(childQb, childTable); const proto = await ( await Model.getBaseModelSQL({ id: childTable.id, @@ -550,7 +552,7 @@ class BaseModelSqlv2 { await childModel.selectObject({ qb }); - let children = await this.execAndParse(qb, childTable); + const children = await this.execAndParse(qb, childTable); const proto = await ( await Model.getBaseModelSQL({ @@ -735,7 +737,7 @@ class BaseModelSqlv2 { qb.limit(+rest?.limit || 25); qb.offset(+rest?.offset || 0); - let children = await this.execAndParse(qb, childTable); + const children = await this.execAndParse(qb, childTable); const proto = await ( await Model.getBaseModelSQL({ id: rtnId, dbDriver: this.dbDriver }) ).getProto(); @@ -961,7 +963,7 @@ class BaseModelSqlv2 { applyPaginate(qb, rest); const proto = await childModel.getProto(); - let data = await qb; + const data = await qb; return data.map((c) => { c.__proto__ = proto; return c; @@ -1075,7 +1077,7 @@ class BaseModelSqlv2 { applyPaginate(qb, rest); const proto = await childModel.getProto(); - let data = await this.execAndParse(qb, childTable); + const data = await this.execAndParse(qb, childTable); return data.map((c) => { c.__proto__ = proto; @@ -1193,7 +1195,7 @@ class BaseModelSqlv2 { applyPaginate(qb, rest); const proto = await parentModel.getProto(); - let data = await this.execAndParse(qb, childTable); + const data = await this.execAndParse(qb, childTable); return data.map((c) => { c.__proto__ = proto; @@ -1460,6 +1462,40 @@ class BaseModelSqlv2 { break; } + case 'Barcode': { + const barcodeColumn = await column.getColOptions(); + const barcodeValueColumn = await Column.get({ + colId: barcodeColumn.fk_barcode_value_column_id, + }); + + // If the referenced value cannot be found: cancel current iteration + if (barcodeValueColumn == null) { + break; + } + + switch (barcodeValueColumn.uidt) { + case UITypes.Formula: + try { + const selectQb = await this.getSelectQueryBuilderForFormula( + barcodeValueColumn + ); + qb.select({ + [column.column_name]: selectQb.builder, + }); + } catch { + continue; + } + break; + default: { + qb.select({ + [column.column_name]: barcodeValueColumn.column_name, + }); + break; + } + } + + break; + } case 'Formula': { try { @@ -1558,10 +1594,11 @@ class BaseModelSqlv2 { .max(ai.column_name, { as: 'id' }) )[0].id; } else if (this.isSnowflake) { - id = (( - await this.dbDriver(this.tnPath) - .max(ai.column_name, { as: 'id' }) - ) as any)[0].id; + id = ( + (await this.dbDriver(this.tnPath).max(ai.column_name, { + as: 'id', + })) as any + )[0].id; } response = await this.readByPk(id); } else { @@ -1677,7 +1714,11 @@ class BaseModelSqlv2 { if (this.isMssql && schema) { return this.dbDriver.raw('??.??', [schema, tb.table_name]); } else if (this.isSnowflake) { - return [this.dbDriver.client.config.connection.database, this.dbDriver.client.config.connection.schema, tb.table_name].join('.'); + return [ + this.dbDriver.client.config.connection.database, + this.dbDriver.client.config.connection.schema, + tb.table_name, + ].join('.'); } else { return tb.table_name; } @@ -1817,10 +1858,11 @@ class BaseModelSqlv2 { .max(ai.column_name, { as: 'id' }) )[0].id; } else if (this.isSnowflake) { - id = (( - await this.dbDriver(this.tnPath) - .max(ai.column_name, { as: 'id' }) - ) as any).rows[0].id; + id = ( + (await this.dbDriver(this.tnPath).max(ai.column_name, { + as: 'id', + })) as any + ).rows[0].id; } response = await this.readByPk(id); } else { @@ -2010,9 +2052,7 @@ class BaseModelSqlv2 { const res = []; for (const d of deleteIds) { if (Object.keys(d).length) { - const response = await transaction(this.tnPath) - .del() - .where(d); + const response = await transaction(this.tnPath).del().where(d); res.push(response); } } @@ -2234,6 +2274,7 @@ class BaseModelSqlv2 { f.uidt !== UITypes.Lookup && f.uidt !== UITypes.Formula && f.uidt !== UITypes.QrCode && + f.uidt !== UITypes.Barcode && f.uidt !== UITypes.SpecificDBType ) .sort( @@ -2383,20 +2424,19 @@ class BaseModelSqlv2 { if (this.isSnowflake) { const parentPK = this.dbDriver(parentTn) - .select(parentColumn.column_name) - .where(_wherePk(parentTable.primaryKeys, childId)) - .first(); + .select(parentColumn.column_name) + .where(_wherePk(parentTable.primaryKeys, childId)) + .first(); const childPK = this.dbDriver(childTn) - .select(childColumn.column_name) - .where(_wherePk(childTable.primaryKeys, rowId)) - .first(); + .select(childColumn.column_name) + .where(_wherePk(childTable.primaryKeys, rowId)) + .first(); - await this.dbDriver.raw(`INSERT INTO ?? (??, ??) SELECT (${parentPK.toQuery()}), (${childPK.toQuery()})`, [ - vTn, - vParentCol.column_name, - vChildCol.column_name, - ]) + await this.dbDriver.raw( + `INSERT INTO ?? (??, ??) SELECT (${parentPK.toQuery()}), (${childPK.toQuery()})`, + [vTn, vParentCol.column_name, vChildCol.column_name] + ); } else { await this.dbDriver(vTn).insert({ [vParentCol.column_name]: this.dbDriver(parentTn) @@ -2697,7 +2737,7 @@ class BaseModelSqlv2 { const proto = await this.getProto(); - let data = await groupedQb; + const data = await groupedQb; const result = data?.map((d) => { d.__proto__ = proto; return d; @@ -2800,10 +2840,7 @@ class BaseModelSqlv2 { return await qb; } - private async execAndParse( - qb: Knex.QueryBuilder, - childTable?: Model - ) { + private async execAndParse(qb: Knex.QueryBuilder, childTable?: Model) { let query = qb.toQuery(); if (!this.isPg && !this.isMssql && !this.isSnowflake) { query = unsanitize(qb.toQuery()); diff --git a/packages/nocodb/src/lib/meta/api/columnApis.ts b/packages/nocodb/src/lib/meta/api/columnApis.ts index 00551e639f..d09d1fc534 100644 --- a/packages/nocodb/src/lib/meta/api/columnApis.ts +++ b/packages/nocodb/src/lib/meta/api/columnApis.ts @@ -512,6 +512,12 @@ export async function columnAdd( fk_model_id: table.id, }); break; + case UITypes.Barcode: + await Column.insert({ + ...colBody, + fk_model_id: table.id, + }); + break; case UITypes.Formula: colBody.formula = await substituteColumnAliasWithIdInFormula( colBody.formula_raw || colBody.formula, @@ -738,11 +744,12 @@ export async function columnUpdate(req: Request, res: Response) { UITypes.LinkToAnotherRecord, UITypes.Formula, UITypes.QrCode, + UITypes.Barcode, UITypes.ForeignKey, ].includes(column.uidt) ) { if (column.uidt === colBody.uidt) { - if (column.uidt === UITypes.QrCode) { + if ([UITypes.QrCode, UITypes.Barcode].includes(column.uidt)) { await Column.update(column.id, { ...column, ...colBody, @@ -774,6 +781,7 @@ export async function columnUpdate(req: Request, res: Response) { UITypes.LinkToAnotherRecord, UITypes.Formula, UITypes.QrCode, + UITypes.Barcode, UITypes.ForeignKey, ].includes(colBody.uidt) ) { @@ -1460,6 +1468,7 @@ export async function columnDelete(req: Request, res: Response) { case UITypes.Lookup: case UITypes.Rollup: case UITypes.QrCode: + case UITypes.Barcode: case UITypes.Formula: await Column.delete(req.params.columnId); break; diff --git a/packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts b/packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts index 0dc39df339..8cf290e503 100644 --- a/packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts +++ b/packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts @@ -11,6 +11,7 @@ import * as nc_020_kanban_view from './v2/nc_020_kanban_view'; import * as nc_021_add_fields_in_token from './v2/nc_021_add_fields_in_token'; import * as nc_022_qr_code_column_type from './v2/nc_022_qr_code_column_type'; import * as nc_023_multiple_source from './v2/nc_023_multiple_source'; +import * as nc_024_barcode_column_type from './v2/nc_024_barcode_column_type'; // Create a custom migration source class export default class XcMigrationSourcev2 { @@ -32,7 +33,8 @@ export default class XcMigrationSourcev2 { 'nc_020_kanban_view', 'nc_021_add_fields_in_token', 'nc_022_qr_code_column_type', - 'nc_023_multiple_source' + 'nc_023_multiple_source', + 'nc_024_barcode_column_type', ]); } @@ -68,6 +70,8 @@ export default class XcMigrationSourcev2 { return nc_022_qr_code_column_type; case 'nc_023_multiple_source': return nc_023_multiple_source; + case 'nc_024_barcode_column_type': + return nc_024_barcode_column_type; } } } diff --git a/packages/nocodb/src/lib/migrations/v2/nc_024_barcode_column_type.ts b/packages/nocodb/src/lib/migrations/v2/nc_024_barcode_column_type.ts new file mode 100644 index 0000000000..7ddbc78c91 --- /dev/null +++ b/packages/nocodb/src/lib/migrations/v2/nc_024_barcode_column_type.ts @@ -0,0 +1,26 @@ +import { MetaTable } from '../../utils/globals'; +import { Knex } from 'knex'; + +const up = async (knex: Knex) => { + await knex.schema.createTable(MetaTable.COL_BARCODE, (table) => { + table.string('id', 20).primary().notNullable(); + + table.string('fk_column_id', 20); + table.foreign('fk_column_id').references(`${MetaTable.COLUMNS}.id`); + + table.string('fk_barcode_value_column_id', 20); + table + .foreign('fk_barcode_value_column_id') + .references(`${MetaTable.COLUMNS}.id`); + + table.string('barcode_format', 15); + table.boolean('deleted'); + table.timestamps(true, true); + }); +}; + +const down = async (knex: Knex) => { + await knex.schema.dropTable(MetaTable.COL_BARCODE); +}; + +export { up, down }; diff --git a/packages/nocodb/src/lib/models/BarcodeColumn.ts b/packages/nocodb/src/lib/models/BarcodeColumn.ts new file mode 100644 index 0000000000..6f274f3f86 --- /dev/null +++ b/packages/nocodb/src/lib/models/BarcodeColumn.ts @@ -0,0 +1,69 @@ +import Noco from '../Noco'; +import { CacheGetType, CacheScope, MetaTable } from '../utils/globals'; +import NocoCache from '../cache/NocoCache'; +import { extractProps } from '../meta/helpers/extractProps'; + +export default class BarcodeColumn { + id: string; + fk_column_id: string; + fk_barcode_value_column_id: string; + barcode_format: string; + + constructor(data: Partial) { + Object.assign(this, data); + } + + public static async insert( + data: Partial, + ncMeta = Noco.ncMeta + ) { + await ncMeta.metaInsert2(null, null, MetaTable.COL_BARCODE, { + fk_column_id: data.fk_column_id, + fk_barcode_value_column_id: data.fk_barcode_value_column_id, + barcode_format: data.barcode_format, + }); + + return this.read(data.fk_column_id, ncMeta); + } + public static async read(columnId: string, ncMeta = Noco.ncMeta) { + let column = + columnId && + (await NocoCache.get( + `${CacheScope.COL_BARCODE}:${columnId}`, + CacheGetType.TYPE_OBJECT + )); + if (!column) { + column = await ncMeta.metaGet2( + null, //, + null, //model.db_alias, + MetaTable.COL_BARCODE, + { fk_column_id: columnId } + ); + await NocoCache.set(`${CacheScope.COL_BARCODE}:${columnId}`, column); + } + + return column ? new BarcodeColumn(column) : null; + } + + static async update( + id: string, + barcode: Partial, + ncMeta = Noco.ncMeta + ) { + const updateObj = extractProps(barcode, [ + 'fk_column_id', + 'fk_barcode_value_column_id', + 'barcode_format', + ]); + // get existing cache + const key = `${CacheScope.COL_BARCODE}:${id}`; + let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT); + if (o) { + o = { ...o, ...updateObj }; + // set cache + await NocoCache.set(key, o); + } + // set meta + await ncMeta.metaUpdate(null, null, MetaTable.COL_BARCODE, updateObj, id); + } +} diff --git a/packages/nocodb/src/lib/models/Base.ts b/packages/nocodb/src/lib/models/Base.ts index 8dc170eeae..2255ad6571 100644 --- a/packages/nocodb/src/lib/models/Base.ts +++ b/packages/nocodb/src/lib/models/Base.ts @@ -108,7 +108,7 @@ export default class Base implements BaseType { 'order', 'enabled', ]); - + if (updateObj.config) { updateObj.config = CryptoJS.AES.encrypt( JSON.stringify(base.config), @@ -164,9 +164,7 @@ export default class Base implements BaseType { await NocoCache.setList(CacheScope.BASE, [args.projectId], baseDataList); } - baseDataList.sort( - (a, b) => (a.order ?? Infinity) - (b.order ?? Infinity) - ); + baseDataList.sort((a, b) => (a.order ?? Infinity) - (b.order ?? Infinity)); return baseDataList?.map((baseData) => { return new Base(baseData); @@ -206,7 +204,6 @@ export default class Base implements BaseType { // update order for bases for (const [i, b] of Object.entries(bases)) { - await NocoCache.deepDel( CacheScope.BASE, `${CacheScope.BASE}:${b.id}`, @@ -220,11 +217,11 @@ export default class Base implements BaseType { null, MetaTable.BASES, { - order: b.order + order: b.order, }, b.id ); - + await NocoCache.appendToList( CacheScope.BASE, [b.project_id], diff --git a/packages/nocodb/src/lib/models/Column.ts b/packages/nocodb/src/lib/models/Column.ts index ec1a872b9e..69b20dfe9a 100644 --- a/packages/nocodb/src/lib/models/Column.ts +++ b/packages/nocodb/src/lib/models/Column.ts @@ -6,8 +6,8 @@ import SelectOption from './SelectOption'; import Model from './Model'; import NocoCache from '../cache/NocoCache'; import { + AllowedColumnTypesForQrAndBarcodes, ColumnReqType, - AllowedColumnTypesForQrCode, ColumnType, UITypes, } from 'nocodb-sdk'; @@ -24,6 +24,7 @@ import Filter from './Filter'; import addFormulaErrorIfMissingColumn from '../meta/helpers/addFormulaErrorIfMissingColumn'; import { NcError } from '../meta/helpers/catchError'; import QrCodeColumn from './QrCodeColumn'; +import BarcodeColumn from './BarcodeColumn'; export default class Column implements ColumnType { public fk_model_id: string; @@ -234,6 +235,17 @@ export default class Column implements ColumnType { ); break; } + case UITypes.Barcode: { + await BarcodeColumn.insert( + { + fk_column_id: colId, + fk_barcode_value_column_id: column.fk_barcode_value_column_id, + barcode_format: column.barcode_format, + }, + ncMeta + ); + break; + } case UITypes.Formula: { await FormulaColumn.insert( { @@ -413,6 +425,9 @@ export default class Column implements ColumnType { case UITypes.QrCode: res = await QrCodeColumn.read(this.id, ncMeta); break; + case UITypes.Barcode: + res = await BarcodeColumn.read(this.id, ncMeta); + break; // default: // res = await DbColumn.read(this.id); // break; @@ -590,6 +605,20 @@ export default class Column implements ColumnType { } } + { + const barcodeCols = await ncMeta.metaList2( + null, + null, + MetaTable.COL_BARCODE, + { + condition: { fk_barcode_value_column_id: id }, + } + ); + for (const barcodeCol of barcodeCols) { + await Column.delete(barcodeCol.fk_column_id, ncMeta); + } + } + // get lookup columns and delete { let lookups = await NocoCache.getList(CacheScope.COL_LOOKUP, [id]); @@ -728,6 +757,10 @@ export default class Column implements ColumnType { colOptionTableName = MetaTable.COL_QRCODE; cacheScopeName = CacheScope.COL_QRCODE; break; + case UITypes.Barcode: + colOptionTableName = MetaTable.COL_BARCODE; + cacheScopeName = CacheScope.COL_BARCODE; + break; } if (colOptionTableName && cacheScopeName) { @@ -905,6 +938,19 @@ export default class Column implements ColumnType { break; } + case UITypes.Barcode: { + await ncMeta.metaDelete(null, null, MetaTable.COL_BARCODE, { + fk_column_id: colId, + }); + + await NocoCache.deepDel( + CacheScope.COL_BARCODE, + `${CacheScope.COL_BARCODE}:${colId}`, + CacheDelDirection.CHILD_TO_PARENT + ); + break; + } + case UITypes.MultiSelect: case UITypes.SingleSelect: { await ncMeta.metaDelete(null, null, MetaTable.COL_SELECT_OPTIONS, { @@ -954,7 +1000,7 @@ export default class Column implements ColumnType { } // get qr code columns and delete if target type is not supported by QR code column type - if (!AllowedColumnTypesForQrCode.includes(updateObj.uidt)) { + if (!AllowedColumnTypesForQrAndBarcodes.includes(updateObj.uidt)) { const qrCodeCols = await ncMeta.metaList2( null, null, @@ -963,9 +1009,20 @@ export default class Column implements ColumnType { condition: { fk_qr_value_column_id: colId }, } ); + const barcodeCols = await ncMeta.metaList2( + null, + null, + MetaTable.COL_BARCODE, + { + condition: { fk_barcode_value_column_id: colId }, + } + ); for (const qrCodeCol of qrCodeCols) { await Column.delete(qrCodeCol.fk_column_id, ncMeta); } + for (const barcodeCol of barcodeCols) { + await Column.delete(barcodeCol.fk_column_id, ncMeta); + } } // get existing cache diff --git a/packages/nocodb/src/lib/utils/globals.ts b/packages/nocodb/src/lib/utils/globals.ts index ea037b1f10..4acdfabec5 100644 --- a/packages/nocodb/src/lib/utils/globals.ts +++ b/packages/nocodb/src/lib/utils/globals.ts @@ -11,6 +11,7 @@ export enum MetaTable { COL_ROLLUP = 'nc_col_rollup_v2', COL_FORMULA = 'nc_col_formula_v2', COL_QRCODE = 'nc_col_qrcode_v2', + COL_BARCODE = 'nc_col_barcode_v2', FILTER_EXP = 'nc_filter_exp_v2', // HOOK_FILTER_EXP = 'nc_hook_filter_exp_v2', SORT = 'nc_sort_v2', @@ -114,6 +115,7 @@ export enum CacheScope { COL_ROLLUP = 'colRollup', COL_FORMULA = 'colFormula', COL_QRCODE = 'colQRCode', + COL_BARCODE = 'colBarcode', FILTER_EXP = 'filterExp', SORT = 'sort', SHARED_VIEW = 'sharedView', diff --git a/packages/nocodb/tests/unit/factory/column.ts b/packages/nocodb/tests/unit/factory/column.ts index c2843f070a..fe9c3fa4b8 100644 --- a/packages/nocodb/tests/unit/factory/column.ts +++ b/packages/nocodb/tests/unit/factory/column.ts @@ -184,6 +184,36 @@ const createQrCodeColumn = async ( return qrCodeColumn; }; +const createBarcodeColumn = async ( + context, + { + title, + table, + referencedBarcodeValueTableColumnTitle, + }: { + title: string; + table: Model; + referencedBarcodeValueTableColumnTitle: string; + } +) => { + const referencedBarcodeValueTableColumnId = await table + .getColumns() + .then( + (cols) => + cols.find( + (column) => column.title == referencedBarcodeValueTableColumnTitle + )['id'] + ); + + const barcodeColumn = await createColumn(context, table, { + title: title, + uidt: UITypes.Barcode, + column_name: title, + fk_barcode_value_column_id: referencedBarcodeValueTableColumnId, + }); + return barcodeColumn; +}; + const createLtarColumn = async ( context, { @@ -232,6 +262,7 @@ export { defaultColumns, createColumn, createQrCodeColumn, + createBarcodeColumn, createRollupColumn, createLookupColumn, createLtarColumn, diff --git a/tests/playwright/pages/Dashboard/BarcodeOverlay/index.ts b/tests/playwright/pages/Dashboard/BarcodeOverlay/index.ts new file mode 100644 index 0000000000..ce70f07bcc --- /dev/null +++ b/tests/playwright/pages/Dashboard/BarcodeOverlay/index.ts @@ -0,0 +1,25 @@ +import { expect } from '@playwright/test'; +import BasePage from '../../Base'; +import { FormPage } from '../Form'; +import { GalleryPage } from '../Gallery'; +import { GridPage } from '../Grid'; +import { KanbanPage } from '../Kanban'; + +export class BarcodeOverlay extends BasePage { + constructor(parent: GridPage | GalleryPage | KanbanPage | FormPage) { + super(parent.rootPage); + } + + get() { + return this.rootPage.locator(`.nc-barcode-large`); + } + + async verifyBarcodeSvgValue(expectedValue: string) { + const foundBarcodeSvg = await this.get().getByTestId('barcode').innerHTML(); + await expect(foundBarcodeSvg).toContain(expectedValue); + } + + async clickCloseButton() { + await this.get().locator('.ant-modal-close-x').click(); + } +} diff --git a/tests/playwright/pages/Dashboard/Grid/Column/index.ts b/tests/playwright/pages/Dashboard/Grid/Column/index.ts index 15bd509e27..1f1b9ecea7 100644 --- a/tests/playwright/pages/Dashboard/Grid/Column/index.ts +++ b/tests/playwright/pages/Dashboard/Grid/Column/index.ts @@ -30,6 +30,8 @@ export class ColumnPageObject extends BasePage { type = 'SingleLineText', formula = '', qrCodeValueColumnTitle = '', + barcodeValueColumnTitle = '', + barcodeFormat = '', childTable = '', childColumn = '', relationType = '', @@ -44,6 +46,8 @@ export class ColumnPageObject extends BasePage { type?: string; formula?: string; qrCodeValueColumnTitle?: string; + barcodeValueColumnTitle?: string; + barcodeFormat?: string; childTable?: string; childColumn?: string; relationType?: string; @@ -113,6 +117,14 @@ export class ColumnPageObject extends BasePage { }) .click(); break; + case 'Barcode': + await this.get().locator('.ant-select-single').nth(1).click(); + await this.rootPage + .locator(`.ant-select-item`, { + hasText: new RegExp(`^${barcodeValueColumnTitle}$`), + }) + .click(); + break; case 'Lookup': await this.get().locator('.ant-select-single').nth(1).click(); await this.rootPage @@ -218,6 +230,28 @@ export class ColumnPageObject extends BasePage { await this.save(); } + async changeReferencedColumnForBarcode({ titleOfReferencedColumn }: { titleOfReferencedColumn: string }) { + await this.get().locator('.nc-barcode-value-column-select .ant-select-single').click(); + await this.rootPage + .locator(`.ant-select-item`, { + hasText: titleOfReferencedColumn, + }) + .click(); + + await this.save(); + } + + async changeBarcodeFormat({ barcodeFormatName }: { barcodeFormatName: string }) { + await this.get().locator('.nc-barcode-format-select .ant-select-single').click(); + await this.rootPage + .locator(`.ant-select-item`, { + hasText: barcodeFormatName, + }) + .click(); + + await this.save(); + } + async delete({ title }: { title: string }) { await this.getColumnHeader(title).locator('svg.ant-dropdown-trigger').click(); // await this.rootPage.locator('li[role="menuitem"]:has-text("Delete")').waitFor(); diff --git a/tests/playwright/pages/Dashboard/Grid/index.ts b/tests/playwright/pages/Dashboard/Grid/index.ts index 39da3232a3..74c7b60078 100644 --- a/tests/playwright/pages/Dashboard/Grid/index.ts +++ b/tests/playwright/pages/Dashboard/Grid/index.ts @@ -6,12 +6,14 @@ import { ColumnPageObject } from './Column'; import { ToolbarPage } from '../common/Toolbar'; import { ProjectMenuObject } from '../common/ProjectMenu'; import { QrCodeOverlay } from '../QrCodeOverlay'; +import { BarcodeOverlay } from '../BarcodeOverlay'; export class GridPage extends BasePage { readonly dashboard: DashboardPage; readonly addNewTableButton: Locator; readonly dashboardPage: DashboardPage; readonly qrCodeOverlay: QrCodeOverlay; + readonly barcodeOverlay: BarcodeOverlay; readonly column: ColumnPageObject; readonly cell: CellPageObject; readonly toolbar: ToolbarPage; @@ -22,6 +24,7 @@ export class GridPage extends BasePage { this.dashboard = dashboardPage; this.addNewTableButton = dashboardPage.get().locator('.nc-add-new-table'); this.qrCodeOverlay = new QrCodeOverlay(this); + this.barcodeOverlay = new BarcodeOverlay(this); this.column = new ColumnPageObject(this); this.cell = new CellPageObject(this); this.toolbar = new ToolbarPage(this); diff --git a/tests/playwright/pages/Dashboard/common/Cell/index.ts b/tests/playwright/pages/Dashboard/common/Cell/index.ts index a277d31ee6..708768ae6f 100644 --- a/tests/playwright/pages/Dashboard/common/Cell/index.ts +++ b/tests/playwright/pages/Dashboard/common/Cell/index.ts @@ -156,6 +156,48 @@ export class CellPageObject extends BasePage { await _verify(expectedSrcValue); } + async verifyBarcodeCellShowsInvalidInputMessage({ index, columnHeader }: { index: number; columnHeader: string }) { + const _verify = async expectedInvalidInputMessage => { + await expect + .poll(async () => { + const barcodeCell = await this.get({ + index, + columnHeader, + }); + const barcodeInvalidInputMessage = await barcodeCell.getByTestId('barcode-invalid-input-message'); + return await barcodeInvalidInputMessage.textContent(); + }) + .toEqual(expectedInvalidInputMessage); + }; + + await _verify('Barcode error - please check compatibility between input and barcode type'); + } + + async verifyBarcodeCell({ + index, + columnHeader, + expectedSvgValue, + }: { + index: number; + columnHeader: string; + expectedSvgValue: string; + }) { + const _verify = async expectedBarcodeSvg => { + await expect + .poll(async () => { + const barcodeCell = await this.get({ + index, + columnHeader, + }); + const barcodeSvg = await barcodeCell.getByTestId('barcode'); + return await barcodeSvg.innerHTML(); + }) + .toEqual(expectedBarcodeSvg); + }; + + await _verify(expectedSvgValue); + } + // todo: Improve param names (i.e value => values) // verifyVirtualCell // : virtual relational cell- HM, BT, MM diff --git a/tests/playwright/tests/columnBarcode.spec.ts b/tests/playwright/tests/columnBarcode.spec.ts new file mode 100644 index 0000000000..52cc5e5be2 --- /dev/null +++ b/tests/playwright/tests/columnBarcode.spec.ts @@ -0,0 +1,174 @@ +import { expect, test } from '@playwright/test'; +import { DashboardPage } from '../pages/Dashboard'; +import setup from '../setup'; +import { GridPage } from '../pages/Dashboard/Grid'; + +interface ExpectedBarcodeData { + referencedValue: string; + barcodeSvg: string; +} + +test.describe('Virtual Columns', () => { + let dashboard: DashboardPage; + let grid: GridPage; + let context: any; + + test.beforeEach(async ({ page }) => { + context = await setup({ page }); + dashboard = new DashboardPage(page, context.project); + grid = dashboard.grid; + }); + + test.describe('Barcode Column', () => { + const initiallyExpectedBarcodeCellValues: ExpectedBarcodeData[] = [ + { + referencedValue: 'A Corua (La Corua)', + barcodeSvg: + 'A Corua (La Corua)', + }, + { + referencedValue: 'Abha', + barcodeSvg: + 'Abha', + }, + ]; + + const barcodeCellValuesForBerlin = { + referencedValue: 'Berlin', + barcodeSvg: + 'Berlin', + }; + + const barcodeCellValuesForIstanbul = { + referencedValue: 'Istanbul', + barcodeSvg: + 'Istanbul', + }; + + const barcodeCode39SvgForBerlin = + 'BERLIN'; + + const expectedBarcodeCellValuesAfterCityNameChange = [ + barcodeCellValuesForBerlin, + ...initiallyExpectedBarcodeCellValues.slice(1), + ]; + + async function barcodeColumnVerify(barcodeColumnTitle: string, expectedBarcodeCodeData: ExpectedBarcodeData[]) { + for (let i = 0; i < expectedBarcodeCodeData.length; i++) { + await grid.cell.verifyBarcodeCell({ + index: i, + columnHeader: barcodeColumnTitle, + expectedSvgValue: expectedBarcodeCodeData[i].barcodeSvg, + }); + } + } + test('creation, showing, updating value and change barcode column title and reference column', async () => { + // Add barcode code column referencing the City column + // and compare the base64 encoded codes/src attributes for the first 3 rows. + // Column data from City table (Sakila DB) + /** + * City LastUpdate Address List Country + * A Corua (La Corua) 2006-02-15 04:45:25 939 Probolinggo Loop Spain + * Abha 2006-02-15 04:45:25 733 Mandaluyong Place Saudi Arabia + * Abu Dhabi 2006-02-15 04:45:25 535 Ahmadnagar Manor United Arab Emirates + */ + // close 'Team & Auth' tab + await dashboard.closeTab({ title: 'Team & Auth' }); + + await dashboard.treeView.openTable({ title: 'City' }); + + await grid.column.create({ + title: 'Barcode1', + type: 'Barcode', + barcodeValueColumnTitle: 'City', + }); + + await barcodeColumnVerify('Barcode1', initiallyExpectedBarcodeCellValues); + + await grid.cell.fillText({ columnHeader: 'City', index: 0, text: 'Berlin' }); + + await barcodeColumnVerify('Barcode1', expectedBarcodeCellValuesAfterCityNameChange); + + await grid.cell.get({ columnHeader: 'Barcode1', index: 0 }).click(); + const barcodeGridOverlay = grid.barcodeOverlay; + await barcodeGridOverlay.verifyBarcodeSvgValue(barcodeCellValuesForBerlin.barcodeSvg); + await barcodeGridOverlay.clickCloseButton(); + + // Change the barcode column title + await grid.column.openEdit({ title: 'Barcode1' }); + await grid.column.fillTitle({ title: 'Barcode1 Renamed' }); + await grid.column.save({ isUpdated: true }); + await barcodeColumnVerify('Barcode1 Renamed', expectedBarcodeCellValuesAfterCityNameChange); + + // Change the referenced column title + await grid.column.openEdit({ title: 'City' }); + await grid.column.fillTitle({ title: 'City Renamed' }); + await grid.column.save({ isUpdated: true }); + await barcodeColumnVerify('Barcode1 Renamed', expectedBarcodeCellValuesAfterCityNameChange); + + // Change the referenced column + await grid.column.create({ title: 'New City Column' }); + await grid.cell.fillText({ columnHeader: 'New City Column', index: 0, text: 'Istanbul' }); + await grid.column.openEdit({ title: 'Barcode1 Renamed' }); + await grid.column.changeReferencedColumnForBarcode({ titleOfReferencedColumn: 'New City Column' }); + + await barcodeColumnVerify('Barcode1 Renamed', [barcodeCellValuesForIstanbul]); + + await dashboard.closeTab({ title: 'City' }); + }); + + test('deletion of the barcode column: a) directly and b) indirectly when the reference value column is deleted', async () => { + await dashboard.closeTab({ title: 'Team & Auth' }); + + await dashboard.treeView.openTable({ title: 'City' }); + + await grid.column.create({ title: 'column_name_a' }); + await grid.column.verify({ title: 'column_name_a' }); + await grid.column.create({ + title: 'Barcode2', + type: 'Barcode', + barcodeValueColumnTitle: 'column_name_a', + }); + await grid.column.verify({ title: 'Barcode2', isVisible: true }); + await grid.column.delete({ title: 'Barcode2' }); + await grid.column.verify({ title: 'Barcode2', isVisible: false }); + + await grid.column.create({ + title: 'Barcode2', + type: 'Barcode', + barcodeValueColumnTitle: 'column_name_a', + }); + await grid.column.verify({ title: 'Barcode2', isVisible: true }); + await grid.column.delete({ title: 'column_name_a' }); + await grid.column.verify({ title: 'Barcode2', isVisible: false }); + + await dashboard.closeTab({ title: 'City' }); + }); + + test('a) showing an error message for non-compatible barcode input and b) changing the format of the Barcode is reflected in the change of the actual rendered barcode', async () => { + await dashboard.closeTab({ title: 'Team & Auth' }); + + await dashboard.treeView.openTable({ title: 'City' }); + + await grid.column.create({ + title: 'Barcode1', + type: 'Barcode', + barcodeValueColumnTitle: 'City', + }); + + await grid.column.openEdit({ + title: 'Barcode1', + }); + await grid.column.changeBarcodeFormat({ barcodeFormatName: 'CODE39' }); + + await grid.cell.verifyBarcodeCellShowsInvalidInputMessage({ + index: 0, + columnHeader: 'Barcode1', + }); + + await grid.cell.fillText({ columnHeader: 'City', index: 0, text: 'Berlin' }); + + await barcodeColumnVerify('Barcode1', [{ referencedValue: 'Berlin', barcodeSvg: barcodeCode39SvgForBerlin }]); + }); + }); +});