吸收数字员工审批跨端冲突可视化

This commit is contained in:
baiyanyun
2026-06-11 21:53:08 +08:00
parent 84d34236e8
commit e57c94c19d
15 changed files with 625 additions and 208 deletions

View File

@@ -920,8 +920,8 @@ export function recordApprovalAction(
});
}
export function pullApprovalUpdates(): Promise<{ ok: boolean; applied?: number; ignored?: number; skipped?: boolean; error?: string | null }> {
return apiFetch<{ ok: boolean; applied?: number; ignored?: number; skipped?: boolean; error?: string | null }>('/api/approval/updates/pull', {
export function pullApprovalUpdates(): Promise<{ ok: boolean; applied?: number; ignored?: number; conflicted?: number; skipped?: boolean; error?: string | null }> {
return apiFetch<{ ok: boolean; applied?: number; ignored?: number; conflicted?: number; skipped?: boolean; error?: string | null }>('/api/approval/updates/pull', {
method: 'POST',
body: JSON.stringify({}),
});

View File

@@ -63,7 +63,7 @@ declare global {
setSkillEnabled?: (skillId: string, enabled: boolean) => Promise<{ ok?: boolean; skill_id?: string; enabled?: boolean; error?: string }>;
pullSkillPolicies?: () => Promise<{ ok?: boolean; skipped?: boolean; error?: string | null }>;
pullRiskApprovalPolicy?: () => Promise<{ ok?: boolean; skipped?: boolean; error?: string | null }>;
pullApprovalUpdates?: () => Promise<{ ok?: boolean; applied?: number; ignored?: number; skipped?: boolean; error?: string | null }>;
pullApprovalUpdates?: () => Promise<{ ok?: boolean; applied?: number; ignored?: number; conflicted?: number; skipped?: boolean; error?: string | null }>;
submitApprovalDecision?: (input: Record<string, unknown>) => Promise<{ ok?: boolean; error?: string | null }>;
pullNotificationUpdates?: () => Promise<{ ok?: boolean; applied?: number; ignored?: number; skipped?: boolean; error?: string | null }>;
submitNotificationAction?: (input: Record<string, unknown>) => Promise<{ ok?: boolean; error?: string | null }>;
@@ -1308,7 +1308,7 @@ async function pullBridgeRouteDecisionPolicy(): Promise<RouteDecisionPolicyPullR
}
}
async function pullBridgeApprovalUpdates(): Promise<{ ok: boolean; applied?: number; ignored?: number; skipped?: boolean; error?: string | null }> {
async function pullBridgeApprovalUpdates(): Promise<{ ok: boolean; applied?: number; ignored?: number; conflicted?: number; skipped?: boolean; error?: string | null }> {
const bridge = window.QimingClawBridge?.digital;
if (!bridge?.pullApprovalUpdates) {
return { ok: false, error: 'qimingclaw_bridge_unavailable' };
@@ -1322,6 +1322,7 @@ async function pullBridgeApprovalUpdates(): Promise<{ ok: boolean; applied?: num
ok: result?.ok !== false,
applied: Number(result?.applied ?? 0),
ignored: Number(result?.ignored ?? 0),
conflicted: Number(result?.conflicted ?? 0),
...(result?.skipped ? { skipped: true } : {}),
...(result?.error ? { error: result.error } : {}),
};
@@ -3560,6 +3561,7 @@ function businessApprovalRows(snapshot: QimingclawSnapshot | null): BusinessView
const runtime = runtimeById.get(approvalId);
const summary = approvalActionSummary(snapshot, approvalId, approval);
const workflow = approvalWorkflowSummary(approval);
const conflict = approvalConflictSummary(approval);
const entity = businessEntityFromRefs({
plan_id: approval.plan_id,
task_id: approval.task_id,
@@ -3588,6 +3590,12 @@ function businessApprovalRows(snapshot: QimingclawSnapshot | null): BusinessView
approval_step_index: workflow.step_index,
approval_step_count: workflow.step_count,
next_step_label: workflow.next_step_label,
approval_conflict_active: conflict.active,
approval_conflict_reason: conflict.reason,
approval_conflict_remote_status: conflict.remote_status,
approval_conflict_detected_at: conflict.detected_at,
approval_conflict_local_status: conflict.local_status,
approval_conflict_remote_current_step_id: conflict.remote_current_step_id,
};
});
}
@@ -3728,6 +3736,19 @@ function approvalWorkflowSummary(approval: Record<string, unknown>): ApprovalWor
};
}
function approvalConflictSummary(approval: Record<string, unknown>): Record<string, unknown> {
const payload = asRecord(approval.payload) ?? {};
const conflict = asRecord(payload.conflict) ?? {};
return {
active: conflict.active === true,
reason: stringValue(conflict.reason) || null,
remote_status: stringValue(conflict.remote_status ?? conflict.remoteStatus) || null,
detected_at: stringValue(conflict.detected_at ?? conflict.detectedAt) || null,
local_status: stringValue(conflict.local_status ?? conflict.localStatus) || null,
remote_current_step_id: stringValue(conflict.remote_current_step_id ?? conflict.remoteCurrentStepId) || null,
};
}
function approvalActionSummary(
snapshot: QimingclawSnapshot | null,
approvalId: string,
@@ -4994,6 +5015,7 @@ function buildWorkdayDecisions(
const plan = planId ? plansById.get(planId) : null;
const task = taskId ? tasksById.get(taskId) : null;
const summary = approvalSummaries.get(decisionId);
const hasConflict = summary?.approval_conflict_active === true;
return {
id: decisionId,
title: stringValue(approval.title) || stringValue(payload?.title) || '待确认事项',
@@ -5016,7 +5038,17 @@ function buildWorkdayDecisions(
approval_step_index: numberValue(summary?.approval_step_index),
approval_step_count: numberValue(summary?.approval_step_count),
next_step_label: stringValue(summary?.next_step_label) || null,
actions: isDecisionOpenStatus(status)
conflict: hasConflict
? {
active: true,
reason: stringValue(summary?.approval_conflict_reason) || null,
remote_status: stringValue(summary?.approval_conflict_remote_status) || null,
detected_at: stringValue(summary?.approval_conflict_detected_at) || null,
local_status: stringValue(summary?.approval_conflict_local_status) || null,
remote_current_step_id: stringValue(summary?.approval_conflict_remote_current_step_id) || null,
}
: null,
actions: isDecisionOpenStatus(status) && !hasConflict
? [
{ id: 'approved', label: '同意', tone: 'primary' },
{ id: 'rejected', label: '驳回', tone: 'secondary' },