= ({
允许裁剪的文档可选择是否纳入人工审批。关闭后仍会生成并进入版本账本,只是不走三权。
+ {!liveEdit && !needsGovernedDocument(plan) && (
+
+ {NO_GOVERNED_DOCUMENT_MESSAGE}。请勾选「纳入人工审批」并指定负责人;成员不足三人时可用「由编制人兼任审核、批准」。
+
+ )}
{needsThreeMembersForDuties(plan, requireApprovalKeys) && members.length < 3 && (
{
assert.equal(locateDutyIssue(switchedOn, 'research')?.code, 'DUTY_ASSIGNMENT_REQUIRED');
});
+ it('blocks activation preview when no document is under human approval', () => {
+ const unmanaged = plan([
+ { task_key: 'research', enabled: true, approval_configurable: true, duties: {} },
+ { task_key: 'quote', enabled: true, approval_configurable: true, duties: {} },
+ ]);
+ assert.equal(needsGovernedDocument(unmanaged), false);
+ assert.deepEqual(clientDutyIssues(unmanaged, [{ userId: 1 }]), []);
+
+ const authorOnly = plan([
+ {
+ task_key: 'intake',
+ enabled: true,
+ approval_configurable: true,
+ duty_policy: 'author_only',
+ duties: { author_user_id: 7 },
+ },
+ ]);
+ assert.equal(needsGovernedDocument(authorOnly), true);
+
+ const separated = plan([
+ {
+ task_key: 'intake',
+ enabled: true,
+ approval_configurable: true,
+ duties: { author_user_id: 1, reviewer_user_id: 2, approver_user_id: 3 },
+ },
+ ]);
+ assert.equal(needsGovernedDocument(separated), true);
+ });
+
it('allows an all-none configurable draft without three members', () => {
const draft = plan([
{ task_key: 'research', enabled: true, approval_configurable: true, duties: {} },
diff --git a/web/src/lib/projectSettingsDuties.ts b/web/src/lib/projectSettingsDuties.ts
index a6c08abd..0baf14ff 100644
--- a/web/src/lib/projectSettingsDuties.ts
+++ b/web/src/lib/projectSettingsDuties.ts
@@ -189,6 +189,17 @@ export function clientDutyIssues(
return issues;
}
+/** 对齐服务端 validateActivationPlan:激活前至少一份文档为完整人工审批(含兼任派生的 A/A/A)。 */
+export const NO_GOVERNED_DOCUMENT_MESSAGE = '至少一份文档必须纳入人工审批才能激活工作流';
+
+export function needsGovernedDocument(plan: WorkflowPlan): boolean {
+ return plan.stages.some(
+ (stage) =>
+ stage.enabled &&
+ stage.doc_tasks.some((task) => task.enabled && resolveClientDocTaskDuty(task).mode === 'full'),
+ );
+}
+
export function needsThreeMembersForDuties(plan: WorkflowPlan, requireApprovalKeys: string[] = []): boolean {
return plan.stages.some(
(stage) =>