feat(web): call real docgov lifecycle APIs from action handlers

Replace local lifecycleState mocks; add handleRejectApprove; use holdsDocDuty for chat shortcuts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-17 13:48:59 +00:00
co-authored by Cursor
parent aed7f3f9b5
commit b87fc5007f
+199 -66
View File
@@ -49,6 +49,15 @@ import {
import { mapApiMembersToProjectMembers } from './lib/projectMemberAdapter';
import { mergeWorkflowDocumentsFromServer } from './lib/deliverableSync';
import { resolveCurrentDocRole } from './lib/currentDocRole';
import { holdsDocDuty } from './lib/docGovActionVisibility';
import {
claimDocument,
submitForReview,
reviewDecision,
approverDecision,
mapLeaseToAstEditLease,
type DocGovRecord,
} from './api/docgov';
import { canAddBlockComment } from './lib/commentPolicy';
import {
pollUntilPostTaskServerDoc,
@@ -2934,8 +2943,18 @@ export default function App() {
currentDocRole === 'author';
const isReviewPassShortcut =
(text.includes('通过') || text.includes('同意')) &&
((docAST.lifecycleState === 'reviewing' && currentDocRole === 'reviewer') ||
(docAST.lifecycleState === 'approving' && currentDocRole === 'approver'));
((docAST.lifecycleState === 'reviewing' &&
holdsDocDuty(currentUser.id, 'reviewer', {
authorId: docAST.authorId,
reviewerId: docAST.reviewerId,
approverId: docAST.approverId,
})) ||
(docAST.lifecycleState === 'approving' &&
holdsDocDuty(currentUser.id, 'approver', {
authorId: docAST.authorId,
reviewerId: docAST.reviewerId,
approverId: docAST.approverId,
})));
const sendActiveDoc =
deliverables.find((d) => d.docId === activeDocId) ||
visibleDeliverables.find((d) => d.docId === activeDocId);
@@ -3192,8 +3211,13 @@ export default function App() {
}
if (text.includes('通过') || text.includes('同意')) {
if (docAST.lifecycleState === 'reviewing' && currentDocRole === 'reviewer') {
handlePassReview();
const duties = {
authorId: docAST.authorId,
reviewerId: docAST.reviewerId,
approverId: docAST.approverId,
};
if (docAST.lifecycleState === 'reviewing' && holdsDocDuty(currentUser.id, 'reviewer', duties)) {
void handlePassReview();
setMessages((prev) => [
...prev,
{
@@ -3208,8 +3232,8 @@ export default function App() {
]);
return;
}
if (docAST.lifecycleState === 'approving' && currentDocRole === 'approver') {
handleApprove();
if (docAST.lifecycleState === 'approving' && holdsDocDuty(currentUser.id, 'approver', duties)) {
void handleApprove();
return;
}
}
@@ -3328,80 +3352,189 @@ export default function App() {
setDocAST({ ...docAST });
};
// 法定生命周期流转
const handleClaim = () => {
docAST.isClaimed = true;
setDocAST({ ...docAST });
// 法定生命周期流转(真实 docgov HTTP)
const applyDocGovRecord = (doc: DocGovRecord) => {
const nextState = (doc.current_state || docAST.lifecycleState) as DocLifecycleState;
const nextClaimed = doc.author_claimed ?? docAST.isClaimed;
const lease = mapLeaseToAstEditLease(doc);
const nextAst: DocumentAST = {
...docAST,
lifecycleState: nextState,
isClaimed: nextClaimed,
authorId: doc.author_id ?? docAST.authorId,
author: doc.author_name || docAST.author,
reviewerId: doc.reviewer_id ?? docAST.reviewerId,
reviewer: doc.reviewer_name || docAST.reviewer,
approverId: doc.approver_id ?? docAST.approverId,
approver: doc.approver_name || docAST.approver,
dutyPolicy: doc.duty_policy || docAST.dutyPolicy,
contentSha256: doc.content_sha256 || docAST.contentSha256,
editLease: lease ?? docAST.editLease,
};
setDocAST(nextAst);
setDeliverables((prev) =>
prev.map((d) => (d.docId === activeDocId ? { ...d, isClaimed: true } : d))
prev.map((d) =>
d.docId === activeDocId || d.docId === doc.doc_id
? {
...d,
lifecycleState: nextState,
isClaimed: nextClaimed,
authorId: doc.author_id ?? d.authorId,
author: doc.author_name || d.author,
reviewerId: doc.reviewer_id ?? d.reviewerId,
reviewer: doc.reviewer_name || d.reviewer,
approverId: doc.approver_id ?? d.approverId,
approver: doc.approver_name || d.approver,
dutyPolicy: doc.duty_policy || d.dutyPolicy,
editLease: lease ?? d.editLease,
}
: d,
),
);
};
const handleSubmitReview = () => {
docAST.lifecycleState = 'reviewing';
setDocAST({ ...docAST });
setDeliverables((prev) =>
prev.map((d) => (d.docId === activeDocId ? { ...d, lifecycleState: 'reviewing' } : d))
);
};
const handlePassReview = () => {
docAST.lifecycleState = 'approving';
setDocAST({ ...docAST });
setDeliverables((prev) =>
prev.map((d) => (d.docId === activeDocId ? { ...d, lifecycleState: 'approving' } : d))
);
};
const handleRejectReview = (reason: string) => {
docAST.lifecycleState = 'drafting';
setDocAST({ ...docAST });
setDeliverables((prev) =>
prev.map((d) => (d.docId === activeDocId ? { ...d, lifecycleState: 'drafting' } : d))
);
const pushDocGovError = (actionLabel: string, err: unknown) => {
const message = err instanceof Error ? err.message : String(err);
setMessages((prev) => [
...prev,
{
id: 'rej-' + Date.now(),
id: 'docgov-err-' + Date.now(),
sessionId: activeSessionId,
role: 'system',
content: `⚠️ 李主管(审核负责人)驳回了《${docAST.title}》,已退回编制阶段。驳回原因:${reason}`,
content: `⚠️ ${actionLabel}失败:${message}`,
timestamp: new Date().toTimeString().substring(0, 5),
},
]);
};
const handleApprove = () => {
docAST.lifecycleState = 'approved';
setDocAST({ ...docAST });
setDeliverables((prev) =>
prev.map((d) => (d.docId === activeDocId ? { ...d, lifecycleState: 'approved' } : d))
);
const handleClaim = async () => {
try {
const doc = await claimDocument({
doc_id: docAST.docId,
user_id: currentUser.id,
user_name: currentUser.name,
user_role: 'author',
});
applyDocGovRecord(doc);
} catch (err) {
pushDocGovError('认领文档', err);
}
};
setMessages((prev) => [
...prev,
{
id: 'appr-' + Date.now(),
sessionId: activeSessionId,
role: 'assistant',
authorName: '水木东方协同参谋',
intentTag: '🏆 终审批准生效',
content: `🎉 祝贺!王总监(批准人)已终审批准《${docAST.title}》!系统已自动对该受控交付物进行微粒度章节切片与向量化,成功注入全局标杆案例经验库!`,
timestamp: new Date().toTimeString().substring(0, 5),
recommendedCases: [
{
docId: docAST.docId,
taskKey: docAST.taskKey,
artifactTitle: docAST.title,
sectionTitle: '终审已生效标杆案例',
similarityScore: 1.0,
keyRegulations: ['GB 9706.1-2020', 'YY 0569-2011', 'IEC 60601-1-2'],
summarySnippet: '本交付物已通过编制、审核、批准法定三级签署闭环,作为往期标杆经验向全平台开放自推荐。',
},
],
},
]);
const handleSubmitReview = async () => {
try {
const doc = await submitForReview({
doc_id: docAST.docId,
user_id: currentUser.id,
user_name: currentUser.name,
});
applyDocGovRecord(doc);
} catch (err) {
pushDocGovError('提交送审', err);
}
};
const handlePassReview = async () => {
try {
const doc = await reviewDecision({
doc_id: docAST.docId,
user_id: currentUser.id,
user_name: currentUser.name,
action: 'approve',
});
applyDocGovRecord(doc);
} catch (err) {
pushDocGovError('审核通过', err);
}
};
const handleRejectReview = async (reason: string) => {
try {
const doc = await reviewDecision({
doc_id: docAST.docId,
user_id: currentUser.id,
user_name: currentUser.name,
action: 'reject',
reason,
});
applyDocGovRecord(doc);
setMessages((prev) => [
...prev,
{
id: 'rej-' + Date.now(),
sessionId: activeSessionId,
role: 'system',
content: `⚠️ ${currentUser.name}(审核)驳回了《${docAST.title}》,已退回编制阶段。驳回原因:${reason}`,
timestamp: new Date().toTimeString().substring(0, 5),
},
]);
} catch (err) {
pushDocGovError('审核驳回', err);
}
};
const handleApprove = async () => {
try {
const doc = await approverDecision({
doc_id: docAST.docId,
user_id: currentUser.id,
user_name: currentUser.name,
action: 'approve',
});
applyDocGovRecord(doc);
setMessages((prev) => [
...prev,
{
id: 'appr-' + Date.now(),
sessionId: activeSessionId,
role: 'assistant',
authorName: '水木东方协同参谋',
intentTag: '🏆 终审批准生效',
content: `🎉 祝贺!${currentUser.name}(批准人)已终审批准《${docAST.title}》!系统已自动对该受控交付物进行微粒度章节切片与向量化,成功注入全局标杆案例经验库!`,
timestamp: new Date().toTimeString().substring(0, 5),
recommendedCases: [
{
docId: docAST.docId,
taskKey: docAST.taskKey,
artifactTitle: docAST.title,
sectionTitle: '终审已生效标杆案例',
similarityScore: 1.0,
keyRegulations: ['GB 9706.1-2020', 'YY 0569-2011', 'IEC 60601-1-2'],
summarySnippet:
'本交付物已通过编制、审核、批准法定三级签署闭环,作为往期标杆经验向全平台开放自推荐。',
},
],
},
]);
} catch (err) {
pushDocGovError('终审批准', err);
}
};
const handleRejectApprove = async (reason: string) => {
try {
const doc = await approverDecision({
doc_id: docAST.docId,
user_id: currentUser.id,
user_name: currentUser.name,
action: 'reject',
reason,
rollback_to: 'drafting',
});
applyDocGovRecord(doc);
setMessages((prev) => [
...prev,
{
id: 'rej-appr-' + Date.now(),
sessionId: activeSessionId,
role: 'system',
content: `⚠️ ${currentUser.name}(批准人)驳回了《${docAST.title}》,已退回编制阶段。驳回原因:${reason}`,
timestamp: new Date().toTimeString().substring(0, 5),
},
]);
} catch (err) {
pushDocGovError('批准驳回', err);
}
};
// 导出 DOCX (Word 渲染器)
@@ -4206,7 +4339,7 @@ export default function App() {
onPassReview={handlePassReview}
onRejectReview={handleRejectReview}
onApprove={handleApprove}
onRejectApprove={handleRejectReview}
onRejectApprove={handleRejectApprove}
onShareToProject={handleShareToProject}
onRevokeShare={handleRevokeShare}
/>