From 11bd0d9d67d688da3c3134013f5df4bcca3d71d0 Mon Sep 17 00:00:00 2001 From: Yurii Date: Thu, 30 Apr 2026 14:15:05 +0300 Subject: [PATCH] TS migration --- client/src/model.ts | 2 +- client/src/views/fields/array.ts | 9 +- client/src/views/fields/base.ts | 4 +- .../views/fields/{currency.js => currency.ts} | 276 ++++++++++-------- client/src/views/fields/date.ts | 9 +- client/src/views/fields/datetime.ts | 4 +- client/src/views/fields/float.ts | 8 +- client/src/views/fields/int.ts | 10 +- package-lock.json | 10 +- package.json | 2 +- 10 files changed, 186 insertions(+), 148 deletions(-) rename client/src/views/fields/{currency.js => currency.ts} (60%) diff --git a/client/src/model.ts b/client/src/model.ts index a79406ee88..66d5dfd1ca 100644 --- a/client/src/model.ts +++ b/client/src/model.ts @@ -279,7 +279,7 @@ export default class Model = Record, + options?: {silent?: boolean} & Record, ): this { if (attribute == null) { diff --git a/client/src/views/fields/array.ts b/client/src/views/fields/array.ts index 1125e10bac..6b2faa9ceb 100644 --- a/client/src/views/fields/array.ts +++ b/client/src/views/fields/array.ts @@ -28,7 +28,12 @@ /** @module views/fields/array */ -import BaseFieldView, {BaseOptions as BaseOptions, BaseParams as BaseParams, BaseViewSchema} from 'views/fields/base'; +import BaseFieldView, { + BaseOptions as BaseOptions, + BaseParams as BaseParams, + BaseViewSchema, + FieldValidator, +} from 'views/fields/base'; import RegExpPattern from 'helpers/reg-exp-pattern'; import MultiSelect from 'ui/multi-select'; import ModalView, {ModalOptions} from 'views/modal'; @@ -163,7 +168,7 @@ class ArrayFieldView< protected maxItemLength: number | null = null - protected validations = ['required', 'maxCount'] + protected validations: (FieldValidator | string)[] = ['required', 'maxCount'] protected readonly MAX_ITEM_LENGTH = 100 diff --git a/client/src/views/fields/base.ts b/client/src/views/fields/base.ts index d22729ed91..5decced3e0 100644 --- a/client/src/views/fields/base.ts +++ b/client/src/views/fields/base.ts @@ -99,6 +99,8 @@ export interface BaseViewSchema { model: Model; } +export type FieldValidator = () => boolean; + type Mode = 'list' | 'listLink' | 'detail' | 'edit' | 'search'; /** @@ -182,7 +184,7 @@ export default class BaseFieldView< * * Functions are supported as of v8.3. */ - protected validations: Array<(() => boolean) | string> = ['required'] + protected validations: (FieldValidator | string)[] = ['required'] /** * List mode. diff --git a/client/src/views/fields/currency.js b/client/src/views/fields/currency.ts similarity index 60% rename from client/src/views/fields/currency.js rename to client/src/views/fields/currency.ts index c72326bfcb..b07963f9d1 100644 --- a/client/src/views/fields/currency.js +++ b/client/src/views/fields/currency.ts @@ -1,3 +1,5 @@ +// noinspection JSUnusedLocalSymbols + /************************************************************************ * This file is part of EspoCRM. * @@ -30,85 +32,111 @@ import FloatFieldView from 'views/fields/float'; import Select from 'ui/select'; +import {BaseOptions, BaseParams, BaseViewSchema, FieldValidator} from 'views/fields/base'; + +/** + * Parameters. + */ +export interface CurrencyParams extends BaseParams { + /** + * A min value. + */ + min?: number; + /** + * A max value. + */ + max?: number; + /** + * Required. + */ + required?: boolean; + /** + * Disable formatting. + */ + disableFormatting?: boolean; + /** + * Decimal places. + * + * @todo ? + */ + decimalPlaces?: number | null; + /** + * onlyDefaultCurrency + */ + onlyDefaultCurrency?: boolean + /** + * Stored as decimal. + */ + decimal?: boolean + /** + * Scale (for decimal). + */ + scale?: number +} + +/** + * Options. + */ +export interface CurrencyOptions extends BaseOptions { + /** + * Hide currency. + */ + hideCurrency?: boolean; +} /** * A currency field. - * - * @extends IntFieldView */ -class CurrencyFieldView extends FloatFieldView { +class CurrencyFieldView< + S extends BaseViewSchema = BaseViewSchema, + O extends CurrencyOptions = CurrencyOptions, + P extends CurrencyParams = CurrencyParams, +> extends FloatFieldView { - /** - * @typedef {Object} module:views/fields/currency~options - * @property { - * module:views/fields/currency~params & - * module:views/fields/base~params & - * Record - * } [params] Parameters. - */ + readonly type: string = 'currency' - /** - * @typedef {Object} module:views/fields/currency~params - * @property {number} [min] A max value. - * @property {number} [max] A max value. - * @property {boolean} [required] Required. - * @property {boolean} [disableFormatting] Disable formatting. - * @property {number|null} [decimalPlaces] A number of decimal places. @todo - * @property {boolean} [onlyDefaultCurrency] Only the default currency. - * @property {boolean} [decimal] Stored as decimal. - * @property {number} [scale] Scale (for decimal). - */ + protected editTemplate = 'fields/currency/edit' + protected detailTemplate = 'fields/currency/detail' + protected listTemplate = 'fields/currency/list' - /** - * @param { - * module:views/fields/currency~options & - * module:views/fields/base~options - * } options Options. - */ - constructor(options) { - super(options); - } - - type = 'currency' - - editTemplate = 'fields/currency/edit' - detailTemplate = 'fields/currency/detail' // noinspection JSUnusedGlobalSymbols - detailTemplate1 = 'fields/currency/detail-1' + protected detailTemplate1 = 'fields/currency/detail-1' // noinspection JSUnusedGlobalSymbols - detailTemplate2 = 'fields/currency/detail-2' + protected detailTemplate2 = 'fields/currency/detail-2' // noinspection JSUnusedGlobalSymbols - detailTemplate3 = 'fields/currency/detail-3' - listTemplate = 'fields/currency/list' - // noinspection JSUnusedGlobalSymbols - listTemplate1 = 'fields/currency/list-1' - // noinspection JSUnusedGlobalSymbols - listTemplate2 = 'fields/currency/list-2' - // noinspection JSUnusedGlobalSymbols - listTemplate3 = 'fields/currency/list-3' - detailTemplateNoCurrency = 'fields/currency/detail-no-currency' + protected detailTemplate3 = 'fields/currency/detail-3' - maxDecimalPlaces = 3 + // noinspection JSUnusedGlobalSymbols + protected listTemplate1 = 'fields/currency/list-1' + // noinspection JSUnusedGlobalSymbols + protected listTemplate2 = 'fields/currency/list-2' + // noinspection JSUnusedGlobalSymbols + protected listTemplate3 = 'fields/currency/list-3' - /** - * @inheritDoc - * @type {Array<(function (): boolean)|string>} - */ - validations = [ + protected detailTemplateNoCurrency = 'fields/currency/detail-no-currency' + + protected maxDecimalPlaces: number = 3 + + protected validations: (FieldValidator | string)[] = [ 'required', 'number', 'range', ] /** - * @protected - * @type {string} * @since 9.2.6 */ - currencyAttribute + protected currencyAttribute: string - /** @inheritDoc */ - data() { + protected currencyFieldName: string + protected isSingleCurrency: boolean + protected defaultCurrency: string + protected currencyList: string[] + protected decimalPlaces: number + + private $currency: JQuery + + protected data() { const currencyValue = this.model.get(this.currencyFieldName) || this.getPreferences().get('defaultCurrency') || this.getConfig().get('defaultCurrency'); @@ -126,8 +154,7 @@ class CurrencyFieldView extends FloatFieldView { }; } - /** @inheritDoc */ - setup() { + protected setup() { super.setup(); this.currencyFieldName = this.currencyAttribute ?? this.name + 'Currency'; @@ -141,7 +168,7 @@ class CurrencyFieldView extends FloatFieldView { this.isSingleCurrency = this.currencyList.length <= 1; - const currencyValue = this.currencyValue = this.model.get(this.currencyFieldName) || + const currencyValue = this.model.get(this.currencyFieldName) || this.defaultCurrency; if (!this.currencyList.includes(currencyValue)) { @@ -150,8 +177,7 @@ class CurrencyFieldView extends FloatFieldView { } } - /** @inheritDoc */ - setupAutoNumericOptions() { + protected setupAutoNumericOptions() { this.autoNumericOptions = { digitGroupSeparator: this.thousandSeparator || '', decimalCharacter: this.decimalMark, @@ -160,6 +186,7 @@ class CurrencyFieldView extends FloatFieldView { decimalPlaces: this.decimalPlaces, allowDecimalPadding: true, showWarnings: false, + // @ts-ignore formulaMode: true, }; @@ -170,18 +197,17 @@ class CurrencyFieldView extends FloatFieldView { } } - getCurrencyFormat() { + protected getCurrencyFormat(): number { return this.getConfig().get('currencyFormat') || 1; } _getTemplateName() { if (this.mode === this.MODE_DETAIL || this.mode === this.MODE_LIST) { - let prop; + let prop: string; if (this.mode === this.MODE_LIST) { prop = 'listTemplate' + this.getCurrencyFormat().toString(); - } - else { + } else { prop = 'detailTemplate' + this.getCurrencyFormat().toString(); } @@ -190,68 +216,66 @@ class CurrencyFieldView extends FloatFieldView { } if (prop in this) { - return this[prop]; + return (this as any)[prop]; } } + // @ts-ignore return super._getTemplateName(); } - formatNumber(value) { + protected formatNumber(value: number | null): string | null { return this.formatNumberDetail(value); } - formatNumberDetail(value) { - if (value !== null) { - const currencyDecimalPlaces = this.decimalPlaces; - - if (currencyDecimalPlaces === 0) { - value = Math.round(value); - } - else if (currencyDecimalPlaces) { - value = Math.round( - value * Math.pow(10, currencyDecimalPlaces)) / (Math.pow(10, currencyDecimalPlaces) - ); - } - else { - value = Math.round( - value * Math.pow(10, this.maxDecimalPlaces)) / (Math.pow(10, this.maxDecimalPlaces) - ); - } - - const parts = value.toString().split("."); - - parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator); - - if (currencyDecimalPlaces === 0) { - return parts[0]; - } - else if (currencyDecimalPlaces) { - let decimalPartLength = 0; - - if (parts.length > 1) { - decimalPartLength = parts[1].length; - } else { - parts[1] = ''; - } - - if (currencyDecimalPlaces && decimalPartLength < currencyDecimalPlaces) { - const limit = currencyDecimalPlaces - decimalPartLength; - - for (let i = 0; i < limit; i++) { - parts[1] += '0'; - } - } - } - - return parts.join(this.decimalMark); + protected formatNumberDetail(value: number | null): string { + if (value === null) { + return ''; } - return ''; + const currencyDecimalPlaces = this.decimalPlaces; + + if (currencyDecimalPlaces === 0) { + value = Math.round(value); + } else if (currencyDecimalPlaces) { + value = Math.round( + value * Math.pow(10, currencyDecimalPlaces)) / (Math.pow(10, currencyDecimalPlaces) + ); + } else { + value = Math.round( + value * Math.pow(10, this.maxDecimalPlaces)) / (Math.pow(10, this.maxDecimalPlaces) + ); + } + + const parts = value.toString().split('.'); + + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator); + + if (currencyDecimalPlaces === 0) { + return parts[0]; + } else if (currencyDecimalPlaces) { + let decimalPartLength = 0; + + if (parts.length > 1) { + decimalPartLength = parts[1].length; + } else { + parts[1] = ''; + } + + if (currencyDecimalPlaces && decimalPartLength < currencyDecimalPlaces) { + const limit = currencyDecimalPlaces - decimalPartLength; + + for (let i = 0; i < limit; i++) { + parts[1] += '0'; + } + } + } + + return parts.join(this.decimalMark); } - parse(value) { - value = (value !== '') ? value : null; + protected parse(input: string): number | string | null { + let value = (input !== '') ? input : null; if (value === null) { return null; @@ -273,14 +297,14 @@ class CurrencyFieldView extends FloatFieldView { } } - if (!this.params.decimal) { - value = parseFloat(value); + if (this.params.decimal) { + return value; } - return value; + return parseFloat(value); } - afterRender() { + protected afterRender() { super.afterRender(); if (this.mode === this.MODE_EDIT) { @@ -312,16 +336,18 @@ class CurrencyFieldView extends FloatFieldView { return true; } + + return false; } - fetch() { - let value = this.$element.val().trim(); + fetch(): Record { + const valueString = ((this.$element?.val() ?? '') as string).trim(); - value = this.parse(value); + const value = this.parse(valueString); - const data = {}; + const data = {} as Record; - let currencyValue = this.$currency.length ? + let currencyValue: any = this.$currency.length ? this.$currency.val() : this.defaultCurrency; diff --git a/client/src/views/fields/date.ts b/client/src/views/fields/date.ts index 3d9cc9e1d1..42fddfe5a1 100644 --- a/client/src/views/fields/date.ts +++ b/client/src/views/fields/date.ts @@ -28,7 +28,12 @@ /** @module views/fields/date */ -import BaseFieldView, {BaseOptions as BaseOptions, BaseParams as BaseParams, BaseViewSchema} from 'views/fields/base'; +import BaseFieldView, { + BaseOptions as BaseOptions, + BaseParams as BaseParams, + BaseViewSchema, + FieldValidator, +} from 'views/fields/base'; import moment from 'moment'; import Datepicker from 'ui/datepicker'; import JQuery from 'jquery' @@ -82,7 +87,7 @@ class DateFieldView< protected editTemplate = 'fields/date/edit' protected searchTemplate = 'fields/date/search' - protected validations = [ + protected validations: (FieldValidator | string)[] = [ 'required', 'date', 'after', diff --git a/client/src/views/fields/datetime.ts b/client/src/views/fields/datetime.ts index 6d39b091e3..e5f52c17ed 100644 --- a/client/src/views/fields/datetime.ts +++ b/client/src/views/fields/datetime.ts @@ -30,7 +30,7 @@ import DateFieldView from 'views/fields/date'; import moment from 'moment'; -import {BaseOptions as BaseOptions, BaseViewSchema} from 'views/fields/base'; +import {BaseOptions as BaseOptions, BaseViewSchema, FieldValidator} from 'views/fields/base'; import JQuery from 'jquery' const $ = JQuery; @@ -86,7 +86,7 @@ class DatetimeFieldView< protected editTemplate = 'fields/datetime/edit' - protected validations = [ + protected validations: (FieldValidator | string)[] = [ 'required', 'datetime', 'after', diff --git a/client/src/views/fields/float.ts b/client/src/views/fields/float.ts index 2a1a9c30bb..d4c2140f8b 100644 --- a/client/src/views/fields/float.ts +++ b/client/src/views/fields/float.ts @@ -29,7 +29,7 @@ /** @module views/fields/float */ import IntFieldView from 'views/fields/int'; -import {BaseOptions, BaseParams, BaseViewSchema} from 'views/fields/base'; +import {BaseOptions, BaseParams, BaseViewSchema, FieldValidator} from 'views/fields/base'; /** * Parameters. @@ -73,12 +73,12 @@ class FloatFieldView< readonly type: string = 'float' - editTemplate = 'fields/float/edit' + protected editTemplate = 'fields/float/edit' decimalMark = '.' decimalPlacesRawValue = 10 - protected validations = ['required', 'float', 'range'] + protected validations: (FieldValidator | string)[] = ['required', 'float', 'range'] protected setup() { super.setup(); @@ -177,7 +177,7 @@ class FloatFieldView< return false; } - protected parse(input: string): number | null { + protected parse(input: string): number | string | null { let value = (input !== '') ? input : null; if (value === null) { diff --git a/client/src/views/fields/int.ts b/client/src/views/fields/int.ts index 2821f74e81..aa0c1d03ca 100644 --- a/client/src/views/fields/int.ts +++ b/client/src/views/fields/int.ts @@ -28,7 +28,7 @@ /** @module views/fields/int */ -import BaseFieldView, {BaseOptions, BaseParams, BaseViewSchema} from 'views/fields/base'; +import BaseFieldView, {BaseOptions, BaseParams, BaseViewSchema, FieldValidator} from 'views/fields/base'; import AutoNumeric from 'autonumeric'; /** @@ -74,7 +74,7 @@ class IntFieldView< protected editTemplate = 'fields/int/edit' protected searchTemplate = 'fields/int/search' - protected validations = [ + protected validations: (FieldValidator | string)[] = [ 'required', 'int', 'range', @@ -364,7 +364,7 @@ class IntFieldView< return false; } - protected parse(input: string): number | null { + protected parse(input: string): number | string | null { let value = (input !== '') ? input : null; if (value === null) { @@ -401,14 +401,14 @@ class IntFieldView< let data: any; - if (value !== null && isNaN(value)) { + if (typeof value === 'number' && isNaN(value)) { return null; } if (type === 'between') { const valueTo = this.parse(this.$el.find('input.additional').val()); - if (valueTo !== null && isNaN(valueTo)) { + if (typeof valueTo === 'number' && isNaN(valueTo)) { return null; } diff --git a/package-lock.json b/package-lock.json index 6def11cac5..02697ededf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "bootstrap": "github:yurikuzn/espo-bootstrap#0.1.2", "bootstrap-colorpicker": "^2.5.2", "bootstrap-datepicker": "^1.9.0", - "bullbone": "github:espocrm/bullbone#1.4.1", + "bullbone": "github:espocrm/bullbone#1.4.2", "cronstrue": "^1.114.0", "cropper": "^0.7.9", "devbridge-autocomplete": "^1.4.11", @@ -3443,8 +3443,8 @@ } }, "node_modules/bullbone": { - "version": "1.4.1", - "resolved": "git+ssh://git@github.com/espocrm/bullbone.git#ee78dd11f13a5c29465f218bd2852dfd72a9ceed", + "version": "1.4.2", + "resolved": "git+ssh://git@github.com/espocrm/bullbone.git#aab93904c0bce2d18625c15f8e7487f792ed2b5c", "license": "AGPL", "dependencies": { "snabbdom": "^3.6.3" @@ -11008,8 +11008,8 @@ "dev": true }, "bullbone": { - "version": "git+ssh://git@github.com/espocrm/bullbone.git#ee78dd11f13a5c29465f218bd2852dfd72a9ceed", - "from": "bullbone@github:espocrm/bullbone#1.4.1", + "version": "git+ssh://git@github.com/espocrm/bullbone.git#aab93904c0bce2d18625c15f8e7487f792ed2b5c", + "from": "bullbone@github:espocrm/bullbone#1.4.2", "requires": { "snabbdom": "^3.6.3" } diff --git a/package.json b/package.json index 4da1bb417e..10b474e8fc 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "bootstrap": "github:yurikuzn/espo-bootstrap#0.1.2", "bootstrap-colorpicker": "^2.5.2", "bootstrap-datepicker": "^1.9.0", - "bullbone": "github:espocrm/bullbone#1.4.1", + "bullbone": "github:espocrm/bullbone#1.4.2", "cronstrue": "^1.114.0", "cropper": "^0.7.9", "devbridge-autocomplete": "^1.4.11",