@bgroup/wise-form 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,208 +1,161 @@
|
|
|
1
1
|
import type { FormulaManager } from '..';
|
|
2
2
|
import { conditionsTypes } from '../helpers/condition-types';
|
|
3
3
|
import { EvaluationsManager } from '../helpers/evaluations';
|
|
4
|
-
import {
|
|
5
|
-
EvaluatedFormula,
|
|
6
|
-
FormulaObserver,
|
|
7
|
-
IComplexCondition,
|
|
8
|
-
IConditionalField,
|
|
9
|
-
} from '../types/formulas';
|
|
4
|
+
import { EvaluatedFormula, FormulaObserver, IComplexCondition, IConditionalField } from '../types/formulas';
|
|
10
5
|
import { parse } from 'mathjs';
|
|
11
6
|
|
|
12
7
|
export class FormulaConditional {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
8
|
+
#plugin: any;
|
|
9
|
+
#specs: FormulaObserver;
|
|
10
|
+
#emptyValue: undefined;
|
|
11
|
+
get formula() {
|
|
12
|
+
return this.#specs.formula;
|
|
13
|
+
}
|
|
14
|
+
get base() {
|
|
15
|
+
const formula = <IComplexCondition>this.#specs.formula;
|
|
16
|
+
return formula.base;
|
|
17
|
+
}
|
|
18
|
+
#value: string | number | undefined | 0;
|
|
19
|
+
get value() {
|
|
20
|
+
return this.#value;
|
|
21
|
+
}
|
|
22
|
+
get name() {
|
|
23
|
+
return this.#specs.name;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Represents the fields defined in the plugin settings
|
|
27
|
+
*/
|
|
28
|
+
get fields() {
|
|
29
|
+
const formula = <IComplexCondition>this.formula;
|
|
30
|
+
return typeof formula?.fields === 'string' ? [formula?.fields] : formula?.fields;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get conditions() {
|
|
34
|
+
if (typeof this.#specs.formula === 'string') return;
|
|
35
|
+
const formula = this.#specs.formula as IComplexCondition;
|
|
36
|
+
return formula.conditions;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* FormField type
|
|
41
|
+
*/
|
|
42
|
+
#fields: any;
|
|
43
|
+
|
|
44
|
+
#parent: FormulaManager;
|
|
45
|
+
#ceil: boolean;
|
|
46
|
+
#round: boolean;
|
|
47
|
+
#isNotListenToChanges = false;
|
|
48
|
+
constructor(parent, plugin, specs) {
|
|
49
|
+
this.#parent = parent;
|
|
50
|
+
this.#plugin = plugin;
|
|
51
|
+
this.#specs = specs;
|
|
52
|
+
this.#round = specs.round;
|
|
53
|
+
this.#ceil = specs.ceil;
|
|
54
|
+
this.#emptyValue = specs.emptyValue;
|
|
55
|
+
|
|
56
|
+
if (specs.isNotListenToChanges) this.#isNotListenToChanges = specs.isNotListenToChanges;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
initialize() {
|
|
60
|
+
try {
|
|
61
|
+
const { form } = this.#plugin;
|
|
62
|
+
|
|
63
|
+
if (!this.fields) {
|
|
64
|
+
throw new Error(`Fields not found in formula ${this.name}`);
|
|
65
|
+
}
|
|
66
|
+
const fields = this.fields.map(name => {
|
|
67
|
+
const formula = this.#plugin.formulas.get(name);
|
|
68
|
+
if (formula) return formula;
|
|
69
|
+
const field = form.getField(name);
|
|
70
|
+
return field;
|
|
71
|
+
});
|
|
72
|
+
this.#fields = fields;
|
|
73
|
+
if (this.name === 'costoTotalGrafico') {
|
|
74
|
+
console.log('fields', fields);
|
|
75
|
+
}
|
|
76
|
+
if (!this.#isNotListenToChanges)
|
|
77
|
+
fields.forEach(field => {
|
|
78
|
+
if (!field) {
|
|
79
|
+
throw new Error(`Field ${this.name} not found in form ${form.name}`);
|
|
80
|
+
}
|
|
81
|
+
field.on('change', this.calculate.bind(this));
|
|
82
|
+
});
|
|
83
|
+
} catch (e) {}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
evaluate() {
|
|
87
|
+
const formula = <IComplexCondition>this.#specs.formula;
|
|
88
|
+
let evaluatedFormula: any = { formula: formula.base }; // Use the base formula by default
|
|
89
|
+
if (formula.conditions) {
|
|
90
|
+
const conditionsArray = Array.isArray(formula.conditions) ? formula.conditions : [formula.conditions];
|
|
91
|
+
for (const condition of conditionsArray) {
|
|
92
|
+
let conditionMet = false;
|
|
93
|
+
if (condition.conditions) {
|
|
94
|
+
// If there are nested conditions, all must be met
|
|
95
|
+
conditionMet = condition.conditions.every(subCondition => {
|
|
96
|
+
const fieldValues = subCondition.fields.map(fieldName => {
|
|
97
|
+
const field = this.#fields.find(f => f.name === fieldName);
|
|
98
|
+
return field ? field.value : this.#emptyValue;
|
|
99
|
+
});
|
|
100
|
+
return EvaluationsManager.validateAll(subCondition.condition, fieldValues, subCondition.value);
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
const fieldValues = condition.fields.map(fieldName => {
|
|
104
|
+
const field = this.#fields.find(f => f.name === fieldName);
|
|
105
|
+
return field ? field.value : this.#emptyValue;
|
|
106
|
+
});
|
|
107
|
+
const conditionType = !!condition.type && conditionsTypes[condition.type] ? conditionsTypes[condition.type] : conditionsTypes.some;
|
|
108
|
+
// Check if any of the specified fields meet the condition
|
|
109
|
+
conditionMet = EvaluationsManager[conditionType](condition.condition, fieldValues, condition.value);
|
|
110
|
+
}
|
|
39
111
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
112
|
+
if (conditionMet) {
|
|
113
|
+
evaluatedFormula.formula = condition.formula;
|
|
114
|
+
evaluatedFormula.fi = condition;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
44
118
|
}
|
|
119
|
+
return evaluatedFormula;
|
|
120
|
+
}
|
|
45
121
|
|
|
122
|
+
async calculate() {
|
|
46
123
|
/**
|
|
47
|
-
*
|
|
124
|
+
* the formula is taken from the evaluate method since the conditions are evaluated there and
|
|
125
|
+
* can change the formula to be applied
|
|
48
126
|
*/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
this.#plugin = plugin;
|
|
58
|
-
this.#specs = specs;
|
|
59
|
-
this.#round = specs.round;
|
|
60
|
-
this.#ceil = specs.ceil;
|
|
61
|
-
this.#emptyValue = specs.emptyValue;
|
|
62
|
-
|
|
63
|
-
if (specs.isNotListenToChanges)
|
|
64
|
-
this.#isNotListenToChanges = specs.isNotListenToChanges;
|
|
127
|
+
const model = this.#plugin.form.getField(this.name);
|
|
128
|
+
const formula = this.evaluate();
|
|
129
|
+
|
|
130
|
+
if (!formula.formula) {
|
|
131
|
+
this.#value = this.#emptyValue !== undefined ? this.#emptyValue : '';
|
|
132
|
+
model && model.set({ value: this.#value });
|
|
133
|
+
this.#parent.trigger('change');
|
|
134
|
+
return this.#value;
|
|
65
135
|
}
|
|
136
|
+
// todo: Review if this section can be replaced by formulaManager.variables property.
|
|
137
|
+
const { tokens } = this.#parent.getParser(formula);
|
|
138
|
+
const variables = tokens.filter(token => token.type === 'variable').map(item => item.value);
|
|
66
139
|
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
const { form } = this.#plugin;
|
|
70
|
-
|
|
71
|
-
if (!this.fields) {
|
|
72
|
-
throw new Error(`Fields not found in formula ${this.name}`);
|
|
73
|
-
}
|
|
74
|
-
const fields = this.fields.map((name) => {
|
|
75
|
-
const formula = this.#plugin.formulas.get(name);
|
|
76
|
-
if (formula) return formula;
|
|
77
|
-
const field = form.getField(name);
|
|
78
|
-
return field;
|
|
79
|
-
});
|
|
80
|
-
this.#fields = fields;
|
|
81
|
-
if (this.name === 'costoTotalGrafico') {
|
|
82
|
-
console.log('fields', fields);
|
|
83
|
-
}
|
|
84
|
-
if (!this.#isNotListenToChanges)
|
|
85
|
-
fields.forEach((field) => {
|
|
86
|
-
if (!field) {
|
|
87
|
-
throw new Error(
|
|
88
|
-
`Field ${this.name} not found in form ${form.name}`
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
field.on('change', this.calculate.bind(this));
|
|
92
|
-
});
|
|
93
|
-
} catch (e) {}
|
|
94
|
-
}
|
|
140
|
+
const params = await this.#parent.getParams(variables);
|
|
95
141
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (formula.conditions) {
|
|
100
|
-
const conditionsArray = Array.isArray(formula.conditions) ? formula.conditions : [formula.conditions];
|
|
101
|
-
for (const condition of conditionsArray) {
|
|
102
|
-
let conditionMet = false;
|
|
103
|
-
if (condition.conditions) {
|
|
104
|
-
// If there are nested conditions, all must be met
|
|
105
|
-
conditionMet = condition.conditions.every(
|
|
106
|
-
(subCondition) => {
|
|
107
|
-
const fieldValues = subCondition.fields.map(
|
|
108
|
-
(fieldName) => {
|
|
109
|
-
const field = this.#fields.find(
|
|
110
|
-
(f) => f.name === fieldName
|
|
111
|
-
);
|
|
112
|
-
return field
|
|
113
|
-
? field.value
|
|
114
|
-
: this.#emptyValue;
|
|
115
|
-
}
|
|
116
|
-
);
|
|
117
|
-
return EvaluationsManager.validateAll(
|
|
118
|
-
subCondition.condition,
|
|
119
|
-
fieldValues,
|
|
120
|
-
subCondition.value
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
);
|
|
124
|
-
} else {
|
|
125
|
-
const fieldValues = condition.fields.map((fieldName) => {
|
|
126
|
-
const field = this.#fields.find(
|
|
127
|
-
(f) => f.name === fieldName
|
|
128
|
-
);
|
|
129
|
-
return field ? field.value : this.#emptyValue;
|
|
130
|
-
});
|
|
131
|
-
const conditionType =
|
|
132
|
-
!!condition.type && conditionsTypes[condition.type]
|
|
133
|
-
? conditionsTypes[condition.type]
|
|
134
|
-
: conditionsTypes.some;
|
|
135
|
-
// Check if any of the specified fields meet the condition
|
|
136
|
-
conditionMet = EvaluationsManager[conditionType](
|
|
137
|
-
condition.condition,
|
|
138
|
-
fieldValues,
|
|
139
|
-
condition.value
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (conditionMet) {
|
|
144
|
-
evaluatedFormula.formula = condition.formula;
|
|
145
|
-
evaluatedFormula.fi = condition;
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return evaluatedFormula;
|
|
151
|
-
}
|
|
142
|
+
try {
|
|
143
|
+
const keys = Object.keys(params);
|
|
144
|
+
let result = keys.length === 1 ? params[keys[0]] : parse(formula.formula as string).evaluate(params);
|
|
152
145
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return this.#value;
|
|
167
|
-
}
|
|
168
|
-
// todo: Review if this section can be replaced by formulaManager.variables property.
|
|
169
|
-
const { tokens } = this.#parent.getParser(formula);
|
|
170
|
-
const variables = tokens
|
|
171
|
-
.filter((token) => token.type === 'variable')
|
|
172
|
-
.map((item) => item.value);
|
|
173
|
-
|
|
174
|
-
const params = await this.#parent.getParams(variables);
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
const keys = Object.keys(params);
|
|
178
|
-
let result =
|
|
179
|
-
keys.length === 1
|
|
180
|
-
? params[keys[0]]
|
|
181
|
-
: parse(formula.formula as string).evaluate(params);
|
|
182
|
-
|
|
183
|
-
const isInvalidResult = [
|
|
184
|
-
-Infinity,
|
|
185
|
-
Infinity,
|
|
186
|
-
undefined,
|
|
187
|
-
null,
|
|
188
|
-
NaN,
|
|
189
|
-
].includes(result);
|
|
190
|
-
if (this.#round && !isInvalidResult) result = Math.round(result);
|
|
191
|
-
if (this.#ceil && !isInvalidResult) result = Math.ceil(result);
|
|
192
|
-
this.#value =
|
|
193
|
-
isInvalidResult || typeof result === 'object'
|
|
194
|
-
? this.#emptyValue
|
|
195
|
-
: Number(result.toFixed(2));
|
|
196
|
-
|
|
197
|
-
this.#parent.trigger('change');
|
|
198
|
-
|
|
199
|
-
model && model.set({ value: this.#value });
|
|
200
|
-
return this.#value;
|
|
201
|
-
} catch (e) {
|
|
202
|
-
console.log('formula', this.name, formula.formula, params);
|
|
203
|
-
console.error(e);
|
|
204
|
-
throw new Error('Error calculating the formula');
|
|
205
|
-
}
|
|
146
|
+
const isInvalidResult = [-Infinity, Infinity, undefined, null, NaN].includes(result);
|
|
147
|
+
if (this.#round && !isInvalidResult) result = Math.round(result);
|
|
148
|
+
if (this.#ceil && !isInvalidResult) result = Math.ceil(result);
|
|
149
|
+
this.#value = isInvalidResult || typeof result === 'object' ? this.#emptyValue : Number(Number(result).toFixed(2));
|
|
150
|
+
|
|
151
|
+
this.#parent.trigger('change');
|
|
152
|
+
|
|
153
|
+
model && model.set({ value: this.#value });
|
|
154
|
+
return this.#value;
|
|
155
|
+
} catch (e) {
|
|
156
|
+
console.log('formula', this.name, formula.formula, params);
|
|
157
|
+
console.error(e);
|
|
158
|
+
throw new Error('Error calculating the formula');
|
|
206
159
|
}
|
|
160
|
+
}
|
|
207
161
|
}
|
|
208
|
-
|
|
@@ -33,7 +33,7 @@ export class CallbackManager {
|
|
|
33
33
|
|
|
34
34
|
initialize() {
|
|
35
35
|
const instance = this.#field;
|
|
36
|
-
const checkField = async settings => {
|
|
36
|
+
const checkField = async (settings, index) => {
|
|
37
37
|
const dependency = this.#model.getField(this.#model.getFieldName(settings.field));
|
|
38
38
|
await dependency.isReady;
|
|
39
39
|
const required = ['field', 'callback'];
|
|
@@ -47,6 +47,12 @@ export class CallbackManager {
|
|
|
47
47
|
throw new Error(`${settings.callback} is not a registered callback ${settings.name}`);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// Asignar un ID único a cada configuración de callback para tracking individual
|
|
51
|
+
if (!settings.__callbackId) {
|
|
52
|
+
const instanceName = (instance as any).name || 'unknown';
|
|
53
|
+
settings.__callbackId = `${instanceName}_${settings.callback}_${settings.field}_${index}_${Date.now()}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
50
56
|
// saved in listener array to be able to remove the listener if is required.
|
|
51
57
|
const event = settings.event || 'value.change';
|
|
52
58
|
const caller = () => this.executeCallback(settings);
|
|
@@ -61,12 +67,18 @@ export class CallbackManager {
|
|
|
61
67
|
|
|
62
68
|
/**
|
|
63
69
|
* Generates a unique execution key for tracking callback recursion.
|
|
64
|
-
*
|
|
70
|
+
* Uses the unique __callbackId from settings if available, otherwise falls back to name-based key.
|
|
65
71
|
*/
|
|
66
|
-
#getExecutionKey(
|
|
72
|
+
#getExecutionKey(settings: any, fieldName: string): string {
|
|
73
|
+
// Si el settings tiene un __callbackId único, usarlo para permitir múltiples callbacks del mismo tipo
|
|
74
|
+
if (settings?.__callbackId) {
|
|
75
|
+
return settings.__callbackId;
|
|
76
|
+
}
|
|
77
|
+
// Fallback para compatibilidad con código legacy
|
|
78
|
+
const callbackName = settings?.callback || 'unknown';
|
|
67
79
|
const fieldNameStr = fieldName || 'unknown';
|
|
68
|
-
const
|
|
69
|
-
return `${callbackName}:${fieldNameStr}:${
|
|
80
|
+
const dependencyFieldName = typeof settings?.field === 'string' ? settings.field : settings?.field?.field || 'unknown';
|
|
81
|
+
return `${callbackName}:${fieldNameStr}:${dependencyFieldName}`;
|
|
70
82
|
}
|
|
71
83
|
|
|
72
84
|
executeCallback = async settings => {
|
|
@@ -84,7 +96,7 @@ export class CallbackManager {
|
|
|
84
96
|
const fieldName = (this.#field as { name?: string })?.name || 'unknown';
|
|
85
97
|
const dependencyFieldName = typeof settings.field === 'string' ? settings.field : settings.field?.field || 'unknown';
|
|
86
98
|
|
|
87
|
-
const executionKey = this.#getExecutionKey(
|
|
99
|
+
const executionKey = this.#getExecutionKey(settings, fieldName);
|
|
88
100
|
|
|
89
101
|
// Check if already executing (prevent concurrent executions)
|
|
90
102
|
if (currentlyExecutingCallbacks.has(executionKey)) {
|
|
@@ -105,6 +117,7 @@ export class CallbackManager {
|
|
|
105
117
|
`\n Dependency: ${dependencyFieldName}`,
|
|
106
118
|
`\n Current depth: ${currentDepth + 1}`,
|
|
107
119
|
`\n Execution key: ${executionKey}`,
|
|
120
|
+
`\n Callback ID: ${settings?.__callbackId || 'N/A'}`,
|
|
108
121
|
`\n This indicates a circular dependency in callbacks. Please review the form configuration.`
|
|
109
122
|
);
|
|
110
123
|
return;
|