@steedos-labs/plugin-workflow 3.0.12 → 3.0.13

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.
@@ -6,8 +6,164 @@ icon_slds: approval
6
6
  is_creator: true
7
7
  mobile: true
8
8
  sort: 10
9
- tabs:
10
- - object_instance_tasks
9
+ # tabs:
10
+ # - object_instance_tasks
11
+
12
+ showSidebar: true
13
+ enable_nav_schema: true
14
+ nav_schema: {
15
+ "type": "wrapper",
16
+ "size": "none",
17
+ "body": [
18
+ {
19
+ "type": "steedos-app-menu",
20
+ "stacked": true,
21
+ "appId": "approve_workflow",
22
+ "ignoreNavSchema": true
23
+ },
24
+ {
25
+ "type": "wrapper",
26
+ "size": "none",
27
+ "className": "instances-sidebar-wrapper mt-1",
28
+ "body": [
29
+ {
30
+ "type": "service",
31
+ "id": "u:instanceNav",
32
+ "className": "bg-none",
33
+ "onEvent": {
34
+ "@data.changed.instances": {
35
+ "actions": [
36
+ {
37
+ "actionType": "reload"
38
+ }
39
+ ]
40
+ }
41
+ },
42
+ "body": [
43
+ {
44
+ "type": "button",
45
+ "label": "刷新",
46
+ "className": "instance-nav-reload hidden",
47
+ "onEvent": {
48
+ "click": {
49
+ "actions": [
50
+ {
51
+ "actionType": "reload",
52
+ "componentId": "u:instanceNav"
53
+ }
54
+ ]
55
+ }
56
+ }
57
+ },
58
+ {
59
+ "type": "input-tree",
60
+ "name": "tree",
61
+ "treeContainerClassName": "h-full",
62
+ "className": "instance-box-tree h-full w-full p-0",
63
+ "id": "u:9f3dd961ca12",
64
+ "stacked": true,
65
+ "multiple": false,
66
+ "enableNodePath": false,
67
+ "hideRoot": true,
68
+ "showIcon": true,
69
+ "initiallyOpen": false,
70
+ "virtualThreshold": 100000,
71
+ "value": "${value}",
72
+ "size": "md",
73
+ "onEvent": {
74
+ "change": {
75
+ "actions": [
76
+ {
77
+ "actionType": "setValue",
78
+ "componentId": "instances_list_service",
79
+ "args": {
80
+ "value": {
81
+ "isFlowDataDone": false
82
+ }
83
+ }
84
+ },
85
+ {
86
+ "actionType": "custom",
87
+ "script": "//获取上一次的flowId和categoryId\nconst lastFlowId = event.data.flowId;\nconst lastCategoryId = event.data.categoryId;\n//从value中获取最新的flowId\nvar flowIdRegex = /&flowId=([^&]+)/;\nvar flowIdMatch = event.data.value.match(flowIdRegex);\nconst flowId = flowIdMatch && flowIdMatch.length > 0 ? flowIdMatch[1] : \"\";\n//从value中获取最新的categoryId\nvar categoryIdRegex = /&categoryId=([^&]+)/;\nvar categoryIdMatch = event.data.value.match(categoryIdRegex);\nconst categoryId = categoryIdMatch && categoryIdMatch.length > 0 ? categoryIdMatch[1] : \"\";\n//获取上一次的listname和最新的listname\nconst lastListName = event.data.listName;\nconst listName = event.data.value.split('?')[0].split('instances/grid/')[1];\n//切换流程时清除过滤条件\nif (lastListName == \"monitor\" && listName == \"monitor\" && (flowId != lastFlowId || categoryId != lastCategoryId)) {\n listViewPropsStoreKey = window.location.pathname + \"/crud\";\n sessionStorage.removeItem(listViewPropsStoreKey);\n sessionStorage.removeItem(listViewPropsStoreKey + \"/query\");\n}"
88
+ },
89
+ {
90
+ "actionType": "setValue",
91
+ "componentId": "instances_list_service",
92
+ "args": {
93
+ "value": {
94
+ "additionalFilters": [
95
+ "${event.data.options.name}",
96
+ "=",
97
+ "${event.data.options.value}"
98
+ ]
99
+ }
100
+ },
101
+ "expression": "${event.data.options.level>=10}"
102
+ },
103
+ {
104
+ "args": {
105
+ "link": "${event.data.value}",
106
+ "blank": false
107
+ },
108
+ "actionType": "link"
109
+ }
110
+ ]
111
+ }
112
+ },
113
+ "menuTpl": {
114
+ "type": "wrapper",
115
+ "className": "flex flex-row p-0 m-0",
116
+ "body": [
117
+ {
118
+ "type": "tpl",
119
+ "className": "flex-1 w-6/12",
120
+ "tpl": "${label}",
121
+ "id": "u:9dee51f00db4"
122
+ },
123
+ {
124
+ "type": "tpl",
125
+ "className": "-mx-11 ",
126
+ "tpl": "",
127
+ "badge": {
128
+ "className": "h-0",
129
+ "offset": [
130
+ -20,
131
+ 12
132
+ ],
133
+ "mode": "text",
134
+ "text": "${tag | toInt}",
135
+ "overflowCount": 999
136
+ },
137
+ "id": "u:2329cd1fecc2"
138
+ }
139
+ ],
140
+ "id": "u:545154bcc334"
141
+ },
142
+ "unfoldedLevel": 2,
143
+ "source": "${options}"
144
+ }
145
+ ],
146
+ "api": {
147
+ "method": "get",
148
+ "url": "${context.rootUrl}/api/${appId}/workflow/nav",
149
+ "headers": {
150
+ "Authorization": "Bearer ${context.tenantId},${context.authToken}"
151
+ },
152
+ "messages": {},
153
+ "adaptor": "payload.data.value = window.location.pathname + decodeURIComponent(window.location.search); return payload;"
154
+ },
155
+ "messages": {},
156
+ "dsType": "api"
157
+ }
158
+ ],
159
+ "isFixedHeight": false,
160
+ "isFixedWidth": false
161
+ }
162
+ ],
163
+ "isFixedHeight": false,
164
+ "isFixedWidth": false
165
+ }
166
+
11
167
  admin_menus:
