@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
@@ -803,6 +803,79 @@ UUFlowManager.setFormFieldVariable = async function (fields, __values, space_id)
803
803
  }
804
804
  };
805
805
 
806
+ /**
807
+ * 按需为条件表达式中以 {fieldCode.xxx} 形式引用了属性的 lookup/odata 字段查询完整记录
808
+ * 仅当字段值是纯字符串(新版 lookup ID)时才查询,旧版对象值直接跳过
809
+ */
810
+ UUFlowManager.enrichLookupFieldValues = async function (fields, __values, lines) {
811
+ if (!lines || !fields) return;
812
+
813
+ // 1. 收集所有条件表达式
814
+ const allConditions = lines
815
+ .filter(l => l.condition)
816
+ .map(l => l.condition)
817
+ .join(' ');
818
+
819
+ if (!allConditions) return;
820
+
821
+ // 2. 提取所有 {fieldCode.xxx} 中的 fieldCode(不限定 .xxx 的内容,如 @label)
822
+ const dotAccessRegex = /\{(\w+)\./g;
823
+ const referencedFieldCodes = new Set();
824
+ let match;
825
+ while ((match = dotAccessRegex.exec(allConditions)) !== null) {
826
+ referencedFieldCodes.add(match[1]);
827
+ }
828
+
829
+ if (referencedFieldCodes.size === 0) return;
830
+
831
+ // 3. 建立 fieldCode → field 的映射(含 section 内嵌字段)
832
+ const fieldMap = {};
833
+ for (const field of fields) {
834
+ if (field.type === 'section' && field.fields) {
835
+ for (const f of field.fields) { fieldMap[f.code] = f; }
836
+ } else if (field.type === 'table' && field.fields) {
837
+ for (const f of field.fields) { fieldMap[f.code] = f; }
838
+ } else {
839
+ fieldMap[field.code] = field;
840
+ }
841
+ }
842
+
843
+ // 4. 仅对 odata/lookup 类型、且值为纯字符串的字段查询
844
+ for (const code of referencedFieldCodes) {
845
+ const field = fieldMap[code];
846
+ if (!field) continue;
847
+ if (field.type !== 'odata' && field.type !== 'lookup') continue;
848
+
849
+ const val = __values[code];
850
+ if (!val || typeof val === 'object') continue; // 已是对象,无需处理
851
+
852
+ // 从 odata 的 url 或 lookup 的 reference_to 获取对象名
853
+ let objectName = null;
854
+ if (field.type === 'odata' && field.url) {
855
+ // url 格式通常为: /api/odata/v4/{spaceId}/{objectName}
856
+ const parts = field.url.replace(/\/$/, '').split('/');
857
+ objectName = parts[parts.length - 1];
858
+ } else if (field.type === 'lookup' && field.reference_to) {
859
+ objectName = field.reference_to;
860
+ }
861
+
862
+ if (!objectName) continue;
863
+
864
+ try {
865
+ const obj = getObject(objectName);
866
+ const record = await obj.findOne(val, { fields: { name: 1 } });
867
+ if (record) {
868
+ __values[code] = {
869
+ _id: val,
870
+ "@label": record.name || val
871
+ };
872
+ }
873
+ } catch (e) {
874
+ console.warn(`[workflow/engine] enrichLookupFieldValues: failed to enrich field "${code}" from "${objectName}": ${e.message}`);
875
+ }
876
+ }
877
+ };
878
+
806
879
  function isSkipStep(instance, step) {
807
880
  return instance.skip_steps?.includes(step._id);
808
881
  }
@@ -864,7 +937,7 @@ UUFlowManager.initFormulaValues = async function (instance, values) {
864
937
  * @param {Object} values - Form values
865
938
  * @returns {Array} Array of next step IDs
866
939
  */
867
- UUFlowManager.getNextSteps = async function (instance, flow, step, judge, values) {
940
+ UUFlowManager.getNextSteps = async function (instance, flow, step, judge, values, showSkipStep=false) {
868
941
  const step_type = step.step_type;
869
942
  let nextSteps = [];
870
943
 
@@ -885,6 +958,9 @@ UUFlowManager.getNextSteps = async function (instance, flow, step, judge, values
885
958
  // Set form field variables
886
959
  await UUFlowManager.setFormFieldVariable(formVersion.fields, __values, instance.space);
887
960
 
961
+ // 按需为 lookup/odata 字段补充对象值,使 {field.@label} 等属性访问可用
962
+ await UUFlowManager.enrichLookupFieldValues(formVersion.fields, __values, step.lines);
963
+
888
964
  // Evaluate conditions
889
965
  const reg = /(\{[^{}]*\})/g;
890
966
  const prefix = "__values";
@@ -1001,7 +1077,7 @@ UUFlowManager.getNextSteps = async function (instance, flow, step, judge, values
1001
1077
  for (const nextStepId of nextSteps) {
1002
1078
  const _step = await UUFlowManager.getStep(instance, flow, nextStepId);
1003
1079
 
1004
- if (isSkipStep(instance, _step)) {
1080
+ if (!showSkipStep && isSkipStep(instance, _step)) {
1005
1081
  if (!judge && _step.step_type === 'sign') {
1006
1082
  judge = 'approved';
1007
1083
  }
@@ -1818,7 +1894,7 @@ UUFlowManager.handleSkipProcessed = async function (instance_id, flow, maxDepth
1818
1894
  step: next_step_id,
1819
1895
  name: next_step.name,
1820
1896
  start_date: now,
1821
- due_date: UUFlowManager.getDueDate(next_step.timeout_hours, space_id),
1897
+ due_date: await UUFlowManager.getDueDate(next_step.timeout_hours, space_id),
1822
1898
  approves: []
1823
1899
  };
1824
1900
 
@@ -2150,7 +2226,7 @@ UUFlowManager.engine_step_type_is_start_or_submit_or_condition = async function
2150
2226
  step: next_step_id,
2151
2227
  name: next_step_name,
2152
2228
  start_date: new Date(),
2153
- due_date: UUFlowManager.getDueDate(next_step.timeout_hours, space_id),
2229
+ due_date: await UUFlowManager.getDueDate(next_step.timeout_hours, space_id),
2154
2230
  approves: []
2155
2231
  };
2156
2232
 
@@ -2436,7 +2512,7 @@ UUFlowManager.engine_step_type_is_sign = async function (
2436
2512
  step: next_step_id,
2437
2513
  name: next_step_name,
2438
2514
  start_date: new Date(),
2439
- due_date: UUFlowManager.getDueDate(next_step.timeout_hours, space_id),
2515
+ due_date: await UUFlowManager.getDueDate(next_step.timeout_hours, space_id),
2440
2516
  approves: []
2441
2517
  };
2442
2518
 
@@ -2668,7 +2744,7 @@ UUFlowManager.engine_step_type_is_sign = async function (
2668
2744
  step: next_step_id,
2669
2745
  name: next_step_name,
2670
2746
  start_date: new Date(),
2671
- due_date: UUFlowManager.getDueDate(next_step.timeout_hours, space_id),
2747
+ due_date: await UUFlowManager.getDueDate(next_step.timeout_hours, space_id),
2672
2748
  approves: []
2673
2749
  };
2674
2750
 
@@ -2926,7 +3002,7 @@ UUFlowManager.engine_step_type_is_counterSign = async function (
2926
3002
  step: next_step_id,
2927
3003
  name: next_step_name,
2928
3004
  start_date: new Date(),
2929
- due_date: UUFlowManager.getDueDate(next_step.timeout_hours, space_id),
3005
+ due_date: await UUFlowManager.getDueDate(next_step.timeout_hours, space_id),
2930
3006
  approves: []
2931
3007
  };
2932
3008
 
@@ -3154,6 +3230,7 @@ UUFlowManager.create_instance = async function (instance_from_client, user_info)
3154
3230
  ins_obj.applicant_organization_name = instance_from_client["applicant_organization_name"] || space_user_org_info.organization_name;
3155
3231
  ins_obj.applicant_organization_fullname = instance_from_client["applicant_organization_fullname"] || space_user_org_info.organization_fullname;
3156
3232
  ins_obj.applicant_company = instance_from_client["applicant_company"] || space_user.company_id;
3233
+ ins_obj.submitterOrApplicant = [...new Set([ins_obj.submitter, ins_obj.applicant])];
3157
3234
  ins_obj.state = 'draft';
3158
3235
  ins_obj.code = '';
3159
3236
  ins_obj.is_archived = false;
@@ -3233,11 +3310,11 @@ UUFlowManager.getCurrentStepAutoSubmit = function (timeout_auto_submit, lines) {
3233
3310
  };
3234
3311
 
3235
3312
  UUFlowManager.getDueDate = async function (hours, spaceId) {
3236
- if (hours) {
3237
- // TODO: 实现异步获取超时日期
3238
- // return await steedosCore.getTimeoutDateWithoutHolidays(new Date(), hours, spaceId);
3313
+ if (!hours) {
3314
+ return undefined;
3239
3315
  }
3240
- return undefined;
3316
+ const { getTimeoutDateWithoutHolidays } = require('../utils/business_timeout');
3317
+ return await getTimeoutDateWithoutHolidays(new Date(), hours, spaceId);
3241
3318
  };
3242
3319
  //////////
3243
3320
 
@@ -3332,6 +3409,7 @@ UUFlowManager.submit_instance = async function (instance_from_client, user_info)
3332
3409
  setObj.applicant_organization_name = applicant_org_info.organization_name;
3333
3410
  setObj.applicant_organization_fullname = applicant_org_info.organization_fullname;
3334
3411
  setObj.applicant_company = applicant.company_id;
3412
+ setObj.submitterOrApplicant = [...new Set([submitter_id, applicant_id])];
3335
3413
  instance_traces[0].approves[0].user = applicant_id;
3336
3414
  instance_traces[0].approves[0].user_name = user.name;
3337
3415
  }
@@ -4190,6 +4268,7 @@ UUFlowManager.cancelProcessDelegation = async function (spaceId, toId) {
4190
4268
  if (ins.state === 'draft') {
4191
4269
  setObj.submitter = a.user;
4192
4270
  setObj.submitter_name = a.user_name;
4271
+ setObj.submitterOrApplicant = [...new Set([a.user, ins.applicant])];
4193
4272
  }
4194
4273
 
4195
4274
  await db.instances.updateOne({
@@ -4292,7 +4371,8 @@ UUFlowManager.updateCCcount = async function (insId) {
4292
4371
 
4293
4372
  UUFlowManager.timeoutAutoSubmit = async function (ins_id) {
4294
4373
  const db = {
4295
- instances: await getCollection('instances')
4374
+ instances: await getCollection('instances'),
4375
+ users: await getCollection('users')
4296
4376
  };
4297
4377
 
4298
4378
  const query = {
@@ -4312,66 +4392,89 @@ UUFlowManager.timeoutAutoSubmit = async function (ins_id) {
4312
4392
 
4313
4393
  const instances = await db.instances.find(query).toArray();
4314
4394
 
4315
- await Promise.all(instances.map(async (ins) => {
4395
+ for (const ins of instances) {
4316
4396
  try {
4317
4397
  const flow_id = ins.flow;
4318
4398
  const instance_id = ins._id;
4319
4399
  const trace = ins.traces[ins.traces.length - 1];
4400
+
4401
+ // Only process if trace is not finished and due_date has passed
4402
+ if (trace.is_finished || !trace.due_date || new Date(trace.due_date) > new Date()) {
4403
+ continue;
4404
+ }
4405
+
4320
4406
  const flow = await UUFlowManager.getFlow(flow_id);
4321
4407
  const step = await UUFlowManager.getStep(ins, flow, trace.step);
4322
4408
  const step_type = step.step_type;
4323
4409
 
4324
- const toLine = step.lines.find(l => l.timeout_line === true);
4325
- if (!toLine) return;
4410
+ // Find the timeout line configuration on the current step
4411
+ const toLine = step.lines ? step.lines.find(l => l.timeout_line === true) : null;
4412
+ if (!toLine) continue;
4326
4413
 
4327
4414
  let nextStepId = toLine.to_step;
4328
4415
  let nextStep = await UUFlowManager.getStep(ins, flow, nextStepId);
4329
4416
 
4417
+ // If the next step is a condition step, resolve it to the actual next step
4330
4418
  if (nextStep.step_type === 'condition') {
4331
4419
  const nextSteps = await UUFlowManager.getNextSteps(ins, flow, nextStep, "");
4332
- console.error(nextSteps);
4420
+ if (!nextSteps || nextSteps.length === 0) {
4421
+ console.error(`[timeout_auto_submit] No next steps resolved for condition step, instance: ${instance_id}`);
4422
+ continue;
4423
+ }
4333
4424
  nextStepId = nextSteps[0];
4334
4425
  nextStep = await UUFlowManager.getStep(ins, flow, nextStepId);
4335
4426
  }
4336
4427
 
4337
- const nextUserIds = await await HandlersManager.getHandlers(instance_id, nextStepId);
4428
+ const nextUserIds = await HandlersManager.getHandlers(instance_id, nextStepId);
4338
4429
  const judge = step_type === "sign" ? "approved" : "submitted";
4339
4430
 
4340
- const approve_from_client = {
4341
- instance: instance_id,
4342
- trace: trace._id,
4343
- judge: judge,
4344
- next_steps: [{
4345
- step: nextStepId,
4346
- users: nextUserIds
4347
- }]
4348
- };
4431
+ // Process each unfinished approve in the trace sequentially to avoid race conditions
4432
+ const unfinishedApproves = trace.approves.filter(a => !a.is_finished);
4433
+ for (const a of unfinishedApproves) {
4434
+ try {
4435
+ // Create a separate approve_from_client for each approve to avoid shared mutation
4436
+ const approve_from_client = {
4437
+ _id: a._id,
4438
+ instance: instance_id,
4439
+ trace: trace._id,
4440
+ judge: judge,
4441
+ next_steps: [{
4442
+ step: nextStepId,
4443
+ users: nextUserIds
4444
+ }]
4445
+ };
4349
4446
 
4350
- await Promise.all(trace.approves.map(async (a) => {
4351
- approve_from_client._id = a._id;
4352
- const current_user_info = await db.users.findOne(
4353
- { _id: a.handler },
4354
- { projection: { services: 0 } }
4355
- );
4447
+ const current_user_info = await db.users.findOne(
4448
+ { _id: a.handler },
4449
+ { projection: { services: 0 } }
4450
+ );
4356
4451
 
4357
- const updatedInstance = await UUFlowManager.workflow_engine(
4358
- approve_from_client,
4359
- current_user_info,
4360
- current_user_info._id,
4361
- true
4362
- );
4452
+ if (!current_user_info) {
4453
+ console.error(`[timeout_auto_submit] User not found: ${a.handler}, instance: ${instance_id}`);
4454
+ continue;
4455
+ }
4363
4456
 
4364
- await pushManager.send_instance_notification(
4365
- "auto_submit_pending_inbox",
4366
- updatedInstance,
4367
- "",
4368
- current_user_info
4369
- );
4370
- }));
4457
+ const updatedInstance = await UUFlowManager.workflow_engine(
4458
+ approve_from_client,
4459
+ current_user_info,
4460
+ current_user_info._id,
4461
+ true
4462
+ );
4463
+
4464
+ await pushManager.send_instance_notification(
4465
+ "auto_submit_pending_inbox",
4466
+ updatedInstance,
4467
+ "",
4468
+ current_user_info
4469
+ );
4470
+ } catch (approveError) {
4471
+ console.error(`[timeout_auto_submit] Error processing approve ${a._id} for instance ${instance_id}: `, approveError.stack);
4472
+ }
4473
+ }
4371
4474
  } catch (error) {
4372
- console.error('AUTO TIMEOUT_AUTO_SUBMIT ERROR: ', error.stack);
4475
+ console.error(`[timeout_auto_submit] Error processing instance ${ins._id}: `, error.stack);
4373
4476
  }
4374
- }));
4477
+ }
4375
4478
 
4376
4479
  return true;
4377
4480
  };
@@ -4783,6 +4886,7 @@ UUFlowManager.draft_save_instance = async function (ins, userId) {
4783
4886
  setObj.applicant_organization = applicant.organization;
4784
4887
  setObj.applicant_organization_name = organization.name;
4785
4888
  setObj.applicant_organization_fullname = organization.fullname;
4889
+ setObj.submitterOrApplicant = [...new Set([instance.submitter, applicant_id])];
4786
4890
  setObj[key_str + 'user'] = applicant_id;
4787
4891
  setObj[key_str + 'user_name'] = user.name;
4788
4892
  }
@@ -0,0 +1,44 @@
1
+ name: badge_recalc
2
+ amis_schema: |-
3
+ {
4
+ "type": "service",
5
+ "body": [
6
+ {
7
+ "type": "button",
8
+ "label": "重算角标",
9
+ "id": "u:badge_recalc",
10
+ "editorState": "default",
11
+ "onEvent": {
12
+ "click": {
13
+ "weight": 0,
14
+ "actions": [
15
+ {
16
+ "actionType": "url",
17
+ "args": {
18
+ "url": "/api/workflow/badge-recalc-console",
19
+ "blank": true
20
+ }
21
+ }
22
+ ]
23
+ }
24
+ }
25
+ }
26
+ ],
27
+ "data": {
28
+ "context": {},
29
+ "dataComponentId": "",
30
+ "record_id": "",
31
+ "record": {},
32
+ "permissions": {}
33
+ },
34
+ "id": "u:badge_recalc_service",
35
+ "bodyClassName": "p-0",
36
+ "dsType": "api",
37
+ "definitions": {}
38
+ }
39
+ is_enable: true
40
+ label: 重算角标
41
+ locked: false
42
+ 'on': record_more
43
+ type: amis_button
44
+ visible: true
@@ -26,12 +26,12 @@ filters: !!js/function |
26
26
  }
27
27
  sort: [['start_date','desc']]
28
28
  searchable_fields:
29
+ - field: flow
29
30
  - field: instance_name
30
31
  - field: submitter_name
31
32
  - field: applicant_organization_name
32
33
  - field: submit_date
33
34
  - field: instance_state
34
- - field: is_archived
35
35
  extra_columns:
36
36
  - extras
37
37
  - is_read
@@ -25,13 +25,12 @@ filters: !!js/function |
25
25
  }
26
26
  sort: [['modified','desc']]
27
27
  searchable_fields:
28
+ - field: flow
28
29
  - field: instance_name
29
30
  - field: submitter_name
30
- - field: flow
31
31
  - field: applicant_organization_name
32
32
  - field: submit_date
33
33
  - field: instance_state
34
- - field: is_archived
35
34
  extra_columns:
36
35
  - extras
37
36
  disableSwitch: true
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ instance_exportVisible: function (object_name, record_id, record_permissions, data) {
3
+ var box = data.listName;
4
+ var flowId = data.flowId;
5
+ if (box === "monitor" && flowId) {
6
+ return true;
7
+ }
8
+ return false;
9
+ }
10
+ }
@@ -0,0 +1,83 @@
1
+ name: instance_export
2
+ amis_schema: |-
3
+ {
4
+ "type": "service",
5
+ "body": [
6
+ {
7
+ "type": "dropdown-button",
8
+ "label": "导出",
9
+ "id": "u:instance_export",
10
+ "level": "default",
11
+ "icon": "fa fa-download",
12
+ "buttons": [
13
+ {
14
+ "type": "button",
15
+ "label": "本月",
16
+ "onEvent": {
17
+ "click": {
18
+ "actions": [
19
+ {
20
+ "actionType": "custom",
21
+ "script": "var spaceId = event.data.context.tenantId; var flowId = event.data.flowId; if(spaceId && flowId){ var timezoneoffset = new Date().getTimezoneOffset(); var url = Steedos.absoluteUrl('/api/workflow/export/instances?space_id=' + spaceId + '&flow_id=' + flowId + '&type=0&timezoneoffset=' + timezoneoffset); window.open(url, '_blank'); } else { window.toastr && window.toastr.warning('请先选择流程'); }"
22
+ }
23
+ ]
24
+ }
25
+ }
26
+ },
27
+ {
28
+ "type": "button",
29
+ "label": "上月",
30
+ "onEvent": {
31
+ "click": {
32
+ "actions": [
33
+ {
34
+ "actionType": "custom",
35
+ "script": "var spaceId = event.data.context.tenantId; var flowId = event.data.flowId; if(spaceId && flowId){ var timezoneoffset = new Date().getTimezoneOffset(); var url = Steedos.absoluteUrl('/api/workflow/export/instances?space_id=' + spaceId + '&flow_id=' + flowId + '&type=1&timezoneoffset=' + timezoneoffset); window.open(url, '_blank'); } else { window.toastr && window.toastr.warning('请先选择流程'); }"
36
+ }
37
+ ]
38
+ }
39
+ }
40
+ },
41
+ {
42
+ "type": "button",
43
+ "label": "本年度",
44
+ "onEvent": {
45
+ "click": {
46
+ "actions": [
47
+ {
48
+ "actionType": "custom",
49
+ "script": "var spaceId = event.data.context.tenantId; var flowId = event.data.flowId; if(spaceId && flowId){ var timezoneoffset = new Date().getTimezoneOffset(); var url = Steedos.absoluteUrl('/api/workflow/export/instances?space_id=' + spaceId + '&flow_id=' + flowId + '&type=2&timezoneoffset=' + timezoneoffset); window.open(url, '_blank'); } else { window.toastr && window.toastr.warning('请先选择流程'); }"
50
+ }
51
+ ]
52
+ }
53
+ }
54
+ },
55
+ {
56
+ "type": "button",
57
+ "label": "所有",
58
+ "onEvent": {
59
+ "click": {
60
+ "actions": [
61
+ {
62
+ "actionType": "custom",
63
+ "script": "var spaceId = event.data.context.tenantId; var flowId = event.data.flowId; if(spaceId && flowId){ var timezoneoffset = new Date().getTimezoneOffset(); var url = Steedos.absoluteUrl('/api/workflow/export/instances?space_id=' + spaceId + '&flow_id=' + flowId + '&type=3&timezoneoffset=' + timezoneoffset); window.open(url, '_blank'); } else { window.toastr && window.toastr.warning('请先选择流程'); }"
64
+ }
65
+ ]
66
+ }
67
+ }
68
+ }
69
+ ]
70
+ }
71
+ ],
72
+ "regions": [
73
+ "body"
74
+ ],
75
+ "bodyClassName": "p-0",
76
+ "id": "u:instance_export_service"
77
+ }
78
+ is_enable: true
79
+ label: 导出
80
+ 'on': list
81
+ type: amis_button
82
+ visible: true
83
+ sort: 900
@@ -25,13 +25,12 @@ filters: !!js/function |
25
25
  }
26
26
  sort: [['modified','desc']]
27
27
  searchable_fields:
28
+ - field: flow
28
29
  - field: name
29
30
  - field: submitter_name
30
- - field: flow
31
31
  - field: applicant_organization_name
32
32
  - field: submit_date
33
33
  - field: state
34
- - field: is_archived
35
34
  extra_columns:
36
35
  - extras
37
36
  disableSwitch: true
@@ -16,11 +16,10 @@ filters: !!js/function |
16
16
  }
17
17
  sort: [['modified','desc']]
18
18
  searchable_fields:
19
+ - field: flow
19
20
  - field: name
20
21
  - field: submitter_name
21
- - field: flow
22
22
  - field: applicant_organization_name
23
23
  - field: submit_date
24
24
  - field: state
25
- - field: is_archived
26
25
  disableSwitch: true
@@ -25,12 +25,12 @@ filters: !!js/function |
25
25
  }
26
26
  sort: [['submit_date','desc']]
27
27
  searchable_fields:
28
+ - field: flow
28
29
  - field: name
29
30
  - field: submitter_name
30
31
  - field: applicant_organization_name
31
32
  - field: submit_date
32
33
  - field: state
33
- - field: is_archived
34
34
  extra_columns:
35
35
  - extras
36
36
  - values
@@ -25,13 +25,12 @@ filters: !!js/function |
25
25
  }
26
26
  sort: [['submit_date','desc']]
27
27
  searchable_fields:
28
+ - field: flow
28
29
  - field: name
29
30
  - field: submitter_name
30
- - field: flow
31
31
  - field: applicant_organization_name
32
32
  - field: submit_date
33
33
  - field: state
34
- - field: is_archived
35
34
  extra_columns:
36
35
  - extras
37
36
  disableSwitch: true
@@ -106,7 +106,7 @@
106
106
  "actions": [
107
107
  {
108
108
  "actionType": "custom",
109
- "script": "doAction({actionType: 'setValue', componentId: 'u:print-width', args: {value: Number(event.data.value)}}); $('.steedos-instance-related-view-wrapper .instance-form').css('width', event.data.value + 'mm');"
109
+ "script": "doAction({actionType: 'setValue', componentId: 'u:print-width', args: {value: Number(event.data.value)}}); $('.steedos-instance-related-view-wrapper .instance-form').css('width', event.data.value + 'mm'); $('.steedos-instance-related-view-wrapper .instance-approve-history').css('width', event.data.value + 'mm');"
110
110
  }
111
111
  ]
112
112
  }
@@ -125,7 +125,7 @@
125
125
  "actions": [
126
126
  {
127
127
  "actionType": "custom",
128
- "script": "$('.steedos-instance-related-view-wrapper .instance-form').css('width', event.data.value + 'mm');"
128
+ "script": "$('.steedos-instance-related-view-wrapper .instance-form').css('width', event.data.value + 'mm'); $('.steedos-instance-related-view-wrapper .instance-approve-history').css('width', event.data.value + 'mm');"
129
129
  }
130
130
  ]
131
131
  }
@@ -241,6 +241,10 @@
241
241
  "width": "190mm",
242
242
  "transition": "width 0.5s ease-in-out"
243
243
  },
244
+ ".steedos-instance-related-view-wrapper .instance-approve-history": {
245
+ "width": "190mm",
246
+ "transition": "width 0.5s ease-in-out"
247
+ },
244
248
  ".steedos-instance-related-view-wrapper .steedos-amis-instance-view-content": {
245
249
  "display": "inline-block"
246
250
  },
@@ -174,7 +174,10 @@ router.post('/am/ai/form-design', async function auth(req, res, next) {
174
174
  - reference_to: 字符串,关联对象的 API 名称,如 "contracts"、"accounts"
175
175
  - lookupLabelField: 字符串,用于搜索和显示的名称字段,默认 "name"
176
176
  - pickerMultiple: 布尔值,是否允许多选,默认 false
177
- - lookupFilters: 字符串,OData 过滤表达式,如 "status eq 'active'"
177
+ - lookupFilters: 字符串,DevExpress JSON 格式的过滤表达式,支持 {fieldName} 占位符引用其他表单字段值(运行时自动替换为实际值)
178
+ - 静态过滤示例:'[["enablestate","=","2"],"and",["hidden","<>",true]]'
179
+ - 动态过滤示例(引用其他字段值):'[["company_id","=","{company_id}"],"and",["enablestate","=","2"]]' — 当表单中 company_id 字段值变化时,{company_id} 会被自动替换为该字段的实际值
180
+ - 格式规则:eq → "=",ne → "<>",gt → ">",ge → ">=",lt → "<",le → "<=",contains → "contains",startswith → "startswith"。布尔值不加引号(true/false)
178
181
  - lookupDisplayFields: 数组,关联数据后额外展示的字段,格式 [{ "field": "字段API名", "label": "显示标签" }]
179
182
  - lookupFillRules: 数组,填充规则,选择关联记录后将源字段值自动填充到表单其他字段。格式 [{ "sourceField": "关联对象的字段API名", "targetField": "当前表单中的目标字段name" }]
180
183
  - 示例:选择合同后自动填充合同金额到表单的金额字段:lookupFillRules: [{ "sourceField": "amount", "targetField": "contract_amount" }]
@@ -252,6 +255,8 @@ if (field.name === 'quantity' || field.name === 'unit_price') {
252
255
  10. 修改已有 grid 字段时,必须保留其完整的 gridData 和 children 数据,不要丢弃未被修改的子字段。如果用户未要求改动 grid 内容,应原样返回 grid 的所有属性
253
256
  11. 每个字段的类型特有属性必须完整返回:number 类型的 min/max/precision/thousandSeparator、text/textarea 的 maxLength/minLength、lookup 的 reference_to/lookupLabelField/lookupFillRules、member/org 的 pickerFillRules、reference 的 reference_to/displayFields 等。不要遗漏这些属性
254
257
  12. ⚠️ **type 与 formula 属性的严格对应**:只要字段包含公式表达式(formula 属性),其 type 就**必须**是 "formula",绝对不能是 "number"、"text" 或其他类型。"number" 类型仅用于用户手工输入数字的字段(无公式)。错误示例:{ type: "number", formula: "${a + b}" },正确示例:{ type: "formula", formula: "${a + b}", precision: 2 }。grid/table 的 children 子字段同样适用此规则
258
+ 13. ⚠️ **升级场景禁止自动设置配色**:当用户提供了 instance_template、form_script、flow_events 等旧版数据进行升级转换时,section(分组)、grid(网格)、table(子表)字段**不要自动添加** colorScheme、sectionColor、titleColor 等配色属性。配色属性只有在用户明确要求设置颜色/配色/主题色时才添加。升级的目标是忠实还原旧版表单的结构和逻辑,不要擅自美化
259
+ 14. **原样保留字段的 is_wide 属性**:is_wide 用于控制字段是否占满整行宽度。输出时必须保持每个字段的 is_wide 与输入完全一致:输入为 true 则输出 true,输入为 false 则输出 false,输入中没有该属性则不要添加。禁止擅自修改或统一设置 is_wide 的值
255
260
 
256
261
  ## 额外待转换的数据(升级场景)
257
262
 
@@ -377,15 +382,16 @@ if (field.name === 'quantity' || field.name === 'unit_price') {
377
382
 
378
383
  ### 旧公式字段跨对象引用迁移
379
384
  - 旧版表单中,公式字段可能通过 \`{applicant.organization.name}\`、\`{applicant.name}\` 等语法引用成员的关联属性。这种跨对象引用在新版中不再通过公式实现,而是通过**成员字段的 pickerFillRules(填充规则)**来替代。
385
+ - ⚠️ **重要:applicant(申请人)是系统内置字段**,name 固定为 \`__applicant\`,不要创建新的 member 字段。在 fields 数组中输出 { "name": "__applicant", "label": "申请人", "type": "member", "pickerFillRules": [...] } 即可覆盖系统默认配置。该字段在拟稿状态下可编辑(用户可更换申请人),pickerFillRules 会在选人后自动触发填充。
380
386
  - 迁移方式:
381
387
  1. 将引用跨对象属性的公式字段改为**普通文本字段**(type: "text")作为目标字段
382
- 2. 在成员字段(type: "member")上配置 **pickerFillRules**,使用点号表示法的 sourceField(如 "organization.name"),targetField 指向该文本字段
388
+ 2. \`__applicant\` 字段上配置 **pickerFillRules**,使用点号表示法的 sourceField(如 "organization.name"),targetField 指向该文本字段
383
389
  3. 如果 targetField 字段应为只读展示(即旧版是纯展示的公式字段),设置 readonly: true
384
390
  - 迁移示例:
385
391
  旧版公式字段:\`{applicant.organization.name}\` 用于显示申请人所属部门名称
386
392
  新版迁移方式:
387
393
  1. 创建文本字段 { "name": "dept_name", "label": "所属部门", "type": "text", "readonly": true }
388
- 2. 在成员字段上配置 pickerFillRules: [{ "sourceField": "organization.name", "targetField": "dept_name" }]
394
+ 2. 输出 __applicant 字段:{ "name": "__applicant", "label": "申请人", "type": "member", "pickerFillRules": [{ "sourceField": "organization.name", "targetField": "dept_name" }] }
389
395
  - 常见映射:
390
396
  - \`{applicant.organization.name}\` → sourceField: "organization.name"
391
397
  - \`{applicant.organization.fullname}\` → sourceField: "organization.fullname"