decimal field type
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldSanitizers;
|
||||
|
||||
use Espo\Core\FieldSanitize\Sanitizer;
|
||||
use Espo\Core\FieldSanitize\Sanitizer\Data;
|
||||
|
||||
class Decimal implements Sanitizer
|
||||
{
|
||||
public function sanitize(Data $data, string $field): void
|
||||
{
|
||||
if (!$data->has($field)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $data->get($field);
|
||||
|
||||
if (is_string($value) || $value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_float($value) || is_int($value)) {
|
||||
$value = (string) $value;
|
||||
}
|
||||
|
||||
$data->set($field, $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldValidators;
|
||||
|
||||
use Espo\Core\Currency\CalculatorUtil;
|
||||
use Espo\ORM\Entity;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class DecimalType
|
||||
{
|
||||
public function __construct()
|
||||
{}
|
||||
|
||||
public function checkRequired(Entity $entity, string $field): bool
|
||||
{
|
||||
return $this->isNotEmpty($entity, $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $validationValue
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function checkMax(Entity $entity, string $field, $validationValue): bool
|
||||
{
|
||||
if (!$this->isNotEmpty($entity, $field)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return CalculatorUtil::compare($entity->get($field), $validationValue) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $validationValue
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function checkMin(Entity $entity, string $field, $validationValue): bool
|
||||
{
|
||||
if (!$this->isNotEmpty($entity, $field)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return CalculatorUtil::compare($entity->get($field), $validationValue) >= 0;
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function rawCheckValid(stdClass $data, string $field): bool
|
||||
{
|
||||
$value = $data->$field ?? null;
|
||||
|
||||
if ($value === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($value === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (preg_match('/^-?[0-9]+\.?[0-9]*$/', $value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function isNotEmpty(Entity $entity, string $field): bool
|
||||
{
|
||||
return $entity->has($field) && $entity->get($field) !== null;
|
||||
}
|
||||
}
|
||||
@@ -64,4 +64,9 @@ class FieldType
|
||||
public const JSON_ARRAY = 'jsonArray';
|
||||
public const JSON_OBJECT = 'jsonObject';
|
||||
public const PASSWORD = 'password';
|
||||
|
||||
/**
|
||||
* @since 9.3.0
|
||||
*/
|
||||
public const DECIMAL = 'decimal';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\Orm\FieldConverters;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\FieldConverter;
|
||||
use Espo\ORM\Defs\FieldDefs;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Defs\Params\FieldParam;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
|
||||
class Decimal implements FieldConverter
|
||||
{
|
||||
private const DEFAULT_PRECISION = 13;
|
||||
private const DEFAULT_SCALE = 4;
|
||||
|
||||
public function convert(FieldDefs $fieldDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $fieldDefs->getName();
|
||||
|
||||
$dbType = $fieldDefs->getParam(FieldParam::DB_TYPE) ?? Types::DECIMAL;
|
||||
$precision = $fieldDefs->getParam(FieldParam::PRECISION) ?? self::DEFAULT_PRECISION;
|
||||
$scale = $fieldDefs->getParam(FieldParam::SCALE) ?? self::DEFAULT_SCALE;
|
||||
|
||||
$defs = AttributeDefs::create($name)
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withDbType($dbType)
|
||||
->withParam(AttributeParam::PRECISION, $precision)
|
||||
->withParam(AttributeParam::SCALE, $scale);
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute($defs);
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,7 @@
|
||||
"personName": "Person Name",
|
||||
"autoincrement": "Auto-increment",
|
||||
"bool": "Boolean",
|
||||
"decimal": "Decimal",
|
||||
"currency": "Currency",
|
||||
"currencyConverted": "Currency (Converted)",
|
||||
"date": "Date",
|
||||
|
||||
@@ -137,6 +137,7 @@
|
||||
},
|
||||
"fieldInfo": {
|
||||
"varchar": "A single-line text.",
|
||||
"decimal": "A decimal number with fixed-point precision.",
|
||||
"enum": "Selectbox, only one value can be selected.",
|
||||
"text": "A multiline text with markdown support.",
|
||||
"date": "Date w/o time.",
|
||||
|
||||
@@ -179,6 +179,17 @@
|
||||
"lessThanOrEquals"
|
||||
]
|
||||
},
|
||||
"decimal": {
|
||||
"view": "views/admin/dynamic-logic/conditions/field-types/base",
|
||||
"typeList": [
|
||||
"isEmpty",
|
||||
"isNotEmpty",
|
||||
"greaterThan",
|
||||
"lessThan",
|
||||
"greaterThanOrEquals",
|
||||
"lessThanOrEquals"
|
||||
]
|
||||
},
|
||||
"currency": {
|
||||
"view": "views/admin/dynamic-logic/conditions/field-types/base",
|
||||
"typeList": [
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"params": [
|
||||
{
|
||||
"name": "required",
|
||||
"type": "bool",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "default",
|
||||
"type": "decimal"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"type": "decimal"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"type": "decimal"
|
||||
},
|
||||
{
|
||||
"name": "decimalPlaces",
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"name": "audited",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "readOnly",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "readOnlyAfterCreate",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "precision",
|
||||
"type": "int",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "scale",
|
||||
"type": "int",
|
||||
"hidden": true
|
||||
}
|
||||
],
|
||||
"filter": true,
|
||||
"validationList": [
|
||||
"required",
|
||||
"min",
|
||||
"max"
|
||||
],
|
||||
"mandatoryValidationList": [
|
||||
"valid"
|
||||
],
|
||||
"sanitizerClassNameList": [
|
||||
"Espo\\Classes\\FieldSanitizers\\Decimal"
|
||||
],
|
||||
"converterClassName": "Espo\\Core\\Utils\\Database\\Orm\\FieldConverters\\Decimal"
|
||||
}
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
namespace Espo\Tools\DynamicLogic;
|
||||
|
||||
use Espo\Core\Currency\CalculatorUtil;
|
||||
use Espo\Core\Field\Date;
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\Core\Utils\DateTime\SystemClock;
|
||||
@@ -214,18 +215,34 @@ class ConditionChecker
|
||||
}
|
||||
|
||||
if ($type === Type::GreaterThan) {
|
||||
if (is_string($setValue) || is_string($value)) {
|
||||
return $this->compare($setValue, $value) > 0;
|
||||
}
|
||||
|
||||
return $setValue > $value;
|
||||
}
|
||||
|
||||
if ($type === Type::LessThan) {
|
||||
if (is_string($setValue) || is_string($value)) {
|
||||
return $this->compare($setValue, $value) < 0;
|
||||
}
|
||||
|
||||
return $setValue < $value;
|
||||
}
|
||||
|
||||
if ($type === Type::GreaterThanOrEquals) {
|
||||
if (is_string($setValue) || is_string($value)) {
|
||||
return $this->compare($setValue, $value) >= 0;
|
||||
}
|
||||
|
||||
return $setValue >= $value;
|
||||
}
|
||||
|
||||
if ($type === Type::LessThanOrEquals) {
|
||||
if (is_string($setValue) || is_string($value)) {
|
||||
return $this->compare($setValue, $value) <= 0;
|
||||
}
|
||||
|
||||
return $setValue <= $value;
|
||||
}
|
||||
|
||||
@@ -346,4 +363,20 @@ class ConditionChecker
|
||||
|
||||
return Date::fromDateTime($dateTime);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws BadCondition
|
||||
*/
|
||||
private function compare(mixed $arg1, mixed $arg2): int
|
||||
{
|
||||
$arg1 = (string) $arg1;
|
||||
$arg2 = (string) $arg2;
|
||||
|
||||
if (!is_numeric($arg1) || !is_numeric($arg2)) {
|
||||
throw new BadCondition();
|
||||
}
|
||||
|
||||
return CalculatorUtil::compare($arg1, $arg2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
/** @module views/fields/decimal */
|
||||
|
||||
import IntFieldView from 'views/fields/int';
|
||||
|
||||
/**
|
||||
* A decimal field.
|
||||
*
|
||||
* @extends IntFieldView<module:views/fields/decimal~params>
|
||||
*/
|
||||
class DecimalFieldView extends IntFieldView {
|
||||
|
||||
/**
|
||||
* @typedef {Object} module:views/fields/decimal~options
|
||||
* @property {
|
||||
* module:views/fields/decimal~params &
|
||||
* module:views/fields/base~params &
|
||||
* Record
|
||||
* } [params] Parameters.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} module:views/fields/decimal~params
|
||||
* @property {string} [min] A max value.
|
||||
* @property {string} [max] A max value.
|
||||
* @property {boolean} [required] Required.
|
||||
* @property {boolean} [disableFormatting] Disable formatting.
|
||||
* @property {number|null} [decimalPlaces] A number of decimal places.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {
|
||||
* module:views/fields/float~options &
|
||||
* module:views/fields/base~options
|
||||
* } options Options.
|
||||
*/
|
||||
constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
type = 'decimal'
|
||||
|
||||
editTemplate = 'fields/float/edit'
|
||||
|
||||
decimalMark = '.'
|
||||
decimalPlacesRawValue = 10
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @type {Array<(function (): boolean)|string>}
|
||||
*/
|
||||
validations = [
|
||||
'required',
|
||||
'range',
|
||||
]
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {number|null}
|
||||
*/
|
||||
decimalPlaces
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
if (this.getPreferences().has('decimalMark')) {
|
||||
this.decimalMark = this.getPreferences().get('decimalMark');
|
||||
} else if (this.getConfig().has('decimalMark')) {
|
||||
this.decimalMark = this.getConfig().get('decimalMark');
|
||||
}
|
||||
|
||||
if (!this.decimalMark) {
|
||||
this.decimalMark = '.';
|
||||
}
|
||||
|
||||
if (this.decimalMark === this.thousandSeparator) {
|
||||
this.thousandSeparator = '';
|
||||
}
|
||||
|
||||
this.decimalPlaces = this.params.decimalPlaces ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
setupAutoNumericOptions() {
|
||||
// noinspection JSValidateTypes
|
||||
this.autoNumericOptions = {
|
||||
digitGroupSeparator: this.thousandSeparator || '',
|
||||
decimalCharacter: this.decimalMark,
|
||||
modifyValueOnWheel: false,
|
||||
selectOnFocus: false,
|
||||
decimalPlaces: this.decimalPlaces,
|
||||
decimalPlacesRawValue: this.decimalPlacesRawValue,
|
||||
allowDecimalPadding: true,
|
||||
showWarnings: false,
|
||||
formulaMode: true,
|
||||
};
|
||||
|
||||
if (this.decimalPlaces === null) {
|
||||
this.autoNumericOptions.decimalPlaces = this.decimalPlacesRawValue;
|
||||
this.autoNumericOptions.decimalPlacesRawValue = this.decimalPlacesRawValue;
|
||||
this.autoNumericOptions.allowDecimalPadding = false;
|
||||
}
|
||||
}
|
||||
|
||||
getValueForDisplay() {
|
||||
const value = isNaN(this.model.get(this.name)) ? null : this.model.get(this.name);
|
||||
|
||||
return this.formatNumber(value);
|
||||
}
|
||||
|
||||
formatNumber(value) {
|
||||
if (this.disableFormatting) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return this.formatNumberDetail(value);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string|null} value
|
||||
* @return {string}
|
||||
*/
|
||||
formatNumberDetail(value) {
|
||||
if (value === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const decimalPlaces = this.decimalPlaces;
|
||||
|
||||
const parts = value.toString().split('.');
|
||||
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);
|
||||
|
||||
if (decimalPlaces === 0) {
|
||||
return parts[0];
|
||||
}
|
||||
|
||||
if (decimalPlaces) {
|
||||
let decimalPartLength = 0;
|
||||
|
||||
if (parts.length > 1) {
|
||||
parts[1] = parts[1].replace(/0+$/, '');
|
||||
|
||||
decimalPartLength = parts[1].length;
|
||||
} else {
|
||||
parts[1] = '';
|
||||
}
|
||||
|
||||
if (decimalPlaces && decimalPartLength < decimalPlaces) {
|
||||
const limit = decimalPlaces - decimalPartLength;
|
||||
|
||||
for (let i = 0; i < limit; i++) {
|
||||
parts[1] += '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parts.join(this.decimalMark);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|null} value
|
||||
* @return {string|null}
|
||||
*/
|
||||
parse(value) {
|
||||
value = (value !== '') ? value : null;
|
||||
|
||||
if (value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
value = value
|
||||
.split(this.thousandSeparator)
|
||||
.join('')
|
||||
.split(this.decimalMark)
|
||||
.join('.');
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Record}
|
||||
*/
|
||||
fetch() {
|
||||
const rawValue = this.mainInputElement?.value ?? null;
|
||||
|
||||
const value = this.parse(rawValue);
|
||||
|
||||
return {[this.name]: value};
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
validateRange() {
|
||||
const value = this.model.get(this.name);
|
||||
|
||||
if (value === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const minValue = this.params.min;
|
||||
const maxValue = this.params.max;
|
||||
|
||||
if (minValue !== null && maxValue !== null) {
|
||||
if (Number(value) < Number(minValue) || Number(value) > Number(maxValue)) {
|
||||
const msg = this.translate('fieldShouldBeBetween', 'messages')
|
||||
.replace('{field}', this.getLabelText())
|
||||
.replace('{min}', minValue)
|
||||
.replace('{max}', maxValue);
|
||||
|
||||
this.showValidationMessage(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (minValue !== null) {
|
||||
if (Number(value) < Number(minValue)) {
|
||||
const msg = this.translate('fieldShouldBeGreater', 'messages')
|
||||
.replace('{field}', this.getLabelText())
|
||||
.replace('{value}', minValue);
|
||||
|
||||
this.showValidationMessage(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
} else if (maxValue !== null) {
|
||||
if (Number(value) > Number(maxValue)) {
|
||||
const msg = this.translate('fieldShouldBeLess', 'messages')
|
||||
.replace('{field}', this.getLabelText())
|
||||
.replace('{value}', maxValue);
|
||||
this.showValidationMessage(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default DecimalFieldView;
|
||||
@@ -600,6 +600,15 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"anyOf": [
|
||||
{"const": "decimal"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace tests\integration\Espo\Record;
|
||||
use Espo\Core\Application;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\FieldValidation\Type;
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Record\CreateParams;
|
||||
use Espo\Core\Record\ServiceContainer;
|
||||
use Espo\Core\Record\UpdateParams;
|
||||
@@ -575,4 +576,145 @@ class FieldValidationTest extends BaseTestCase
|
||||
|
||||
$this->assertFalse($isThrown);
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnhandledExceptionInspection
|
||||
*/
|
||||
public function testDecimal(): void
|
||||
{
|
||||
$metadata = $this->getMetadata();
|
||||
$metadata->set('entityDefs', 'Account', [
|
||||
'fields' => [
|
||||
'test' => [
|
||||
'type' => FieldType::DECIMAL,
|
||||
'min' => '-1.0',
|
||||
'max' => '100.0',
|
||||
]
|
||||
],
|
||||
]);
|
||||
$metadata->save();
|
||||
|
||||
$this->getDataManager()->rebuild();
|
||||
|
||||
$this->reCreateApplication();
|
||||
|
||||
$service = $this->getContainer()
|
||||
->getByClass(ServiceContainer::class)
|
||||
->getByClass(Account::class);
|
||||
|
||||
$isThrown = false;
|
||||
try {
|
||||
$service->create((object) [
|
||||
'name' => 'Test-0',
|
||||
'test' => null,
|
||||
], CreateParams::create());
|
||||
} catch (BadRequest) {
|
||||
$isThrown = true;
|
||||
}
|
||||
$this->assertFalse($isThrown);
|
||||
|
||||
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||||
$isThrown = false;
|
||||
try {
|
||||
$service->create((object) [
|
||||
'name' => 'Test-1',
|
||||
'test' => '10.0',
|
||||
], CreateParams::create());
|
||||
} catch (BadRequest) {
|
||||
$isThrown = true;
|
||||
}
|
||||
$this->assertFalse($isThrown);
|
||||
|
||||
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||||
$isThrown = false;
|
||||
try {
|
||||
$service->create((object) [
|
||||
'name' => 'Test-2',
|
||||
'test' => '-1.0',
|
||||
], CreateParams::create());
|
||||
} catch (BadRequest) {
|
||||
$isThrown = true;
|
||||
}
|
||||
$this->assertFalse($isThrown);
|
||||
|
||||
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||||
$isThrown = false;
|
||||
try {
|
||||
$service->create((object) [
|
||||
'name' => 'Test-3',
|
||||
'test' => '100.0',
|
||||
], CreateParams::create());
|
||||
} catch (BadRequest) {
|
||||
$isThrown = true;
|
||||
}
|
||||
$this->assertFalse($isThrown);
|
||||
|
||||
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||||
$isThrown = false;
|
||||
try {
|
||||
$service->create((object) [
|
||||
'name' => 'Test-4',
|
||||
'test' => '-10.0',
|
||||
], CreateParams::create());
|
||||
} catch (BadRequest) {
|
||||
$isThrown = true;
|
||||
}
|
||||
$this->assertTrue($isThrown);
|
||||
|
||||
$isThrown = false;
|
||||
try {
|
||||
$service->create((object) [
|
||||
'name' => 'Test-5',
|
||||
'test' => '100.1',
|
||||
], CreateParams::create());
|
||||
} catch (BadRequest) {
|
||||
$isThrown = true;
|
||||
}
|
||||
$this->assertTrue($isThrown);
|
||||
|
||||
$isThrown = false;
|
||||
try {
|
||||
$service->create((object) [
|
||||
'name' => 'Test-6',
|
||||
'test' => 'abc',
|
||||
], CreateParams::create());
|
||||
} catch (BadRequest) {
|
||||
$isThrown = true;
|
||||
}
|
||||
$this->assertTrue($isThrown);
|
||||
|
||||
$isThrown = false;
|
||||
try {
|
||||
$service->create((object) [
|
||||
'name' => 'Test-7',
|
||||
'test' => ['test'],
|
||||
], CreateParams::create());
|
||||
} catch (BadRequest) {
|
||||
$isThrown = true;
|
||||
}
|
||||
$this->assertTrue($isThrown);
|
||||
|
||||
$isThrown = false;
|
||||
try {
|
||||
$service->create((object) [
|
||||
'name' => 'Test-8',
|
||||
'test' => 10.0,
|
||||
], CreateParams::create());
|
||||
} catch (BadRequest) {
|
||||
$isThrown = true;
|
||||
}
|
||||
$this->assertFalse($isThrown);
|
||||
|
||||
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||||
$isThrown = false;
|
||||
try {
|
||||
$service->create((object) [
|
||||
'name' => 'Test-9',
|
||||
'test' => 10,
|
||||
], CreateParams::create());
|
||||
} catch (BadRequest) {
|
||||
$isThrown = true;
|
||||
}
|
||||
$this->assertFalse($isThrown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1295,4 +1295,196 @@ class CheckerTest extends TestCase
|
||||
|
||||
$this->assertTrue($checker->check($item));
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnhandledExceptionInspection
|
||||
*/
|
||||
public function testDecimalGreaterThan(): void
|
||||
{
|
||||
$map = [
|
||||
'value' => '1.0',
|
||||
];
|
||||
|
||||
$checker = new ConditionChecker($this->initEntity($map));
|
||||
|
||||
$this->assertTrue(
|
||||
$checker->check(
|
||||
Item::fromGroupDefinition([
|
||||
(object) [
|
||||
'type' => 'greaterThan',
|
||||
'attribute' => 'value',
|
||||
'value' => '0.0',
|
||||
]
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
$checker->check(
|
||||
Item::fromGroupDefinition([
|
||||
(object) [
|
||||
'type' => 'greaterThan',
|
||||
'attribute' => 'value',
|
||||
'value' => '1.0',
|
||||
]
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
$checker->check(
|
||||
Item::fromGroupDefinition([
|
||||
(object) [
|
||||
'type' => 'greaterThan',
|
||||
'attribute' => 'value',
|
||||
'value' => '2.0',
|
||||
]
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnhandledExceptionInspection
|
||||
*/
|
||||
public function testDecimalLessThan(): void
|
||||
{
|
||||
$map = [
|
||||
'value' => '1.0',
|
||||
];
|
||||
|
||||
$checker = new ConditionChecker($this->initEntity($map));
|
||||
|
||||
$this->assertTrue(
|
||||
$checker->check(
|
||||
Item::fromGroupDefinition([
|
||||
(object) [
|
||||
'type' => 'lessThan',
|
||||
'attribute' => 'value',
|
||||
'value' => '2.0',
|
||||
]
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
$checker->check(
|
||||
Item::fromGroupDefinition([
|
||||
(object) [
|
||||
'type' => 'lessThan',
|
||||
'attribute' => 'value',
|
||||
'value' => '1.0',
|
||||
]
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
$checker->check(
|
||||
Item::fromGroupDefinition([
|
||||
(object) [
|
||||
'type' => 'lessThan',
|
||||
'attribute' => 'value',
|
||||
'value' => '0.0',
|
||||
]
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnhandledExceptionInspection
|
||||
*/
|
||||
public function testDecimalGreaterThanOrEquals(): void
|
||||
{
|
||||
$map = [
|
||||
'value' => '1.0',
|
||||
];
|
||||
|
||||
$checker = new ConditionChecker($this->initEntity($map));
|
||||
|
||||
$this->assertTrue(
|
||||
$checker->check(
|
||||
Item::fromGroupDefinition([
|
||||
(object) [
|
||||
'type' => 'greaterThanOrEquals',
|
||||
'attribute' => 'value',
|
||||
'value' => '0.0',
|
||||
]
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$checker->check(
|
||||
Item::fromGroupDefinition([
|
||||
(object) [
|
||||
'type' => 'greaterThanOrEquals',
|
||||
'attribute' => 'value',
|
||||
'value' => '1.0',
|
||||
]
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
$checker->check(
|
||||
Item::fromGroupDefinition([
|
||||
(object) [
|
||||
'type' => 'greaterThanOrEquals',
|
||||
'attribute' => 'value',
|
||||
'value' => '2.0',
|
||||
]
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnhandledExceptionInspection
|
||||
*/
|
||||
public function testDecimalLessThanOrEquals(): void
|
||||
{
|
||||
$map = [
|
||||
'value' => '1',
|
||||
];
|
||||
|
||||
$checker = new ConditionChecker($this->initEntity($map));
|
||||
|
||||
$this->assertTrue(
|
||||
$checker->check(
|
||||
Item::fromGroupDefinition([
|
||||
(object) [
|
||||
'type' => 'lessThanOrEquals',
|
||||
'attribute' => 'value',
|
||||
'value' => '2.0',
|
||||
]
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$checker->check(
|
||||
Item::fromGroupDefinition([
|
||||
(object) [
|
||||
'type' => 'lessThanOrEquals',
|
||||
'attribute' => 'value',
|
||||
'value' => '1.0',
|
||||
]
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
$checker->check(
|
||||
Item::fromGroupDefinition([
|
||||
(object) [
|
||||
'type' => 'lessThanOrEquals',
|
||||
'attribute' => 'value',
|
||||
'value' => '0.0',
|
||||
]
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user