12
168
  # 分部管理员可以创建和设置流程及相关参数。
13
169
  - _id: menu_workflow
@@ -86,4 +242,4 @@ admin_menus:
86
242
  permission_sets:
87
243
  - admin
88
244
  object_name: object_workflows
89
- parent: menu_workflow
245
+ parent: menu_workflow
@@ -787,7 +787,7 @@ getHandlersManager.getHandlers = async function (instance_id, step_id, login_use
787
787
  users = applicantSuperiors;
788
788
  }
789
789
 
790
- await excuteTriggers(login_user_id, flow_id, instance_id, current_step, users);
790
+ await excuteTriggers({ when: 'cacluateNextStepUsers', userId: login_user_id, flowId: flow_id, insId: instance_id, nextStep: current_step, nextUserIds: users });
791
791
  return users;
792
792
  };
793
793
 
@@ -0,0 +1,84 @@
1
+ /*
2
+ * @Author: sunhaolin@hotoa.com
3
+ * @Date: 2022-12-22 15:02:53
4
+ * @LastEditors: sunhaolin@hotoa.com
5
+ * @LastEditTime: 2022-12-22 15:03:32
6
+ * @Description:
7
+ */
8
+ var _eval;
9
+
10
+ _eval = require('eval');
11
+ const { getCollection } = require('../utils/collection');
12
+ const _ = require('lodash');
13
+ module.exports = {
14
+ instanceNumberBuilder: async function (spaceId, name) {
15
+ var _NUMBER, _YYYY, context, date, e, numberRules, padding, res, rules, script;
16
+ const db = await getCollection("instance_number_rules");
17
+ numberRules = await db.findOne({
18
+ space: spaceId,
19
+ name: name
20
+ });
21
+ if (!numberRules) {
22
+ throw new Meteor.Error('error!', `${name}`);
23
+ }
24
+ date = new Date();
25
+ context = {};
26
+ context._ = _;
27
+ _YYYY = date.getFullYear();
28
+ _NUMBER = (numberRules.number || 0) + 1;
29
+ context.YYYY = _.clone(_YYYY);
30
+ context.MM = date.getMonth() + 1;
31
+ context.mm = date.getMonth() + 1;
32
+ if (context.MM < 10) {
33
+ context.MM = "0" + context.MM;
34
+ }
35
+ context.DD = date.getDate();
36
+ context.dd = date.getDate();
37
+ if (context.DD < 10) {
38
+ context.DD = "0" + context.DD;
39
+ }
40
+ if (context.YYYY !== numberRules.year) {
41
+ _NUMBER = numberRules.first_number || 1;
42
+ }
43
+ let pLenConst = process.env.STEEDOS_WORKFLOW_INSTANCE_NUMBER_RULES_PADDING_LEN
44
+ let paddingLen = 5
45
+ if (pLenConst) {
46
+ const _paddingLen = parseInt(pLenConst);
47
+ if (_paddingLen >= 0) { // 处理 _paddingLen 值为NaN的情况
48
+ paddingLen = _paddingLen
49
+ }
50
+ }
51
+ padding = function (num, length) {
52
+ if (length == 0) {
53
+ return num;
54
+ }
55
+ var diff, len;
56
+ len = (num + '').length;
57
+ diff = length - len;
58
+ if (diff > 0) {
59
+ return Array(diff + 1).join('0') + num;
60
+ }
61
+ return num;
62
+ };
63
+ context.NUMBER = padding(_.clone(_NUMBER), paddingLen);
64
+ rules = numberRules.rules.replace("{YYYY}", "' + YYYY + '").replace("{MM}", "' + MM + '").replace("{NUMBER}", "' + NUMBER + '");
65
+ script = `var newNo = '${rules}'; exports.newNo = newNo`;
66
+ try {
67
+ res = _eval(script, "newNo", context, false).newNo;
68
+ await db.update({
69
+ _id: numberRules._id
70
+ }, {
71
+ $set: {
72
+ year: _YYYY,
73
+ number: _NUMBER
74
+ }
75
+ });
76
+ } catch (error) {
77
+ e = error;
78
+ res = {
79
+ _error: e
80
+ };
81
+ }
82
+ return res;
83
+ }
84
+ };
@@ -40,6 +40,39 @@ async function _count(query) {
40
40
  return await getObject('instance_tasks').count(query);
41
41
  }
