fix: 完善动态错误指引与发票日期校验(09-05-error-explainability-payment-readiness)
这个提交包含在:
@@ -77,6 +77,30 @@ export function mapApiErrorMessage(
|
||||
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") {
|
||||
|
||||
@@ -1,4 +1,77 @@
|
||||
export const ERROR_REASON_MESSAGES: Record<string, string> = {
|
||||
"workflow.task_already_processed": "此审批任务已处理,请刷新待办查看最新进度。",
|
||||
"workflow.entity_unavailable": "审批对应的单据不存在或当前不可处理,请联系管理员核查。",
|
||||
"workflow.self_approval_forbidden": "不能审批本人申请或代提交的单据,请由符合资格的其他审批人处理。",
|
||||
"workflow.transfer_target_unavailable": "转审人不存在或已停用,请重新选择在职审批人。",
|
||||
"workflow.transfer_target_ineligible": "所选人员没有这类业务的审批资格,请重新选择审批人。",
|
||||
"workflow.self_remind_forbidden": "当前待办由你处理,无需催办自己,请直接处理待办。",
|
||||
"workflow.step_missing": "审批流程节点信息异常,请联系管理员核查,不要重复提交。",
|
||||
"workflow.transfer_unsupported": "此类业务暂不支持转审,请按原审批流程处理。",
|
||||
"workflow.transfer_same_assignee": "不能转审给自己,请选择其他符合资格的审批人。",
|
||||
"workflow.task_list_query_invalid": "待办筛选或分页条件无效,请重新选择筛选条件后重试。",
|
||||
"workflow.approver_query_invalid": "审批人查询条件无效,请缩短关键词并检查分页条件后重试。",
|
||||
"workflow.batch_entity_unsupported": "批量审批仅支持当前指定业务类型,请取消其他类型任务的选择。",
|
||||
"workflow.remind_cooldown": "此任务24小时内已由你催办,请勿重复催办,稍后再试。",
|
||||
"invoice_pool.pdf_required": "请上传 PDF 发票文件",
|
||||
"invoice_pool.ocr_pdf_too_large": "PDF 发票不能超过 7 MiB",
|
||||
"invoice_verification.list_query_invalid": "查验记录查询数量无效,请选择1至50条后重试。",
|
||||
"invoice_pool.single_file_required": "请每次上传一份PDF发票,可分次添加多张发票。",
|
||||
"invoice_pool.empty_file": "发票文件为空,请重新选择有效的PDF文件。",
|
||||
"invoice_pool.upload_too_large": "发票文件超过当前上传大小限制,请压缩PDF后重试;仍失败请联系管理员。",
|
||||
"invoice_pool.file_required": "请先选择要上传的PDF发票。",
|
||||
"invoice_pool.owner_unavailable": "发票所属员工不存在或已停用,请重新选择在职员工。",
|
||||
"invoice_pool.ocr_type_invalid": "发票识别类型无效,请重新选择票据类型后上传。",
|
||||
"invoice_pool.edit_empty": "没有可保存的修改,请修改发票信息后再保存。",
|
||||
"invoice_pool.not_editable": "此发票当前不能编辑,请刷新查看状态;已关联的发票需先按报销流程解除占用。",
|
||||
"invoice_pool.date_invalid": "开票日期无效,请填写真实有效的日期。",
|
||||
"invoice_pool.amount_invalid": "发票金额格式无效,请填写有效金额后重试。",
|
||||
"invoice_pool.tax_amount_invalid": "发票税额格式无效,请填写有效税额后重试。",
|
||||
"invoice_pool.total_amount_invalid": "价税合计格式无效,请填写有效金额后重试。",
|
||||
"invoice_pool.status_transition_invalid": "此发票不能按当前方式更改状态,请刷新页面后重试。",
|
||||
"invoice_pool.archive_bound": "发票已关联业务单据,不能归档;请先完成业务处理或解除关联。",
|
||||
"invoice_pool.archive_status_invalid": "此发票当前不能归档,请等待识别完成或刷新查看状态。",
|
||||
"invoice_pool.ocr_retry_status_invalid": "只有识别失败的发票可以重新识别,请刷新查看当前结果。",
|
||||
"invoice_pool.ocr_unavailable": "发票OCR识别服务当前不可用,请联系管理员恢复服务后重试。",
|
||||
"invoice_pool.override_bound": "发票已关联业务单据,不能设置重复票据例外;请先处理原关联。",
|
||||
"invoice_pool.override_status_invalid": "此发票当前不能设置重复票据例外,请刷新查看状态。",
|
||||
"invoice_pool.dedup_identity_missing": "发票缺少查重标识,请补全发票代码和号码;仍无法处理请联系管理员。",
|
||||
"invoice_pool.edit_input_invalid": "发票信息格式无效,请检查日期、金额及必填字段后保存。",
|
||||
"invoice_pool.override_input_invalid": "请填写有效的关联报销编号和重复票据例外原因。",
|
||||
"invoice_pool.ocr_fields_missing": "发票识别信息不完整,请补全票种要求的代码、号码、日期、金额及销售方等信息后保存。",
|
||||
"workflow.comment_invalid": "请填写处理意见,长度为1至500字。",
|
||||
"workflow.transfer_input_invalid": "请选择有效的转审人,并填写10至500字的转审原因。",
|
||||
"workflow.batch_approve_input_invalid": "请选择1至50条有效审批任务,审批意见不超过500字。",
|
||||
"workflow.batch_reject_input_invalid": "请选择1至50条有效审批任务,并填写1至500字的退回意见。",
|
||||
"workflow.remind_input_invalid": "催办说明不能超过200字,请修改后重试。",
|
||||
"workflow.summary_query_invalid": "待办统计范围无效,请从待办页面重新进入。",
|
||||
"workflow.task_id_invalid": "审批任务标识无效,请从待办列表重新打开。",
|
||||
"invoice_verification.disabled": "发票查验服务暂未启用,请联系管理员确认;发票OCR识别是独立服务。",
|
||||
"invoice_verification.already_pending": "发票正在查验,请稍后刷新结果,不要重复提交。",
|
||||
"invoice_verification.record_missing": "此发票还没有查验记录,请先发起查验,再按需重试。",
|
||||
"auth.login_input_invalid": "请填写登录账号和密码后重试。",
|
||||
"auth.email_input_invalid": "请输入格式正确的邮箱地址后重试。",
|
||||
"auth.email_code_input_invalid": "请检查邮箱地址和验证码的填写格式后重试。",
|
||||
"auth.password_change_input_invalid": "请完整填写原密码和新密码后重试。",
|
||||
"auth.password_reset_input_invalid": "请完整填写邮箱、验证码和新密码后重试。",
|
||||
"auth.device_context_missing": "登录设备信息缺失,请刷新登录页面后重试;仍无法登录请联系管理员。",
|
||||
"auth.password_policy_failed": "新密码需至少12位,包含大小写字母和数字,并避免常见弱密码。",
|
||||
"auth.deeplink_target_invalid": "此入口的目标地址无效,请从系统首页重新进入对应业务。",
|
||||
"auth.code_invalid_or_expired": "验证码无效或已过期,请重新获取后重试。",
|
||||
"profile.notification_preferences_invalid": "通知设置无效,请通过页面开关选择是否接收通知后重试。",
|
||||
"invoice_pool.list_query_invalid": "票据筛选或分页参数无效,请重置筛选后重试。",
|
||||
"invoice_pool.selection_query_invalid": "选择发票的报销单或查询条件无效,请返回报销单重新打开“我的发票”。",
|
||||
"invoice_pool.status_query_invalid": "待查询票据列表无效,请刷新后重试;一次最多查询50张票据。",
|
||||
"invoice_pool.reimbursement_not_editable": "该报销单当前不可添加发票,请刷新确认状态;仅草稿或退回待修改的单据可继续选票。",
|
||||
"invoice_pool.id_invalid": "票据标识无效,请返回“我的发票”重新选择。",
|
||||
"payment_account.reveal_reason_required": "请填写2至500字的查看理由后再查看完整付款账号。",
|
||||
"payment_account.bank_input_invalid": "请检查银行名称;新增银行还需填写2至16位字母、数字或下划线组成的银行编码。",
|
||||
"payment_account.account_input_invalid": "请检查所属公司、开户银行、户名及账号;账号需为6至32位数字。",
|
||||
"payment_account.id_invalid": "账户或银行标识无效,请返回列表重新选择。",
|
||||
"payment_account.legal_entity_unavailable": "所选公司不存在或已停用,请刷新后选择可用公司;如无可选公司,请联系管理员。",
|
||||
"payment_account.bank_unavailable": "所选银行不存在或已停用,请刷新后选择真实开户银行,不要为通过检查改填其他银行。",
|
||||
"payment_account.bank_code_exists": "该银行编码已存在,请使用已有银行或核实编码后重试。",
|
||||
"payment_account.bank_in_use": "该银行仍有启用的付款账户,暂不能停用;请先核实相关账户的使用安排。",
|
||||
"payment_account.legal_entity_change_forbidden": "已有付款账户不能直接更换所属公司,请保留原公司,并为其他公司另行维护账户。",
|
||||
"reimbursement.finance_owner_ceo_unavailable": "财务本人报销需要非申请人的总经理审批,请联系管理员核实审批配置。",
|
||||
"reimbursement.finance_owner_ceo_required": "财务本人报销只能由非申请人的在职总经理审批或接收转审,不能委托其他角色代批。",
|
||||
"reimbursement.invoice_prefill_incomplete": "票据日期或金额尚未完整识别,请先处理该票据;其他已识别票据可继续添加。",
|
||||
|
||||
@@ -75,7 +75,7 @@ test("E2E 轻交互 OCR:票据缺关键字段后可在当前页修正并标记
|
||||
await page.getByTestId("invoice-edit-open").click();
|
||||
await page.getByTestId("invoice-edit-set-available").check();
|
||||
await page.getByTestId("invoice-edit-save").click();
|
||||
await expect(page.locator("main").getByText(/票据仍缺少关键字段/)).toBeVisible({ timeout: 15_000 });
|
||||
await expect(page.locator("main").getByText("票据仍缺少关键字段:发票代码、销售方,补齐后才能标记为可用。", { exact: true })).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
await page.getByTestId("invoice-edit-code").fill("044003204111");
|
||||
await page.getByTestId("invoice-edit-seller-name").fill("北京悦海堂餐饮管理有限公司");
|
||||
|
||||
@@ -216,6 +216,10 @@ test.describe("visual portal", () => {
|
||||
await page.goto(mobile ? "/mobile" : "/portal");
|
||||
await expect(page.getByText("报销待组建付款批次 · BX-SYN", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("付款与回单待处理 · PB-SYN", { exact: true })).toBeVisible();
|
||||
await expect(page.getByRole("link").filter({ hasText: "报销待组建付款批次 · BX-SYN" }))
|
||||
.toHaveAttribute("href", mobile ? "/mobile/reimbursements/synthetic" : "/reimbursements/synthetic");
|
||||
await expect(page.getByRole("link").filter({ hasText: "付款与回单待处理 · PB-SYN" }))
|
||||
.toHaveAttribute("href", mobile ? "/mobile/payment-batches?id=synthetic" : "/payment-batches?id=synthetic");
|
||||
await expect(page.getByText("待付款批次 9 个", { exact: true })).toHaveCount(0);
|
||||
await expect(page.getByText("异常付款 1 项", { exact: true })).toHaveCount(0);
|
||||
if (mobile) await expect(page.getByTestId("mobile-business-handoffs")).toContainText("2 项");
|
||||
@@ -225,6 +229,7 @@ test.describe("visual portal", () => {
|
||||
items = [];
|
||||
await page.reload();
|
||||
await expect(page.getByText("付款与回单待处理 · PB-SYN", { exact: true })).toHaveCount(0);
|
||||
await expect(page.getByText("报销待组建付款批次 · BX-SYN", { exact: true })).toHaveCount(0);
|
||||
await expect(page.getByText("待付款批次 9 个", { exact: true })).toHaveCount(0);
|
||||
});
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户