借款登记支持上传原借入回单识别预填(09-17-company-loan-principal-repayment)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

- 新增识别与保存接口:可读 PDF 本地解析,必要时有界调用现有回单 OCR;预填上下文加密绑定经办人、文件和 30 分钟
- 适配建行单位客户专用回单表格版式:按位置归行排序、竖排栏头、顶行无标签日期、大小写金额
- 拦截非最终、失败/处理中、多笔交易(含跨页无关联、编号冲突)、非人民币、公司支出和其他收款主体
- 金额、日期、编号整段校验;借款与原件同事务保存,失败回滚并清理对象;同一出借人原件唯一
- 保存绑定所选出借人版本;前端冻结核对时版本,迟到结果与切换账号不覆盖、不重发
- 测试:单测、隔离 HTTP/浏览器、变异;对账浏览器场景固定 UTC;五轮独立复核通过
- 无新增迁移

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
2026-10-10 10:34:40 +08:00
共同撰写人 Claude Opus 5.5
父节点 45737ccb9a
当前提交 2063ff1e93
共修改 22 个文件,包含 2056 行新增和 47 行删除
+4 -3
查看文件
@@ -147,7 +147,7 @@ module.exports = async function employeeBrowser(h) {
for (const kind of ['agreement', 'incoming', 'accounting_confirmation']) {
const result = page.waitForResponse(r => r.url().endsWith(`/company-loans/${loan.id}/evidences/${kind}`) && r.request().method() === 'POST');
await page.locator(`[id="${loan.id}-${kind}"]`).setInputFiles({ name: 'synthetic.pdf', mimeType: 'application/pdf',
buffer: Buffer.from('%PDF-1.4\n1 0 obj << /Type /Catalog >> endobj\n%%EOF\n') });
buffer: Buffer.from(`%PDF-1.4\n% synthetic-${kind}-${loan.id}\n1 0 obj << /Type /Catalog >> endobj\n%%EOF\n`) });
assert.equal((await result).status(), 200);
}
await loanCard.getByRole('button', { name: '确认期初余额', exact: true }).click();
@@ -159,8 +159,9 @@ module.exports = async function employeeBrowser(h) {
await expect(dialog.getByLabel('授权出借人').locator('option').nth(1)).toBeAttached();
for (let i = 0; !(await dialog.getByLabel('授权出借人').locator(`option[value="${lender.id}"]`).count()); i++) {
assert.ok(i < 20);
const response = page.waitForResponse(r => r.url().includes('/loan-lenders?page='));
await dialog.getByRole('button', { name: '下一页出借人', exact: true }).click(); await response;
// Await both together so a timeout reaches the catch below (and its screenshot).
await Promise.all([page.waitForResponse(r => r.url().includes('/loan-lenders?page=')),
dialog.getByRole('button', { name: '下一页出借人', exact: true }).click({ timeout: 30000 })]);
}
await dialog.getByLabel('授权出借人').selectOption(lender.id);
await dialog.getByRole('checkbox', { name: new RegExp(loan.loan_no) }).check();