@steedos-labs/plugin-workflow 3.0.12 → 3.0.14

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.
Files changed (28) hide show
  1. package/AI_PLUGIN_GUIDE.md +939 -0
  2. package/README.md +24 -1
  3. package/main/default/applications/approve_workflow.app.yml +160 -3
  4. package/main/default/manager/handlers_manager.js +1 -1
  5. package/main/default/manager/instance_number_rules.js +84 -0
  6. package/main/default/manager/instance_tasks_manager.js +79 -1
  7. package/main/default/manager/uuflow_manager.js +53 -45
  8. package/main/default/objects/flows/buttons/design_form_layout.button.js +1 -3
  9. package/main/default/objects/flows/flows.object.yml +4 -3
  10. package/main/default/objects/instances/buttons/instance_reassign.button.yml +5 -4
  11. package/main/default/pages/instance_detail.page.amis.json +2 -100
  12. package/main/default/pages/instance_tasks_detail.page.amis.json +0 -100
  13. package/main/default/pages/page_instance_view.page.amis.json +1 -1
  14. package/main/default/routes/api_auto_number.router.js +233 -0
  15. package/main/default/routes/api_files.router.js +21 -0
  16. package/main/default/routes/api_have_read.router.js +20 -2
  17. package/main/default/routes/api_workflow_chart.router.js +23 -3
  18. package/main/default/routes/api_workflow_instance_upgrade.router.js +5 -0
  19. package/main/default/routes/api_workflow_nav.router.js +160 -136
  20. package/main/default/routes/api_workflow_next_step.router.js +111 -30
  21. package/main/default/routes/flow_form_design.ejs +16 -1
  22. package/main/default/services/instance.service.js +10 -1
  23. package/package.json +1 -1
  24. package/public/workflow/index.css +208 -10
  25. package/main/default/pages/instance_tasks_list.page.amis.json +0 -330
  26. package/main/default/pages/instance_tasks_list.page.yml +0 -12
  27. package/main/default/pages/instances_list.page.amis.json +0 -327
  28. package/main/default/pages/instances_list.page.yml +0 -12