42
42
 
43
+ /**
44
+ * 更新相关通知为已读
45
+ * @param {String} instance 申请单ID
46
+ * @param {String} handler 处理人ID
47
+ */
48
+ async function _updateNotificationsAsRead(instance, handler) {
49
+ const notificationsObj = getObject('notifications')
50
+ await notificationsObj.directUpdateMany([
51
+ ['owner', '=', handler],
52
+ ['is_read', '!=', true],
53
+ ['related_to.o', '=', 'instances'],
54
+ ['related_to.ids', '=', instance]
55
+ ], {
56
+ is_read: true
57
+ })
58
+ }
59
+
60
+ /**
61
+ * 更新相关通知为已删除
62
+ * @param {String} instance 申请单ID
63
+ * @param {String} handler 处理人ID
64
+ */
65
+ async function _updateNotificationsAsDeleted(instance, handler) {
66
+ const notificationsObj = getObject('notifications')
67
+ await notificationsObj.directUpdateMany([
68
+ ['owner', '=', handler],
69
+ ['related_to.o', '=', 'instances'],
70
+ ['related_to.ids', '=', instance]
71
+ ], {
72
+ is_deleted: true
73
+ })
74
+ }
75
+
43
76
  /**
44
77
  * 新增instance_tasks记录
45
78
  * @param {String} insId 申请单ID
@@ -90,6 +123,16 @@ async function update_instance_tasks(insId, traceId, approveId, doc) {
90
123
  delete taskDoc._id
91
124
  }
92
125
  const result = await _update(approveId, taskDoc)
126
+
127
+ // 如果任务已完成,更新相关通知为已读
128
+ if (taskDoc.is_finished) {
129
+ const instance = taskDoc.instance || insId
130
+ const handler = taskDoc.handler
131
+ if (instance && handler) {
132
+ await _updateNotificationsAsRead(instance, handler)
133
+ }
134
+ }
135
+
93
136
  return result
94
137
  }
95
138
 
@@ -114,6 +157,15 @@ async function update_many_instance_tasks(insId, traceId, approveIds, keys) {
114
157
  }
115
158
  const result = await _update(aId, taskDoc)
116
159
  results.push(result)
160
+
161
+ // 如果任务已完成,更新相关通知为已读
162
+ if (taskDoc.is_finished) {
163
+ const instance = taskDoc.instance || insId
164
+ const handler = taskDoc.handler
165
+ if (instance && handler) {
166
+ await _updateNotificationsAsRead(instance, handler)
167
+ }
168
+ }
117
169
  }
118
170
  return results
119
171
  }
@@ -124,7 +176,18 @@ async function update_many_instance_tasks(insId, traceId, approveIds, keys) {
124
176
  * @returns 1
125
177
  */
