[AAE-20769] Fix precision for incoming values in bigdecimal field (#9434)

* [AAE-20769] Fix precision for incoming values in bigdecimal field

* CR
This commit is contained in:
Bartosz Sekula
2024-03-18 10:35:40 +01:00
committed by GitHub
parent b4f27d15b8
commit 69de85ad06
9 changed files with 260 additions and 14 deletions

View File

@@ -38,7 +38,8 @@ import {
checkboxWidgetFormVisibilityMock,
dateWidgetFormVisibilityMock,
multilineWidgetFormVisibilityMock,
displayTextWidgetFormVisibilityMock
displayTextWidgetFormVisibilityMock,
displayBigDecimalWidgetMock
} from './mock/form-renderer.component.mock';
import { FormService } from '../services/form.service';
import { CoreTestingModule } from '../../testing';
@@ -849,4 +850,15 @@ describe('Form Renderer Component', () => {
expectElementToBeHidden(displayTextContainer);
});
});
describe('Display Bigdecimal Widget', () => {
it('should round decimal field value to correct precision', async () => {
formRendererComponent.formDefinition = formService.parseForm(displayBigDecimalWidgetMock.formRepresentation.formDefinition);
fixture.detectChanges();
await fixture.whenStable();
const decimalInputElement = fixture.nativeElement.querySelector('#Decimal0tzu53');
expect(decimalInputElement.value).toBeTruthy('10.12');
});
});
});

View File

