@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.
- 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-DXnimQAi.js → index-B9naSD1C.js} +173 -173
- package/designer/dist/assets/index-DEEcIiu0.css +1 -0
- package/designer/dist/index.html +2 -2
- package/main/default/manager/push_manager.js +93 -113
- package/main/default/manager/uuflow_manager.js +151 -47
- package/main/default/objects/categories/buttons/badge_recalc.button.yml +44 -0
- package/main/default/objects/instance_tasks/listviews/inbox.listview.yml +1 -1
- package/main/default/objects/instance_tasks/listviews/outbox.listview.yml +1 -2
- package/main/default/objects/instances/buttons/instance_export.button.js +10 -0
- package/main/default/objects/instances/buttons/instance_export.button.yml +83 -0
- package/main/default/objects/instances/listviews/completed.listview.yml +1 -2
- package/main/default/objects/instances/listviews/draft.listview.yml +1 -2
- package/main/default/objects/instances/listviews/monitor.listview.yml +1 -1
- package/main/default/objects/instances/listviews/pending.listview.yml +1 -2
- package/main/default/pages/page_instance_print.page.amis.json +6 -2
- package/main/default/routes/api_workflow_ai_form_design.router.js +9 -3
- package/main/default/routes/api_workflow_ai_form_design_stream.router.js +9 -3
- package/main/default/routes/api_workflow_export.router.js +97 -152
- package/main/default/routes/api_workflow_instance_forward.router.js +15 -1
- package/main/default/routes/api_workflow_instance_permissions.router.js +2 -2
- package/main/default/routes/api_workflow_nav.router.js +7 -1
- package/main/default/routes/api_workflow_next_step.router.js +1 -1
- package/main/default/services/instance.service.js +1 -1
- package/main/default/utils/business_hours.js +210 -0
- package/main/default/utils/business_timeout.js +211 -0
- package/package.json +1 -1
- package/package.service.js +9 -1
- package/public/amis-renderer/amis-renderer.css +1 -1
- package/public/amis-renderer/amis-renderer.js +1 -1
- package/public/workflow/index.css +7 -2
- package/src/rests/badgeRecalcConsole.js +593 -0
- package/src/rests/badgeRecalcExecute.js +308 -0
- package/src/rests/index.js +2 -0
- package/src/timeout_auto_submit.js +81 -0
- 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
|
-
|
|
3238
|
-
// return await steedosCore.getTimeoutDateWithoutHolidays(new Date(), hours, spaceId);
|
|
3313
|
+
if (!hours) {
|
|
3314
|
+
return undefined;
|
|
3239
3315
|
}
|
|
3240
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4325
|
-
|
|
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
|
-
|
|
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
|
|
4428
|
+
const nextUserIds = await HandlersManager.getHandlers(instance_id, nextStepId);
|
|
4338
4429
|
const judge = step_type === "sign" ? "approved" : "submitted";
|
|
4339
4430
|
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
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
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
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
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
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
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
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(
|
|
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,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: 字符串,
|
|
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.
|
|
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.
|
|
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"
|