126
178
  async function remove_instance_tasks(approveId) {
179
+ // 删除前先获取任务信息用于更新通知
180
+ const taskDoc = await getObject('instance_tasks').findOne(approveId)
127
181
  const result = await _remove(approveId)
182
+
183
+ // 删除任务后,更新相关通知为已删除
184
+ if (taskDoc) {
185
+ const { instance, handler } = taskDoc
186
+ if (instance && handler) {
187
+ await _updateNotificationsAsDeleted(instance, handler)
188
+ }
189
+ }
190
+
128
191
  return result
129
192
  }
130
193
 
@@ -136,8 +199,18 @@ async function remove_instance_tasks(approveId) {
136
199
  async function remove_many_instance_tasks(approveIds) {
137
200
  const results = []
138
201
  for (const aId of approveIds) {
202
+ // 删除前先获取任务信息用于更新通知
203
+ const taskDoc = await getObject('instance_tasks').findOne(aId)
139
204
  const r = await _remove(aId)
140
205
  results.push(r)
206
+
207
+ // 删除任务后,更新相关通知为已删除
208
+ if (taskDoc) {
209
+ const { instance, handler } = taskDoc
210
+ if (instance && handler) {
211
+ await _updateNotificationsAsDeleted(instance, handler)
212
+ }
213
+ }
141
214
  }
142
215
  return results
143
216
  }
@@ -153,11 +226,16 @@ async function remove_instance_tasks_by_instance_id(insId) {
153
226
  filters: [
154
227
  ['instance', '=', insId]
155
228
  ],
156
- fields: ['_id']
229
+ fields: ['_id', 'instance', 'handler']
157
230
  })
158
231
  for (const t of taskDocs) {
159
232
  const r = await _remove(t._id)
160
233
  results.push(r)
234
+
235
+ // 删除任务后,更新相关通知为已删除
236
+ if (t.instance && t.handler) {
237
+ await _updateNotificationsAsDeleted(t.instance, t.handler)
238
+ }
161
239
  }
162
240
  return results
163
241
  }
@@ -557,7 +557,7 @@ UUFlowManager.isFlowSpaceMatched = function (flow, space_id) {
557
557
  }
558
558
  };
559
559
 
