@steedos-labs/plugin-workflow 3.0.85 → 3.0.87
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.md +19 -1
- package/designer/dist/amis-renderer/amis-renderer.css +1 -1
- package/designer/dist/amis-renderer/amis-renderer.js +1 -1
- package/designer/dist/assets/index-ClEul34C.css +1 -0
- package/designer/dist/assets/index-DWcq4xcG.js +1006 -0
- package/designer/dist/index.html +2 -2
- package/main/default/client/flow2_render.client.js +1 -1
- package/main/default/manager/instance_number_rules.js +246 -2
- package/main/default/manager/uuflow_manager.js +26 -4
- package/main/default/pages/page_instance_print.page.amis.json +2 -2
- package/main/default/routes/api_auto_number.router.js +11 -178
- package/main/default/routes/api_workflow_chart.router.js +16 -3
- package/main/default/routes/api_workflow_instance_forward.router.js +13 -1
- package/main/default/routes/api_workflow_next_step_users.router.js +4 -3
- package/main/default/routes/buildFormDesignSystemPrompt.js +4 -3
- package/main/default/server/ejs/export_instances.ejs +87 -211
- package/package.json +1 -1
- package/public/amis-renderer/amis-renderer.css +1 -1
- package/public/amis-renderer/amis-renderer.js +1 -1
- package/designer/dist/assets/index-B56yvQDB.css +0 -1
- package/designer/dist/assets/index-DpFVuLlI.js +0 -1004
package/designer/dist/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<link rel="shortcut icon" type="image/svg+xml" href="/images/logo.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>designer</title>
|
|
8
|
-
<script type="module" crossorigin src="/api/workflow/designer-v2/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/api/workflow/designer-v2/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/api/workflow/designer-v2/assets/index-DWcq4xcG.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/api/workflow/designer-v2/assets/index-ClEul34C.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
|
@@ -8,8 +8,245 @@
|
|
|
8
8
|
var _eval;
|
|
9
9
|
|
|
10
10
|
_eval = require('eval');
|
|
11
|
-
const { getCollection } = require('../utils/collection');
|
|
11
|
+
const { getCollection, _makeNewID } = require('../utils/collection');
|
|
12
12
|
const _ = require('lodash');
|
|
13
|
+
|
|
14
|
+
const escapeRegExp = (str) => String(str || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
15
|
+
|
|
16
|
+
const getFieldKey = (field) => field?.code || field?.name;
|
|
17
|
+
|
|
18
|
+
const isAutoNumberTypeField = (field) => {
|
|
19
|
+
const fieldType = (field?.type || '').toString().toLowerCase();
|
|
20
|
+
return fieldType === 'autonumber' || field?.type === 'autoNumber';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const extractAutoNumberName = (formula) => {
|
|
24
|
+
if (typeof formula !== 'string') return null;
|
|
25
|
+
const match = formula.match(/auto_number\s*\(\s*['"]?([^'"()]+?)['"]?\s*\)/);
|
|
26
|
+
return match ? match[1].trim() : null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getAutoNumberRuleName = (field, options = {}) => {
|
|
30
|
+
if (field?.default_value && typeof field.default_value === 'string') {
|
|
31
|
+
const byFormula = extractAutoNumberName(field.default_value);
|
|
32
|
+
if (byFormula) return byFormula;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const fromProps =
|
|
36
|
+
field?.auto_number_name ||
|
|
37
|
+
field?.autoNumberName ||
|
|
38
|
+
field?.auto_number_rule ||
|
|
39
|
+
field?.autoNumberRule ||
|
|
40
|
+
field?.auto_number_rule_name ||
|
|
41
|
+
field?.autoNumberRuleName;
|
|
42
|
+
if (fromProps && typeof fromProps === 'string') {
|
|
43
|
+
return fromProps.trim();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const formName = options.formName || options.formId || 'form';
|
|
47
|
+
const fieldKey = getFieldKey(field);
|
|
48
|
+
const fieldLabel = field?.label || fieldKey;
|
|
49
|
+
return `${formName} / ${fieldLabel} (${fieldKey})`;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const getAutoNumberRuleId = (spaceId, field, options = {}) => {
|
|
53
|
+
const fromProps =
|
|
54
|
+
field?.auto_number_id ||
|
|
55
|
+
field?.autoNumberId ||
|
|
56
|
+
field?.auto_number_rule_id ||
|
|
57
|
+
field?.autoNumberRuleId;
|
|
58
|
+
if (fromProps && typeof fromProps === 'string') {
|
|
59
|
+
return fromProps.trim();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const padNumber = (num, length) => {
|
|
64
|
+
if (length <= 0) return String(num);
|
|
65
|
+
const str = String(num);
|
|
66
|
+
return str.length >= length ? str : '0'.repeat(length - str.length) + str;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const formatDatePart = (dateFormat, yyyy, mm, dd) => {
|
|
70
|
+
const mmStr = mm < 10 ? '0' + mm : String(mm);
|
|
71
|
+
const ddStr = dd < 10 ? '0' + dd : String(dd);
|
|
72
|
+
switch (dateFormat) {
|
|
73
|
+
case 'YYYY': return String(yyyy);
|
|
74
|
+
case 'YYYYMM': return String(yyyy) + mmStr;
|
|
75
|
+
case 'YYYYMMDD': return String(yyyy) + mmStr + ddStr;
|
|
76
|
+
default: return '';
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const shouldResetNumber = (resetCycle, storedRule, yyyy, mm, dd) => {
|
|
81
|
+
switch (resetCycle) {
|
|
82
|
+
case 'yearly': return yyyy !== storedRule.year;
|
|
83
|
+
case 'monthly': return yyyy !== storedRule.year || mm !== storedRule.month;
|
|
84
|
+
case 'daily': return yyyy !== storedRule.year || mm !== storedRule.month || dd !== storedRule.day;
|
|
85
|
+
default: return false;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const buildAutoNumberValue = (fieldConfig, number, date = new Date()) => {
|
|
90
|
+
const prefix = fieldConfig.autoNumberPrefix || '';
|
|
91
|
+
const suffix = fieldConfig.autoNumberSuffix || '';
|
|
92
|
+
const paddingLen = fieldConfig.autoNumberPadding != null ? fieldConfig.autoNumberPadding : 4;
|
|
93
|
+
const dateFormat = fieldConfig.autoNumberDateFormat || 'YYYYMM';
|
|
94
|
+
const yyyy = date.getFullYear();
|
|
95
|
+
const mm = date.getMonth() + 1;
|
|
96
|
+
const dd = date.getDate();
|
|
97
|
+
const datePart = formatDatePart(dateFormat, yyyy, mm, dd);
|
|
98
|
+
const paddedNumber = padNumber(number, paddingLen);
|
|
99
|
+
|
|
100
|
+
return datePart ? `${prefix}${datePart}-${paddedNumber}${suffix}` : `${prefix}${paddedNumber}${suffix}`;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const isAutoNumberPreviewValue = (value, fieldConfig) => {
|
|
104
|
+
if (typeof value !== 'string' || !value) return false;
|
|
105
|
+
|
|
106
|
+
const prefix = escapeRegExp(fieldConfig.autoNumberPrefix || '');
|
|
107
|
+
const suffix = escapeRegExp(fieldConfig.autoNumberSuffix || '');
|
|
108
|
+
const paddingLen = fieldConfig.autoNumberPadding != null ? fieldConfig.autoNumberPadding : 4;
|
|
109
|
+
const firstSeq = escapeRegExp(padNumber(1, paddingLen));
|
|
110
|
+
const dateFormat = fieldConfig.autoNumberDateFormat || 'YYYYMM';
|
|
111
|
+
let datePattern = '';
|
|
112
|
+
|
|
113
|
+
if (dateFormat === 'YYYY') datePattern = '\\d{4}';
|
|
114
|
+
if (dateFormat === 'YYYYMM') datePattern = '\\d{6}';
|
|
115
|
+
if (dateFormat === 'YYYYMMDD') datePattern = '\\d{8}';
|
|
116
|
+
|
|
117
|
+
const pattern = datePattern
|
|
118
|
+
? `^${prefix}${datePattern}-${firstSeq}${suffix}$`
|
|
119
|
+
: `^${prefix}${firstSeq}${suffix}$`;
|
|
120
|
+
|
|
121
|
+
return new RegExp(pattern).test(value);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const isAutoNumberField = (field) => {
|
|
125
|
+
if (!field) return false;
|
|
126
|
+
const hasLegacyAutoNumberFormula = field.default_value && typeof field.default_value === 'string' && field.default_value.trim().startsWith('auto_number(');
|
|
127
|
+
return hasLegacyAutoNumberFormula || isAutoNumberTypeField(field);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const collectAutoNumberFields = (fields, permissions = {}, options = {}) => {
|
|
131
|
+
const autoNumberFields = [];
|
|
132
|
+
const pushedFieldKeys = new Set();
|
|
133
|
+
|
|
134
|
+
const canGenerateAutoNumber = (field) => {
|
|
135
|
+
const fieldKey = getFieldKey(field);
|
|
136
|
+
if (!fieldKey) return false;
|
|
137
|
+
return permissions[fieldKey] === 'editable';
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const tryPushAutoNumberField = (field) => {
|
|
141
|
+
if (!isAutoNumberField(field) || !canGenerateAutoNumber(field)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const fieldKey = getFieldKey(field);
|
|
145
|
+
const autoNumberName = getAutoNumberRuleName(field, options);
|
|
146
|
+
if (!fieldKey || !autoNumberName || pushedFieldKeys.has(fieldKey)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
pushedFieldKeys.add(fieldKey);
|
|
150
|
+
|
|
151
|
+
const fieldInfo = {
|
|
152
|
+
code: fieldKey,
|
|
153
|
+
auto_number_name: autoNumberName,
|
|
154
|
+
legacy_auto_number_name: fieldKey,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
if (isAutoNumberTypeField(field)) {
|
|
158
|
+
fieldInfo.isNewType = true;
|
|
159
|
+
const autoNumberId = getAutoNumberRuleId(options.spaceId, field, options);
|
|
160
|
+
if (autoNumberId) {
|
|
161
|
+
fieldInfo.auto_number_id = autoNumberId;
|
|
162
|
+
}
|
|
163
|
+
fieldInfo.autoNumberPrefix = field.autoNumberPrefix;
|
|
164
|
+
fieldInfo.autoNumberSuffix = field.autoNumberSuffix;
|
|
165
|
+
fieldInfo.autoNumberPadding = field.autoNumberPadding;
|
|
166
|
+
fieldInfo.autoNumberDateFormat = field.autoNumberDateFormat;
|
|
167
|
+
fieldInfo.autoNumberResetCycle = field.autoNumberResetCycle;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
autoNumberFields.push(fieldInfo);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
for (const field of fields || []) {
|
|
174
|
+
if (field.type === 'section') {
|
|
175
|
+
for (const sectionField of field.fields || []) {
|
|
176
|
+
tryPushAutoNumberField(sectionField);
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
tryPushAutoNumberField(field);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return autoNumberFields;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const generateAutoNumberFromFieldConfig = async (spaceId, fieldConfig) => {
|
|
187
|
+
const db = await getCollection('instance_number_rules');
|
|
188
|
+
const ruleId = fieldConfig.auto_number_id;
|
|
189
|
+
const ruleName = fieldConfig.auto_number_name;
|
|
190
|
+
const resetCycle = fieldConfig.autoNumberResetCycle || 'monthly';
|
|
191
|
+
const now = new Date();
|
|
192
|
+
const yyyy = now.getFullYear();
|
|
193
|
+
const mm = now.getMonth() + 1;
|
|
194
|
+
const dd = now.getDate();
|
|
195
|
+
|
|
196
|
+
let numberRules = ruleId
|
|
197
|
+
? await db.findOne({ _id: ruleId })
|
|
198
|
+
: await db.findOne({ space: spaceId, name: ruleName });
|
|
199
|
+
|
|
200
|
+
if (!numberRules) {
|
|
201
|
+
const legacyRuleName = fieldConfig.legacy_auto_number_name;
|
|
202
|
+
const legacyRule = legacyRuleName && legacyRuleName !== ruleName
|
|
203
|
+
? await db.findOne({ space: spaceId, name: legacyRuleName })
|
|
204
|
+
: null;
|
|
205
|
+
await db.insertOne({
|
|
206
|
+
_id: ruleId || _makeNewID(),
|
|
207
|
+
space: spaceId,
|
|
208
|
+
name: ruleName,
|
|
209
|
+
year: legacyRule?.year || yyyy,
|
|
210
|
+
month: legacyRule?.month || mm,
|
|
211
|
+
day: legacyRule?.day || dd,
|
|
212
|
+
first_number: legacyRule?.first_number || 1,
|
|
213
|
+
number: legacyRule?.number || 0,
|
|
214
|
+
rules: ''
|
|
215
|
+
});
|
|
216
|
+
numberRules = ruleId
|
|
217
|
+
? await db.findOne({ _id: ruleId })
|
|
218
|
+
: await db.findOne({ space: spaceId, name: ruleName });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let nextNumber = (numberRules.number || 0) + 1;
|
|
222
|
+
if (shouldResetNumber(resetCycle, numberRules, yyyy, mm, dd)) {
|
|
223
|
+
nextNumber = numberRules.first_number || 1;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const autoNumber = buildAutoNumberValue(fieldConfig, nextNumber, now);
|
|
227
|
+
|
|
228
|
+
await db.updateOne(
|
|
229
|
+
{ _id: numberRules._id },
|
|
230
|
+
{ $set: { name: ruleName, year: yyyy, month: mm, day: dd, number: nextNumber } }
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
return autoNumber;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const fillAutoNumberValues = async ({ spaceId, values, fields, permissions, formId, formName }) => {
|
|
237
|
+
const nextValues = values || {};
|
|
238
|
+
const autoNumberFields = collectAutoNumberFields(fields, permissions, { spaceId, formId, formName });
|
|
239
|
+
|
|
240
|
+
for (const field of autoNumberFields) {
|
|
241
|
+
if (!field.isNewType) continue;
|
|
242
|
+
const value = nextValues[field.code];
|
|
243
|
+
if (value && !isAutoNumberPreviewValue(value, field)) continue;
|
|
244
|
+
nextValues[field.code] = await generateAutoNumberFromFieldConfig(spaceId, field);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return nextValues;
|
|
248
|
+
};
|
|
249
|
+
|
|
13
250
|
module.exports = {
|
|
14
251
|
instanceNumberBuilder: async function (spaceId, name) {
|
|
15
252
|
var _NUMBER, _YYYY, context, date, e, numberRules, padding, res, rules, script;
|
|
@@ -80,5 +317,12 @@ module.exports = {
|
|
|
80
317
|
};
|
|
81
318
|
}
|
|
82
319
|
return res;
|
|
83
|
-
}
|
|
320
|
+
},
|
|
321
|
+
collectAutoNumberFields,
|
|
322
|
+
fillAutoNumberValues,
|
|
323
|
+
generateAutoNumberFromFieldConfig,
|
|
324
|
+
getAutoNumberRuleName,
|
|
325
|
+
getFieldKey,
|
|
326
|
+
isAutoNumberPreviewValue,
|
|
327
|
+
isAutoNumberTypeField
|
|
84
328
|
};
|
|
@@ -13,6 +13,7 @@ const WorkflowManager = require('./workflow_manager');
|
|
|
13
13
|
const { getCollection, _makeNewID } = require('../utils/collection');
|
|
14
14
|
const WorkingTime = require('../utils/workingTime');
|
|
15
15
|
const pushManager = require('./push_manager');
|
|
16
|
+
const autoNumberManager = require('./instance_number_rules');
|
|
16
17
|
const { t } = require('@steedos/i18n')
|
|
17
18
|
const moment = require('moment')
|
|
18
19
|
const { getObject } = require('@steedos/objectql');
|
|
@@ -1568,9 +1569,20 @@ UUFlowManager.workflow_engine = async function (approve_from_client, current_use
|
|
|
1568
1569
|
setObj[`${key_str}read_date`] = new Date();
|
|
1569
1570
|
}
|
|
1570
1571
|
|
|
1572
|
+
const form = await UUFlowManager.getForm(instance.form);
|
|
1573
|
+
const form_v = await UUFlowManager.getFormVersion(form, instance.form_version);
|
|
1574
|
+
const valuesWithAutoNumber = await autoNumberManager.fillAutoNumberValues({
|
|
1575
|
+
spaceId: space_id,
|
|
1576
|
+
values,
|
|
1577
|
+
fields: form_v.fields || [],
|
|
1578
|
+
permissions: step.permissions,
|
|
1579
|
+
formId: instance.form,
|
|
1580
|
+
formName: form.name
|
|
1581
|
+
});
|
|
1582
|
+
|
|
1571
1583
|
// Filter approve values based on permissions
|
|
1572
1584
|
setObj[`${key_str}values`] = await UUFlowManager.getApproveValues(
|
|
1573
|
-
|
|
1585
|
+
valuesWithAutoNumber,
|
|
1574
1586
|
step.permissions,
|
|
1575
1587
|
instance.form,
|
|
1576
1588
|
instance.form_version
|
|
@@ -1822,14 +1834,14 @@ UUFlowManager.handleSkipProcessed = async function (instance_id, flow, maxDepth
|
|
|
1822
1834
|
const consecutiveOnly = process.env.STEEDOS_WORKFLOW_SKIP_CONSECUTIVE_ONLY;
|
|
1823
1835
|
let effectiveProcessedUserIds = processedUserIds;
|
|
1824
1836
|
if (consecutiveOnly === 'true' || consecutiveOnly === '1') {
|
|
1825
|
-
//
|
|
1837
|
+
// 仅连续步骤模式:只检查紧邻的上一步骤的处理人;滑步产生的 skipped 记录也算连续链路
|
|
1826
1838
|
const previousTraceIds = lastTrace.previous_trace_ids || [];
|
|
1827
1839
|
const previousTraces = (instance.traces || []).filter(t => previousTraceIds.includes(t._id));
|
|
1828
1840
|
effectiveProcessedUserIds = [];
|
|
1829
1841
|
for (const prevTrace of previousTraces) {
|
|
1830
1842
|
if (prevTrace.approves) {
|
|
1831
1843
|
for (const approve of prevTrace.approves) {
|
|
1832
|
-
if (isNormalApprove(approve) && approve.is_finished
|
|
1844
|
+
if (isNormalApprove(approve) && approve.is_finished) {
|
|
1833
1845
|
if (!effectiveProcessedUserIds.includes(approve.user)) {
|
|
1834
1846
|
effectiveProcessedUserIds.push(approve.user);
|
|
1835
1847
|
}
|
|
@@ -3514,6 +3526,7 @@ UUFlowManager.submit_instance = async function (instance_from_client, user_info)
|
|
|
3514
3526
|
// 先执行暂存的操作
|
|
3515
3527
|
// ================begin================
|
|
3516
3528
|
const form = await UUFlowManager.getForm(instance.form, { fields: { historys: 0 } });
|
|
3529
|
+
const form_v = await UUFlowManager.getFormVersion(form, instance.form_version);
|
|
3517
3530
|
// 获取Flow当前版本开始节点
|
|
3518
3531
|
const start_step = flow.current.steps.find(step => step.step_type === 'start');
|
|
3519
3532
|
|
|
@@ -3560,6 +3573,15 @@ UUFlowManager.submit_instance = async function (instance_from_client, user_info)
|
|
|
3560
3573
|
instance_traces[0].name = start_step.name;
|
|
3561
3574
|
}
|
|
3562
3575
|
|
|
3576
|
+
values = await autoNumberManager.fillAutoNumberValues({
|
|
3577
|
+
spaceId: space_id,
|
|
3578
|
+
values,
|
|
3579
|
+
fields: form_v.fields || [],
|
|
3580
|
+
permissions: step.permissions,
|
|
3581
|
+
formId: instance.form,
|
|
3582
|
+
formName: form.name
|
|
3583
|
+
});
|
|
3584
|
+
|
|
3563
3585
|
// 调整approves的values删除values中在当前步骤中没有编辑权限的字段值
|
|
3564
3586
|
instance_traces[0].approves[0].values = await UUFlowManager.getApproveValues(
|
|
3565
3587
|
values,
|
|
@@ -5200,4 +5222,4 @@ UUFlowManager.caculateExtras = async function (values = {}, formDoc, formVersion
|
|
|
5200
5222
|
return _.isEmpty(extras) ? undefined : extras;
|
|
5201
5223
|
};
|
|
5202
5224
|
|
|
5203
|
-
module.exports = UUFlowManager;
|
|
5225
|
+
module.exports = UUFlowManager;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"actions": [
|
|
20
20
|
{
|
|
21
21
|
"actionType": "custom",
|
|
22
|
-
"script": "var
|
|
22
|
+
"script": "var flowName='';try{var scoped=event&&event.data&&event.data._scoped;var instancePage=scoped&&scoped.getComponentById&&scoped.getComponentById('u:instancePage');var pageData=instancePage&&instancePage.getData&&instancePage.getData();flowName=pageData&&pageData.context&&pageData.context.flow&&pageData.context.flow.name;}catch(e){flowName='';}flowName=(flowName||'').trim().replace(/\\s+/g,' ');if(!flowName){var msg='打印页面数据异常,未找到流程名称';if(event&&event.context&&event.context.env&&event.context.env.notify){event.context.env.notify('warning',msg);}else{alert(msg);}return;}document.title=flowName;window.print();"
|
|
23
23
|
}
|
|
24
24
|
]
|
|
25
25
|
}
|
|
@@ -576,4 +576,4 @@
|
|
|
576
576
|
]
|
|
577
577
|
}
|
|
578
578
|
}
|
|
579
|
-
}
|
|
579
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const express = require("express");
|
|
2
2
|
const router = express.Router();
|
|
3
|
-
const
|
|
3
|
+
const autoNumberManager = require("../manager/instance_number_rules");
|
|
4
|
+
const { instanceNumberBuilder } = autoNumberManager;
|
|
4
5
|
const objectql = require('@steedos/objectql');
|
|
5
6
|
const _ = require('lodash');
|
|
6
7
|
const UUFlowManager = require('../manager/uuflow_manager');
|
|
7
8
|
const { requireAuthentication } = require("@steedos/auth");
|
|
8
|
-
const { getCollection } = require('../utils/collection');
|
|
9
9
|
|
|
10
10
|
// 获取用户当前的 approve
|
|
11
11
|
const getUserApprove = ({ instance, userId }) => {
|
|
@@ -39,192 +39,25 @@ const getUserApprove = ({ instance, userId }) => {
|
|
|
39
39
|
return currentApprove;
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
// ===== 新设计器 autoNumber 字段:基于字段配置直接生成编号 =====
|
|
43
|
-
|
|
44
|
-
// 序号补零
|
|
45
|
-
const padNumber = (num, length) => {
|
|
46
|
-
if (length <= 0) return String(num);
|
|
47
|
-
const str = String(num);
|
|
48
|
-
return str.length >= length ? str : '0'.repeat(length - str.length) + str;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// 根据日期格式生成日期片段
|
|
52
|
-
const formatDatePart = (dateFormat, yyyy, mm, dd) => {
|
|
53
|
-
const mmStr = mm < 10 ? '0' + mm : String(mm);
|
|
54
|
-
const ddStr = dd < 10 ? '0' + dd : String(dd);
|
|
55
|
-
switch (dateFormat) {
|
|
56
|
-
case 'YYYY': return String(yyyy);
|
|
57
|
-
case 'YYYYMM': return String(yyyy) + mmStr;
|
|
58
|
-
case 'YYYYMMDD': return String(yyyy) + mmStr + ddStr;
|
|
59
|
-
default: return ''; // 'none' 或 undefined
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// 根据重置周期判断是否需要重置序号
|
|
64
|
-
const shouldResetNumber = (resetCycle, storedRule, yyyy, mm, dd) => {
|
|
65
|
-
switch (resetCycle) {
|
|
66
|
-
case 'yearly': return yyyy !== storedRule.year;
|
|
67
|
-
case 'monthly': return yyyy !== storedRule.year || mm !== storedRule.month;
|
|
68
|
-
case 'daily': return yyyy !== storedRule.year || mm !== storedRule.month || dd !== storedRule.day;
|
|
69
|
-
default: return false; // 'none' - 永不重置
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
42
|
/**
|
|
74
43
|
* 基于字段配置生成自动编号(新设计器 autoNumber 类型字段)
|
|
75
44
|
* 同样使用 instance_number_rules 集合做序号持久化,如果尚未创建规则记录则自动创建
|
|
76
45
|
*/
|
|
77
46
|
const generateAutoNumberFromFieldConfig = async (spaceId, fieldConfig) => {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const ruleName = fieldConfig.auto_number_name;
|
|
81
|
-
const prefix = fieldConfig.autoNumberPrefix || '';
|
|
82
|
-
const suffix = fieldConfig.autoNumberSuffix || '';
|
|
83
|
-
const paddingLen = fieldConfig.autoNumberPadding != null ? fieldConfig.autoNumberPadding : 4;
|
|
84
|
-
const dateFormat = fieldConfig.autoNumberDateFormat || 'YYYYMM';
|
|
85
|
-
const resetCycle = fieldConfig.autoNumberResetCycle || 'monthly';
|
|
86
|
-
|
|
87
|
-
const now = new Date();
|
|
88
|
-
const yyyy = now.getFullYear();
|
|
89
|
-
const mm = now.getMonth() + 1;
|
|
90
|
-
const dd = now.getDate();
|
|
91
|
-
|
|
92
|
-
let numberRules = await db.findOne({ space: spaceId, name: ruleName });
|
|
93
|
-
|
|
94
|
-
if (!numberRules) {
|
|
95
|
-
// 首次使用该字段,自动创建规则记录
|
|
96
|
-
await db.insertOne({
|
|
97
|
-
space: spaceId,
|
|
98
|
-
name: ruleName,
|
|
99
|
-
year: yyyy,
|
|
100
|
-
month: mm,
|
|
101
|
-
day: dd,
|
|
102
|
-
first_number: 1,
|
|
103
|
-
number: 0,
|
|
104
|
-
rules: '' // 新类型字段不使用 rules 模板
|
|
105
|
-
});
|
|
106
|
-
numberRules = await db.findOne({ space: spaceId, name: ruleName });
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
let nextNumber = (numberRules.number || 0) + 1;
|
|
110
|
-
|
|
111
|
-
if (shouldResetNumber(resetCycle, numberRules, yyyy, mm, dd)) {
|
|
112
|
-
nextNumber = numberRules.first_number || 1;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const paddedNumber = padNumber(nextNumber, paddingLen);
|
|
116
|
-
const datePart = formatDatePart(dateFormat, yyyy, mm, dd);
|
|
117
|
-
const autoNumber = prefix + datePart + paddedNumber + suffix;
|
|
118
|
-
|
|
119
|
-
await db.updateOne(
|
|
120
|
-
{ _id: numberRules._id },
|
|
121
|
-
{ $set: { year: yyyy, month: mm, day: dd, number: nextNumber } }
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
return autoNumber;
|
|
47
|
+
return autoNumberManager.generateAutoNumberFromFieldConfig(spaceId, fieldConfig);
|
|
125
48
|
};
|
|
126
49
|
|
|
127
50
|
// 获取可编辑的自动编号字段
|
|
128
|
-
const getEditableAutoNumberFields = async (step, formId, formVersion) => {
|
|
51
|
+
const getEditableAutoNumberFields = async (step, formId, formVersion, spaceId) => {
|
|
129
52
|
const permissions = step.permissions || {};
|
|
130
53
|
const form = await UUFlowManager.getForm(formId);
|
|
131
54
|
const formV = await UUFlowManager.getFormVersion(form, formVersion);
|
|
132
|
-
|
|
133
|
-
const autoNumberFields = [];
|
|
134
|
-
const pushedFieldKeys = new Set();
|
|
135
|
-
|
|
136
|
-
const getFieldKey = (field) => field?.code || field?.name;
|
|
137
|
-
const isAutoNumberTypeField = (field) => {
|
|
138
|
-
const fieldType = (field?.type || '').toString().toLowerCase();
|
|
139
|
-
return fieldType === 'autonumber' || field?.type === 'autoNumber';
|
|
140
|
-
};
|
|
141
|
-
const canGenerateAutoNumber = (field) => {
|
|
142
|
-
const fieldKey = getFieldKey(field);
|
|
143
|
-
if (!fieldKey) return false;
|
|
144
|
-
const permission = permissions[fieldKey];
|
|
145
|
-
if (permission === 'editable') return true;
|
|
146
|
-
return false;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
// 检查字段是否是自动编号字段(通过 formula 判断)
|
|
150
|
-
const isAutoNumberField = (field) => {
|
|
151
|
-
if (!field) return false;
|
|
152
|
-
const hasLegacyAutoNumberFormula = field.default_value && typeof field.default_value === 'string' && field.default_value.trim().startsWith('auto_number(');
|
|
153
|
-
return hasLegacyAutoNumberFormula || isAutoNumberTypeField(field);
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
// 从 formula 中提取自动编号规则名称
|
|
157
|
-
const extractAutoNumberName = (formula) => {
|
|
158
|
-
// 匹配 auto_number("xxx") 或 auto_number('xxx') 或 auto_number(xxx)
|
|
159
|
-
const match = formula.match(/auto_number\s*\(\s*['"]?([^'"()]+?)['"]?\s*\)/);
|
|
160
|
-
return match ? match[1].trim() : null;
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const getAutoNumberRuleName = (field) => {
|
|
164
|
-
// 1) 兼容旧字段公式:default_value = auto_number(...)
|
|
165
|
-
if (field?.default_value && typeof field.default_value === 'string') {
|
|
166
|
-
const byFormula = extractAutoNumberName(field.default_value);
|
|
167
|
-
if (byFormula) return byFormula;
|
|
168
|
-
}
|
|
169
55
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
field?.autoNumberRule ||
|
|
176
|
-
field?.auto_number_rule_name ||
|
|
177
|
-
field?.autoNumberRuleName;
|
|
178
|
-
if (fromProps && typeof fromProps === 'string') {
|
|
179
|
-
return fromProps.trim();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// 3) 对于新设计器 autoNumber 字段,默认回退到字段标识(code/name)
|
|
183
|
-
return getFieldKey(field);
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
const tryPushAutoNumberField = (field) => {
|
|
187
|
-
if (!isAutoNumberField(field) || !canGenerateAutoNumber(field)) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const fieldKey = getFieldKey(field);
|
|
191
|
-
const autoNumberName = getAutoNumberRuleName(field);
|
|
192
|
-
if (!fieldKey || !autoNumberName || pushedFieldKeys.has(fieldKey)) {
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
pushedFieldKeys.add(fieldKey);
|
|
196
|
-
|
|
197
|
-
const fieldInfo = {
|
|
198
|
-
code: fieldKey,
|
|
199
|
-
auto_number_name: autoNumberName,
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
// 新设计器 autoNumber 字段,携带配置属性用于直接生成编号
|
|
203
|
-
if (isAutoNumberTypeField(field)) {
|
|
204
|
-
fieldInfo.isNewType = true;
|
|
205
|
-
fieldInfo.autoNumberPrefix = field.autoNumberPrefix;
|
|
206
|
-
fieldInfo.autoNumberSuffix = field.autoNumberSuffix;
|
|
207
|
-
fieldInfo.autoNumberPadding = field.autoNumberPadding;
|
|
208
|
-
fieldInfo.autoNumberDateFormat = field.autoNumberDateFormat;
|
|
209
|
-
fieldInfo.autoNumberResetCycle = field.autoNumberResetCycle;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
autoNumberFields.push(fieldInfo);
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
for (const field of formV.fields || []) {
|
|
216
|
-
if (field.type === "section") {
|
|
217
|
-
// 处理 section 中的字段
|
|
218
|
-
for (const sectionField of field.fields || []) {
|
|
219
|
-
tryPushAutoNumberField(sectionField);
|
|
220
|
-
}
|
|
221
|
-
} else {
|
|
222
|
-
// 处理普通字段
|
|
223
|
-
tryPushAutoNumberField(field);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return autoNumberFields;
|
|
56
|
+
return autoNumberManager.collectAutoNumberFields(formV.fields || [], permissions, {
|
|
57
|
+
spaceId,
|
|
58
|
+
formId,
|
|
59
|
+
formName: form.name,
|
|
60
|
+
});
|
|
228
61
|
};
|
|
229
62
|
|
|
230
63
|
router.post('/api/workflow/v2/auto_number', requireAuthentication, async function (req, res) {
|
|
@@ -319,7 +152,7 @@ router.post('/api/workflow/v2/auto_number', requireAuthentication, async functio
|
|
|
319
152
|
}
|
|
320
153
|
|
|
321
154
|
// 获取可编辑的自动编号字段
|
|
322
|
-
const autoNumberFields = await getEditableAutoNumberFields(step, instance.form, instance.form_version);
|
|
155
|
+
const autoNumberFields = await getEditableAutoNumberFields(step, instance.form, instance.form_version, instance.space);
|
|
323
156
|
if (autoNumberFields.length === 0) {
|
|
324
157
|
return res.status(200).send({
|
|
325
158
|
success: true,
|
|
@@ -379,4 +212,4 @@ router.post('/api/workflow/v2/auto_number', requireAuthentication, async functio
|
|
|
379
212
|
});
|
|
380
213
|
|
|
381
214
|
|
|
382
|
-
exports.default = router;
|
|
215
|
+
exports.default = router;
|
|
@@ -40,6 +40,15 @@ const FlowversionAPI = {
|
|
|
40
40
|
return str.replace(/\"/g, """).replace(/\n/g, "<br/>");
|
|
41
41
|
},
|
|
42
42
|
|
|
43
|
+
getSafeEdgeLabel: function (str) {
|
|
44
|
+
// Mermaid edge label (A-->|text|B) 对部分符号(如括号/竖线)解析不稳定,先做保守清洗。
|
|
45
|
+
return FlowversionAPI.replaceErrorSymbol(String(str || ''))
|
|
46
|
+
.replace(/[|]/g, '|')
|
|
47
|
+
.replace(/[()()\[\]{}<>]/g, ' ')
|
|
48
|
+
.replace(/\s+/g, ' ')
|
|
49
|
+
.trim();
|
|
50
|
+
},
|
|
51
|
+
|
|
43
52
|
getStepHandlerName: async function (step, insId, instance) {
|
|
44
53
|
const db = {
|
|
45
54
|
users: await getCollection('users'),
|
|
@@ -194,8 +203,12 @@ const FlowversionAPI = {
|
|
|
194
203
|
let toStep = steps.find(s => s._id === line.to_step);
|
|
195
204
|
let toStepName = await FlowversionAPI.getStepName(toStep, cachedStepNames, instance_id, instance);
|
|
196
205
|
if (step.step_type === "condition" && line.name) {
|
|
197
|
-
let lineName = FlowversionAPI.
|
|
198
|
-
|
|
206
|
+
let lineName = FlowversionAPI.getSafeEdgeLabel(line.name);
|
|
207
|
+
if (lineName) {
|
|
208
|
+
nodes.push(` ${step._id}("${stepName}")-->|${lineName}|${line.to_step}("${toStepName}")`);
|
|
209
|
+
} else {
|
|
210
|
+
nodes.push(` ${step._id}("${stepName}")-->${line.to_step}("${toStepName}")`);
|
|
211
|
+
}
|
|
199
212
|
} else {
|
|
200
213
|
nodes.push(` ${step._id}("${stepName}")-->${line.to_step}("${toStepName}")`);
|
|
201
214
|
}
|
|
@@ -1056,4 +1069,4 @@ router.get('/api/workflow/chart/traces', requireAuthentication, async function (
|
|
|
1056
1069
|
router.get('/api/workflow/chart/traces_expand', requireAuthentication, async function (req, res) {
|
|
1057
1070
|
await FlowversionAPI.sendHtmlResponse(req, res, "traces_expand");
|
|
1058
1071
|
});
|
|
1059
|
-
exports.default = router;
|
|
1072
|
+
exports.default = router;
|
|
@@ -192,7 +192,7 @@ router.post("/api/workflow/v2/instance/forward", requireAuthentication, async fu
|
|
|
192
192
|
|
|
193
193
|
// Process fields
|
|
194
194
|
for (const field of fields) {
|
|
195
|
-
const exists_field = _.find(old_fields, f => f.
|
|
195
|
+
const exists_field = _.find(old_fields, f => f.code == field.code);
|
|
196
196
|
if (exists_field) common_fields.push(field);
|
|
197
197
|
|
|
198
198
|
const select_input_field = _.find(old_fields, f =>
|
|
@@ -241,6 +241,10 @@ router.post("/api/workflow/v2/instance/forward", requireAuthentication, async fu
|
|
|
241
241
|
old_v = new_multiSelected.join(",");
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
if (f.type == "lookup" && _.isObject(old_v) && old_v._id) {
|
|
245
|
+
old_v = old_v._id;
|
|
246
|
+
}
|
|
247
|
+
|
|
244
248
|
new_values[key] = old_v;
|
|
245
249
|
}
|
|
246
250
|
}
|
|
@@ -281,6 +285,10 @@ router.post("/api/workflow/v2/instance/forward", requireAuthentication, async fu
|
|
|
281
285
|
old_v = new_multiSelected.join(",");
|
|
282
286
|
}
|
|
283
287
|
|
|
288
|
+
if (f.type == "lookup" && _.isObject(old_v) && old_v._id) {
|
|
289
|
+
old_v = old_v._id;
|
|
290
|
+
}
|
|
291
|
+
|
|
284
292
|
new_table_row_values[key] = old_v;
|
|
285
293
|
}
|
|
286
294
|
}
|
|
@@ -324,6 +332,10 @@ router.post("/api/workflow/v2/instance/forward", requireAuthentication, async fu
|
|
|
324
332
|
old_v = new_multiSelected.join(",");
|
|
325
333
|
}
|
|
326
334
|
|
|
335
|
+
if (field.type == "lookup" && _.isObject(old_v) && old_v._id) {
|
|
336
|
+
old_v = old_v._id;
|
|
337
|
+
}
|
|
338
|
+
|
|
327
339
|
new_values[key] = old_v;
|
|
328
340
|
}
|
|
329
341
|
}
|