大改动:全部人员用ID关联(JWT带uid + audit actor_id + assessorId改用户ID + 看板按ID匹配 + 历史回填)

- JWT 载荷增加 uid;登录返回 id;前端持久化 uid 并在变更请求中携带 userId
- 操作人服务端解析(优先JWT.uid,回退body.userId),审计写入 actor_id + 当时显示名
- audit_logs 增加 actor_id 列;持久化与加载带 actorId
- assessments.assessorId 改存用户ID,列表/详情按ID解析显示名(assessorName)
- 看板待办「分给我的」改为按 userId 匹配;发起人显示真实姓名
- 审批线指派按 salesId(用户ID) 计算
- scripts/backfill-actor-ids.sql 回填历史(旧账号名→当前用户ID),本地+生产已执行
This commit is contained in:
freedakgmail
2026-06-13 19:18:17 +08:00
parent e86e60208f
commit 5afe021b56
10 changed files with 194 additions and 47 deletions
+21 -4
View File
@@ -24,6 +24,15 @@ function authHeader(): Record<string, string> {
}
}
/** 当前登录用户 ID(用于在记录中以 ID 关联操作人;演示模式下作为 body 兜底)。 */
export function currentUserId(): string | undefined {
try {
return localStorage.getItem('risk-agent-uid') ?? undefined;
} catch {
return undefined;
}
}
/** API 调用错误。 */
export class ApiError extends Error {
constructor(
@@ -118,6 +127,8 @@ export interface AssessmentListItem {
readonly acceptability?: string;
readonly createdAt: string;
readonly assessorId: string;
/** 发起人显示名(按 assessorId 解析)。 */
readonly assessorName?: string;
readonly status: WorkflowStatus;
readonly auditLog: AuditLogEntry[];
readonly archived?: boolean;
@@ -439,6 +450,7 @@ export async function archiveAssessment(
return request('POST', `/api/assessments/${id}/archive`, {
archived,
...(user !== undefined ? { user } : {}),
...(currentUserId() !== undefined ? { userId: currentUserId() } : {}),
});
}
@@ -473,6 +485,8 @@ export interface AssessmentDetailResponse {
readonly expiresAt?: string | null;
/** 审批人指派(按审批线,软约束)。 */
readonly assignment?: { riskReviewerName: string | null; managerName: string | null } | null;
/** 发起人显示名(按 assessorId 解析)。 */
readonly assessorName?: string | null;
}
/** 获取单条评估详情。 */
@@ -510,6 +524,7 @@ export async function reviewAssessment(
return request('POST', `/api/assessments/${id}/review`, {
action,
user,
...(currentUserId() !== undefined ? { userId: currentUserId() } : {}),
...(comment !== undefined && comment !== '' ? { comment } : {}),
});
}
@@ -525,6 +540,7 @@ export async function approveAssessment(
return request('POST', `/api/assessments/${id}/approve`, {
action,
user,
...(currentUserId() !== undefined ? { userId: currentUserId() } : {}),
...(comment !== undefined && comment !== '' ? { comment } : {}),
...(rejectTo !== undefined ? { rejectTo } : {}),
});
@@ -532,12 +548,12 @@ export async function approveAssessment(
/** 重新提交评估。 */
export async function resubmitAssessment(id: string, user: string): Promise<{ status: WorkflowStatus }> {
return request('POST', `/api/assessments/${id}/resubmit`, { user });
return request('POST', `/api/assessments/${id}/resubmit`, { user, ...(currentUserId() !== undefined ? { userId: currentUserId() } : {}) });
}
/** 申报:将草稿评估报送风控审核。 */
export async function submitAssessment(id: string, user: string): Promise<{ status: WorkflowStatus }> {
return request('POST', `/api/assessments/${id}/submit`, { user });
return request('POST', `/api/assessments/${id}/submit`, { user, ...(currentUserId() !== undefined ? { userId: currentUserId() } : {}) });
}
/** 应用预测准确度校准(调整目标净利率基准,管理层)。 */
@@ -550,7 +566,7 @@ export async function submitRedlineVerdict(
id: string,
params: { redlineId: string; status: '命中' | '未命中'; note?: string; user: string; role?: string; title?: string },
): Promise<{ redlineId: string; status: string; acceptability: string }> {
return request('POST', `/api/assessments/${id}/redline-verdict`, params);
return request('POST', `/api/assessments/${id}/redline-verdict`, { ...params, ...(currentUserId() !== undefined ? { userId: currentUserId() } : {}) });
}
/** 管理层对「已通过」或「已放弃」项目直接调整工作流状态(留痕)。 */
@@ -563,6 +579,7 @@ export async function overrideAssessmentStatus(
return request('POST', `/api/assessments/${id}/override`, {
status,
user,
...(currentUserId() !== undefined ? { userId: currentUserId() } : {}),
...(comment !== undefined && comment !== '' ? { comment } : {}),
});
}
@@ -987,7 +1004,7 @@ export async function updateAssessment(
const res = await fetch(`${API_BASE}/api/assessments/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', ...authHeader() },
body: JSON.stringify(data),
body: JSON.stringify({ ...data, ...(currentUserId() !== undefined ? { userId: currentUserId() } : {}) }),
});
if (!res.ok) {
const err = await res.json().catch(() => ({})) as { error?: string };