@@ -15,11 +15,12 @@
* limitations under the License.
*/
import { Component, ViewEncapsulation, Input, OnDestroy, Injector, OnChanges } from '@angular/core';
import { Component, ViewEncapsulation, Input, OnDestroy, Injector, OnChanges, OnInit, Inject } from '@angular/core';
import { FormRulesManager, formRulesManagerFactory } from '../models/form-rules.model';
import { FormModel } from './widgets/core/form.model';
import { ContainerModel, FormFieldModel, TabModel } from './widgets';
import { FormService } from '../services/form.service';
import { FORM_FIELD_MODEL_RENDER_MIDDLEWARE, FormFieldModelRenderMiddleware } from './middlewares/middleware';
@Component({
selector: 'adf-form-renderer',
@@ -31,10 +32,11 @@ import { FormService } from '../services/form.service';
useFactory: formRulesManagerFactory,
deps: [Injector]
}
],
encapsulation: ViewEncapsulation.None
})
export class FormRendererComponent<T> implements OnChanges, OnDestroy {
export class FormRendererComponent<T> implements OnInit, OnChanges, OnDestroy {
/** Toggle debug options. */
@Input()
showDebugButton: boolean = false;
@@ -46,7 +48,16 @@ export class FormRendererComponent<T> implements OnChanges, OnDestroy {
fields: FormFieldModel[];
constructor(public formService: FormService, private formRulesManager: FormRulesManager<T>) {}
constructor(
public formService: FormService,
private formRulesManager: FormRulesManager<T>,
@Inject(FORM_FIELD_MODEL_RENDER_MIDDLEWARE)
private middlewareServices: FormFieldModelRenderMiddleware[]
) {}
ngOnInit(): void {
this.runMiddlewareServices();
}
ngOnChanges(): void {
this.formRulesManager.initialize(this.formDefinition);
@@ -123,4 +134,16 @@ export class FormRendererComponent<T> implements OnChanges, OnDestroy {
const colspan = container ? container.field.colspan : 1;
return (100 / container.field.numberOfColumns) * colspan + '';
}
private runMiddlewareServices(): void {
const formFields = this.formDefinition.getFormFields();
formFields.forEach(field => {
this.middlewareServices.forEach((middlewareService) => {
if (middlewareService.type === field.type) {
field = middlewareService.getParsedField(field);
}
});
});
}
}

View File

@@ -0,0 +1,83 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FormFieldModel, FormFieldTypes, FormModel } from '../widgets';
import { DecimalRenderMiddlewareService } from './decimal-middleware.service';
describe('DecimalRenderMiddlewareService', () => {
let decimalMiddlewareService: DecimalRenderMiddlewareService;
let formFieldModel: FormFieldModel;
beforeEach(() => {
decimalMiddlewareService = new DecimalRenderMiddlewareService();
const form = new FormModel();
formFieldModel = new FormFieldModel(form, {
type: FormFieldTypes.DECIMAL,
id: 'id',
precision: 3,
value: '10.1060'
});
});
it('should return field with proper precisison', () => {
formFieldModel.value = '10.1000000000000';
formFieldModel.precision = 3;
const parsedField = decimalMiddlewareService.getParsedField(formFieldModel);
expect(parsedField.value).toBe('10.100');
});
it('should round up number with correct precisison', () => {
formFieldModel.value = '10.1039999';
formFieldModel.precision = 3;
const parsedField = decimalMiddlewareService.getParsedField(formFieldModel);
expect(parsedField.value).toBe('10.104');
});
it('should round up number, when removed fraction part starts with number larger or equal 5', () => {
formFieldModel.value = '10.1035000';
formFieldModel.precision = 3;
const parsedField = decimalMiddlewareService.getParsedField(formFieldModel);
expect(parsedField.value).toBe('10.104');
});
it('should NOT round up number, when removed fraction part starts with number smaller than 5', () => {
formFieldModel.value = '10.1034999';
formFieldModel.precision = 3;
const parsedField = decimalMiddlewareService.getParsedField(formFieldModel);
expect(parsedField.value).toBe('10.103');
});
it('should return the same value when precision is correct', () => {
formFieldModel.value = '10.123';
formFieldModel.precision = 3;
const parsedField = decimalMiddlewareService.getParsedField(formFieldModel);
expect(parsedField.value).toBe('10.123');
});
it('should work when value is not defined', () => {
formFieldModel.value = null;
const parsedField = decimalMiddlewareService.getParsedField(formFieldModel);
expect(parsedField.value).toBe(null);
});
it('should work when value is number', () => {
formFieldModel.value = 3.333;
const parsedField = decimalMiddlewareService.getParsedField(formFieldModel);
expect(parsedField.value).toBe(3.333);
});
});

View File

@@ -0,0 +1,53 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { FormFieldModelRenderMiddleware } from './middleware';
import { FormFieldModel, FormFieldTypes } from '../widgets';
@Injectable()
export class DecimalRenderMiddlewareService implements FormFieldModelRenderMiddleware {
type = FormFieldTypes.DECIMAL;
getParsedField(field: FormFieldModel): FormFieldModel {
const allowedMaxPrecision = field.precision;
const value = field.value;
field.value = this.forceMaxPrecisionIfNeeded(value, allowedMaxPrecision);
return field;
}
private forceMaxPrecisionIfNeeded(value: string | number, allowedMaxPrecision): string | number {
let numberOfDecimalDigits = 0;
const stringValue = typeof value === 'string' ? value : `${value}`;
const numberChunks = stringValue.split('.');
if (numberChunks.length === 2) {
numberOfDecimalDigits = numberChunks[1].length;
}
if (numberOfDecimalDigits > allowedMaxPrecision) {
const valueWithCorrectPrecision = parseFloat(value.toString())
.toFixed(allowedMaxPrecision);
return valueWithCorrectPrecision;
}
return value;
}
};

View File

@@ -0,0 +1,26 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { InjectionToken } from '@angular/core';
import { FormFieldModel } from '../../components/widgets';
export interface FormFieldModelRenderMiddleware {
type: string;
getParsedField(field: FormFieldModel): FormFieldModel;
}
export const FORM_FIELD_MODEL_RENDER_MIDDLEWARE = new InjectionToken('RENDER_FORM_FIELD_MODEL_MIDDLEWARE');

View File

@@ -15,6 +15,8 @@
* limitations under the License.
*/
import { FormFieldTypes } from '../widgets';
export const formDisplayValueVisibility = {
formRepresentation: {
id: 'form-3175b074-53c6-4b5b-92df-246b62108db3',
@@ -2086,3 +2088,50 @@ export const displayTextWidgetFormVisibilityMock = {
}
}
};
export const displayBigDecimalWidgetMock = {
formRepresentation: {
id: 'form-098756a5-2222-4c3a-1111-81dabc9a88b6',
name: 'displayBigdecimalForm',
description: '',
version: 0,
standAlone: true,
formDefinition: {
tabs: [],
fields: [
{
id: '45269202-5f2a-438e-b14c-fe13eb4b2aa1',
name: 'Label',
type: 'container',
tab: null,
numberOfColumns: 2,
fields: {
1: [
{
id: 'Decimal0tzu53',
name: 'Bigdecimal',
type: FormFieldTypes.DECIMAL,
required: false,
colspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
visibilityCondition: null,
precision: 2,
value: '10.12345678',
params: {
existingColspan: 1,
maxColspan: 2
}
}
]
}
}
],
outcomes: [],
metadata: {},
variables: []
}
}
};

View File

@@ -23,7 +23,6 @@
pattern="-?[0-9]*(\.[0-9]*)?"
[id]="field.id"
[required]="isRequired()"
[value]="displayValue"
[(ngModel)]="field.value"
(ngModelChange)="onFieldChanged(field)"
[disabled]="field.readOnly"

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { Component, ViewEncapsulation } from '@angular/core';
import { FormService } from '../../../services/form.service';
import { WidgetComponent } from '../widget.component';
@@ -36,14 +36,8 @@ import { WidgetComponent } from '../widget.component';
},
encapsulation: ViewEncapsulation.None
})
export class DecimalWidgetComponent extends WidgetComponent implements OnInit {
displayValue: number;
export class DecimalWidgetComponent extends WidgetComponent {
constructor(public formService: FormService) {
super(formService);
}
ngOnInit(): void {
this.displayValue = this.field.value;
}
}

View File

@@ -37,6 +37,8 @@ import { EditJsonDialogModule } from '../dialogs/edit-json/edit-json.dialog.modu
import { A11yModule } from '@angular/cdk/a11y';
import { ViewerModule } from '../viewer/viewer.module';
import { InplaceFormInputComponent } from './components/inplace-form-input/inplace-form-input.component';
import { FORM_FIELD_MODEL_RENDER_MIDDLEWARE } from './components/middlewares/middleware';
import { DecimalRenderMiddlewareService } from './components/middlewares/decimal-middleware.service';
@NgModule({
imports: [
@@ -70,7 +72,12 @@ import { InplaceFormInputComponent } from './components/inplace-form-input/inpla
...WIDGET_DIRECTIVES,
InplaceFormInputComponent,
WidgetComponent
]
],
providers: [{
provide: FORM_FIELD_MODEL_RENDER_MIDDLEWARE,
useClass: DecimalRenderMiddlewareService,
multi: true
}]
})
export class FormBaseModule {
}