@steedos/service-metadata-objects 3.0.0-beta.13 → 3.0.0-beta.131
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/lib/actionsHandler.js +45 -21
- package/lib/actionsHandler.js.map +1 -1
- package/lib/formula/core.js +35 -8
- package/lib/formula/core.js.map +1 -1
- package/lib/formula/formulaActionHandler.d.ts +1 -1
- package/lib/formula/formulaActionHandler.js +40 -26
- package/lib/formula/formulaActionHandler.js.map +1 -1
- package/lib/summary/summaryActionHandler.d.ts +2 -2
- package/lib/summary/summaryActionHandler.js +21 -11
- package/lib/summary/summaryActionHandler.js.map +1 -1
- package/package.json +6 -7
- package/src/actionsHandler.ts +335 -258
- package/src/formula/core.ts +44 -10
- package/src/formula/formulaActionHandler.ts +500 -379
- package/src/summary/summaryActionHandler.ts +279 -208
|
@@ -1,435 +1,556 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
SteedosFieldFormulaTypeConfig,
|
|
3
|
+
SteedosFieldFormulaQuoteTypeConfig,
|
|
4
|
+
SteedosFormulaVarTypeConfig,
|
|
5
|
+
SteedosFormulaVarPathTypeConfig,
|
|
6
|
+
FormulaUserKey,
|
|
7
|
+
FormulaUserSessionKey,
|
|
8
|
+
SteedosFormulaBlankValue,
|
|
9
|
+
} from "./type";
|
|
10
|
+
import { pickFormulaVars } from "./core";
|
|
3
11
|
// import { isCodeObject } from '../util'; TODO
|
|
4
|
-
import { Register } from
|
|
12
|
+
import { Register } from "@steedos/metadata-registrar";
|
|
5
13
|
|
|
6
|
-
import _ = require(
|
|
7
|
-
const clone = require(
|
|
14
|
+
import _ = require("lodash");
|
|
15
|
+
const clone = require("clone");
|
|
8
16
|
|
|
9
17
|
// TODO
|
|
10
|
-
const isCodeObject = (objectApiName)=>{
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const refMapName = '$formula_ref_maps';
|
|
18
|
+
const isCodeObject = (objectApiName) => {
|
|
19
|
+
return objectApiName ? false : true;
|
|
20
|
+
};
|
|
15
21
|
|
|
22
|
+
const refMapName = "$formula_ref_maps";
|
|
16
23
|
|
|
17
|
-
function getDynamicCalcMapCacherKey(
|
|
18
|
-
|
|
24
|
+
function getDynamicCalcMapCacherKey(
|
|
25
|
+
objectApiName: string,
|
|
26
|
+
mainObjectApiName: string,
|
|
27
|
+
): string {
|
|
28
|
+
return `$dynamic_calc_map.${objectApiName}.${mainObjectApiName}`;
|
|
19
29
|
}
|
|
20
30
|
|
|
21
|
-
export class FormulaActionHandler{
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
export class FormulaActionHandler {
|
|
32
|
+
broker: any = null;
|
|
33
|
+
constructor(broker) {
|
|
34
|
+
this.broker = broker;
|
|
35
|
+
}
|
|
26
36
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
async deleteAll(objectConfig) {
|
|
38
|
+
try {
|
|
39
|
+
if (!objectConfig) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// console.log(`deleteAll formula`, `${this.cacherKey(objectConfig.name)}.*`)
|
|
43
|
+
// await this.broker.call('metadata.fuzzyDelete', {key: `${this.cacherKey(objectConfig.name)}.*`}, {meta: {}})
|
|
44
|
+
await Register.fuzzyDelete(
|
|
45
|
+
this.broker,
|
|
46
|
+
`${this.cacherKey(objectConfig.name)}.*`,
|
|
47
|
+
);
|
|
48
|
+
return true;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
this.broker.logger.error(error);
|
|
40
51
|
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
41
54
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
55
|
+
async getObjectConfig(objectApiName: string) {
|
|
56
|
+
const data = await this.broker.call("objects.get", {
|
|
57
|
+
objectApiName: objectApiName,
|
|
58
|
+
});
|
|
59
|
+
return data ? data.metadata : null;
|
|
60
|
+
}
|
|
46
61
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
addFieldFormulaQuotesConfig(
|
|
63
|
+
quote: SteedosFieldFormulaQuoteTypeConfig,
|
|
64
|
+
quotes: Array<SteedosFieldFormulaQuoteTypeConfig>,
|
|
65
|
+
) {
|
|
66
|
+
if (quote.field_name === "_id") {
|
|
67
|
+
// _id字段不记为引用关系,因为其值不会变化,相关记录属性变更时不需要重算被引用的字段公式
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
let existQuote = quotes.find((item) => {
|
|
71
|
+
return (
|
|
72
|
+
item.field_name === quote.field_name &&
|
|
73
|
+
item.object_name === quote.object_name
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
if (!existQuote) {
|
|
77
|
+
quotes.push(quote);
|
|
58
78
|
}
|
|
79
|
+
}
|
|
59
80
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
/**
|
|
82
|
+
* 把公式中a.b.c,比如account.website这样的变量转为SteedosFieldFormulaQuoteTypeConfig和SteedosFieldFormulaVarTypeConfig追加到quotes和vars中
|
|
83
|
+
* 因为getObjectConfigs拿到的对象肯定不包括被禁用和假删除的对象,所以不需要额外判断相关状态
|
|
84
|
+
* @param formulaVar 公式中的单个变量,比如account.website
|
|
85
|
+
* @param fieldConfig
|
|
86
|
+
* @param objectConfigs
|
|
87
|
+
* @param quotes
|
|
88
|
+
*/
|
|
89
|
+
async computeFormulaVarAndQuotes(
|
|
90
|
+
formulaVar: string,
|
|
91
|
+
objectConfig: any,
|
|
92
|
+
quotes: Array<SteedosFieldFormulaQuoteTypeConfig>,
|
|
93
|
+
vars: Array<SteedosFormulaVarTypeConfig>,
|
|
94
|
+
) {
|
|
95
|
+
let isSimpleVar = objectConfig === null; //明确传入null时表示要计算的是普通变量。
|
|
96
|
+
// global.user.profile这种 amis 全局变量不参与变量计算,直接跳过
|
|
97
|
+
let isAmisGlobalVar = /^global\./.test(formulaVar);
|
|
98
|
+
if (isAmisGlobalVar) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// 公式变量以FormulaUserSessionKey(即$user)值开头,说明是user变量
|
|
102
|
+
let isUserVar = new RegExp(
|
|
103
|
+
`^${FormulaUserKey.replace("$", "\\$")}\\b`,
|
|
104
|
+
).test(formulaVar);
|
|
105
|
+
let isUserSessionVar = new RegExp(
|
|
106
|
+
`^${FormulaUserSessionKey.replace("$", "\\$")}\\b`,
|
|
107
|
+
).test(formulaVar);
|
|
108
|
+
let varItems = formulaVar.split(".");
|
|
109
|
+
let paths: Array<SteedosFormulaVarPathTypeConfig> = [];
|
|
110
|
+
let formulaVarItem: SteedosFormulaVarTypeConfig = {
|
|
111
|
+
key: formulaVar,
|
|
112
|
+
paths: paths,
|
|
113
|
+
};
|
|
114
|
+
if (isUserVar || isUserSessionVar) {
|
|
115
|
+
// user var不可以按普通变量处理,因为其变量值依赖paths属性
|
|
116
|
+
isSimpleVar = false;
|
|
117
|
+
objectConfig = "space_users";
|
|
118
|
+
}
|
|
119
|
+
if (isSimpleVar) {
|
|
120
|
+
// 只要不是user var则标记为普通变量,后续取参数值时直接通过key取值,而不用走变量上的paths属性。
|
|
121
|
+
// 普通变量直接跳过后面所有代码即可,因为不需要处理paths和公式关系链等逻辑。
|
|
122
|
+
// 注意普通公式也是支持$user全局变量的,但是isSimpleVar是false,所以走的是后面的逻辑
|
|
123
|
+
formulaVarItem.is_simple_var = true;
|
|
124
|
+
vars.push(formulaVarItem);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (isUserSessionVar) {
|
|
128
|
+
// $userSession变量直接跳后后面所有代码即可,因为不需要处理paths和公式关系链等逻辑。
|
|
129
|
+
formulaVarItem.is_user_session_var = true;
|
|
130
|
+
vars.push(formulaVarItem);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (isUserVar) {
|
|
134
|
+
// 如果是user变量,则不需要计算quotes引用,但是其paths值需要正常记录下来
|
|
135
|
+
formulaVarItem.is_user_var = true;
|
|
136
|
+
// vars.push(formulaVarItem);
|
|
137
|
+
// return;
|
|
138
|
+
} else {
|
|
139
|
+
if (formulaVar.startsWith("$")) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`computeFormulaVarAndQuotes:The formula var '${formulaVar}' is starts with '$' but not a user session var that starts with $user.`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (!objectConfig) {
|
|
145
|
+
// 不是$user变量时,需要传入objectConfig参数
|
|
146
|
+
throw new Error(
|
|
147
|
+
`computeFormulaVarAndQuotes:The 'objectConfig' is required for the formula var '${formulaVar}'`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
let tempObjectConfig = objectConfig;
|
|
152
|
+
let i = -1;
|
|
153
|
+
for await (let varItem of varItems) {
|
|
154
|
+
i++;
|
|
155
|
+
// let i:number = _index as unknown as number;
|
|
156
|
+
// let varItem = varItems[i];
|
|
157
|
+
let tempFieldConfig: any;
|
|
158
|
+
const isUserKey = varItem === FormulaUserKey;
|
|
159
|
+
if (varItem === "_id") {
|
|
160
|
+
// 支持_id属性
|
|
161
|
+
tempFieldConfig = {
|
|
162
|
+
name: varItem,
|
|
163
|
+
type: "text",
|
|
164
|
+
};
|
|
165
|
+
} else if (isUserKey) {
|
|
166
|
+
// 如果是$user变量,则特殊处理下
|
|
167
|
+
tempFieldConfig = {
|
|
168
|
+
name: varItem,
|
|
169
|
+
type: "lookup",
|
|
170
|
+
reference_to: "space_users",
|
|
78
171
|
};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
172
|
+
} else {
|
|
173
|
+
tempFieldConfig = tempObjectConfig.fields[varItem];
|
|
174
|
+
}
|
|
175
|
+
if (!tempFieldConfig) {
|
|
176
|
+
// 不是对象上的字段,则直接退出,这里注意公式中引用零代码中的字段的话,公式中字段名需要手动加上__c后缀(因为用户填写的api名称不带__c,是内核会自动加后缀),否则会找不到
|
|
177
|
+
// throw new Error(`computeFormulaVarAndQuotes:Can't find the field '${varItem}' on the object '${tempObjectConfig.name}' for the formula var '${formulaVar}'`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
let isFormulaType = tempFieldConfig.type === "formula";
|
|
181
|
+
if (!isUserKey) {
|
|
182
|
+
let tempFieldFormulaVarPath: SteedosFormulaVarPathTypeConfig = {
|
|
183
|
+
field_name: varItem,
|
|
184
|
+
reference_from: tempObjectConfig.name,
|
|
185
|
+
};
|
|
186
|
+
if (isFormulaType) {
|
|
187
|
+
tempFieldFormulaVarPath.is_formula = true;
|
|
83
188
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
189
|
+
// 当是$user时,不需要把第一个path记录下来,只需要记录其后续的路径即可
|
|
190
|
+
paths.push(tempFieldFormulaVarPath);
|
|
191
|
+
}
|
|
192
|
+
if (!isUserVar) {
|
|
193
|
+
// $user变量不需要记录引用关系,因为没法确定每条record当时对应的当前用户ID是多少
|
|
194
|
+
// 自己可以引用自己,i大于0就是其他对象上的引用
|
|
195
|
+
let tempFieldFormulaQuote: SteedosFieldFormulaQuoteTypeConfig = {
|
|
196
|
+
object_name: tempObjectConfig.name,
|
|
197
|
+
field_name: tempFieldConfig.name,
|
|
198
|
+
};
|
|
199
|
+
if (i === 0 && i === varItems.length - 1) {
|
|
200
|
+
// 是引用的本对象上自身的字段,即自己引用自己
|
|
201
|
+
tempFieldFormulaQuote.is_own = true;
|
|
91
202
|
}
|
|
92
|
-
if(
|
|
93
|
-
|
|
94
|
-
formulaVarItem.is_user_session_var = true;
|
|
95
|
-
vars.push(formulaVarItem);
|
|
96
|
-
return;
|
|
203
|
+
if (isFormulaType) {
|
|
204
|
+
tempFieldFormulaQuote.is_formula = true;
|
|
97
205
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
206
|
+
this.addFieldFormulaQuotesConfig(tempFieldFormulaQuote, quotes);
|
|
207
|
+
}
|
|
208
|
+
if (
|
|
209
|
+
tempFieldConfig.type === "lookup" ||
|
|
210
|
+
tempFieldConfig.type === "master_detail"
|
|
211
|
+
) {
|
|
212
|
+
// 引用类型字段
|
|
213
|
+
// tempFieldConfig.multiple多选 saleforce 公式不支持,但是amis支持,所以这里不报错
|
|
214
|
+
// if (tempFieldConfig.multiple) {
|
|
215
|
+
// // TODO:暂时不支持数组的解析,见:公式字段中要实现lookup关联到数组字段的情况 #783
|
|
216
|
+
// throw new Error(
|
|
217
|
+
// `computeFormulaVarAndQuotes:The field '${tempFieldConfig.name}' for the formula var '${formulaVar}' is a multiple ${tempFieldConfig.type} type, it is not supported yet.`,
|
|
218
|
+
// );
|
|
219
|
+
// }
|
|
220
|
+
// if (i === varItems.length - 1) {
|
|
221
|
+
// // 引用类型字段后面必须继续引用该字段的相关属性,否则直接报错
|
|
222
|
+
// throw new Error(`computeFormulaVarAndQuotes:The field '${tempFieldConfig.name}' for the formula var '${formulaVar}' is a ${tempFieldConfig.type} type, so you must add more property after it.`);
|
|
223
|
+
// }
|
|
224
|
+
if (tempFieldConfig.reference_to_field && paths.length) {
|
|
225
|
+
paths[paths.length - 1].reference_to_field =
|
|
226
|
+
tempFieldConfig.reference_to_field;
|
|
103
227
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
228
|
+
} else {
|
|
229
|
+
// 不是引用类型字段,则直接退出
|
|
230
|
+
if (i < varItems.length - 1) {
|
|
231
|
+
// 考虑到 amis 公式,比如 ${name.length} 或 ${owner.name.length} 这种,name不是lookup字段,不需要进一步处理引用关系,但是也不能 throw new Error
|
|
232
|
+
// 此时 formulaVarItem.key 中应该是name 或 owner.name,而不是name.length 或 owner.name.length,即key中要把结尾的.length去除
|
|
233
|
+
formulaVarItem.key = formulaVarItem.key.replace(
|
|
234
|
+
new RegExp(`((.+)?${varItem})..+`),
|
|
235
|
+
"$1",
|
|
236
|
+
);
|
|
237
|
+
// // 提前找到非跨对象字段,说明varItems中后面没计算的变量是多余错误的,因为.后面肯定是跨对象引用出来的字段(除非是$user等全局变量)
|
|
238
|
+
// throw new Error(
|
|
239
|
+
// `computeFormulaVarAndQuotes:The field '${tempFieldConfig.name}' for the formula var '${formulaVar}' is not a lookup/master_detail type, so you can't get more property for it.`,
|
|
240
|
+
// );
|
|
112
241
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
field_name: varItem,
|
|
148
|
-
reference_from: tempObjectConfig.name
|
|
149
|
-
};
|
|
150
|
-
if (isFormulaType) {
|
|
151
|
-
tempFieldFormulaVarPath.is_formula = true;
|
|
152
|
-
}
|
|
153
|
-
// 当是$user时,不需要把第一个path记录下来,只需要记录其后续的路径即可
|
|
154
|
-
paths.push(tempFieldFormulaVarPath);
|
|
155
|
-
}
|
|
156
|
-
if (!isUserVar) {
|
|
157
|
-
// $user变量不需要记录引用关系,因为没法确定每条record当时对应的当前用户ID是多少
|
|
158
|
-
// 自己可以引用自己,i大于0就是其他对象上的引用
|
|
159
|
-
let tempFieldFormulaQuote: SteedosFieldFormulaQuoteTypeConfig = {
|
|
160
|
-
object_name: tempObjectConfig.name,
|
|
161
|
-
field_name: tempFieldConfig.name
|
|
162
|
-
};
|
|
163
|
-
if(i === 0 && i === varItems.length - 1){
|
|
164
|
-
// 是引用的本对象上自身的字段,即自己引用自己
|
|
165
|
-
tempFieldFormulaQuote.is_own = true;
|
|
166
|
-
}
|
|
167
|
-
if (isFormulaType) {
|
|
168
|
-
tempFieldFormulaQuote.is_formula = true;
|
|
169
|
-
}
|
|
170
|
-
this.addFieldFormulaQuotesConfig(tempFieldFormulaQuote, quotes);
|
|
171
|
-
}
|
|
172
|
-
if (tempFieldConfig.type === "lookup" || tempFieldConfig.type === "master_detail") {
|
|
173
|
-
// 引用类型字段
|
|
174
|
-
if (tempFieldConfig.multiple) {
|
|
175
|
-
// TODO:暂时不支持数组的解析,见:公式字段中要实现lookup关联到数组字段的情况 #783
|
|
176
|
-
throw new Error(`computeFormulaVarAndQuotes:The field '${tempFieldConfig.name}' for the formula var '${formulaVar}' is a multiple ${tempFieldConfig.type} type, it is not supported yet.`);
|
|
177
|
-
}
|
|
178
|
-
// if (i === varItems.length - 1) {
|
|
179
|
-
// // 引用类型字段后面必须继续引用该字段的相关属性,否则直接报错
|
|
180
|
-
// throw new Error(`computeFormulaVarAndQuotes:The field '${tempFieldConfig.name}' for the formula var '${formulaVar}' is a ${tempFieldConfig.type} type, so you must add more property after it.`);
|
|
181
|
-
// }
|
|
182
|
-
if(tempFieldConfig.reference_to_field && paths.length){
|
|
183
|
-
paths[paths.length - 1].reference_to_field = tempFieldConfig.reference_to_field;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
// 不是引用类型字段,则直接退出
|
|
188
|
-
if (i < varItems.length - 1) {
|
|
189
|
-
// 提前找到非跨对象字段,说明varItems中后面没计算的变量是多余错误的,因为.后面肯定是跨对象引用出来的字段(除非是$user等全局变量)
|
|
190
|
-
throw new Error(`computeFormulaVarAndQuotes:The field '${tempFieldConfig.name}' for the formula var '${formulaVar}' is not a lookup/master_detail type, so you can't get more property for it.`);
|
|
191
|
-
}
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
if (typeof tempFieldConfig.reference_to !== "string") {
|
|
195
|
-
// 暂时只支持reference_to为字符的情况,其他类型直接跳过
|
|
196
|
-
throw new Error(`Field ${tempFieldConfig.name} in formula ${formulaVar} does not define the "Reference Object" property or its "Reference Object" property is a function.`);
|
|
197
|
-
}
|
|
198
|
-
tempObjectConfig = await this.getObjectConfig(tempFieldConfig.reference_to);
|
|
199
|
-
if (!tempObjectConfig) {
|
|
200
|
-
// 没找到相关引用对象,直接退出
|
|
201
|
-
// 如果不是零代码对象,直接报错,否则直接返回,待相关零代码对象加载进来时,会再进入该函数
|
|
202
|
-
if(isCodeObject(tempFieldConfig.reference_to)){
|
|
203
|
-
throw new Error(`computeFormulaVarAndQuotes:Can't find the object reference_to '${tempFieldConfig.reference_to}' by the field '${tempFieldConfig.name}' for the formula var '${formulaVar}'`);
|
|
204
|
-
}
|
|
205
|
-
else{
|
|
206
|
-
// await this.broker.call('metadata.add', {key: this.cacherKey(getDynamicCalcMapCacherKey(tempFieldConfig.reference_to, objectConfig.name)), data: {objectApiName: objectConfig.name}}, {meta: {}})
|
|
207
|
-
await Register.add(this.broker, {key: this.cacherKey(getDynamicCalcMapCacherKey(tempFieldConfig.reference_to, objectConfig.name)), data: {objectApiName: objectConfig.name}}, {});
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
if (typeof tempFieldConfig.reference_to !== "string") {
|
|
245
|
+
// 暂时只支持reference_to为字符的情况,其他类型直接跳过
|
|
246
|
+
throw new Error(
|
|
247
|
+
`Field ${tempFieldConfig.name} in formula ${formulaVar} does not define the "Reference Object" property or its "Reference Object" property is a function.`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
tempObjectConfig = await this.getObjectConfig(
|
|
251
|
+
tempFieldConfig.reference_to,
|
|
252
|
+
);
|
|
253
|
+
if (!tempObjectConfig) {
|
|
254
|
+
// 没找到相关引用对象,直接退出
|
|
255
|
+
// 如果不是零代码对象,直接报错,否则直接返回,待相关零代码对象加载进来时,会再进入该函数
|
|
256
|
+
if (isCodeObject(tempFieldConfig.reference_to)) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`computeFormulaVarAndQuotes:Can't find the object reference_to '${tempFieldConfig.reference_to}' by the field '${tempFieldConfig.name}' for the formula var '${formulaVar}'`,
|
|
259
|
+
);
|
|
260
|
+
} else {
|
|
261
|
+
// await this.broker.call('metadata.add', {key: this.cacherKey(getDynamicCalcMapCacherKey(tempFieldConfig.reference_to, objectConfig.name)), data: {objectApiName: objectConfig.name}}, {meta: {}})
|
|
262
|
+
await Register.add(
|
|
263
|
+
this.broker,
|
|
264
|
+
{
|
|
265
|
+
key: this.cacherKey(
|
|
266
|
+
getDynamicCalcMapCacherKey(
|
|
267
|
+
tempFieldConfig.reference_to,
|
|
268
|
+
objectConfig.name,
|
|
269
|
+
),
|
|
270
|
+
),
|
|
271
|
+
data: { objectApiName: objectConfig.name },
|
|
272
|
+
},
|
|
273
|
+
{},
|
|
274
|
+
);
|
|
275
|
+
return;
|
|
211
276
|
}
|
|
212
|
-
|
|
277
|
+
}
|
|
213
278
|
}
|
|
279
|
+
vars.push(formulaVarItem);
|
|
280
|
+
}
|
|
214
281
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
for await(const formulaVar of formulaVars) {
|
|
231
|
-
await this.computeFormulaVarAndQuotes(formulaVar, objectConfig, quotes, vars);
|
|
232
|
-
}
|
|
233
|
-
return { quotes, vars };
|
|
282
|
+
/**
|
|
283
|
+
* 根据公式内容,取出其中{}中的变量,返回计算后的公式引用集合
|
|
284
|
+
* @param formula
|
|
285
|
+
* @param fieldConfig
|
|
286
|
+
*/
|
|
287
|
+
async computeFormulaVarsAndQuotes(formula: string, objectConfig: any) {
|
|
288
|
+
let quotes: Array<SteedosFieldFormulaQuoteTypeConfig> = [];
|
|
289
|
+
let vars: Array<SteedosFormulaVarTypeConfig> = [];
|
|
290
|
+
let formulaVars: any = [];
|
|
291
|
+
try {
|
|
292
|
+
formulaVars = pickFormulaVars(formula);
|
|
293
|
+
} catch (ex) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`pickFormulaVars:Catch an error "${ex}" while pick vars from the formula "${formula}" for "${JSON.stringify(objectConfig)}"`,
|
|
296
|
+
);
|
|
234
297
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
298
|
+
for await (const formulaVar of formulaVars) {
|
|
299
|
+
await this.computeFormulaVarAndQuotes(
|
|
300
|
+
formulaVar,
|
|
301
|
+
objectConfig,
|
|
302
|
+
quotes,
|
|
303
|
+
vars,
|
|
304
|
+
);
|
|
238
305
|
}
|
|
306
|
+
return { quotes, vars };
|
|
307
|
+
}
|
|
239
308
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
let formulaConfig: SteedosFieldFormulaTypeConfig = {
|
|
244
|
-
_id: `${objectConfig.name}.${fieldConfig.name}`,
|
|
245
|
-
object_name: objectConfig.name,
|
|
246
|
-
field_name: fieldConfig.name,
|
|
247
|
-
formula: formula,
|
|
248
|
-
data_type: fieldConfig.data_type,
|
|
249
|
-
formula_blank_value: <SteedosFormulaBlankValue>fieldConfig.formula_blank_value,
|
|
250
|
-
quotes: result.quotes,
|
|
251
|
-
vars: result.vars
|
|
252
|
-
};
|
|
309
|
+
cacherKey(APIName: string): string {
|
|
310
|
+
return `$steedos.#formula.${APIName}`;
|
|
311
|
+
}
|
|
253
312
|
|
|
254
|
-
|
|
255
|
-
|
|
313
|
+
async getObjectFieldFormulaConfig(fieldConfig: any, objectConfig: any) {
|
|
314
|
+
const formula = fieldConfig.formula;
|
|
315
|
+
let result = await this.computeFormulaVarsAndQuotes(formula, objectConfig);
|
|
316
|
+
let formulaConfig: SteedosFieldFormulaTypeConfig = {
|
|
317
|
+
_id: `${objectConfig.name}.${fieldConfig.name}`,
|
|
318
|
+
object_name: objectConfig.name,
|
|
319
|
+
field_name: fieldConfig.name,
|
|
320
|
+
formula: formula,
|
|
321
|
+
data_type: fieldConfig.data_type,
|
|
322
|
+
formula_blank_value: <SteedosFormulaBlankValue>(
|
|
323
|
+
fieldConfig.formula_blank_value
|
|
324
|
+
),
|
|
325
|
+
quotes: result.quotes,
|
|
326
|
+
vars: result.vars,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
return formulaConfig;
|
|
330
|
+
}
|
|
256
331
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
332
|
+
async getObjectFieldsFormulaConfig(config: any, datasource: string) {
|
|
333
|
+
const objectFieldsFormulaConfig = [];
|
|
334
|
+
for await (const field of _.values(config.fields)) {
|
|
335
|
+
// console.log('key', key);
|
|
336
|
+
// const field = config.fields[key]
|
|
337
|
+
if (field.type === "formula") {
|
|
338
|
+
// if(datasource !== "meteor" && datasource !== "default"){
|
|
339
|
+
// throw new Error(`The type of the field '${field.name}' on the object '${config.name}' can't be 'formula', because it is not in the default datasource.`);
|
|
340
|
+
// }
|
|
341
|
+
try {
|
|
342
|
+
// 这里一定要加try catch,否则某个字段报错后,后续其他字段及其他对象就再也没有正常加载了
|
|
343
|
+
const fieldFormulaConfig = await this.getObjectFieldFormulaConfig(
|
|
344
|
+
clone(field),
|
|
345
|
+
config,
|
|
346
|
+
);
|
|
347
|
+
objectFieldsFormulaConfig.push(fieldFormulaConfig);
|
|
348
|
+
} catch (error) {
|
|
349
|
+
console.error(error);
|
|
274
350
|
}
|
|
275
|
-
|
|
351
|
+
}
|
|
276
352
|
}
|
|
353
|
+
return objectFieldsFormulaConfig;
|
|
354
|
+
}
|
|
277
355
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
})
|
|
287
|
-
}
|
|
288
|
-
checkInfiniteLoop(data.key)
|
|
289
|
-
if(_.find(refs, function(ref){return ref === data.value})){
|
|
290
|
-
throw new Error(`Infinite Loop: ${JSON.stringify(data)}`);
|
|
356
|
+
checkRefMapData(maps, data) {
|
|
357
|
+
let refs = [];
|
|
358
|
+
function checkInfiniteLoop(value) {
|
|
359
|
+
_.each(maps, function (item) {
|
|
360
|
+
if (item.value === value) {
|
|
361
|
+
refs.push(item.key);
|
|
362
|
+
checkInfiniteLoop(item.key);
|
|
291
363
|
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
checkInfiniteLoop(data.key);
|
|
367
|
+
if (
|
|
368
|
+
_.find(refs, function (ref) {
|
|
369
|
+
return ref === data.value;
|
|
370
|
+
})
|
|
371
|
+
) {
|
|
372
|
+
throw new Error(`Infinite Loop: ${JSON.stringify(data)}`);
|
|
292
373
|
}
|
|
374
|
+
}
|
|
293
375
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
376
|
+
async getFormulaReferenceMaps(broker: any): Promise<any> {
|
|
377
|
+
const maps = [];
|
|
378
|
+
// let records = (await broker.call('metadata.filter', { key: this.cacherKey(`${refMapName}.*`) }, { meta: {} })) || {};
|
|
379
|
+
let records: any =
|
|
380
|
+
(await Register.filter(this.broker, this.cacherKey(`${refMapName}.*`))) ||
|
|
381
|
+
{};
|
|
382
|
+
for await (const item of records) {
|
|
383
|
+
const metadata = item?.metadata;
|
|
384
|
+
if (metadata) {
|
|
385
|
+
maps.push(item);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return { metadata: maps };
|
|
389
|
+
}
|
|
390
|
+
async addFormulaReferenceMaps(broker: any, key: string, value: string) {
|
|
391
|
+
let { metadata } = (await this.getFormulaReferenceMaps(broker)) || [];
|
|
392
|
+
let maps = [];
|
|
393
|
+
if (metadata) {
|
|
394
|
+
maps = metadata;
|
|
305
395
|
}
|
|
306
|
-
async addFormulaReferenceMaps(broker: any, key: string, value: string){
|
|
307
|
-
let { metadata } = (await this.getFormulaReferenceMaps(broker)) || [];
|
|
308
|
-
let maps = [];
|
|
309
|
-
if(metadata){
|
|
310
|
-
maps = metadata;
|
|
311
|
-
}
|
|
312
396
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
397
|
+
let data = {
|
|
398
|
+
key: key,
|
|
399
|
+
value: value,
|
|
400
|
+
};
|
|
317
401
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
402
|
+
// //checkData
|
|
403
|
+
this.checkRefMapData(maps, data);
|
|
404
|
+
// maps.push(data);
|
|
405
|
+
await Register.add(
|
|
406
|
+
this.broker,
|
|
407
|
+
{ key: this.cacherKey(`${refMapName}.${key}.${value}`), data: data },
|
|
408
|
+
{},
|
|
409
|
+
);
|
|
410
|
+
// await broker.call('metadata.add', {key: this.cacherKey(`${refMapName}.${key}.${value}`), data: data}, {meta: {}})
|
|
411
|
+
}
|
|
324
412
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
413
|
+
async addFormulaMetadata(config: any, datasource: string) {
|
|
414
|
+
const objectFieldsFormulaConfig = await this.getObjectFieldsFormulaConfig(
|
|
415
|
+
config,
|
|
416
|
+
datasource,
|
|
417
|
+
);
|
|
418
|
+
const objectFormulas = await this.filter({
|
|
419
|
+
params: { objectApiName: config.name },
|
|
420
|
+
});
|
|
421
|
+
const formulaFieldsName = _.map(objectFormulas, "field_name");
|
|
422
|
+
const objectFields = _.map(config.fields, "name");
|
|
423
|
+
const diff = _.difference(formulaFieldsName, objectFields);
|
|
424
|
+
for await (const fieldFormula of objectFieldsFormulaConfig) {
|
|
425
|
+
for await (const quote of fieldFormula.quotes) {
|
|
426
|
+
await this.addFormulaReferenceMaps(
|
|
427
|
+
this.broker,
|
|
428
|
+
`${fieldFormula._id}`,
|
|
429
|
+
`${quote.object_name}.${quote.field_name}`,
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
await Register.add(
|
|
433
|
+
this.broker,
|
|
434
|
+
{ key: this.cacherKey(fieldFormula._id), data: fieldFormula },
|
|
435
|
+
{},
|
|
436
|
+
);
|
|
437
|
+
// await this.broker.call('metadata.add', {key: this.cacherKey(fieldFormula._id), data: fieldFormula}, {meta: {}})
|
|
344
438
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
439
|
+
for (const deletedFieldName of diff) {
|
|
440
|
+
if (deletedFieldName) {
|
|
441
|
+
await Register.delete(
|
|
442
|
+
this.broker,
|
|
443
|
+
this.cacherKey(`${config.name}.${deletedFieldName}`),
|
|
444
|
+
);
|
|
445
|
+
}
|
|
349
446
|
}
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
350
449
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if(recalcObjectConfig){
|
|
359
|
-
recalcObjects[recalcObjectApiName] = recalcObjectConfig
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
for (const objectName in recalcObjects) {
|
|
365
|
-
if (Object.prototype.hasOwnProperty.call(recalcObjects, objectName)) {
|
|
366
|
-
const recalcObjectConfig = recalcObjects[objectName];
|
|
367
|
-
await this.addFormulaMetadata(recalcObjectConfig, recalcObjectConfig.datasource);
|
|
368
|
-
await Register.delete(this.broker, this.cacherKey(getDynamicCalcMapCacherKey(objectConfig.name, objectName)))
|
|
369
|
-
// await this.broker.call('metadata.delete', {key: this.cacherKey(getDynamicCalcMapCacherKey(objectConfig.name, objectName))}, {meta: {}})
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
450
|
+
async getObjectDynamicCalcFormulaMap(objectApiName) {
|
|
451
|
+
return await Register.filter(
|
|
452
|
+
this.broker,
|
|
453
|
+
this.cacherKey(getDynamicCalcMapCacherKey(objectApiName, "*")),
|
|
454
|
+
);
|
|
455
|
+
// return await this.broker.call('metadata.filter', {key: this.cacherKey(getDynamicCalcMapCacherKey(objectApiName, '*'))}, {meta: {}});
|
|
456
|
+
}
|
|
373
457
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
458
|
+
async recalcObjectsFormulaMap(objectConfig) {
|
|
459
|
+
const recalcObjects = {};
|
|
460
|
+
const dynamicCalcFormulaMap = await this.getObjectDynamicCalcFormulaMap(
|
|
461
|
+
objectConfig.name,
|
|
462
|
+
);
|
|
463
|
+
for await (const item of dynamicCalcFormulaMap) {
|
|
464
|
+
const recalcObjectApiName = item?.metadata?.objectApiName;
|
|
465
|
+
if (recalcObjectApiName) {
|
|
466
|
+
const recalcObjectConfig =
|
|
467
|
+
await this.getObjectConfig(recalcObjectApiName);
|
|
468
|
+
if (recalcObjectConfig) {
|
|
469
|
+
recalcObjects[recalcObjectApiName] = recalcObjectConfig;
|
|
380
470
|
}
|
|
381
|
-
|
|
471
|
+
}
|
|
382
472
|
}
|
|
383
473
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
return configs;
|
|
474
|
+
for (const objectName in recalcObjects) {
|
|
475
|
+
if (Object.prototype.hasOwnProperty.call(recalcObjects, objectName)) {
|
|
476
|
+
const recalcObjectConfig = recalcObjects[objectName];
|
|
477
|
+
await this.addFormulaMetadata(
|
|
478
|
+
recalcObjectConfig,
|
|
479
|
+
recalcObjectConfig.datasource,
|
|
480
|
+
);
|
|
481
|
+
await Register.delete(
|
|
482
|
+
this.broker,
|
|
483
|
+
this.cacherKey(
|
|
484
|
+
getDynamicCalcMapCacherKey(objectConfig.name, objectName),
|
|
485
|
+
),
|
|
486
|
+
);
|
|
487
|
+
// await this.broker.call('metadata.delete', {key: this.cacherKey(getDynamicCalcMapCacherKey(objectConfig.name, objectName))}, {meta: {}})
|
|
488
|
+
}
|
|
400
489
|
}
|
|
490
|
+
}
|
|
401
491
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
492
|
+
async add(objectConfig) {
|
|
493
|
+
try {
|
|
494
|
+
await this.addFormulaMetadata(objectConfig, objectConfig.datasource);
|
|
495
|
+
return true;
|
|
496
|
+
} catch (error) {
|
|
497
|
+
this.broker.logger.error(error);
|
|
408
498
|
}
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
409
501
|
|
|
410
|
-
|
|
411
|
-
|
|
502
|
+
async filter(ctx) {
|
|
503
|
+
let { objectApiName, fieldApiName } = ctx.params;
|
|
504
|
+
if (!objectApiName) {
|
|
505
|
+
objectApiName = "*";
|
|
506
|
+
}
|
|
507
|
+
if (!fieldApiName) {
|
|
508
|
+
fieldApiName = "*";
|
|
509
|
+
}
|
|
510
|
+
const key = this.cacherKey(`${objectApiName}.${fieldApiName}`);
|
|
511
|
+
const configs = [];
|
|
512
|
+
// const res = await this.broker.call('metadata.filter', {key: key}, {meta: {}})
|
|
513
|
+
const res = await Register.filter(this.broker, key);
|
|
514
|
+
_.forEach(res, (item) => {
|
|
515
|
+
configs.push(item?.metadata);
|
|
516
|
+
});
|
|
517
|
+
return configs;
|
|
518
|
+
}
|
|
412
519
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
520
|
+
async get(ctx) {
|
|
521
|
+
let { fieldApiFullName } = ctx.params;
|
|
522
|
+
const key = this.cacherKey(fieldApiFullName);
|
|
523
|
+
// const res = await this.broker.call('metadata.get', {key: key}, {meta: {}});
|
|
524
|
+
const res = await Register.get(this.broker, key);
|
|
525
|
+
return res?.metadata;
|
|
526
|
+
}
|
|
419
527
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
key: fieldFormula._id,
|
|
423
|
-
value: `${quote.object_name}.${quote.field_name}`
|
|
424
|
-
}
|
|
425
|
-
this.checkRefMapData(maps, data);
|
|
426
|
-
}
|
|
528
|
+
async verifyObjectFieldFormulaConfig(ctx) {
|
|
529
|
+
let { fieldConfig, objectConfig } = ctx.params;
|
|
427
530
|
|
|
428
|
-
|
|
531
|
+
const fieldFormula = await this.getObjectFieldFormulaConfig(
|
|
532
|
+
fieldConfig,
|
|
533
|
+
objectConfig,
|
|
534
|
+
);
|
|
535
|
+
let { metadata } = (await this.getFormulaReferenceMaps(ctx.broker)) || [];
|
|
536
|
+
let maps = [];
|
|
537
|
+
if (metadata) {
|
|
538
|
+
maps = metadata;
|
|
429
539
|
}
|
|
430
540
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
541
|
+
for await (const quote of fieldFormula.quotes) {
|
|
542
|
+
let data = {
|
|
543
|
+
key: fieldFormula._id,
|
|
544
|
+
value: `${quote.object_name}.${quote.field_name}`,
|
|
545
|
+
};
|
|
546
|
+
this.checkRefMapData(maps, data);
|
|
434
547
|
}
|
|
548
|
+
|
|
549
|
+
return await this.getObjectFieldFormulaConfig(fieldConfig, objectConfig);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
async getFormulaVarsAndQuotes(ctx) {
|
|
553
|
+
let { formula, objectConfig } = ctx.params;
|
|
554
|
+
return await this.computeFormulaVarsAndQuotes(formula, objectConfig);
|
|
555
|
+
}
|
|
435
556
|
}
|