@steedos-labs/plugin-workflow 3.0.14 → 3.0.16
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/README_TEMPLATE.md +222 -0
- package/convert-templates.js +51 -0
- package/main/default/objects/flows/flows.object.yml +16 -0
- package/main/default/objects/instance_tasks/buttons/instance_new.button.yml +9 -9
- package/main/default/objects/instances/buttons/instance_delete_many.button.yml +1 -1
- package/main/default/pages/flow_selector.page.amis.json +1 -1
- package/main/default/pages/page_instance_print.page.amis.json +33 -0
- package/main/default/services/flows.service.js +3 -2
- package/package.json +4 -2
- package/public/workflow/index.css +107 -2
- package/run.js +388 -0
- package/src/rests/index.js +1 -0
- package/src/rests/migrateTemplates.js +154 -0
- package/src/util/templateConverter.js +335 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
|
|
3
|
+
// Helper to find field definition
|
|
4
|
+
function findFieldDef(fields, fieldName) {
|
|
5
|
+
if (!fields) return null;
|
|
6
|
+
|
|
7
|
+
// Handle array fields (e.g. 'line_items.$.description')
|
|
8
|
+
const cleanName = fieldName.replace(/\.\$\./g, '.').replace(/\.\$$/, '');
|
|
9
|
+
|
|
10
|
+
// Direct match
|
|
11
|
+
if (fields[cleanName]) return fields[cleanName];
|
|
12
|
+
|
|
13
|
+
// Search in nested fields
|
|
14
|
+
for (const key in fields) {
|
|
15
|
+
if (fields[key].name === cleanName) return fields[key];
|
|
16
|
+
if (fields[key].code === cleanName) return fields[key];
|
|
17
|
+
// If field is inside a grid/object
|
|
18
|
+
if (key.includes('.') && key.endsWith(cleanName)) return fields[key];
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getFieldLabel(fieldDef, fieldName) {
|
|
24
|
+
if (fieldDef && fieldDef.label) {
|
|
25
|
+
return fieldDef.label;
|
|
26
|
+
}
|
|
27
|
+
// Fallback: try to derive from name or just return name
|
|
28
|
+
return fieldName;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function fieldToAmis(fieldName, fieldDef, schema, isColumn = false, isPrint = false) {
|
|
32
|
+
// Handle section type special case
|
|
33
|
+
if (fieldDef && fieldDef.type === 'section') {
|
|
34
|
+
const displayName = fieldDef.name || fieldDef.code || '';
|
|
35
|
+
return `<span class="antd-TplField"><span><div class="font-bold">${displayName}<span class="antd-Form-star">*</span><pre class="font-normal"></pre></div></span></span>`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let finalName = fieldName;
|
|
39
|
+
let processedFormula = null;
|
|
40
|
+
if (fieldDef) {
|
|
41
|
+
if (fieldDef.code) {
|
|
42
|
+
finalName = fieldDef.code;
|
|
43
|
+
} else if (fieldDef.name) {
|
|
44
|
+
finalName = fieldDef.name;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Print Template Simplification
|
|
48
|
+
if (isPrint && !isColumn) {
|
|
49
|
+
const type = fieldDef.type;
|
|
50
|
+
if (['text', 'input', 'textarea', 'url', 'email', 'number', 'currency', 'percent', 'password', 'date', 'datetime', 'boolean', 'select', 'lookup', 'master_detail', 'radio', 'checkbox'].includes(type) || fieldDef.formula) {
|
|
51
|
+
return `{{${finalName}}}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const config = {
|
|
57
|
+
name: finalName,
|
|
58
|
+
id: `u:${Math.random().toString(36).substr(2, 9)}`
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (isColumn) {
|
|
62
|
+
if (schema && schema.atts && schema.atts.label) {
|
|
63
|
+
config.label = schema.atts.label;
|
|
64
|
+
} else {
|
|
65
|
+
config.label = getFieldLabel(fieldDef, fieldName);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
config.label = false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Type mapping
|
|
72
|
+
let type = 'input-text'; // default
|
|
73
|
+
if (fieldDef) {
|
|
74
|
+
switch (fieldDef.type) {
|
|
75
|
+
case 'text':
|
|
76
|
+
case 'input':
|
|
77
|
+
type = 'input-text';
|
|
78
|
+
break;
|
|
79
|
+
case 'textarea': type = 'textarea'; break;
|
|
80
|
+
case 'number': type = 'input-number'; break;
|
|
81
|
+
case 'date': type = 'input-date'; break;
|
|
82
|
+
case 'datetime': type = 'input-datetime'; break;
|
|
83
|
+
case 'boolean': type = 'checkbox'; break;
|
|
84
|
+
case 'radio': type = 'radios'; break;
|
|
85
|
+
case 'checkbox': type = 'checkboxes'; break;
|
|
86
|
+
case 'select': type = 'select'; break;
|
|
87
|
+
case 'lookup': type = 'select'; break; // Simplified for now
|
|
88
|
+
case 'table':
|
|
89
|
+
type = 'steedos-input-table';
|
|
90
|
+
config.columnsTogglable = false;
|
|
91
|
+
if (!isColumn) {
|
|
92
|
+
const editableCondition = "${record.step.permissions['" + config.name + "'] == 'editable'}";
|
|
93
|
+
config.addableOn = editableCondition;
|
|
94
|
+
config.editableOn = editableCondition;
|
|
95
|
+
config.removableOn = editableCondition;
|
|
96
|
+
}
|
|
97
|
+
if (fieldDef.fields) {
|
|
98
|
+
config.fields = fieldDef.fields.map(f => {
|
|
99
|
+
return fieldToAmis(f.name || f.code, f, null, true);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (fieldDef.options) {
|
|
106
|
+
let options = fieldDef.options;
|
|
107
|
+
if (typeof options === 'string') {
|
|
108
|
+
options = options.split('\n').map(o => o.trim()).filter(o => o);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (Array.isArray(options)) {
|
|
112
|
+
config.options = options.map(opt => {
|
|
113
|
+
if (typeof opt === 'string') {
|
|
114
|
+
return { label: opt, value: opt };
|
|
115
|
+
}
|
|
116
|
+
if (typeof opt === 'object') {
|
|
117
|
+
return {
|
|
118
|
+
label: opt.label || opt.name || opt.value,
|
|
119
|
+
value: opt.value || opt.code || opt.name
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return opt;
|
|
123
|
+
});
|
|
124
|
+
} else {
|
|
125
|
+
config.options = options;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (fieldDef.required) {
|
|
129
|
+
config.required = true;
|
|
130
|
+
}
|
|
131
|
+
if (fieldDef.precision) {
|
|
132
|
+
config.precision = fieldDef.precision;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (fieldDef.formula && (type === 'input-text' || type === 'input-number' || type === 'textarea')) {
|
|
136
|
+
let formula = fieldDef.formula;
|
|
137
|
+
const replaceCallback = (match, p1) => {
|
|
138
|
+
const cleanP1 = p1.replace(/(/g, '_').replace(/)/g, '');
|
|
139
|
+
if (type === 'input-number') {
|
|
140
|
+
return `(${cleanP1} || 0)`;
|
|
141
|
+
} else {
|
|
142
|
+
return `(${cleanP1} || "")`;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
formula = formula.replace(/\{([^}]+)\}/g, replaceCallback);
|
|
146
|
+
|
|
147
|
+
processedFormula = "${" + formula + "}";
|
|
148
|
+
|
|
149
|
+
if (isColumn) {
|
|
150
|
+
config.value = processedFormula;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
config.type = type;
|
|
156
|
+
|
|
157
|
+
if (!isColumn) {
|
|
158
|
+
if (processedFormula) {
|
|
159
|
+
config.static = true;
|
|
160
|
+
const formulaComp = {
|
|
161
|
+
name: config.name,
|
|
162
|
+
id: config.id,
|
|
163
|
+
label: false,
|
|
164
|
+
formula: processedFormula,
|
|
165
|
+
type: "formula"
|
|
166
|
+
};
|
|
167
|
+
return [config, formulaComp];
|
|
168
|
+
} else {
|
|
169
|
+
config.staticOn = "${record.step.permissions['" + config.name + "'] != 'editable'}";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return config;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function parseArgs(args) {
|
|
177
|
+
const argMap = {};
|
|
178
|
+
const re = /(\w+)\s*=\s*(["'])(.*?)\2|(\w+)\s*=\s*([^"'\s]+)/g;
|
|
179
|
+
let m;
|
|
180
|
+
while ((m = re.exec(args)) !== null) {
|
|
181
|
+
if (m[1]) argMap[m[1]] = m[3];
|
|
182
|
+
else argMap[m[4]] = m[5];
|
|
183
|
+
}
|
|
184
|
+
return argMap;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 转换模板内容
|
|
189
|
+
* @param {string} content 原始Blaze模板
|
|
190
|
+
* @param {object} fields 表单字段定义
|
|
191
|
+
* @param {boolean} isPrint 是否为打印模板
|
|
192
|
+
*/
|
|
193
|
+
function convertTemplate(content, fields, isPrint) {
|
|
194
|
+
if (!content) return content;
|
|
195
|
+
let newContent = content;
|
|
196
|
+
|
|
197
|
+
// Phase 1: Blaze -> Liquid
|
|
198
|
+
newContent = newContent.replace(/\{\{![\s\S]*?\}\}/g, '');
|
|
199
|
+
newContent = newContent.replace(/\{\{#if\s+([^}]+)\}\}/g, '{% if $1 %}');
|
|
200
|
+
newContent = newContent.replace(/\{\{else\}\}/g, '{% else %}');
|
|
201
|
+
newContent = newContent.replace(/\{\{\/if\}\}/g, '{% endif %}');
|
|
202
|
+
newContent = newContent.replace(/\{\{#unless\s+([^}]+)\}\}/g, '{% unless $1 %}');
|
|
203
|
+
newContent = newContent.replace(/\{\{\/unless\}\}/g, '{% endunless %}');
|
|
204
|
+
newContent = newContent.replace(/\{\{#each\s+([^}]+)\}\}/g, '{% for item in $1 %}');
|
|
205
|
+
newContent = newContent.replace(/\{\{\/each\}\}/g, '{% endfor %}');
|
|
206
|
+
newContent = newContent.replace(/\{\{\{\s*([^}]+)\s*\}\}\}/g, '{{ $1 | raw }}');
|
|
207
|
+
|
|
208
|
+
// Remove table-page-footer from all templates
|
|
209
|
+
newContent = newContent.replace(/<table[^>]*class="[^"]*table-page-footer[\s\S]*?<\/table>/g, '');
|
|
210
|
+
|
|
211
|
+
// Phase 2: afFormGroup -> AMIS
|
|
212
|
+
// Remove table-page-footer (Retaining table-page-title based on requirement)
|
|
213
|
+
if (isPrint) {
|
|
214
|
+
newContent = newContent.replace(/<tr([^>]*)style=(["'])([^"']*?height:\s*0px[^"']*?)\2([^>]*)>/gi, '<tr$1style=$2$3; display: none;$2$4>');
|
|
215
|
+
|
|
216
|
+
if (!newContent.includes('border: 2px solid black;')) {
|
|
217
|
+
const tableCSS = `
|
|
218
|
+
<style>
|
|
219
|
+
.form-table {
|
|
220
|
+
border-collapse: collapse;
|
|
221
|
+
border: 2px solid black;
|
|
222
|
+
}
|
|
223
|
+
.form-table th,
|
|
224
|
+
.form-table td {
|
|
225
|
+
border: 1px solid black;
|
|
226
|
+
padding: 4px 6px;
|
|
227
|
+
}
|
|
228
|
+
</style>
|
|
229
|
+
`;
|
|
230
|
+
if (newContent.includes('<style>')) {
|
|
231
|
+
newContent = newContent.replace('<style>', tableCSS + '\n<style>');
|
|
232
|
+
} else {
|
|
233
|
+
newContent = tableCSS + '\n' + newContent;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// --- Sub-phase 2a: Convert TD wrapped afFormGroup tags ---
|
|
239
|
+
newContent = newContent.replace(/<td([^>]*)>(\s*)\{\{>\s*afFormGroup\s+([^}]+)\}\}(\s*)<\/td>/g, (match, tdAttrs, space1, args, space2) => {
|
|
240
|
+
const argMap = parseArgs(args);
|
|
241
|
+
const fieldName = argMap.name;
|
|
242
|
+
if (!fieldName) return match;
|
|
243
|
+
|
|
244
|
+
const fieldDef = findFieldDef(fields, fieldName);
|
|
245
|
+
const amisSchema = fieldToAmis(fieldName, fieldDef, { atts: argMap }, false, isPrint);
|
|
246
|
+
|
|
247
|
+
let contentInner = '';
|
|
248
|
+
if (typeof amisSchema === 'string') {
|
|
249
|
+
contentInner = amisSchema;
|
|
250
|
+
} else if (Array.isArray(amisSchema)) {
|
|
251
|
+
contentInner = amisSchema.map(s => `{% amis %}\n${JSON.stringify(s, null, 2)}\n{% endamis %}`).join('\n ');
|
|
252
|
+
} else {
|
|
253
|
+
contentInner = `{% amis %}\n${JSON.stringify(amisSchema, null, 2)}\n{% endamis %}`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (fieldDef && fieldDef.type === 'section') {
|
|
257
|
+
let newAttrs = tdAttrs || ' ';
|
|
258
|
+
if (newAttrs.includes('style="')) {
|
|
259
|
+
newAttrs = newAttrs.replace('style="', 'style="background: rgb(241, 241, 241); ');
|
|
260
|
+
} else {
|
|
261
|
+
newAttrs = newAttrs + ' style="background: rgb(241, 241, 241);"';
|
|
262
|
+
}
|
|
263
|
+
return `<td${newAttrs}>${space1}${contentInner}${space2}</td>`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return `<td${tdAttrs}>${space1}${contentInner}${space2}</td>`;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// --- Sub-phase 2b: Convert remaining raw afFormGroup tags ---
|
|
270
|
+
newContent = newContent.replace(/\{\{>\s*afFormGroup\s+([^}]+)\}\}/g, (match, args) => {
|
|
271
|
+
const argMap = parseArgs(args);
|
|
272
|
+
const fieldName = argMap.name;
|
|
273
|
+
if (!fieldName) return match;
|
|
274
|
+
|
|
275
|
+
const fieldDef = findFieldDef(fields, fieldName);
|
|
276
|
+
const amisSchema = fieldToAmis(fieldName, fieldDef, { atts: argMap }, false, isPrint);
|
|
277
|
+
|
|
278
|
+
if (typeof amisSchema === 'string') {
|
|
279
|
+
return amisSchema;
|
|
280
|
+
}
|
|
281
|
+
if (Array.isArray(amisSchema)) {
|
|
282
|
+
return amisSchema.map(s => `{% amis %}\n${JSON.stringify(s, null, 2)}\n{% endamis %}`).join('\n ');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return `{% amis %}\n${JSON.stringify(amisSchema, null, 2)}\n{% endamis %}`;
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// --- Sub-phase 2c: Convert afFieldLabelText ---
|
|
289
|
+
newContent = newContent.replace(/\{\{afFieldLabelText\s+name=["'](.+?)["']\}\}/g, '$1');
|
|
290
|
+
|
|
291
|
+
// --- Sub-phase 2d: Convert instanceSignText ---
|
|
292
|
+
newContent = newContent.replace(/\{\{>\s*instanceSignText\s+([^}]+)\}\}/g, (match, args) => {
|
|
293
|
+
const argMap = parseArgs(args);
|
|
294
|
+
const fieldName = argMap.name;
|
|
295
|
+
if (!fieldName) return match;
|
|
296
|
+
|
|
297
|
+
const fieldDef = findFieldDef(fields, fieldName);
|
|
298
|
+
let stepName = "";
|
|
299
|
+
if (fieldDef && fieldDef.formula) {
|
|
300
|
+
const formulaMatch = fieldDef.formula.match(/signature\.traces\.([^}]+)/);
|
|
301
|
+
if (formulaMatch) {
|
|
302
|
+
stepName = formulaMatch[1].replace('}', '');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const randomSuffix = Math.random().toString(36).substr(2, 6);
|
|
307
|
+
const amisSchema = {
|
|
308
|
+
"type": "sfield-approvalcomments",
|
|
309
|
+
"config": {
|
|
310
|
+
"type": "approval_comments",
|
|
311
|
+
"label": false,
|
|
312
|
+
"amis": {
|
|
313
|
+
"mode": "horizontal",
|
|
314
|
+
"name": fieldName,
|
|
315
|
+
"id": `u:${fieldName}`
|
|
316
|
+
},
|
|
317
|
+
"name": `confirm_${randomSuffix}`,
|
|
318
|
+
"steps": [
|
|
319
|
+
{
|
|
320
|
+
"name": stepName
|
|
321
|
+
}
|
|
322
|
+
]
|
|
323
|
+
},
|
|
324
|
+
"id": `u:sfield-approvalcomments-${randomSuffix}`
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
return `{% amis %}\n${JSON.stringify(amisSchema, null, 2)}\n{% endamis %}`;
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
return newContent;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
module.exports = {
|
|
334
|
+
convertTemplate
|
|
335
|
+
};
|