122 lines
4.4 KiB
TypeScript
122 lines
4.4 KiB
TypeScript
import type {
|
||
ApprovalDecisionView,
|
||
GovernanceCommandResponse,
|
||
UserNotification,
|
||
} from '@/types/api';
|
||
import { governanceCommand } from './api';
|
||
|
||
export type ApprovalDecisionResolution = 'approve' | 'reject';
|
||
|
||
const REJECT_ACTION_IDS = new Set(['reject', 'rejected', 'defer', 'deferred']);
|
||
const APPROVE_SHORTCUTS = new Set(['同意', '批准', '通过', '同意执行', '批准执行', 'approve', 'approved', 'yes', 'ok']);
|
||
const REJECT_SHORTCUTS = new Set(['拒绝', '驳回', '不同意', '否决', '拒绝执行', '驳回执行', 'reject', 'rejected', 'no']);
|
||
|
||
export function isApprovalOpen(status?: string | null): boolean {
|
||
return ['pending', 'requested'].includes(String(status || '').toLowerCase());
|
||
}
|
||
|
||
export function approvalDecisionId(decision: ApprovalDecisionView): string {
|
||
return decision.approval_id || decision.id;
|
||
}
|
||
|
||
export function approvalResolutionFromAction(actionId: string): ApprovalDecisionResolution {
|
||
return REJECT_ACTION_IDS.has(actionId.toLowerCase()) ? 'reject' : 'approve';
|
||
}
|
||
|
||
export function parseApprovalShortcut(input: string): ApprovalDecisionResolution | null {
|
||
const normalized = input
|
||
.trim()
|
||
.toLowerCase()
|
||
.replace(/[\s。.!!??]/g, '');
|
||
if (APPROVE_SHORTCUTS.has(normalized)) return 'approve';
|
||
if (REJECT_SHORTCUTS.has(normalized)) return 'reject';
|
||
return null;
|
||
}
|
||
|
||
export function notificationToApprovalDecision(
|
||
notification: UserNotification,
|
||
): ApprovalDecisionView | null {
|
||
if (notification.kind !== 'need_approval' || notification.status !== 'unread') {
|
||
return null;
|
||
}
|
||
const approvalId = notification.approval_id || notification.correlation_id;
|
||
if (!approvalId) return null;
|
||
return {
|
||
id: approvalId,
|
||
approval_id: approvalId,
|
||
plan_id: notification.plan_id || null,
|
||
task_id: notification.task_id || null,
|
||
run_id: null,
|
||
target_kind: notification.task_id ? 'task' : null,
|
||
target_id: notification.task_id || notification.plan_id || approvalId,
|
||
title: notification.title || '真实执行事项需要确认',
|
||
scenario: notification.body || '请确认下一步处理方式。',
|
||
status: 'requested',
|
||
status_label: '待决策',
|
||
plan_title: notification.plan_id || '真实计划',
|
||
task_title: notification.task_id || approvalId,
|
||
actions: [
|
||
{ id: 'approve', label: '同意', tone: 'primary' },
|
||
{ id: 'reject', label: '驳回', tone: 'secondary' },
|
||
],
|
||
};
|
||
}
|
||
|
||
export function mergeApprovalDecisions(
|
||
canonical: ApprovalDecisionView[],
|
||
legacy: ApprovalDecisionView[],
|
||
): ApprovalDecisionView[] {
|
||
const merged = new Map<string, ApprovalDecisionView>();
|
||
canonical.forEach((decision) => {
|
||
merged.set(approvalDecisionId(decision), decision);
|
||
});
|
||
legacy.forEach((decision) => {
|
||
const id = approvalDecisionId(decision);
|
||
if (!merged.has(id)) merged.set(id, decision);
|
||
});
|
||
return Array.from(merged.values());
|
||
}
|
||
|
||
export async function submitApprovalDecision(
|
||
decision: ApprovalDecisionView,
|
||
actionIdOrResolution: string,
|
||
options: {
|
||
source: string;
|
||
comment?: string;
|
||
correlationPrefix?: string;
|
||
},
|
||
): Promise<GovernanceCommandResponse> {
|
||
const approvalId = approvalDecisionId(decision);
|
||
if (!approvalId) {
|
||
throw new Error('缺少 approval_id,无法处理审批。');
|
||
}
|
||
const resolution = approvalResolutionFromAction(actionIdOrResolution);
|
||
const action = decision.actions.find((item) => item.id === actionIdOrResolution);
|
||
const comment = options.comment || action?.label || (resolution === 'reject' ? '驳回' : '同意');
|
||
const stamp = Date.now();
|
||
const prefix = (options.correlationPrefix || options.source || 'approval')
|
||
.replace(/[^a-zA-Z0-9_-]/g, '-')
|
||
.replace(/-+/g, '-');
|
||
const response = await governanceCommand({
|
||
command_kind: resolution === 'reject' ? 'reject_approval' : 'approve_approval',
|
||
context_refs: {
|
||
approval_id: approvalId,
|
||
plan_id: decision.plan_id || undefined,
|
||
task_id: decision.task_id || undefined,
|
||
run_id: decision.run_id || undefined,
|
||
},
|
||
payload: {
|
||
source: options.source,
|
||
comment,
|
||
target_kind: decision.target_kind || undefined,
|
||
target_id: decision.target_id || undefined,
|
||
},
|
||
correlation_id: `${prefix}-${approvalId}-${resolution}-${stamp}`,
|
||
idempotency_key: `${prefix}-${approvalId}-${resolution}-${stamp}`,
|
||
});
|
||
if (!response.accepted) {
|
||
throw new Error(response.message || `审批命令未被接受:${response.command_id || approvalId}`);
|
||
}
|
||
return response;
|
||
}
|