@steedos-labs/plugin-workflow 3.0.54 → 3.0.56

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 (37) hide show
  1. package/designer/dist/amis-renderer/amis-renderer.css +1 -1
  2. package/designer/dist/amis-renderer/amis-renderer.js +1 -1
  3. package/designer/dist/assets/{index-DXnimQAi.js → index-B9naSD1C.js} +173 -173
  4. package/designer/dist/assets/index-DEEcIiu0.css +1 -0
  5. package/designer/dist/index.html +2 -2
  6. package/main/default/manager/push_manager.js +93 -113
  7. package/main/default/manager/uuflow_manager.js +151 -47
  8. package/main/default/objects/categories/buttons/badge_recalc.button.yml +44 -0
  9. package/main/default/objects/instance_tasks/listviews/inbox.listview.yml +1 -1
  10. package/main/default/objects/instance_tasks/listviews/outbox.listview.yml +1 -2
  11. package/main/default/objects/instances/buttons/instance_export.button.js +10 -0
  12. package/main/default/objects/instances/buttons/instance_export.button.yml +83 -0
  13. package/main/default/objects/instances/listviews/completed.listview.yml +1 -2
  14. package/main/default/objects/instances/listviews/draft.listview.yml +1 -2
  15. package/main/default/objects/instances/listviews/monitor.listview.yml +1 -1
  16. package/main/default/objects/instances/listviews/pending.listview.yml +1 -2
  17. package/main/default/pages/page_instance_print.page.amis.json +6 -2
  18. package/main/default/routes/api_workflow_ai_form_design.router.js +9 -3
  19. package/main/default/routes/api_workflow_ai_form_design_stream.router.js +9 -3
  20. package/main/default/routes/api_workflow_export.router.js +97 -152
  21. package/main/default/routes/api_workflow_instance_forward.router.js +15 -1
  22. package/main/default/routes/api_workflow_instance_permissions.router.js +2 -2
  23. package/main/default/routes/api_workflow_nav.router.js +7 -1
  24. package/main/default/routes/api_workflow_next_step.router.js +1 -1
  25. package/main/default/services/instance.service.js +1 -1
  26. package/main/default/utils/business_hours.js +210 -0
  27. package/main/default/utils/business_timeout.js +211 -0
  28. package/package.json +1 -1
  29. package/package.service.js +9 -1
  30. package/public/amis-renderer/amis-renderer.css +1 -1
  31. package/public/amis-renderer/amis-renderer.js +1 -1
  32. package/public/workflow/index.css +7 -2
  33. package/src/rests/badgeRecalcConsole.js +593 -0
  34. package/src/rests/badgeRecalcExecute.js +308 -0
  35. package/src/rests/index.js +2 -0
  36. package/src/timeout_auto_submit.js +81 -0
  37. package/designer/dist/assets/index-xR8ApdWL.css +0 -1
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ /**
3
+ * Business timeout calculation utilities.
4
+ * Ported from steedos-platform packages/core/src/holidays/business_timeout.ts
5
+ *
6
+ * Uses @steedos/objectql (already a project dependency) to access holidays and
7
+ * business_hours objects, instead of @steedos/core.
8
+ */
9
+ const moment = require('moment');
10
+ const _ = require('lodash');
11
+ const { getObject } = require('@steedos/objectql');
12
+ const {
13
+ BusinessHoursCheckedType,
14
+ getBusinessHoursPerDay,
15
+ computeIsBusinessDate,
16
+ computeNextBusinessDate
17
+ } = require('./business_hours');
18
+
19
+ // Simple in-memory cache for holidays and business hours with TTL
20
+ const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
21
+ const _holidaysCache = {}; // key: `${spaceId}-${year}`, value: { values: [...], timestamp }
22
+ const _businessHoursCache = {}; // key: spaceId, value: { records: [...], timestamp }
23
+
24
+ /**
25
+ * 从数据库中取出指定工作区内的节假日数据,支持缓存
26
+ * @param {string} spaceId
27
+ * @param {Array<number>} years
28
+ */
29
+ async function getHolidays(spaceId, years) {
30
+ const holidaysFields = ["name", "type", "date", "adjusted_to", "space"];
31
+ for (const year of years) {
32
+ const cacheKey = `${spaceId}-${year}`;
33
+ const cached = _holidaysCache[cacheKey];
34
+ if (!cached || (Date.now() - cached.timestamp > CACHE_TTL_MS)) {
35
+ const yearStartDate = moment.utc(`${year}-01-01T00:00:00Z`).toDate();
36
+ const yearEndDate = moment.utc(`${year}-12-31T23:59:59Z`).toDate();
37
+ const objectHolidays = getObject("holidays");
38
+ const holidaysRecords = await objectHolidays.find({
39
+ filters: [["space", "=", spaceId], ["date", "between", [yearStartDate, yearEndDate]]],
40
+ fields: holidaysFields
41
+ });
42
+ _holidaysCache[cacheKey] = {
43
+ space: spaceId,
44
+ year: year,
45
+ values: (holidaysRecords && holidaysRecords.length) ? holidaysRecords : [],
46
+ timestamp: Date.now()
47
+ };
48
+ }
49
+ }
50
+ const result = [];
51
+ for (const year of years) {
52
+ const cacheKey = `${spaceId}-${year}`;
53
+ if (_holidaysCache[cacheKey] && _holidaysCache[cacheKey].values) {
54
+ result.push(..._holidaysCache[cacheKey].values);
55
+ }
56
+ }
57
+ return result;
58
+ }
59
+
60
+ /**
61
+ * 从数据库中取出指定工作区内的默认工作时间数据,支持缓存
62
+ * @param {string} spaceId
63
+ */
64
+ async function getDefaultBusinessHours(spaceId) {
65
+ const cached = _businessHoursCache[spaceId];
66
+ if (cached && (Date.now() - cached.timestamp <= CACHE_TTL_MS)) {
67
+ return cached.records;
68
+ }
69
+ const objectBusinessHours = getObject("business_hours");
70
+ const records = await objectBusinessHours.find({
71
+ filters: [["space", "=", spaceId], ["is_default", "=", true]],
72
+ fields: ["name", "start", "end", "lunch_start", "lunch_end", "utc_offset", "working_days", "space"]
73
+ });
74
+ if (records && records.length) {
75
+ _businessHoursCache[spaceId] = { records, timestamp: Date.now() };
76
+ }
77
+ return records || [];
78
+ }
79
+
80
+ /**
81
+ * 计算以某个时间点开始,超时几个小时后,超时时间点是什么时候 (纯计算,不查数据库)
82
+ * @param {Date} start
83
+ * @param {number} timeoutHours
84
+ * @param {Array} holidays
85
+ * @param {object} businessHours
86
+ * @param {number} digitsForHours
87
+ * @returns {Date}
88
+ */
89
+ function computeTimeoutDateWithoutHolidays(start, timeoutHours, holidays, businessHours, digitsForHours = 2) {
90
+ if (timeoutHours <= 0) {
91
+ return start;
92
+ }
93
+ const utcOffset = businessHours.utc_offset;
94
+ const businessHoursPerDay = getBusinessHoursPerDay(businessHours, digitsForHours);
95
+ const startBhct = computeIsBusinessDate(start, holidays, businessHours, digitsForHours);
96
+ let offsetMinutes = 0;
97
+ let startMoment = moment.utc(start);
98
+ let startClosingMoment = moment.utc(start);
99
+ if (startBhct <= 0) {
100
+ if (startBhct === BusinessHoursCheckedType.offLunch) {
101
+ startMoment.hours(businessHoursPerDay.lunchEndValue.hours - utcOffset);
102
+ startMoment.minutes(businessHoursPerDay.lunchEndValue.minutes);
103
+ start = startMoment.toDate();
104
+ } else if (startBhct === BusinessHoursCheckedType.offAm) {
105
+ startMoment.hours(businessHoursPerDay.startValue.hours - utcOffset);
106
+ startMoment.minutes(businessHoursPerDay.startValue.minutes);
107
+ start = startMoment.toDate();
108
+ startClosingMoment = moment.utc(start);
109
+ startClosingMoment.hours(businessHoursPerDay.lunchStartValue.hours - utcOffset);
110
+ startClosingMoment.minutes(businessHoursPerDay.lunchStartValue.minutes);
111
+ offsetMinutes = startClosingMoment.diff(startMoment, 'minute');
112
+ startMoment.hours(businessHoursPerDay.lunchEndValue.hours - utcOffset);
113
+ startMoment.minutes(businessHoursPerDay.lunchEndValue.minutes);
114
+ start = startMoment.toDate();
115
+ } else {
116
+ const nextBusinessDate = computeNextBusinessDate(start, holidays, businessHours, digitsForHours);
117
+ start = nextBusinessDate.start;
118
+ startMoment = moment.utc(start);
119
+ startClosingMoment = moment.utc(start);
120
+ startClosingMoment.hours(businessHoursPerDay.lunchStartValue.hours - utcOffset);
121
+ startClosingMoment.minutes(businessHoursPerDay.lunchStartValue.minutes);
122
+ offsetMinutes = startClosingMoment.diff(startMoment, 'minute');
123
+ startMoment.hours(businessHoursPerDay.lunchEndValue.hours - utcOffset);
124
+ startMoment.minutes(businessHoursPerDay.lunchEndValue.minutes);
125
+ start = startMoment.toDate();
126
+ }
127
+ } else {
128
+ if (startBhct === BusinessHoursCheckedType.onAm) {
129
+ startClosingMoment.hours(businessHoursPerDay.lunchStartValue.hours - utcOffset);
130
+ startClosingMoment.minutes(businessHoursPerDay.lunchStartValue.minutes);
131
+ offsetMinutes = startClosingMoment.diff(startMoment, 'minute');
132
+ startMoment.hours(businessHoursPerDay.lunchEndValue.hours - utcOffset);
133
+ startMoment.minutes(businessHoursPerDay.lunchEndValue.minutes);
134
+ start = startMoment.toDate();
135
+ } else {
136
+ startMoment = moment.utc(start);
137
+ }
138
+ }
139
+ startClosingMoment.hours(businessHoursPerDay.endValue.hours - utcOffset);
140
+ startClosingMoment.minutes(businessHoursPerDay.endValue.minutes);
141
+ offsetMinutes += startClosingMoment.diff(startMoment, 'minute');
142
+ const timeoutMinutes = timeoutHours * 60;
143
+ if (timeoutMinutes <= offsetMinutes) {
144
+ let subMinutes = offsetMinutes - timeoutMinutes;
145
+ if (subMinutes > 0) {
146
+ if (subMinutes >= businessHoursPerDay.computedPmMinutes) {
147
+ subMinutes += businessHoursPerDay.computedLunchMinutes;
148
+ }
149
+ startClosingMoment.subtract(subMinutes, 'm');
150
+ }
151
+ return startClosingMoment.toDate();
152
+ } else {
153
+ let nextMoment = startClosingMoment;
154
+ for (let i = 0; offsetMinutes < timeoutMinutes; i++) {
155
+ const nextBusinessDate = computeNextBusinessDate(nextMoment.toDate(), holidays, businessHours, digitsForHours);
156
+ if (nextBusinessDate) {
157
+ nextMoment = moment.utc(nextBusinessDate.end);
158
+ offsetMinutes += businessHoursPerDay.computedMinutes;
159
+ } else {
160
+ throw new Error("computeTimeoutDateWithoutHolidays:Maximum number of calls, The number of days in holidays may exceed 365.");
161
+ }
162
+ }
163
+ if (offsetMinutes > timeoutMinutes) {
164
+ let subMinutes = offsetMinutes - timeoutMinutes;
165
+ if (subMinutes >= businessHoursPerDay.computedPmMinutes) {
166
+ subMinutes += businessHoursPerDay.computedLunchMinutes;
167
+ }
168
+ nextMoment.subtract(subMinutes, 'm');
169
+ return nextMoment.toDate();
170
+ }
171
+ return nextMoment.toDate();
172
+ }
173
+ }
174
+
175
+ /**
176
+ * 计算在某个工作区下的节假日、工作时间数据基础上,以某个时间点开始,超时几个小时后,超时时间点是什么时候
177
+ * @param {Date} start
178
+ * @param {number} timeoutHours
179
+ * @param {string} spaceId
180
+ * @param {number} digitsForHours
181
+ * @returns {Promise<Date>}
182
+ */
183
+ async function getTimeoutDateWithoutHolidays(start, timeoutHours, spaceId, digitsForHours = 2) {
184
+ let enableHolidays = false;
185
+ try {
186
+ const { getSteedosConfig } = require('@steedos/objectql');
187
+ const config = getSteedosConfig();
188
+ enableHolidays = !!config.enable_holidays;
189
+ } catch (e) {
190
+ // If config is not available, treat as holidays not enabled
191
+ }
192
+ if (!enableHolidays) {
193
+ return moment(start).add(timeoutHours, 'h').toDate();
194
+ }
195
+ const defaultBusinessHoursRecords = await getDefaultBusinessHours(spaceId);
196
+ const defaultBusinessHoursRecord = defaultBusinessHoursRecords && defaultBusinessHoursRecords[0];
197
+ if (!defaultBusinessHoursRecord) {
198
+ // 未配置工作时间,直接按未开启enable_holidays处理
199
+ return moment(start).add(timeoutHours, 'h').toDate();
200
+ }
201
+ const startUTCYear = start.getUTCFullYear();
202
+ const holidaysRecords = await getHolidays(spaceId, [startUTCYear, startUTCYear + 1]);
203
+ return computeTimeoutDateWithoutHolidays(start, timeoutHours, holidaysRecords, defaultBusinessHoursRecord, digitsForHours);
204
+ }
205
+
206
+ module.exports = {
207
+ getHolidays,
208
+ getDefaultBusinessHours,
209
+ getTimeoutDateWithoutHolidays,
210
+ computeTimeoutDateWithoutHolidays
211
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steedos-labs/plugin-workflow",
3
- "version": "3.0.54",
3
+ "version": "3.0.56",
4
4
  "main": "package.service.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -6,6 +6,7 @@ const packageLoader = require('@steedos/service-package-loader');
6
6
  const rests = require('./src/rests');
7
7
  const InstanceRecordQueue = require('./src/instance_record_queue')
8
8
  const WebhookQueue = require('./src/webhook_queue')
9
+ const TimeoutAutoSubmit = require('./src/timeout_auto_submit')
9
10
 
10
11
  /**
11
12
  * @typedef {import('moleculer').Context} Context Moleculer's Context
@@ -158,6 +159,13 @@ module.exports = {
158
159
  })
159
160
  }
160
161
 
162
+ // 超时自动提交
163
+ if (process.env.STEEDOS_CRON_TIMEOUT_AUTO_SUBMIT_INTERVAL) {
164
+ TimeoutAutoSubmit.Configure({
165
+ sendInterval: process.env.STEEDOS_CRON_TIMEOUT_AUTO_SUBMIT_INTERVAL
166
+ })
167
+ }
168
+
161
169
  }
162
170
  },
163
171
 
@@ -165,6 +173,6 @@ module.exports = {
165
173
  * Service stopped lifecycle event handler
166
174
  */
167
175
  async stopped() {
168
-
176
+ TimeoutAutoSubmit.Stop();
169
177
  }
170
178
  };