560
- const isAmisFormula = (formula) => {
560
+ UUFlowManager.isAmisFormula = (formula) => {
561
561
  // 有${}包裹的表达式就识别为amis公式
562
562
  return /\$\{.+\}/.test(formula);
563
563
  };
@@ -796,6 +796,57 @@ function isSkipStep(instance, step) {
796
796
  return instance.skip_steps?.includes(step._id);
797
797
  }
798
798
 
799
+ UUFlowManager.initFormulaValues = async function (instance, values) {
800
+ // Get values for condition calculation
801
+ let __values = values !== undefined ? values : UUFlowManager.getUpdatedValues(instance);
802
+
803
+ // Get current and start approve
804
+ let current_approve = null;
805
+ let start_approve = null;
806
+
807
+ for (const trace of instance.traces) {
808
+ if (trace.is_finished === false) {
809
+ current_approve = trace.approves[0];
810
+ }
811
+ if (!trace.previous_trace_ids || trace.previous_trace_ids.length === 0) {
812
+ start_approve = trace.approves[0];
813
+ }
814
+ }
815
+
816
+ // Set variables for condition evaluation
817
+ __values["applicant"] = {
818
+ roles: await UUFlowManager.getUserRoles(instance.applicant, instance.space),
819
+ name: instance.applicant_name,
820
+ organization: {
821
+ fullname: instance.applicant_organization_fullname,
822
+ name: instance.applicant_organization_name
823
+ },
824
+ id: instance.applicant
825
+ };
826
+
827
+ __values["submitter"] = {
828
+ roles: await UUFlowManager.getUserRoles(start_approve.handler, instance.space),
829
+ name: start_approve.handler_name,
830
+ organization: {
831
+ fullname: start_approve.handler_organization_fullname,
832
+ name: start_approve.handler_organization_name
833
+ },
834
+ id: start_approve.handler
835
+ };
836
+
837
+ __values["approver"] = {
838
+ roles: await UUFlowManager.getUserRoles(current_approve.handler, instance.space),
839
+ name: current_approve.handler_name,
840
+ organization: {
841
+ fullname: current_approve.handler_organization_fullname,
842
+ name: current_approve.handler_organization_name
843
+ },
844
+ id: current_approve.handler
845
+ };
846
+
847
+ return __values;
848
+ }
849
+
799
850
  /**
800
851
  * Get next steps for instance
801
852
  * @param {Object} instance - Instance object
@@ -810,49 +861,8 @@ UUFlowManager.getNextSteps = async function (instance, flow, step, judge, values
810
861
  let nextSteps = [];
811
862
 
812
863
  if (step_type === "condition") {
813
- // Get values for condition calculation
814
- let __values = values !== undefined ? values : UUFlowManager.getUpdatedValues(instance);
815
-
816
- // Get current and start approve
817
- let current_approve = null;
818
- let start_approve = null;
819
-
820
- for (const trace of instance.traces) {
821
- if (trace.is_finished === false) {
822
- current_approve = trace.approves[0];
823
- }
824
- if (!trace.previous_trace_ids || trace.previous_trace_ids.length === 0) {
825
- start_approve = trace.approves[0];
826
- }
827
- }
828
-
829
- // Set variables for condition evaluation
830
- __values["applicant"] = {
831
- roles: await UUFlowManager.getUserRoles(instance.applicant, instance.space),
832
- name: instance.applicant_name,
833
- organization: {
834
- fullname: instance.applicant_organization_fullname,
835
- name: instance.applicant_organization_name
836
- }
837
- };
838
-
839
- __values["submitter"] = {
840
- roles: await UUFlowManager.getUserRoles(start_approve.handler, instance.space),
841
- name: start_approve.handler_name,
842
- organization: {
843
- fullname: start_approve.handler_organization_fullname,
844
- name: start_approve.handler_organization_name
845
- }
846
- };
847
-
848
- __values["approver"] = {
849
- roles: await UUFlowManager.getUserRoles(current_approve.handler, instance.space),
850
- name: current_approve.handler_name,
851
- organization: {
852
- fullname: current_approve.handler_organization_fullname,
853
- name: current_approve.handler_organization_name
854
- }
855
- };
864
+
865
+ const __values = await UUFlowManager.initFormulaValues(instance, values);
856
866
 
857
867
  // Get form and form version
858
868
  const form = await UUFlowManager.getForm(instance.form);
@@ -875,7 +885,7 @@ UUFlowManager.getNextSteps = async function (instance, flow, step, judge, values
875
885
  if (step_line.state === "submitted") {
876
886
  let step_line_condition = step_line.condition;
877
887
  let allow = false;
878
- if (isAmisFormula(step_line_condition)) {
888
+ if (UUFlowManager.isAmisFormula(step_line_condition)) {
879
889
  allow = UUFlowManager.calculateConditionWithAmis(__values, step_line_condition);
880
890
  }
881
891
  else {
@@ -37,9 +37,9 @@ amis_schema: |-
37
37
  {
38
38
  "type": "tpl",
39
39
  "tpl": "当前步骤: <p class='font-medium inline'>${record.currentStep.name}</p>",
40
- "inline": true,
41
- "wrapperComponent": "",
42
- "id": "u:1f7dd93080d3"
40
+ "inline": false,
41
+ "id": "u:1f7dd93080d3",
42
+ "className": "m-b-sm"
43
43
  },
44
44
  {
45
45
  "type": "steedos-select-user",
@@ -57,7 +57,8 @@ amis_schema: |-
57
57
  "id": "u:bffe87a42168",
58
58
  "minRows": 3,
59
59
  "maxRows": 20,
60
- "placeholder": "请填写重定位的理由"
60
+ "placeholder": "请填写重定位的理由",
61
+ "className": "m-t-sm"
61
62
  }
62
63
  ],
63
64
  "id": "u:1779a48caedb",
@@ -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": "刷新",