import { formatInvoiceOcrFieldLabels } from "../lib/invoiceOcr"; import { resolveErrorReasonMessage, UNKNOWN_VALIDATION_MESSAGE } from "./errorReasons"; function readStringList(value: unknown): string[] { if (!Array.isArray(value)) { return []; } return value .map((item) => (typeof item === "string" ? item.trim() : "")) .filter((item) => item.length > 0); } function isRecord(value: unknown): value is Record { return value != null && typeof value === "object" && !Array.isArray(value); } function countGuardAmountUnavailableItems(value: unknown): number { return Array.isArray(value) ? value.filter((item) => ( isRecord(item) && typeof item.itemId === "string" && item.itemId.length > 0 && readStringList(item.invoiceIds).length > 0 )).length : 0; } function countGuardAmountExceededItems(value: unknown): number { return Array.isArray(value) ? value.filter((item) => ( isRecord(item) && typeof item.itemId === "string" && item.itemId.length > 0 && typeof item.declaredAmount === "string" && item.declaredAmount.length > 0 && typeof item.invoiceTotal === "string" && item.invoiceTotal.length > 0 )).length : 0; } const FEATURE_FLAG_DISABLED_MESSAGES: Record = { portal_home_v1_disabled: "工作台首页当前未启用,请联系管理员检查环境开关与功能开关配置。", announcements_v1_disabled: "公告模块当前未启用,请联系管理员检查环境开关与功能开关配置。", presence_board_v1_disabled: "团队状态模块当前未启用,请联系管理员检查环境开关与功能开关配置。", attendance_v1_disabled: "历史 OA 自建打卡已关闭,请在企业微信打卡。", attendance_unified_v1_disabled: "企微考勤统一汇总当前未启用,请联系管理员检查环境开关与功能开关配置。", weekly_report_v1_disabled: "周报模块当前未启用,请联系管理员检查环境开关与功能开关配置。", employee_lifecycle_v1_disabled: "人员生命周期模块当前未启用,请联系管理员检查环境开关与功能开关配置。", wecom_hr_master_sync_v1_disabled: "企微 HR 主数据镜像当前未启用,请联系管理员检查环境开关与功能开关配置。", todo_board_v1_disabled: "个人事项功能当前未启用,请联系管理员检查环境开关与功能开关配置。", project_domain_v1_disabled: "项目模块当前未启用,请联系管理员检查环境开关与功能开关配置。", project_linking_v1_disabled: "项目关联功能当前未启用,请联系管理员检查环境开关与功能开关配置。", project_communication_log_v1_disabled: "项目沟通回填当前未启用,请联系管理员检查环境开关与功能开关配置。", project_closure_checklist_v1_disabled: "项目收口清单当前未启用,请联系管理员检查环境开关与功能开关配置。", confidential_doc_refs_v1_disabled: "涉密资料引用当前未启用,请联系管理员检查环境开关与功能开关配置。", }; function resolveFeatureFlagDisabledMessage(value: unknown): string | null { if (typeof value !== "string" || !value.endsWith("_disabled")) { return null; } return ( FEATURE_FLAG_DISABLED_MESSAGES[value] ?? "当前功能未启用,请联系管理员检查环境开关与功能开关配置。" ); } export function mapApiErrorMessage( code: string, details?: Record, fallbackMessage?: string, ): string { if (code === "VALID_001" && details?.reasonCode === "reimbursement.items_invoice_item_guard_failed") { return mapApiErrorMessage(code, { ...details, reasonCode: undefined, items: "invoice_item_guard_failed" }); } if (code === "VALID_001" && details?.reasonCode !== undefined) { if (details.reasonCode === "invoice_pool.ocr_fields_missing") { const missingFields = readStringList(details.missingFields).filter((field) => ["invoiceCode", "invoiceNumber", "invoiceDate", "totalAmount", "sellerName"].includes(field)); if (missingFields.length) { return mapApiErrorMessage(code, { ocr: "missing_required_fields", missingFields }); } } if (details.reasonCode === "workflow.remind_cooldown") { const nextAllowedAt = details.nextAllowedAt; if (typeof nextAllowedAt === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/.test(nextAllowedAt) && Number.isFinite(new Date(nextAllowedAt).getTime())) { return mapApiErrorMessage(code, { workflow: "remind_too_frequent", nextAllowedAt }); } } if (details.reasonCode === "auth.password_policy_failed") { const password = readStringList(details.password).filter((rule) => [ "密码长度必须 ≥ 12", "密码必须包含小写字母", "密码必须包含大写字母", "密码必须包含数字", "密码过于常见/弱", ].includes(rule)); if (password.length) { return mapApiErrorMessage(code, { password }); } } return resolveErrorReasonMessage(details.reasonCode) ?? UNKNOWN_VALIDATION_MESSAGE; } if (code === "AUTH_001") { return "账号或密码错误,请检查后重试。"; } if (code === "AUTH_002") { const lockedUntil = typeof details?.lockedUntil === "string" ? details.lockedUntil : null; if (lockedUntil) { const until = new Date(lockedUntil); if (!Number.isNaN(until.getTime())) { return `账号已锁定至 ${until.toLocaleString()},请稍后重试。`; } } return "账号已锁定,请稍后重试。"; } if (code === "AUTH_003") { return "账号已离职/禁用,请联系管理员。"; } if (code === "AUTH_004") { return "登录状态失效,请重新登录。"; } if (code === "AUTH_005") { return "登录状态已失效,请重新登录。"; } if (code === "PERM_001") { return "当前账号无权限执行该操作。"; } if (code === "PERM_002") { return "你不是当前任务处理人,无法执行该操作。"; } if (code === "SYS_404") { return "目标数据不存在或已被删除。"; } if (code === "SYS_001" && details?.email === "delivery_not_configured") { return "邮件验证码暂不可用,请使用密码登录或联系管理员。"; } if (code === "SYS_001") { return "服务暂不可用,请稍后重试或联系管理员。"; } if (code === "SYS_429") { return "请求过于频繁,请稍后重试。"; } if (code === "VALID_002") { return "输入内容长度不足,请补充后再提交。"; } if (code === "VALID_003") { return "票据疑似重复,请核对后再上传或联系财务处理。"; } if (code === "VALID_004") { return "原始单据已审批通过/付款,当前操作不允许。"; } if (code === "VALID_005") { return "票据已绑定报销单,无需重复绑定。"; } if (code === "VALID_006") { return "票据识别重试次数已达上限,请稍后人工处理。"; } if (code === "VALID_007") { return "票据识别重试过于频繁,请稍后再试。"; } if (code === "VALID_001") { if (details?.amount === "must_be_positive") { return "明细金额必须大于 0。"; } if (details?.items === "at_least_one_required") { return "请先添加至少 1 条报销明细。"; } if (details?.items === "positive_amount_required") { return "所有报销明细金额都必须大于 0,请修改后再提交。"; } if (details?.items === "invoice_item_guard_failed") { const messages: string[] = []; const rawUnlinkedCount = details.unlinkedInvoiceCount; const unlinkedCount = typeof rawUnlinkedCount === "number" && Number.isInteger(rawUnlinkedCount) && rawUnlinkedCount >= 0 ? rawUnlinkedCount : 0; const missingCount = readStringList(details.missingInvoiceItemIds).length; const unavailableCount = countGuardAmountUnavailableItems(details.amountUnavailableItems); const exceededCount = countGuardAmountExceededItems(details.amountExceededItems); if (unlinkedCount > 0) messages.push(`${unlinkedCount} 张发票未选择报销明细`); if (missingCount > 0) messages.push(`${missingCount} 条需票明细尚未关联发票`); if (unavailableCount > 0) messages.push(`${unavailableCount} 条明细的发票金额未识别`); if (exceededCount > 0) messages.push(`${exceededCount} 条明细的申报金额高于发票合计`); return messages.length ? `票据检查未通过:${messages.join(";")}。请按下方提示处理。` : "票据检查未通过,请核对每条明细与发票的关联和金额后重试。"; } if (details?.invoice === "invalid_unlinked_item_state") { return "这张发票的关联状态已变化,请刷新后重试。"; } if (details?.reimbursement === "invalid_status_for_invoice_item_link") { return "当前报销单状态已变化,不能再关联发票,请刷新页面。"; } if (details?.linkedItemId === "item_not_in_reimbursement") { return "所选明细已不存在或不属于本单,请刷新后重新选择。"; } const passwordErrors = readStringList(details?.password); if (passwordErrors.length) { return `新密码不符合要求:${passwordErrors.join(";")}。`; } const featureFlagMessage = resolveFeatureFlagDisabledMessage(details?.featureFlag); if (featureFlagMessage) { return featureFlagMessage; } if (details?.["x-device-fingerprint"] === "missing") { return "当前设备校验信息缺失,请刷新页面后重试。"; } if (details?.code === "invalid_or_expired") { return "验证码无效或已过期,请重新获取。"; } if (details?.payment === "already_paid_conflict") { return "该单据已由其他付款动作处理,请刷新后核对付款记录。"; } if (details?.payment === "source_amount_changed") { return "源单金额已发生变化,本次付款未执行,请刷新后重新制单。"; } if (details?.payment === "payment_batch_v1_required") { return "付款批次已启用,请从付款页面完成制单、复核和执行。"; } if (details?.entityIds === "contains_active_payment_batch") { return "所选报销已进入其他有效付款批次,请刷新候选列表。"; } if (details?.receiptAttachment === "void_required") { return "银行回单不能直接删除,请填写原因后作废。"; } if (details?.status === "invalid_pay_status") { return "当前状态不允许执行付款。"; } if (details?.review === "maker_checker_conflict") { return "复核人与批次创建人不能是同一人。"; } if (details?.review === "batch_review_required") { return "当前批次尚未完成复核,不能执行。"; } if (details?.review === "batch_not_reviewable") { return "当前批次状态不允许继续复核。"; } if (details?.review === "no_eligible_reviewer") { return "当前没有可复核人(复核人必须是非制单人且在职),请联系管理员。"; } if (details?.receiptAttachments === "bank_receipt_required") { return "请先上传银行回单,再登记付款完成。"; } if (details?.transfer === "chain_limit_exceeded") { return "当前任务转审次数已达上限。"; } if (details?.transfer === "same_assignee_not_allowed") { return "不能将任务转审给自己。"; } if (details?.workflow === "self_approval_not_allowed") { return "自己的申请不能审批,请等待下一审批人处理。"; } if (details?.workflow === "self_remind_not_allowed") { return "不能催办分配给自己的审批任务。"; } if (details?.workflow === "remind_owner_required") { return "只有申请人或提交人可以催办该审批。"; } if (details?.workflow === "remind_too_frequent") { const nextAllowedAt = typeof details?.nextAllowedAt === "string" ? details.nextAllowedAt : null; return nextAllowedAt ? `24 小时内已催办过,请在 ${nextAllowedAt.slice(0, 19).replace("T", " ")} 后再试。` : "24 小时内已催办过。"; } if (details?.workflow === "business_approval_role_required") { return "系统管理员不自动获得业务审批权,请配置正式审批角色后处理。"; } if (details?.workflow === "no_actionable_approver") { return "当前审批规则未找到审批人,请联系管理员修正规则。"; } if (details?.toApproverId === "target_not_approver") { return "目标人员不具备审批权限。"; } if (details?.toApproverId === "invalid_target") { return "目标审批人不存在或已停用。"; } if (details?.task === "already_processed") { return "该任务已被处理,请刷新列表。"; } if (details?.invoices === "bound_invoice_required") { return "提交前请先绑定至少 1 张发票。"; } if (details?.invoice === "not_bound") { return "该票据未绑定报销单,无法解绑。"; } if (details?.reimbursement === "invalid_status_for_unbind") { return "仅草稿/驳回状态的报销单允许解绑票据。"; } if (details?.ocr === "disabled") { return "当前环境未开启票据识别,请联系管理员处理。"; } if (details?.ocr === "missing_required_fields") { const labels = formatInvoiceOcrFieldLabels(readStringList(details?.missingFields)); if (labels.length) { return `票据仍缺少关键字段:${labels.join("、")},补齐后才能标记为可用。`; } return "票据仍缺少关键字段,补齐后才能标记为可用。"; } if (details?.verification === "disabled") { return "当前环境未开启发票验真。"; } if (details?.code === "duplicate") { return "编码已存在,请更换后重试。"; } if (details?.legalEntityId === "not_found") { return "所选公司不存在。"; } if (details?.legalEntityId === "archived") { return "所选公司已归档,无法继续使用。"; } if (details?.ownerEmployeeId === "not_found") { return "负责人不存在或已停用。"; } if (details?.parentId === "not_found") { return "上级成本中心不存在。"; } if (details?.parentId === "archived") { return "上级成本中心已归档,无法继续使用。"; } if (details?.parentId === "self_not_allowed") { return "上级成本中心不能选择自身。"; } if (details?.parentId === "cycle_not_allowed") { return "上级成本中心不能形成循环层级。"; } if (details?.parentId === "cross_legal_entity_not_allowed") { return "上级成本中心必须与当前公司一致。"; } if (details?.legalEntity === "has_cost_centers") { return "当前公司下仍存在未归档的成本中心,无法归档。"; } if (details?.costCenter === "has_children") { return "当前成本中心存在下级节点,无法归档。"; } if (details?.holidayCalendar === "bound_as_default") { return `当前日历仍被公司 ${details?.legalEntityCode ?? ""} 作为默认日历引用,无法归档。`; } if (details?.date === "duplicate") { return "同一日历内不允许存在重复日期。"; } if (details?.verification === "already_pending") { return "已有验真任务正在处理中,请稍后查看结果。"; } if (details?.verification === "too_frequent") { return "验真触发过于频繁,请稍后重试。"; } if (details?.verification === "attempts_exceeded") { return "验真重试次数已达上限,请联系财务人工核验。"; } if (details?.budget === "exceeded_block_mode") { return "当前报销金额超出预算,系统已阻断提交。"; } if (details?.budget === "missing_budget_pool") { return "未匹配到预算池;当前强预算校验已阻断提交,请联系管理员补充预算配置。"; } if (details?.leave === "conflict_range") { return "当前时间段与已有请假记录冲突,请调整后再提交。"; } if (details?.leave === "insufficient_balance") { return "当前假种可用额度不足,无法提交。"; } if (details?.leave === "invalid_range") { return "请假开始/结束日期与半天时段不合法。"; } if (details?.leave === "non_working_range") { return "所选时间段均为非工作日,无需请假。"; } if (details?.leave === "missing_reserve_ledger") { return "请假冻结账本状态异常,请联系管理员处理。"; } if (details?.attachment === "security_validation_failed") { if (details?.reason === "quarantined") { return "附件已隔离,无法下载,请联系管理员。"; } if (details?.reason === "missing_object") { return "附件文件不存在或已缺失,请联系管理员。"; } return "附件未通过安全校验,请重新上传允许格式或联系管理员。"; } if (details?.file === "empty_file") { return "附件内容为空,请重新选择文件。"; } if (details?.file === "size_limit" || details?.file === "file_too_large") { return "附件超过大小限制,请压缩后重新上传。"; } if (details?.file === "too_many_files") { const maxFiles = details?.maxFiles; return typeof maxFiles === "number" && Number.isSafeInteger(maxFiles) && maxFiles > 0 ? `单次最多上传 ${maxFiles} 个附件,请分批上传。` : "一次选择的附件过多,请减少数量后分批上传。"; } if (details?.file === "dangerous_filename") { return "附件名称不安全,请重命名后重新上传。"; } if (details?.file === "unsupported_type") { return "仅支持 PDF、JPG、PNG、WEBP 文件,请重新选择。"; } if (details?.file === "type_mismatch") { return "附件扩展名、声明类型与实际内容不一致,请重新选择原始文件或转换为支持格式。"; } if (details?.file === "corrupt_content") { return "附件已损坏或内容不完整,请重新导出后上传。"; } if (details?.file === "pixel_limit") { return "图片尺寸过大,请压缩后重新上传。"; } if (details?.file === "multiple_pages") { return "暂不支持多页或动画图片,请转换为 PDF 或单页安全图片。"; } if (details?.file === "only_single_file_supported") { return "一次只能上传一个附件,请重新选择。"; } if (details?.file === "required") { return "当前假种要求上传附件后才能提交。"; } if (details?.file === "pdf_invoice_required") { return "请上传 PDF 发票文件。"; } if (details?.file === "unsupported_mime_type") { return "附件类型不被当前假种接受,请重新选择。"; } if (details?.withdraw === "no_pending_instance") { return "当前请假单不存在可撤回的审批实例。"; } if (details?.withdraw === "already_processed_by_approver") { return "审批人已处理该请假单,当前不能撤回。"; } if (details?.leaveTypeId === "missing_active_policy") { return "当前假种未配置可用策略,请联系 HR。"; } if (details?.integration === "erp_sync_disabled") { return "该同步功能未开启。"; } if (details?.integration === "travel_sync_disabled") { return "差旅同步功能未开启。"; } if (details?.secret === "field_encryption_key_missing") { return "当前环境未配置 FIELD_ENCRYPTION_KEY,无法保存或更新 Webhook Secret。"; } } return code === "VALID_001" ? UNKNOWN_VALIDATION_MESSAGE : fallbackMessage || "请求失败,请稍后重试。"; }