@steedos/service-metadata-objects 3.0.0-beta.7 → 3.0.0-beta.70

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