@@ -1,104 +1,6 @@
1
1
  {
2
2
  "type": "page",
3
- "body": [{
4
- "type": "wrapper",
5
- "visibleOn": "${AND(display != 'split', ${window:innerWidth > 768},!!!_inDrawer)}",
6
- "className": "bg-white p-0 flex-shrink-0 min-w-[240px] lg:order-first lg:flex lg:flex-col rounded shadow my-4 ml-4 mr-1 my-4 sm:rounded",
7
- "body": [{
8
- "type": "service",
9
- "className": "w-full h-full",
10
- "onEvent": {
11
- "@data.changed.steedos_keyvalues": {
12
- "actions": [{
13
- "actionType": "reload"
14
- }]
15
- }
16
- },
17
- "body": [
18
- {
19
- "type": "button",
20
- "label": "刷新",
21
- "className": "instance-nav-reload hidden",
22
- "onEvent": {
23
- "click": {
24
- "actions": [{
25
- "actionType": "reload",
26
- "componentId": "u:instanceNav"
27
- }]
28
- }
29
- }
30
- },
31
- {
32
- "type": "input-tree",
33
- "treeContainerClassName": "h-full",
34
- "name": "tree",
35
- "className": "instance-box-tree w-full",
36
- "id": "u:9f3dd961ca12",
37
- "stacked": true,
38
- "multiple": false,
39
- "enableNodePath": false,
40
- "hideRoot": true,
41
- "showIcon": true,
42
- "initiallyOpen": false,
43
- "virtualThreshold": 100000,
44
- "value": "/app/${appId}/${objectName}/grid/${listName}",
45
- "size": "md",
46
- "onEvent": {
47
- "change": {
48
- "actions": [{
49
- "args": {
50
- "link": "${event.data.value}",
51
- "blank": false
52
- },
53
- "actionType": "link"
54
- }]
55
- }
56
- },
57
- "menuTpl": {
58
- "type": "wrapper",
59
- "className": "flex flex-row p-0 m-0",
60
- "body": [{
61
- "type": "tpl",
62
- "className": "flex-1 w-6/12",
63
- "tpl": "${label}",
64
- "id": "u:8147616a7b50"
65
- },
66
- {
67
- "type": "tpl",
68
- "className": "-mx-11 ",
69
- "tpl": "",
70
- "badge": {
71
- "className": "h-0",
72
- "offset": [
73
- -20,
74
- 12
75
- ],
76
- "mode": "text",
77
- "text": "${tag | toInt}",
78
- "overflowCount": 999
79
- },
80
- "id": "u:94f078e62361"
81
- }
82
- ],
83
- "id": "u:decad07ad26b"
84
- },
85
- "unfoldedLevel": 2,
86
- "source": "${options}"
87
- }],
88
- "id": "u:instanceNav",
89
- "api": {
90
- "method": "get",
91
- "url": "${context.rootUrl}/api/${appId}/workflow/nav",
92
- "headers": {
93
- "Authorization": "Bearer ${context.tenantId},${context.authToken}"
94
- }
95
- },
96
- "dsType": "api"
97
- }],
98
- "mobile": {
99
- "visibleOn": "false"
100
- }
101
- },
3
+ "body": [
102
4
  {
103
5
  "type": "button",
104
6
  "label": "刷新",
@@ -124,7 +26,7 @@
124
26
  "schemaApi": {
125
27
  "method": "get",
126
28
  "url": "/api/health_check?trace=${recordId}",
127
- "adaptor": "const result = {data: {'type':'wrapper','className':'p-0 h-full','body':[{'type':'steedos-instance-detail','id':'u:40052b3812c1','label':'Instance Detail','instanceId':context.recordId,'boxName':context.side_listview_id}],'id':'u:829a40757f0a'}};console.log('result===>', result); return result;"
29
+ "adaptor": "const urlParams = new URLSearchParams(location.search); const sideListViewId = urlParams.get('side_listview_id'); const result = {data: {'type':'wrapper','className':'p-0 h-full','body':[{'type':'steedos-instance-detail','id':'u:40052b3812c1','label':'Instance Detail','instanceId':context.recordId,'boxName':sideListViewId}],'id':'u:829a40757f0a'}};return result;"
128
30
  },
129
31
  "body": {},
130
32
  "className": "h-full",
@@ -1,106 +1,6 @@
1
1
  {
2
2
  "type": "page",
3
3
  "body": [
4
- {
5
- "type": "wrapper",
6
- "visibleOn": "${AND(display != 'split', ${window:innerWidth > 768})}",
7
- "className": "bg-white p-0 flex-shrink-0 min-w-[240px] lg:order-first lg:flex lg:flex-col rounded shadow my-4 ml-4 mr-1 my-4 sm:rounded",
8
- "body": [
9
- {
10
- "type": "service",
11
- "className": "w-full h-full",
12
- "onEvent": {
13
- "@data.changed.steedos_keyvalues": {
14
- "actions": [
15
- {
16
- "actionType": "reload"
17
- }
18
- ]
19
- }
20
- },
21
- "body": [
22
- {
23
- "type": "button",
24
- "label": "刷新",
25
- "className": "instance-nav-reload hidden",
26
- "onEvent": {
27
- "click": {
28
- "actions": [{
29
- "actionType": "reload",
30
- "componentId": "u:instanceNav"
31
- }]
32
- }
33
- }
34
- },
35
- {
36
- "type": "input-tree",
37
- "treeContainerClassName": "h-full",
38
- "name": "tree",
39
- "className": "instance-box-tree w-full",
40
- "id": "u:9f3dd961ca12",
41
- "stacked": true,
42
- "multiple": false,
43
- "enableNodePath": false,
44
- "hideRoot": true,
45
- "showIcon": true,
46
- "initiallyOpen": false,
47
- "virtualThreshold": 100000,
48
- "value":"/app/${appId}/${objectName}/grid/${listName}",
49
- "size": "md",
50
- "onEvent": {
51
- "change": {
52
- "actions": [
53
- {
54
- "args": {
55
- "link": "${event.data.value}",
56
- "blank": false
57
- },
58
- "actionType": "link"
59
- }
60
- ]
61
- }
62
- },
63
- "menuTpl": {
64
- "type": "wrapper",
65
- "className": "flex flex-row p-0 m-0",
66
- "body": [
67
- {
68
- "type": "tpl",
69
- "className": "flex-1 w-6/12",
70
- "tpl": "${label}"
71
- },
72
- {
73
- "type": "tpl",
74
- "className": "-mx-11 ",
75
- "tpl": "",
76
- "badge": {
77
- "className": "h-0",
78
- "offset": [
79
- -20,
80
- 12
81
- ],
82
- "mode": "text",
83
- "text": "${tag}",
84
- "overflowCount": 999
85
- }
86
- }
87
- ]
88
- },
89
- "unfoldedLevel": 2,
90
- "source": "${options}"
91
- }
92
- ],
93
- "id": "u:instanceNav",
94
- "api": {
95
- "method": "get",
96
- "url": "${context.rootUrl}/api/${appId}/workflow/nav",
97
- "headers": {
98
- "Authorization": "Bearer ${context.tenantId},${context.authToken}"
99
- }
100
- }
101
- }
102
- ]
103
- },
104
4
  {
105
5
  "type": "wrapper",
106
6
  "className": "steedos-instance-detail-wrapper m-0 p-0 flex-1 focus:outline-none lg:order-last sm:m-4 shadow sm:rounded",
@@ -38,7 +38,7 @@
38
38
  "bodyClassName": "p-0 flex flex-1 overflow-hidden h-full",
39
39
  "name": "amis-root-workflow",
40
40
  "initApi": {
41
- "url": "http://127.0.0.1:8443/api/workflow/v2/instance/${recordId}/permission",
41
+ "url": "/api/workflow/v2/instance/${recordId}/permission",
42
42
  "method": "get",
43
43
  "data": {},
44
44
  "requestAdaptor": "",
@@ -0,0 +1,233 @@
1
+ const express = require("express");
2
+ const router = express.Router();
3
+ const { instanceNumberBuilder } = require("../manager/instance_number_rules");
4
+ const objectql = require('@steedos/objectql');
5
+ const _ = require('lodash');
6
+ const UUFlowManager = require('../manager/uuflow_manager');
7
+ const { requireAuthentication } = require("@steedos/auth");
8
+
9
+ // 获取用户当前的 approve
10
+ const getUserApprove = ({ instance, userId }) => {
11
+ const currentTrace = _.find(instance.traces, (trace) => {
12
+ return trace.is_finished != true;
13
+ });
14
+ let currentApprove = null;
15
+ if (currentTrace) {
16
+ currentApprove = _.find(currentTrace.approves, (approve) => {
17
+ return approve.is_finished != true && approve.handler == userId;
18
+ });
19
+ }
20
+
21
+ //传阅的approve返回最新一条
22
+ if (!currentApprove || currentApprove.type == "cc") {
23
+ // 当前是传阅
24
+ _.each(instance.traces, function (t) {
25
+ _.each(t.approves, function (a) {
26
+ if (a.type == "cc" && a.handler == userId && a.is_finished == false) {
27
+ currentApprove = a;
28
+ }
29
+ });
30
+ });
31
+ }
32
+
33
+ if (!currentApprove) return;
34
+
35
+ if (currentApprove._id) {
36
+ currentApprove.id = currentApprove._id;
37
+ }
38
+ return currentApprove;
39
+ };
40
+
41
+ // 获取可编辑的自动编号字段
42
+ const getEditableAutoNumberFields = async (step, formId, formVersion) => {
43
+ const permissions = step.permissions || {};
44
+ const form = await UUFlowManager.getForm(formId);
45
+ const formV = await UUFlowManager.getFormVersion(form, formVersion);
46
+
47
+ const autoNumberFields = [];
48
+
49
+ // 检查字段是否是自动编号字段(通过 formula 判断)
50
+ const isAutoNumberField = (field) => {
51
+ return field.default_value && typeof field.default_value === 'string' && field.default_value.trim().startsWith('auto_number(');
52
+ };
53
+
54
+ // 从 formula 中提取自动编号规则名称
55
+ const extractAutoNumberName = (formula) => {
56
+ // 匹配 auto_number("xxx") 或 auto_number('xxx') 或 auto_number(xxx)
57
+ const match = formula.match(/auto_number\s*\(\s*['"]?([^'"()]+?)['"]?\s*\)/);
58
+ return match ? match[1].trim() : null;
59
+ };
60
+ for (const field of formV.fields || []) {
61
+
62
+ if (field.type === "section") {
63
+ // 处理 section 中的字段
64
+ for (const sectionField of field.fields || []) {
65
+ if (isAutoNumberField(sectionField) && permissions[sectionField.code] === "editable") {
66
+ const autoNumberName = extractAutoNumberName(sectionField.default_value);
67
+ if (autoNumberName) {
68
+ autoNumberFields.push({
69
+ code: sectionField.code,
70
+ auto_number_name: autoNumberName
71
+ });
72
+ }
73
+ }
74
+ }
75
+ } else {
76
+ // 处理普通字段
77
+ if (isAutoNumberField(field) && permissions[field.code] === "editable") {
78
+ const autoNumberName = extractAutoNumberName(field.default_value);
79
+ if (autoNumberName) {
80
+ autoNumberFields.push({
81
+ code: field.code,
82
+ auto_number_name: autoNumberName
83
+ });
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ return autoNumberFields;
90
+ };
91
+
92
+ router.post('/api/workflow/v2/auto_number', requireAuthentication, async function (req, res) {
93
+ try {
94
+ const userSession = req.user;
95
+ const userId = userSession.userId;
96
+ const { instanceId } = req.body;
97
+
98
+ if (!instanceId) {
99
+ return res.status(200).send({
100
+ error: '缺少必填参数: instanceId'
101
+ });
102
+ }
103
+
104
+ // 获取 instance 信息
105
+ const instancesCollection = await objectql.getObject('instances');
106
+ const instance = await instancesCollection.findOne(instanceId);
107
+
108
+ if (!instance) {
109
+ return res.status(200).send({
110
+ error: '未找到该实例'
111
+ });
112
+ }
113
+
114
+ // 获取当前用户的 approve
115
+ const currentApprove = getUserApprove({ instance, userId });
116
+
117
+ if (!currentApprove) {
118
+ return res.status(200).send({
119
+ error: '未找到当前用户的审批节点'
120
+ });
121
+ }
122
+
123
+ // 如果是传阅类型,则不处理自动编号
124
+ if (currentApprove.type === 'cc') {
125
+ return res.status(200).send({
126
+ success: true,
127
+ message: '传阅节点不处理自动编号'
128
+ });
129
+ }
130
+
131
+ // 查找当前 trace 和 approve 的位置
132
+ let traceIndex = -1;
133
+ let approveIndex = -1;
134
+
135
+ for (let i = 0; i < instance.traces.length; i++) {
136
+ const trace = instance.traces[i];
137
+ if (trace.approves && trace.approves.length > 0) {
138
+ const index = trace.approves.findIndex(app => app._id === currentApprove._id);
139
+ if (index !== -1) {
140
+ traceIndex = i;
141
+ approveIndex = index;
142
+ break;
143
+ }
144
+ }
145
+ }
146
+
147
+ if (traceIndex === -1 || approveIndex === -1) {
148
+ return res.status(200).send({
149
+ error: '未找到对应的 trace 和 approve'
150
+ });
151
+ }
152
+
153
+ const currentTrace = instance.traces[traceIndex];
154
+
155
+ // 获取当前 step
156
+ const flowsCollection = await objectql.getObject('flows');
157
+ const flow = await flowsCollection.findOne(instance.flow);
158
+
159
+ if (!flow) {
160
+ return res.status(200).send({
161
+ error: '未找到对应的流程'
162
+ });
163
+ }
164
+
165
+ let step = null;
166
+ if (flow.current && flow.current.steps) {
167
+ step = flow.current.steps.find(s => s._id === currentTrace.step);
168
+ }
169
+
170
+ if (!step && flow.historys) {
171
+ for (const h of flow.historys) {
172
+ step = h.steps.find(s => s._id === currentTrace.step);
173
+ if (step) break;
174
+ }
175
+ }
176
+
177
+ if (!step) {
178
+ return res.status(200).send({
179
+ error: '未找到对应的步骤'
180
+ });
181
+ }
182
+
183
+ // 获取可编辑的自动编号字段
184
+ const autoNumberFields = await getEditableAutoNumberFields(step, instance.form, instance.form_version);
185
+
186
+ if (autoNumberFields.length === 0) {
187
+ return res.status(200).send({
188
+ success: true,
189
+ message: '当前步骤没有可编辑的自动编号字段'
190
+ });
191
+ }
192
+
193
+ // 为每个自动编号字段生成编号
194
+ const result = {};
195
+ const updateObj = {};
196
+
197
+ for (const field of autoNumberFields) {
198
+ // 检查字段是否已有值
199
+ if (currentApprove.values && currentApprove.values[field.code]) {
200
+ result[field.code] = currentApprove.values[field.code];
201
+ continue;
202
+ }
203
+
204
+ // 生成自动编号
205
+ const autoNumber = await instanceNumberBuilder(instance.space, field.auto_number_name);
206
+
207
+ if (autoNumber && autoNumber._error) {
208
+ throw autoNumber._error;
209
+ }
210
+
211
+ result[field.code] = autoNumber;
212
+ updateObj[`traces.${traceIndex}.approves.${approveIndex}.values.${field.code}`] = autoNumber;
213
+ }
214
+
215
+ // 更新 approve 的 values
216
+ if (Object.keys(updateObj).length > 0) {
217
+ await instancesCollection.update(instanceId, updateObj);
218
+ }
219
+
220
+ res.status(200).send({
221
+ success: true,
222
+ fields: result
223
+ });
224
+ } catch (error) {
225
+ console.error(error);
226
+ res.status(200).send({
227
+ error: error.message
228
+ });
229
+ }
230
+ });
231
+
232
+
233
+ exports.default = router;
@@ -0,0 +1,21 @@
1
+ const express = require("express");
2
+ const router = express.Router();
3
+ const { requireAuthentication } = require("@steedos/auth");
4
+ const { getObject } = require('@steedos/objectql')
5
+ const _ = require('lodash');
6
+
7
+ router.delete('/api/workflow/v2/attachment/:instanceId/:id', requireAuthentication, async function (req, res) {
8
+ try {
9
+ const { instanceId, id} = req.params;
10
+ await getObject('cfs_instances_filerecord').delete(id);
11
+ res.status(200).send({
12
+ status: 0
13
+ })
14
+ } catch (error) {
15
+ console.error(error);
16
+ res.status(200).send({
17
+ error: error.message
18
+ });
19
+ }
20
+ });
21
+ exports.default = router;
@@ -30,6 +30,8 @@ router.post('/api/workflow/v2/set_have_read', requireAuthentication, async funct
30
30
  success: true
31
31
  });
32
32
  }
33
+
34
+
33
35
  const db = {
34
36
  instances: await getCollection('instances'),
35
37
  instance_tasks: await getCollection('instance_tasks')
@@ -40,7 +42,8 @@ router.post('/api/workflow/v2/set_have_read', requireAuthentication, async funct
40
42
  fields: {
41
43
  "instance": 1,
42
44
  "is_read": 1,
43
- "type": 1
45
+ "type": 1,
46
+ space: 1
44
47
  }
45
48
  });
46
49
  if(!instance_task){
@@ -48,6 +51,21 @@ router.post('/api/workflow/v2/set_have_read', requireAuthentication, async funct
48
51
  success: true
49
52
  });
50
53
  }
54
+
55
+
56
+ // 更新当前用户相关的通知为已读
57
+ const notificationsObj = objectql.getObject('notifications');
58
+ await notificationsObj.directUpdateMany([
59
+ ['owner', '=', userId],
60
+ ['is_read', '!=', true],
61
+ ['related_to.o', '=', 'instances'],
62
+ ['related_to.ids', '=', instance_task.instance]
63
+ ], {
64
+ is_read: true
65
+ });
66
+ console.log(`b6-microservice.broadcast`, {name: '$notification.users', data: {tenantId: instance_task.space, users: [userId], message: null}});
67
+ await broker.call('b6-microservice.broadcast', {name: '$notification.users', data: {tenantId: instance_task.space, users: [userId], message: null}})
68
+
51
69
  if (instance_task.is_read){
52
70
  return res.status(200).send({
53
71
  success: true
@@ -63,7 +81,7 @@ router.post('/api/workflow/v2/set_have_read', requireAuthentication, async funct
63
81
  }
64
82
  else{
65
83
  await set_approve_have_read(ins._id, myApprove.trace, myApprove._id, { userId });
66
- }
84
+ }
67
85
  return res.status(200).send({
68
86
  success: true
69
87
  });
@@ -450,6 +450,7 @@ const FlowversionAPI = {
450
450
  let query = req.query;
451
451
  let instance_id = query.instance_id;
452
452
  let direction = query.direction || 'TD';
453
+ let noAutoFit = query.noAutoFit === '1' || query.noAutoFit === 'true';
453
454
  const allowDirections = ['TB', 'BT', 'RL', 'LR', 'TD'];
454
455
  if (!_.includes(allowDirections, direction)) {
455
456
  return this.writeResponse(res, 500, "Invalid direction. The value of direction should be in ['TB', 'BT', 'RL', 'LR', 'TD']");
@@ -619,6 +620,7 @@ const FlowversionAPI = {
619
620
  });
620
621
  $(function(){
621
622
  var graphNodes = ${JSON.stringify(graphSyntax)};
623
+ var noAutoFit = ${noAutoFit ? 'true' : 'false'};
622
624
  //方便前面可以通过调用mermaid.currentNodes调式,特意增加currentNodes属性。
623
625
  mermaid.currentNodes = graphNodes;
624
626
  var graphSyntax = graphNodes.join("\\n");
@@ -634,12 +636,30 @@ const FlowversionAPI = {
634
636
  callback(id);
635
637
  }
636
638
  bindFunctions(element[0]);
639
+
640
+ // 修复SVG左侧内容被裁剪的问题
641
+ if(!noAutoFit) {
642
+ setTimeout(function() {
643
+ var svg = $("svg");
644
+ if(svg.length > 0) {
645
+ // 关键修复:允许SVG溢出显示,解决左侧节点被裁剪的问题
646
+ svg.css('overflow', 'visible');
647
+ }
648
+ }, 50);
649
+ }
637
650
  };
638
651
  mermaid.render(id, graphSyntax, insertSvg, element[0]);
652
+
653
+ var currentZoom = 1;
639
654
  var zoomSVG = function(zoom){
640
- var currentWidth = $("svg").width();
641
- var newWidth = currentWidth * zoom;
642
- $("svg").css("maxWidth",newWidth + "px").width(newWidth);
655
+ var svg = $("svg");
656
+ currentZoom = currentZoom * zoom;
657
+ var baseWidth = svg.data('baseWidth') || svg.width();
658
+ if(!svg.data('baseWidth')) {
659
+ svg.data('baseWidth', baseWidth);
660
+ }
661
+ var newWidth = baseWidth * currentZoom;
662
+ svg.css("maxWidth",newWidth + "px").width(newWidth);
643
663
  }
644
664
  //支持鼠标滚轮缩放画布
645
665
  $(window).on("mousewheel",function(event){
@@ -19,6 +19,11 @@ router.post('/api/workflow/v2/instance/upgrade', requireAuthentication, async fu
19
19
  'flow','form','applicant_name','applicant_organization','applicant_organization_fullname','applicant_organization_name',
20
20
  'code', 'flow_version', 'form_version', 'submit_date', 'flow_name', 'code', 'traces', 'state'
21
21
  ]});
22
+ if(!record){
23
+ return res.status(200).send({
24
+ instance: false
25
+ });
26
+ }
22
27
  if(record.state !== 'draft'){
23
28
  return res.status(200).send({
24
29
  instance: true