Skip to content

Commit 9eec35c

Browse files
HannaKurbanlane-formio
authored andcommitted
FIO-9357 fixed calculation based on DataSource component (#202)
* FIO-9357 fixed calculation based on DataSource component * FIO-9357 fixed typo
1 parent aa0336d commit 9eec35c

3 files changed

Lines changed: 223 additions & 3 deletions

File tree

src/process/__tests__/process.test.ts

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4750,6 +4750,221 @@ describe('Process Tests', function () {
47504750
assert.deepEqual(context.scope.errors.length, 0);
47514751
});
47524752

4753+
it('Should calculate value on server for calculations based on dataSource component', async function () {
4754+
const form = {
4755+
_id: '6752ad48eda1569ebc9aaead',
4756+
title: 'cart',
4757+
name: 'Cart',
4758+
path: '9357cart',
4759+
type: 'form',
4760+
display: 'form',
4761+
components: [
4762+
{
4763+
label: 'Products',
4764+
persistent: 'client-only',
4765+
trigger: {
4766+
init: true,
4767+
server: true
4768+
},
4769+
dataSrc: 'url',
4770+
fetch: {
4771+
url: '{{ config.appUrl }}/product/submission',
4772+
method: 'get',
4773+
headers: [
4774+
{
4775+
key: '',
4776+
value: ''
4777+
}
4778+
],
4779+
mapFunction: '',
4780+
forwardHeaders: false,
4781+
},
4782+
allowCaching: true,
4783+
key: 'products',
4784+
type: 'datasource',
4785+
input: true,
4786+
tableView: false
4787+
},
4788+
{
4789+
label: 'Cart',
4790+
reorder: false,
4791+
addAnotherPosition: 'bottom',
4792+
layoutFixed: false,
4793+
enableRowGroups: false,
4794+
initEmpty: false,
4795+
tableView: false,
4796+
key: 'cart',
4797+
type: 'datagrid',
4798+
input: true,
4799+
components: [
4800+
{
4801+
label: 'Product',
4802+
widget: 'choicesjs',
4803+
tableView: true,
4804+
dataSrc: 'custom',
4805+
data: {
4806+
custom: 'values = data.products;'
4807+
},
4808+
valueProperty: '_id',
4809+
template: '\u003Cspan\u003E{{ item.data.name }}\u003C/span\u003E',
4810+
refreshOn: 'products',
4811+
key: 'product',
4812+
type: 'select',
4813+
input: true
4814+
},
4815+
{
4816+
label: 'Quantity',
4817+
applyMaskOn: 'change',
4818+
mask: false,
4819+
tableView: false,
4820+
delimiter: false,
4821+
requireDecimal: false,
4822+
inputFormat: 'plain',
4823+
truncateMultipleSpaces: false,
4824+
key: 'quantity',
4825+
type: 'number',
4826+
input: true,
4827+
defaultValue: 1
4828+
},
4829+
{
4830+
label: 'Price',
4831+
applyMaskOn: 'change',
4832+
mask: false,
4833+
tableView: false,
4834+
delimiter: false,
4835+
requireDecimal: false,
4836+
inputFormat: 'plain',
4837+
truncateMultipleSpaces: false,
4838+
redrawOn: 'cart.product',
4839+
calculateValue: 'var productId = row.product;\nvalue = 0;\nif (productId && data.products && data.products.length) {\n data.products.forEach(function(product) {\n if (product._id === productId) {\n value = product.data.price * (row.quantity || 1);\n }\n });\n}',
4840+
calculateServer: true,
4841+
key: 'price',
4842+
type: 'number',
4843+
input: true
4844+
}
4845+
]
4846+
},
4847+
{
4848+
label: 'Total',
4849+
applyMaskOn: 'change',
4850+
mask: false,
4851+
tableView: false,
4852+
delimiter: false,
4853+
requireDecimal: false,
4854+
inputFormat: 'plain',
4855+
truncateMultipleSpaces: false,
4856+
redrawOn: 'cart',
4857+
calculateValue: 'if (data.cart && data.cart.length) {\n value = data.cart.reduce(function(total, cartItem) {\n return total + cartItem.price;\n }, 0);\n}',
4858+
calculateServer: true,
4859+
key: 'total',
4860+
type: 'number',
4861+
input: true
4862+
},
4863+
{
4864+
type: 'button',
4865+
label: 'Submit',
4866+
key: 'submit',
4867+
disableOnInvalid: true,
4868+
input: true,
4869+
tableView: false
4870+
}
4871+
],
4872+
created: '2024-12-06T07:52:40.891Z',
4873+
modified: '2024-12-06T08:33:40.971Z',
4874+
config: {
4875+
appUrl: 'http://localhost:3000/idwqwhclwioyqbw'
4876+
},
4877+
};
4878+
4879+
const resource = [
4880+
{
4881+
_id: '6752adf3eda1569ebc9ab0cd',
4882+
data: {
4883+
name: 'Cream',
4884+
price: 30
4885+
},
4886+
},
4887+
{
4888+
_id: '6752adf4eda1569ebc9ab0df',
4889+
data: {
4890+
name: 'Perfume',
4891+
price: 100
4892+
},
4893+
},
4894+
{
4895+
_id: '6752adf4eda1569ebc9ab0f1',
4896+
data: {
4897+
name: 'Soap',
4898+
price: 5
4899+
},
4900+
},
4901+
{
4902+
_id: '6752adf4eda1569ebc9ab103',
4903+
data: {
4904+
name: 'Toothpaste',
4905+
price: 10
4906+
},
4907+
},
4908+
{
4909+
_id: '6752adf4eda1569ebc9ab115',
4910+
data: {
4911+
name: 'Shampoo',
4912+
price: 20
4913+
},
4914+
}
4915+
];
4916+
4917+
const submission = {
4918+
data: {
4919+
cart: [
4920+
{
4921+
product: '6752adf4eda1569ebc9ab115',
4922+
quantity: 5,
4923+
price: 100
4924+
},
4925+
{
4926+
product: '6752adf4eda1569ebc9ab103',
4927+
quantity: 3,
4928+
price: 30
4929+
},
4930+
{
4931+
product: '6752adf4eda1569ebc9ab0df',
4932+
quantity: 2,
4933+
price: 200
4934+
}
4935+
],
4936+
total: 330,
4937+
submit: true
4938+
},
4939+
state: 'submitted'
4940+
};
4941+
4942+
const context = {
4943+
form,
4944+
submission,
4945+
data: submission.data,
4946+
components: form.components,
4947+
processors: ProcessTargets.submission,
4948+
scope: {},
4949+
fetch: (): Promise<Response> => {
4950+
return Promise.resolve({
4951+
ok: true,
4952+
json: () => {
4953+
return Promise.resolve(resource);
4954+
}
4955+
} as Response);
4956+
},
4957+
config: {
4958+
server: true,
4959+
},
4960+
};
4961+
await process(context);
4962+
submission.data = context.data;
4963+
context.processors = ProcessTargets.evaluator;
4964+
await process(context);
4965+
assert.deepEqual(context.data, submission.data);
4966+
});
4967+
47534968
describe('Required component validation in nested form in DataGrid/EditGrid', function () {
47544969
const nestedForm = {
47554970
key: 'form',

src/process/calculation/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
CalculationScope,
66
CalculationContext,
77
ProcessorInfo,
8+
FetchScope,
89
} from 'types';
910
import { set } from 'lodash';
1011
import { normalizeContext } from 'utils/formUtil';
@@ -24,9 +25,13 @@ export const calculateProcessSync: ProcessorFnSync<CalculationScope> = (
2425
if (!shouldCalculate(context)) {
2526
return;
2627
}
28+
29+
const calculationContext = (scope as FetchScope).fetched
30+
? {...context, data: {...data, ...(scope as FetchScope).fetched}}
31+
: context;
2732
const evalContextValue = evalContext
28-
? evalContext(normalizeContext(context))
29-
: normalizeContext(context);
33+
? evalContext(normalizeContext(calculationContext))
34+
: normalizeContext(calculationContext);
3035
evalContextValue.value = value || null;
3136
if (!scope.calculated) scope.calculated = [];
3237
const newValue = JSONLogicEvaluator.evaluate(component.calculateValue, evalContextValue, 'value');

src/process/fetch/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export const fetchProcess: ProcessorFn<FetchScope> = async (context: FetchContex
107107
// Make sure the value does not get filtered for now...
108108
if (!(scope as FilterContext).filter) (scope as FilterContext).filter = {};
109109
(scope as FilterContext).filter[path] = true;
110-
scope.fetched[path] = true;
110+
scope.fetched[path] = get(row, key);
111111
} catch (err: any) {
112112
console.log(err.message);
113113
}

0 commit comments

Comments
 (0)