/** * HTTP API 客户端:封装 fetch 调用后端 REST API(Req 16.2)。 * * 所有 API 返回 Promise,错误时抛出 ApiError(含 status 与 message)。 */ /** * 后端 API 基址。 * - 开发模式:默认 http://localhost:3005(Vite 代理或直连本地后端)。 * - 生产模式:默认空串 → 走相对路径 /api/...,由 nginx 同源反代到后端,避免硬编码域名/端口。 * - 可用环境变量 VITE_API_BASE 覆盖。 */ export const API_BASE = (import.meta.env.VITE_API_BASE as string | undefined) ?? (import.meta.env.DEV ? 'http://localhost:3005' : ''); /** 读取本地保存的鉴权令牌(生产 RBAC);演示模式下为空,请求不带令牌。 */ function authHeader(): Record { try { const t = localStorage.getItem('risk-agent-token'); return t !== null && t !== '' ? { Authorization: `Bearer ${t}` } : {}; } catch { return {}; } } /** 当前登录用户 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( readonly status: number, message: string, ) { super(message); this.name = 'ApiError'; } } async function request( method: 'GET' | 'POST', path: string, body?: unknown, ): Promise { const url = `${API_BASE}${path}`; const init: RequestInit = { method, headers: { 'Content-Type': 'application/json', ...authHeader() }, }; if (body !== undefined) { init.body = JSON.stringify(body); } const res = await fetch(url, init); const data = await res.json().catch(() => ({})); if (!res.ok) { throw new ApiError( res.status, typeof data.error === 'string' ? data.error : `HTTP ${res.status}`, ); } return data as T; } /** 分类结果(镜像后端 ClassificationResult)。 */ export interface ClassificationResult { readonly businessType: string; readonly businessTypeConfidence: number; readonly businessTypeCandidates: ReadonlyArray<{ value: string; confidence: number }>; readonly needsBusinessTypeConfirm: boolean; readonly industry: string; readonly industryConfidence: number; readonly industryCandidates: ReadonlyArray<{ value: string; confidence: number }>; readonly needsIndustryConfirm: boolean; } /** 评估运行结果(镜像后端 RunAssessment 响应)。 */ export interface RunAssessmentResult { readonly assessmentId: string; readonly assessment: unknown; readonly report: unknown; readonly classification: ClassificationResult; readonly confirmed: { businessType: string; industry: string }; readonly riskScore: number; readonly riskGrade: string; readonly acceptability: string; readonly residualGaps: readonly string[]; } /** 工作流状态。 */ export type WorkflowStatus = | 'draft' | 'pending_risk_review' | 'risk_reviewed' | 'pending_management_approval' | 'approved' | 'rejected' | 'abandoned'; /** 操作记录条目。 */ export interface AuditLogEntry { readonly role: string; readonly username: string; readonly action: string; readonly comment?: string; readonly timestamp: string; } /** 评估列表项。 */ export interface AssessmentListItem { readonly id: string; readonly projectDescription: string; readonly businessType: string; readonly industry: string; readonly region: string; readonly riskScore?: number; readonly riskGrade?: string; readonly acceptability?: string; readonly createdAt: string; readonly assessorId: string; /** 发起人显示名(按 assessorId 解析)。 */ readonly assessorName?: string; readonly status: WorkflowStatus; readonly auditLog: AuditLogEntry[]; readonly archived?: boolean; readonly recommendation?: { level: string; title: string }; } /** 综合承接建议。 */ export interface Recommendation { readonly level: 'accept' | 'conditional' | 'caution' | 'reject'; readonly title: string; readonly note: string; readonly targetMargin: number; readonly netMargin: number | null; } /** 运营控制指标——单条。 */ export interface ControlMetric { readonly name: string; readonly target: string; readonly current: string | null; readonly rationale: string; readonly action: string; } /** 运营控制指标——分组。 */ export interface ControlGroup { readonly category: '盈利管控' | '运营管控' | '质量管控'; readonly metrics: readonly ControlMetric[]; } /** 运营控制指标集合(建议承接项目的盈利/运营/质量管控看板)。 */ export interface OperatingControls { readonly groups: readonly ControlGroup[]; readonly note: string; } /** 模板列表项。 */ export interface TemplateListItem { readonly id: string; readonly name: string; readonly businessType: string; readonly industry: string; readonly isDefault: boolean; readonly dimensions: ReadonlyArray<{ readonly id: string; readonly name: string; readonly weight: number; readonly enabled: boolean; readonly indicators: ReadonlyArray<{ readonly id: string; readonly name: string; readonly weight: number; readonly enabled: boolean; }>; }>; readonly redlines: ReadonlyArray<{ readonly id: string; readonly triggerCondition: string; readonly consequence: string; readonly enabled: boolean; }>; } /** 输入项目描述,返回分类识别结果。 */ export async function classifyProject( projectDescription: string, ): Promise { return request('POST', '/api/assessments/classify', { projectDescription, }); } /** 运行完整评估流程。 */ export async function runAssessment( params: { readonly projectDescription: string; readonly confirmation?: { readonly businessType?: string; readonly industry?: string }; readonly region?: string; readonly assessorId?: string; readonly assessmentId?: string; readonly expectedSavedAt?: string; readonly clientTotalHeadcount?: number; readonly knownData?: ReadonlyArray; readonly costInputs?: Record; readonly useLlm?: boolean; readonly profitabilityInputs?: ProfitabilityInputs; }, ): Promise { return request('POST', '/api/assessments/run', params); } /** 单个指标的分级选项。 */ export interface IndicatorLevelOption { readonly level: number; readonly label: string; readonly description: string; } /** LLM 对某指标的预填建议。 */ export interface IndicatorSuggestion { readonly indicatorId: string; readonly level: number; readonly confidence: number; readonly rationale: string; } /** 追问指标项(含话术、分级含义与 LLM 预填)。 */ export interface IndicatorQuestion { readonly dimensionId: string; readonly dimensionName: string; readonly indicatorId: string; readonly name: string; readonly askPrompt: string; readonly evidenceRequired: string; readonly levels: readonly IndicatorLevelOption[]; readonly suggestion: IndicatorSuggestion | null; } /** questions 接口响应。 */ export interface QuestionsResponse { readonly businessType: string; readonly industry: string; readonly llmEnabled: boolean; readonly indicators: readonly IndicatorQuestion[]; } /** 获取指定业务类型/行业下的指标清单(含 LLM 预填)。 */ export async function fetchQuestions(params: { readonly projectDescription: string; readonly businessType: string; readonly industry?: string; readonly skipPrefill?: boolean; }): Promise { return request('POST', '/api/assessments/questions', params); } /** 用 LLM 从项目描述抽取岗位明细(名称+人数)。失败时后端返回空数组。 */ export async function extractPositions( projectDescription: string, ): Promise> { const resp = await request<{ positions?: Array<{ name: string; headcount: number }> }>( 'POST', '/api/assessments/extract-positions', { projectDescription }, ); return resp.positions ?? []; } /** LLM 综合研判结果。 */ export interface SynthesisResult { readonly suggestedGrade: string; readonly confidence: number; readonly overall: string; readonly crossRisks: readonly string[]; readonly suggestedConditions: readonly string[]; readonly divergent: boolean; } /** 盈利分析——岗位明细输入。 */ export interface PositionInput { name: string; headcount: number; monthlyGrossSalary: number; socialInsuranceBase?: number; housingFundBase?: number; monthlyBenefits?: number; unitPrice?: number; } /** 盈利分析输入。 */ export interface ProfitabilityInputs { businessType: string; region?: string; pricingModel: 'per_head' | 'cost_plus' | 'fixed_total' | 'volume'; contractMonths?: number; positions: PositionInput[]; managementFeePerHeadMonth?: number; markupRate?: number; contractTotal?: number; attritionMonthlyRate?: number; recruitingCostPerHire?: number; trainingCostPerHead?: number; employerLiabilityInsuranceRate?: number; managementSpan?: number; managementMonthlyCostPerManager?: number; accountPeriodMonths?: number; annualInterestRate?: number; periodExpenseRate?: number; utilizationRate?: number; vatMode?: 'general' | 'simplified_diff'; } /** 盈利分析结果(镜像后端 ProfitabilityResult 关键字段)。 */ export interface ProfitabilityResult { businessType: string; pricingModel: string; region: string; contractMonths: number; vatMode: string; positions: ReadonlyArray<{ name: string; headcount: number; perHead: { grossSalary: number; socialInsurance: number; housingFund: number; benefits: number; employerInsurance: number; recruitingAmortized: number; trainingAmortized: number; managementAllocated: number; fullyLoaded: number; }; loadingFactor: number; monthlyCost: number; monthlyRevenue: number; }>; totalHeadcount: number; loadingFactor: number; monthly: { revenueGross: number; revenueNet: number; laborCost: number; managementCost: number; totalCost: number; grossProfit: number; grossMargin: number; periodExpense: number; financeCost: number; vat: number; surcharge: number; riskReserve: number; badDebtReserve: number; netProfit: number; netMargin: number; }; contract: { revenueGross: number; revenueNet: number; totalCost: number; grossProfit: number; netProfit: number; }; breakeven: { unitPrice?: number; markupRate?: number; utilization?: number }; sensitivity: ReadonlyArray<{ variable: string; baseNetMargin: number; shockedNetMargin: number; deltaPct: number }>; cashflow: { maxAdvance: number; peakMonth: number; points: ReadonlyArray<{ month: number; cumulative: number }>; }; marginCurve: ReadonlyArray<{ priceFactor: number; unitPrice: number | null; netMargin: number }>; assumptions: readonly string[]; } /** 盈利分析(无状态计算)。 */ export async function analyzeProfitability(inputs: ProfitabilityInputs): Promise { return request('POST', '/api/assessments/profitability', inputs); } /** 对已完成评估生成 LLM 综合研判。 */ export async function fetchSynthesis(id: string): Promise { return request('POST', `/api/assessments/${id}/synthesis`, {}); } /** 重新生成综合承接建议(可指定目标净利率)。 */ export async function regenerateRecommendation( id: string, targetMargin?: number, ): Promise { return request( 'POST', `/api/assessments/${id}/recommendation`, targetMargin !== undefined ? { targetMargin } : {}, ); } /** 获取评估列表(全量,向后兼容)。 */ export async function fetchAssessments(): Promise { return request('GET', '/api/assessments'); } /** 分页列表响应。 */ export interface AssessmentPage { readonly items: AssessmentListItem[]; readonly total: number; readonly page: number; readonly pageSize: number; } /** 服务端分页 + 状态过滤 + 关键词搜索(直接走 SQL)。 */ export async function fetchAssessmentsPage(params: { readonly page: number; readonly pageSize: number; readonly status?: string; readonly q?: string; readonly archived?: 'active' | 'archived' | 'all'; readonly assessorId?: string; }): Promise { const sp = new URLSearchParams(); sp.set('page', String(params.page)); sp.set('pageSize', String(params.pageSize)); if (params.status !== undefined && params.status !== '' && params.status !== 'all') { sp.set('status', params.status); } if (params.q !== undefined && params.q.trim() !== '') { sp.set('q', params.q.trim()); } if (params.archived !== undefined && params.archived !== 'active') { sp.set('archived', params.archived); } if (params.assessorId !== undefined && params.assessorId !== '') { sp.set('assessorId', params.assessorId); } return request('GET', `/api/assessments?${sp.toString()}`); } /** 归档 / 取消归档评估。 */ export async function archiveAssessment( id: string, archived: boolean, user?: string, ): Promise<{ id: string; archived: boolean; status: WorkflowStatus }> { return request('POST', `/api/assessments/${id}/archive`, { archived, ...(user !== undefined ? { user } : {}), ...(currentUserId() !== undefined ? { userId: currentUserId() } : {}), }); } /** 工作台统计。 */ export interface AssessmentSummary { readonly total: number; readonly byStatus: Record; readonly archived?: number; } /** 获取各状态评估数统计。销售传本人 assessorId 则按本人统计(服务端对销售亦强制本人)。 */ export async function fetchSummary(assessorId?: string): Promise { const q = assessorId !== undefined && assessorId !== '' ? `?assessorId=${encodeURIComponent(assessorId)}` : ''; return request('GET', `/api/assessments/summary${q}`); } /** 评估详情响应。 */ export interface AssessmentDetailResponse { readonly assessment: unknown; readonly report: unknown; readonly savedAt: string; readonly status: WorkflowStatus; readonly auditLog: AuditLogEntry[]; readonly profitability?: ProfitabilityResult | null; readonly recommendation?: Recommendation | null; readonly operatingControls?: OperatingControls | null; readonly archived?: boolean; /** 红线 id → 中文标题映射(用于将结果中的红线 id 显示为中文)。 */ readonly redlineTitles?: Record; /** 盈利测算原始输入(供编辑时完整带入)。 */ readonly profitabilityInputs?: ProfitabilityInputs | null; /** 评估有效期(到期需重新评估)。 */ readonly expiresAt?: string | null; /** 审批人指派(按审批线,软约束)。 */ readonly assignment?: { riskReviewerName: string | null; managerName: string | null } | null; /** 发起人显示名(按 assessorId 解析)。 */ readonly assessorName?: string | null; } /** 获取单条评估详情。 */ export async function fetchAssessmentDetail(id: string): Promise { return request('GET', `/api/assessments/${id}`); } /** 下载评估报告(json/html 自包含文件)。 */ export async function downloadReport(id: string, format: 'json' | 'html'): Promise { const res = await fetch(`${API_BASE}/api/assessments/${id}/report/export?format=${format}`, { headers: authHeader(), }); if (!res.ok) { const err = await res.json().catch(() => ({})) as { error?: string }; throw new ApiError(res.status, err.error ?? `HTTP ${res.status}`); } const blob = await res.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `risk-report-${id}.${format}`; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); } /** 风控审核。 */ export async function reviewAssessment( id: string, action: 'approve' | 'reject', user: string, comment?: string, ): Promise<{ status: WorkflowStatus }> { return request('POST', `/api/assessments/${id}/review`, { action, user, ...(currentUserId() !== undefined ? { userId: currentUserId() } : {}), ...(comment !== undefined && comment !== '' ? { comment } : {}), }); } /** 管理层审批。通过=最终通过,放弃=终态,驳回可退回风控复审或退回销售。 */ export async function approveAssessment( id: string, action: 'approve' | 'reject' | 'abandon', user: string, comment?: string, rejectTo?: 'risk' | 'origin', ): Promise<{ status: WorkflowStatus }> { return request('POST', `/api/assessments/${id}/approve`, { action, user, ...(currentUserId() !== undefined ? { userId: currentUserId() } : {}), ...(comment !== undefined && comment !== '' ? { comment } : {}), ...(rejectTo !== undefined ? { rejectTo } : {}), }); } /** 重新提交评估。 */ export async function resubmitAssessment(id: string, user: string): Promise<{ status: WorkflowStatus }> { 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, ...(currentUserId() !== undefined ? { userId: currentUserId() } : {}) }); } /** 应用预测准确度校准(调整目标净利率基准,管理层)。 */ export async function applyCalibration(): Promise<{ appliedBase: number; previousBase: number; deviationPct: number }> { return request('POST', '/api/calibration/apply', {}); } /** 查询当前目标净利率基准与据偏差计算的建议基准、是否已校准。 */ export async function fetchCalibration(): Promise<{ currentBase: number; suggestedBase: number; uncalibratedBase: number; calibrated: boolean; bias: string | null; deviationPct: number | null; }> { return request('GET', '/api/calibration'); } /** 风控/管理层对「待核实」红线进行人工裁定(命中/未命中),闭环判定。 */ 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, ...(currentUserId() !== undefined ? { userId: currentUserId() } : {}) }); } /** 管理层对「已通过」或「已放弃」项目直接调整工作流状态(留痕)。 */ export async function overrideAssessmentStatus( id: string, status: WorkflowStatus, user: string, comment?: string, ): Promise<{ status: WorkflowStatus }> { return request('POST', `/api/assessments/${id}/override`, { status, user, ...(currentUserId() !== undefined ? { userId: currentUserId() } : {}), ...(comment !== undefined && comment !== '' ? { comment } : {}), }); } /** 获取默认模板列表。 */ export async function fetchTemplates(): Promise { return request('GET', '/api/templates'); } /* ------------------------------------------------------------------ * * P1-P3 新增功能 API * ------------------------------------------------------------------ */ /** 费率条目。 */ export interface RateEntry { id: number; region: string; category: string; key: string; value: number; effectiveDate: string; version: number; reviewed: boolean; } export async function fetchAllRates(): Promise { return request('GET', '/api/rates'); } export async function createRate(entry: Omit): Promise { return request('POST', '/api/rates', entry); } export async function reviewRate(id: number): Promise { await request('POST', `/api/rates/${id}/review`, {}); } export async function deleteRateEntry(id: number): Promise { await fetch(`${API_BASE}/api/rates/${id}`, { method: 'DELETE', headers: authHeader() }); } /** 社保单位部分各险种费率。 */ export interface SocialInsuranceRates { pension: number; medical: number; unemployment: number; injury: number; maternity: number; } /** 地域完整费率套(与引擎 RegionRates 对齐)。 */ export interface RegionRates { regionName: string; socialInsurance: SocialInsuranceRates; housingFund: number; vatGeneralRate: number; vatSimplifiedRate: number; surchargeRate: number; } /** 地域费率套记录(含复核状态)。 */ export interface RegionRateRecord { region: string; rates: RegionRates; reviewed: boolean; updatedBy: string | null; updatedAt: string; } export async function fetchEngineDefaults(): Promise<{ national: RegionRates; regions: Record }> { return request('GET', '/api/region-rates/engine-defaults'); } export async function fetchRegionRates(): Promise { return request('GET', '/api/region-rates'); } export async function saveRegionRate(region: string, rates: RegionRates, updatedBy?: string): Promise { await request('POST', '/api/region-rates', { region, rates, ...(updatedBy ? { updatedBy } : {}) }); } export async function reviewRegionRate(region: string): Promise { await request('POST', `/api/region-rates/${encodeURIComponent(region)}/review`, {}); } export async function deleteRegionRate(region: string): Promise { await fetch(`${API_BASE}/api/region-rates/${encodeURIComponent(region)}`, { method: 'DELETE', headers: authHeader() }); } /** 红线规则。 */ export interface RedlineRuleItem { id: string; title: string; triggerCondition: string; consequence: string; region: string | null; businessType: string | null; enabled: boolean; version: number; regulationRef: string | null; /** 关联度量(可计算红线):4 个数值度量,或 `ind:<指标id>`(绑定指标风险等级);null 表示人工核实。 */ linkedMetric?: string | null; /** 比较运算符。 */ compareOp?: '>=' | '<=' | '>' | '<' | null; /** 阈值(百分比类按百分数;逾期按天;指标等级按 1-5)。 */ threshold?: number | null; /** 可选第二条件(与主条件 AND 组合)。 */ linkedMetric2?: string | null; compareOp2?: '>=' | '<=' | '>' | '<' | null; threshold2?: number | null; } export async function fetchRedlineRules(): Promise { return request('GET', '/api/redline-rules'); } export async function createRedlineRule(rule: RedlineRuleItem): Promise { return request('POST', '/api/redline-rules', rule); } export async function deleteRedlineRuleApi(id: string): Promise { await fetch(`${API_BASE}/api/redline-rules/${id}`, { method: 'DELETE', headers: authHeader() }); } /** 客户档案。 */ export interface CustomerItem { id: string; name: string; creditRating: string; avgOverdueDays: number; totalContractAmount: number; assessmentCount: number; notes: string | null; } export async function fetchCustomers(): Promise { return request('GET', '/api/customers'); } export async function createCustomer(c: CustomerItem): Promise { return request('POST', '/api/customers', c); } export async function deleteCustomerApi(id: string): Promise { await fetch(`${API_BASE}/api/customers/${id}`, { method: 'DELETE', headers: authHeader() }); } export async function fetchConcentration(id: string): Promise<{ concentration: number; warning: string | null }> { return request('GET', `/api/customers/${id}/concentration`); } /** 客户回款记录。 */ export interface CustomerPayment { id: number; customerId: string; invoiceAmount: number; dueDate: string; paidDate: string | null; note: string | null; } export async function fetchPayments(customerId: string): Promise { return request('GET', `/api/customers/${customerId}/payments`); } export async function addPayment( customerId: string, p: { invoiceAmount: number; dueDate: string; paidDate?: string | null; note?: string | null }, ): Promise<{ saved: boolean; avgOverdueDays: number }> { return request('POST', `/api/customers/${customerId}/payments`, p); } export async function deletePaymentApi(customerId: string, paymentId: number): Promise<{ avgOverdueDays: number }> { const res = await fetch(`${API_BASE}/api/customers/${customerId}/payments/${paymentId}`, { method: 'DELETE', headers: authHeader() }); return res.json(); } /** 各地域最低工资标准。 */ export interface MinWageItem { region: string; monthlyWage: number; updatedBy: string | null; updatedAt: string; } export async function fetchMinWages(): Promise { return request('GET', '/api/min-wages'); } export async function saveMinWage(region: string, monthlyWage: number, updatedBy?: string): Promise { await request('POST', '/api/min-wages', { region, monthlyWage, ...(updatedBy ? { updatedBy } : {}) }); } export async function deleteMinWageApi(region: string): Promise { await fetch(`${API_BASE}/api/min-wages/${encodeURIComponent(region)}`, { method: 'DELETE', headers: authHeader() }); } /** 向导草稿(服务端持久化,跨设备)。列表项不含 form。 */ export interface DraftItem { id: string; assessorId: string | null; sourceAssessmentId: string | null; projectName: string | null; updatedAt: string; } /** 草稿详情(含完整向导快照 form)。 */ export interface DraftRecord extends DraftItem { form: unknown; } /** 列出草稿(可按评估人过滤)。 */ export async function listDrafts(assessorId?: string): Promise { const q = assessorId ? `?assessorId=${encodeURIComponent(assessorId)}` : ''; return request('GET', `/api/drafts${q}`); } /** 取草稿详情(含 form)。 */ export async function getDraft(id: string): Promise { return request('GET', `/api/drafts/${encodeURIComponent(id)}`); } /** 保存(新增/更新)草稿。 */ export async function saveDraft(input: { id: string; assessorId?: string | null; sourceAssessmentId?: string | null; projectName?: string | null; form: unknown; }): Promise { return request('POST', '/api/drafts', input); } /** 删除草稿。 */ export async function deleteDraftApi(id: string): Promise { await fetch(`${API_BASE}/api/drafts/${encodeURIComponent(id)}`, { method: 'DELETE', headers: authHeader() }); } /** 用户角色。 */ export type UserRole = '商务/销售' | '风控' | '管理层' | '系统管理员'; /** 用户账号(不含密码)。 */ export interface UserItem { id: string; username: string; displayName: string | null; role: UserRole; active: boolean; createdAt: string; } /** 列出全部用户(系统管理员)。 */ export async function listUsers(): Promise { return request('GET', '/api/users'); } /** 新增用户。 */ export async function createUserApi(input: { username: string; displayName?: string; password: string; role: UserRole }): Promise { return request('POST', '/api/users', input); } /** 更新用户显示名/角色/启用状态。 */ export async function updateUserApi(id: string, patch: { displayName?: string | null; role?: UserRole; active?: boolean }): Promise { const res = await fetch(`${API_BASE}/api/users/${encodeURIComponent(id)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', ...authHeader() }, body: JSON.stringify(patch), }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new ApiError(res.status, typeof data.error === 'string' ? data.error : `HTTP ${res.status}`); return data as UserItem; } /** 重置用户密码。 */ export async function resetUserPasswordApi(id: string, password: string): Promise { await request('POST', `/api/users/${encodeURIComponent(id)}/password`, { password }); } /** 删除用户。 */ export async function deleteUserApi(id: string): Promise { await fetch(`${API_BASE}/api/users/${encodeURIComponent(id)}`, { method: 'DELETE', headers: authHeader() }); } /* ----------------------------- 审批流程配置 ----------------------------- */ export type ApprovalField = 'riskGrade' | 'netMarginPct' | 'acceptability' | 'redlineHit' | 'contractAmount' | 'businessType'; export type ApprovalOp = '>=' | '<=' | '>' | '<' | '==' | '!='; export interface ApprovalCondition { field: ApprovalField; op: ApprovalOp; value: string | number | boolean; } export interface ApprovalRule { id: string; name: string; enabled: boolean; requireManagement: boolean; conditions: ApprovalCondition[]; } export interface ApprovalLine { salesId: string; riskReviewerId: string | null; managerId: string | null; } export interface ApprovalAssignment { enabled: boolean; defaultRiskReviewerId: string | null; defaultManagerId: string | null; lines: ApprovalLine[]; } export interface ApprovalConfig { defaultRequireManagement: boolean; slaRiskHours: number; slaMgmtHours: number; rejectTo: 'origin' | 'risk'; rules: ApprovalRule[]; assignment: ApprovalAssignment; } /** 审批人指派记录。 */ export interface AssignmentRecord { assessmentId: string; riskReviewerId: string | null; riskReviewerName: string | null; managerId: string | null; managerName: string | null; } /** 全部评估的审批人指派(assessmentId → 记录)。 */ export async function fetchAssignments(): Promise> { return request>('GET', '/api/assignments'); } /** 读取审批流程配置。 */ export async function fetchApprovalConfig(): Promise { return request('GET', '/api/approval-config'); } /** 保存审批流程配置(系统管理员)。 */ export async function saveApprovalConfig(config: ApprovalConfig): Promise { const res = await fetch(`${API_BASE}/api/approval-config`, { method: 'PUT', headers: { 'Content-Type': 'application/json', ...authHeader() }, body: JSON.stringify(config), }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new ApiError(res.status, typeof data.error === 'string' ? data.error : `HTTP ${res.status}`); return data as ApprovalConfig; } /** 方案对比。 */ export interface ScenarioItem { id: string; label: string; inputs: ProfitabilityInputs; result: ProfitabilityResult; } export async function fetchScenarios(assessmentId: string): Promise { return request('GET', `/api/assessments/${assessmentId}/scenarios`); } export async function createScenario(assessmentId: string, label: string, inputs: ProfitabilityInputs): Promise { return request('POST', `/api/assessments/${assessmentId}/scenarios`, { label, inputs }); } /** 相似项目。 */ export interface SimilarProjectItem { id: string; projectDescription: string; businessType: string; industry: string; riskScore: number | null; riskGrade: string | null; netMargin: number | null; rank: number; } export async function fetchSimilarProjects(description: string): Promise { const q = encodeURIComponent(description); return request('GET', `/api/similar?description=${q}`); } /** 看板统计。 */ export interface DashboardStats { byBusinessType: Array<{ dimension: string; count: number; avgRiskScore: number | null; passRate: number | null }>; byIndustry: Array<{ dimension: string; count: number; avgRiskScore: number | null; passRate: number | null }>; byRegion: Array<{ dimension: string; count: number; avgRiskScore: number | null; passRate: number | null }>; riskDist: Array<{ grade: string; count: number }>; trend: Array<{ month: string; count: number }>; } export async function fetchDashboardStats(): Promise { return request('GET', '/api/dashboard/stats'); } /** 系统操作审计日志项。 */ export interface SystemLogItem { id: number; ts: string; actorId: string | null; actorName: string | null; role: string | null; action: string; method: string; path: string; targetId: string | null; targetName: string | null; status: number | null; success: boolean | null; durationMs: number | null; ip: string | null; query: string | null; detail: unknown; } export interface SystemLogPage { items: SystemLogItem[]; total: number; page: number; pageSize: number; actions: string[]; roles: string[]; actors: Array<{ id: string; name: string }>; } /** 查询系统操作审计日志(系统管理员)。 */ export async function fetchSystemLogs(params: { page: number; pageSize: number; actorId?: string; action?: string; role?: string; method?: string; q?: string; from?: string; to?: string; success?: 'true' | 'false'; }): Promise { const sp = new URLSearchParams(); sp.set('page', String(params.page)); sp.set('pageSize', String(params.pageSize)); if (params.action) sp.set('action', params.action); if (params.actorId) sp.set('actorId', params.actorId); if (params.role) sp.set('role', params.role); if (params.method) sp.set('method', params.method); if (params.q) sp.set('q', params.q); if (params.from) sp.set('from', params.from); if (params.to) sp.set('to', params.to); if (params.success) sp.set('success', params.success); return request('GET', `/api/system-logs?${sp.toString()}`); } /** 经验库。 */ export interface ExperienceItem { id: number; assessmentId: string; businessType: string; industry: string; projectSummary: string; lesson: string; tags: string[]; } export async function fetchExperience(businessType?: string, industry?: string): Promise { const sp = new URLSearchParams(); if (businessType) sp.set('businessType', businessType); if (industry) sp.set('industry', industry); const qs = sp.toString(); return request('GET', `/api/experience${qs ? '?' + qs : ''}`); } /** 销售修改项目资料(仅驳回状态)。 */ export async function updateAssessment( id: string, data: { projectDescription?: string; region?: string; profitabilityInputs?: ProfitabilityInputs; user?: string }, ): Promise<{ updated: boolean; profitability: ProfitabilityResult | null; recommendation: Recommendation | null }> { const res = await fetch(`${API_BASE}/api/assessments/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', ...authHeader() }, body: JSON.stringify({ ...data, ...(currentUserId() !== undefined ? { userId: currentUserId() } : {}) }), }); if (!res.ok) { const err = await res.json().catch(() => ({})) as { error?: string }; throw new ApiError(res.status, err.error ?? `HTTP ${res.status}`); } return res.json(); }