借款登记支持上传原借入回单识别预填(09-17-company-loan-principal-repayment)
- 新增识别与保存接口:可读 PDF 本地解析,必要时有界调用现有回单 OCR;预填上下文加密绑定经办人、文件和 30 分钟 - 适配建行单位客户专用回单表格版式:按位置归行排序、竖排栏头、顶行无标签日期、大小写金额 - 拦截非最终、失败/处理中、多笔交易(含跨页无关联、编号冲突)、非人民币、公司支出和其他收款主体 - 金额、日期、编号整段校验;借款与原件同事务保存,失败回滚并清理对象;同一出借人原件唯一 - 保存绑定所选出借人版本;前端冻结核对时版本,迟到结果与切换账号不覆盖、不重发 - 测试:单测、隔离 HTTP/浏览器、变异;对账浏览器场景固定 UTC;五轮独立复核通过 - 无新增迁移 Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -1,3 +1,53 @@
|
||||
## 2026-10-09 N5-P 原借入回单识别预填设计(待批准实施)
|
||||
|
||||
### P-A 现有能力与方案选择
|
||||
|
||||
对应 PRD P1–P6、AC32–AC39。基线为 `45737ccb`,沿 `CompanyLoansPage` 登记入口及 `CompanyLoan` / `LoanEvidence` 模型实施,不新建凭证中心、银行流水系统或业务草稿表。现有创建后的 `confirmation_status=unconfirmed`、三类附件余额确认及公司代表授权继续使用。
|
||||
|
||||
| 已检查来源 | 事实与设计影响 |
|
||||
| --- | --- |
|
||||
| `client/src/pages/CompanyLoansPage.tsx:176`;`server/src/routes/companyLoans.js:262`、`:314` | 创建与上传分离;新入口需一个有原件的保存动作,不能由浏览器串行两次写入冒充原子成功。 |
|
||||
| `server/src/services/paymentReceiptDocument.js:101`;`paymentReceiptDocumentWorker.js:3` | 已有受限 PDF 文字读取。抽出或复用受限读取层供本地字段提取,旧 `inspectReceiptDocument` 输出与付款分类语义保持。 |
|
||||
| `server/src/services/paymentReceiptMatching.js:41`;`paymentReceiptOcr.js:19` | 付款归一化包含金额/账户/编号,但没有到账日期/双方名称,且字段识别依赖腾讯。新增借入专用白名单解析,复用底层客户端及值解析;不改变旧付款输出与判定。 |
|
||||
| `server/prisma/schema.prisma:3973`、`:4063` | 已有经办请求键、同主体/出借人/原引用唯一约束及附件摘要,可实现本期保存和幂等,不计划迁移或新任务队列。 |
|
||||
| `server/src/services/crypto/index.js:103`;`purchaseAttachmentPolicy.js` | 复用认证加密及实际字节验证;预填结果只经短期、专用用途的加密上下文往返,不能把完整 OCR 结果塞入可读签名 token。 |
|
||||
|
||||
采用“浏览器暂存所选文件 → 无业务持久化识别 → 核对 → 携同一文件保存”的方案。用户只选一次原件;网络上识别与保存各传一次,换取无需新建临时附件池、清理任务或跨设备草稿。关闭前尚未保存的内容不续存,成功保存的文件仍按原借款附件治理。
|
||||
|
||||
### P-B 识别合同与最小字段
|
||||
|
||||
拟增加 `POST /company-loans/incoming-receipt/recognize`,严格 multipart 一份 PDF。授权沿财务角色加 `loan.manage`;涉及员工银行卡比对时追加既有银行卡权限,不授予经理全局借款读取能力。先执行文件扩展名/MIME/实际字节与大小检查,再在事务外执行受限解析:5 MiB、20 页、既有 5 秒/128 MB 工作者上限。损坏、加密不可读或不安全文件不能保存为正常识别附件。
|
||||
|
||||
1. 本地提取页面文本后,按明确字段标签与版面行关系提取,不使用任意数字猜测。金额保持十进制字符串/最小单位,日期用真实日历校验,不以打印时间、导出时间代到账日期;两个同类值矛盾则留空。标识保留字符串及前导零,流水号和凭证号分别取值,稳定排序选唯一引用。多页重复同一交易可合并;不能证明同一笔时拒绝预填,不只处理首页。
|
||||
2. 名称、币种、付款/收款账号用于判断候选及冲突;原始文本仅内存处理。给浏览器的字段是最小业务白名单、遮罩账号、`source=pdf_text|ocr|manual`、逐字段缺项/歧义说明、凭证性质和入账方向提示,不返回完整账号、原文或提供商响应。凭证正式性和成功状态分别表示,识别成功不称为银行验真。
|
||||
3. 本地必需字段充分时零云调用;不足时只使用既有已配置银行 OCR,在每份文件的一次受限识别请求内处理必要页,每页最多一次,整请求有总时限,不能随轮询或组件重挂自动重试。复用客户端时读取借入所需日期/名称后立即白名单压缩,不让旧付款归一化丢掉这些字段;不因提供商结果覆盖本地已发现的非最终/失败/多交易限制。OCR关闭或失败保留本地有效字段,并允许财务手工补齐;刷新识别是显式用户动作。
|
||||
4. 借入按付款人→个人、收款人→公司判断,不能复用会拒绝贷方的 `paymentDocumentReady`。公司支出方向是相对于所选公司判断,不能仅见票面“借方”就拒绝个人付款方出具的回单。完整名称/唯一关系可建议匹配已有公司与出借人,歧义不默认选第一条;历史付款账号与员工当前收款账号不同只提示核对,不自动刷新或否定历史关系。确认为其他收款主体、非人民币或明确银行失败/处理中/非最终时,此文件不能经新入口保存为有效借入原件。
|
||||
5. 初步识别与选择主体后的校验可共享同一上下文,无需为切换出借人再次调用 OCR。服务端以最小加密上下文保存字段/性质供下一请求核对,绑定用途 `loan_incoming_prefill`、经办员工、文件 SHA-256、解析版本、签发时间和到期时间(30 分钟),只经请求体传输且 `no-store`;不放 URL、localStorage 或日志。解密后校验用途/时限/身份,不能借字段加密的完整性省略业务校验。
|
||||
|
||||
### P-C 确认保存、重复与原件
|
||||
|
||||
拟增加 `POST /company-loans/from-incoming-receipt`,严格 multipart:同一原件、加密预填上下文、请求键、现有登记字段和明确核对声明。人工改正仅允许本金/日期/引用及用户本来就需填写的登记字段;身份只能选择现有出借人,不接受账户替换、`confirmed` 或银行成功状态。未知结果可人工补填,token中明确矛盾不能被浏览器删字段消除;OCR关闭的人工路径同样需要文件校验和身份绑定。
|
||||
|
||||
- 保存先重验实际原件、摘要、上下文及登记字段,再校验所选出借人/公司和其版本;要求已查看原件、核对借款性质和主体。`openingOutstandingPrincipal` / `balanceAsOf` 必填且不由识别预填为已核实事实;仍执行本金上限、日期顺序和非未来截止日。
|
||||
- 文件写存储放在短事务外。事务内创建借款、`kind=incoming` 的受控附件及两项必要审计;对外只在全部成功后返回。失败只删除本次新写对象;重试读取已成功事务并核验相同字段和文件摘要,不重新上传关联。对象存储与 PG 无分布式事务,进程崩溃可留未引用对象:不将其暴露为成功记录,清理失败留脱敏告警与原存储运维处置边界,不新增通用扫盘/删除服务。
|
||||
- 沿用 `(created_by, request_key)` 幂等与 `(legal_entity_id, lender_id, origin_reference)` 唯一约束;请求等价比较必须包含原件摘要。新保存以出借人行锁串行,同主体/出借人已存在相同 `incoming` 摘要时返回已有登记提示,不新建。普通原件追加路径也参加同一重复核对,覆盖新保存与旧上传的竞争,既有历史重复不自动合并/删除。
|
||||
- 保持“出借人 → 借款 → 编号”方向,与还款创建一致。已有借款原件上传先只读定位出借人,再锁出借人、锁借款并重验归属;不能在已持借款锁后新增出借人锁。新建持出借人锁后只创建新借款/编号,不再锁或修改已有借款,不与付款批次建立反向依赖。实施前用确定性交错验证该顺序,覆盖原件上传与还款创建;发现与既有约束冲突时先修订设计,不靠加长超时。重复摘要仅证明相同文件;不同扫描件且引用不同时,不能宣称已具备全银行交易去重。
|
||||
- 成功仍为余额待核实,原件可按既有用途下载接口读取,审计只留来源、摘要、解析版本和修正字段名等必要元数据,不保存明文 OCR 输入或完整账号。协议/余额核对/调整依据沿原入口,识别不推进授权、余额确认、付款或核销。
|
||||
|
||||
### P-D 页面与兼容
|
||||
|
||||
`CompanyLoansPage` 的登记借入弹窗增加“上传回单预填/手工登记”路径,复用原 Dialog/Input/Button 和现有字段。上传处理中、待核对、需人工补充、文件不适用和保存失败均有就地提示;能纠正的字段可修改,识别结果须明确仍待核对。当前缺少 `frontend-design` skill,本阶段仅复用既有组件及样式,不做新视觉裁决。
|
||||
|
||||
文件只存在当前表单内存,预填结果按身份/文件请求序号隔离;选择新文件显式重置其提取字段,任何迟到返回都不能覆盖新文件或已手改值。识别/保存都传 AbortSignal,卸载/身份变化取消并清理;保持同身份合法401恢复,禁止新身份重放旧写请求。待保存时阻止重复提交,提交结果丢失时以同请求键回读原结果。
|
||||
|
||||
银行候选匹配不更改员工资料;缺出借人使用现有登记出借人入口。原手工 JSON 登记及材料上传仍可用,不强制所有历史借款都经过 OCR。新入口仅支持 PDF,原图片附件能力和旧付款回单支持范围保持。
|
||||
|
||||
### P-E 运行边界与回退
|
||||
|
||||
本期不计划数据库迁移,不增加提供商、生产密钥、全局文件预览或长期识别存储。保存结果使用旧模型,可回退新入口及专用接口后继续按原页面管理,不删除已登记借款或原件;回退仍遵守原关联方模式最低兼容版本限制。
|
||||
|
||||
本地测试只用合成 PDF/人员和提供商替身。真实云识别、当前银行产品额度与费用边界须在首次获准实样验收时确认;不从发票 OCR 的额度结论推定 BankSlipOCR 可用,也不自动启用服务或修改计费。未知可人工填写是本期明确降级,不以真实材料未调用为由伪报自动识别已验。发布按最终候选门禁和另行部署授权执行。
|
||||
|
||||
## 2026-10-09 N5 员工出借人选择方案(已批准,已本地实现)
|
||||
|
||||
本节对应 PRD 的 AC28–AC31。员工资料已存在于 `Employee`;现有 `loan-lenders/identity-options` 仅提供身份选择,借款登记尚未复用银行卡。目标是让财务选择现有员工并核对资料,服务端建立或复用借款出借人关系,保持现有贷款、授权和付款快照模型。
|
||||
|
||||
@@ -1,3 +1,198 @@
|
||||
## 2026-10-10 N5-P 第五轮独立复核通过(待提交授权)
|
||||
|
||||
第五轮复核(`independent-review-r5-20261010.md`)结论为 PASS_WITH_NOTES:此前要求修改的问题均可关闭,没有阻断项。复核方独立验证:
|
||||
- 相关单测 100 项通过;
|
||||
- 前几轮的 27 个探针,以及新增的 14 项关联与边界检查通过;
|
||||
- 两份正式回单在本机复测通过,云端调用 0。
|
||||
|
||||
非阻断观察 N1:OCR 读出无效流水号或凭证号时,值会被拒绝,但字段提示显示为 `missing`(未识别);本地读出同类无效值时显示为 `invalid`。两种情况都不会预填错误值,也不会绕过多页拦截。本轮不改代码,下节“无效编号记 invalid”的说法仅适用于本地文字层。
|
||||
|
||||
AC39 的独立复核部分已完成。下面几项仍需另行处理或单独授权:
|
||||
- 提交、最终候选完整门禁、推送和部署;
|
||||
- 真实回单上的姓名、账号、流水号逐值核对,由财务保存前对照原件完成;
|
||||
- 员工授权翻页超时(根因未定)。
|
||||
|
||||
## 2026-10-10 N5-P 第四轮复核修复(第五轮复核已通过)
|
||||
|
||||
第四轮复核(`independent-review-r4-20261010.md`)结论为 CHANGES_REQUESTED,剩 2 处,都涉及多页文件能否确认为同一笔交易。
|
||||
|
||||
- R4-1 跨页关联:
|
||||
- 每页分别记录带类型的编号(`pageIds`)。两页共享同类型、同值的编号才算关联;同一页上的流水号和凭证号视为属于同一笔交易。全部页面必须连成一组(`unlinkedPages`),否则拦截为 `pages_unverified`。
|
||||
- OCR 读出的页按同一规则加入。已知编号冲突仍判为 `multiple_transactions` 并拦截。
|
||||
- 文字相同但编号类型不同(一个是流水号、一个是凭证号),不算关联。
|
||||
- R4-2 编号整段校验:`parseReference` 要求整段匹配,不再截取前缀参与比较。本地文字层读到带未知后缀、超长或相同前缀不同后缀的编号时,记为 `invalid`;OCR 读出的编号也做整段校验,无效值被拒绝,但提示为 `missing`(见第五轮 N1)。
|
||||
|
||||
验证:
|
||||
|
||||
- 复核方三轮探针共 27 个,用当前代码全部符合预期:第四轮 10 个,第三轮 8 个,第二轮 9 个。
|
||||
- 单测:借入回单 71 项(新增 8 项)。
|
||||
- 变异:新增 3 个,连同 2 个重跑的,共 5 个全部捕获。旧的“页面覆盖”两项变异所针对的代码已被关联逻辑取代、锚点已不存在,已从清单删除。
|
||||
- `browser-r7.log`:HTTP 9 项、浏览器 8 项、数据库 9 项通过。
|
||||
- `regression-r11.log`:完整回归通过,检查项相加 248,去重 189。
|
||||
- 两份真实样本:编号改为整段校验后,流水号仍正常提取,9 个字段齐全,无拦截,云端调用 0,原件不变。
|
||||
|
||||
仍开放:
|
||||
|
||||
- 员工授权翻页超时根因未定。
|
||||
- 待第五轮独立复核。
|
||||
- 未提交、未推送、未部署。
|
||||
|
||||
## 2026-10-10 N5-P 第三轮复核修复(未提交;后续修复见上节)
|
||||
|
||||
第三轮复核(`independent-review-r3-20261010.md`)结论为 CHANGES_REQUESTED,剩 2 项,均属 RR1;RR2、RR3、RR4 已由复核方关闭。
|
||||
|
||||
- R3-1:一页只有读到能通过解析的流水号或凭证号,才算属于本笔交易。文字层与 OCR 使用同一判定标准:无效值、OCR 返回空,以及只有金额相同,都不能解除“多页无法核实”的限制。单页扫描件仍可人工补填。
|
||||
- R3-2:内部保留本地读到的全部流水号和凭证号(`identifiers`),跨来源时按同类编号逐一比较。仅凭证号不同(包括流水号相同的情况)也判为多笔交易。表单预填仍优先使用流水号,这只是显示顺序,不影响比较。
|
||||
|
||||
验证:
|
||||
|
||||
- 复核方第三轮 8 个探针、第二轮 9 个探针,用当前代码重跑全部符合预期(拦截项已拦截、对照项正常放行)。
|
||||
- 单测:借入回单 63 项(新增 6 项);回单分类与 OCR 测试仍通过。
|
||||
- 变异:新增 4 个单元变异全部捕获。“文字层只有金额的页面”首轮未捕获,原因是缺少可读的只有金额页面;在生成器中加入“无流水号”选项并补测后已捕获。
|
||||
- `browser-r6.log`:HTTP 9 项、浏览器 8 项、数据库 9 项通过。
|
||||
- `regression-r10.log`:完整回归通过,检查项相加 248、去重 189。
|
||||
- 两份真实样本:9 个字段全部提取,无拦截,云端调用 0,原件不变。
|
||||
|
||||
仍开放:员工授权翻页超时根因未定(r8–r10 均通过,但没有确定性触发失败分支);待第四轮独立复核;未提交、未推送、未部署。
|
||||
|
||||
## 2026-10-10 N5-P 第二轮复核修复(未提交;后续修复见上节)
|
||||
|
||||
第二轮复核(`independent-review-r2-20261010.md`)结论为 CHANGES_REQUESTED,共 4 类问题,本节记录修复结果。
|
||||
|
||||
- RR1 混合页与跨来源交易:
|
||||
- 按页记录是否读到本页自己的金额、流水号或凭证号;没读到的页单独送一次 OCR(仍受总时限约束、不重试)。
|
||||
- 多页文件仍有页面无法核实时,记 `pages_unverified` 并拦截;单页扫描件仍可人工补录。
|
||||
- OCR 读到的金额或同类编号与本地不同,记 `multiple_transactions` 并拦截。
|
||||
- RR2 乱序说明:凭证限制同时检查原始存储顺序与按位置重建的阅读顺序,结果取并集。共享的付款分类代码不变。
|
||||
- RR3 打印日期:顶行无标签日期规则跳过紧跟在“日期/时间”类标签后的日期;`parseDate` 改为整段校验,可带时刻,也接受 8 位数字日期。上一节写“到账日期同样处理”时,这一项其实尚未完成,以本节为准。
|
||||
- RR4 前端版本:对话框冻结选择出借人时的身份版本和账户版本。列表刷新后版本变化,会清除两个确认勾选并提示重新核对;保存使用冻结的版本。
|
||||
|
||||
验证(证据目录同上):
|
||||
|
||||
- 复核方的 9 个解析探针在修复后全部得到正确结果(会拦截的已拦截,日期留空或标为无效)。
|
||||
- 单测:借入回单 57 项,加回单分类与 OCR 共 86 项通过。
|
||||
- 变异:新增 6 个单元变异全部捕获(`mutations-summary-*.json`)。
|
||||
- 浏览器 `browser-r5.log` 共 8 项,新增电脑与手机各 1 项:断网后恢复触发列表重新获取,勾选被清除;重新核对后保存,提交的是新版本,数据库中借款 1 笔。
|
||||
- 完整回归 `regression-r9.log` 通过,检查项相加 248、去重 189。
|
||||
- 两份真实样本用当前代码复测:9 个字段全部提取,无拦截,云端调用 0,原件摘要不变。
|
||||
|
||||
仍开放:
|
||||
|
||||
- 员工授权翻页超时根因未定。r8、r9 均通过,但都没有确定性触发失败分支。该项属于原有员工场景,不在 N5-P 改动范围内,保留为开放项。
|
||||
- 待第三轮独立复核。
|
||||
- 未提交、未推送、未部署。
|
||||
|
||||
## 2026-10-10 N5-P 复核修复与真实版式适配(未提交;后续修复见上节)
|
||||
|
||||
独立复核(`artifacts/qa-flow/loan-incoming-prefill-20261009/independent-review-20261010.md`)结论为 CHANGES_REQUESTED,4 项 P2 均已复现。本节记录修复结果;下面首轮实现记录中的统计和基线写法有误,以本节为准。
|
||||
|
||||
### 更正
|
||||
|
||||
- 实施基线是 `45737ccb`,不是首轮写的 `c1034bbe`。
|
||||
- 首轮称 r5 共 247 项,实际是各层检查数组直接相加,含重复,去重后为 188 项。r5 也没有运行新入口的浏览器场景,那 6 项来自单独的 `browser-r2.log`。
|
||||
|
||||
### 复核问题修复
|
||||
|
||||
- R1 多笔交易:金额、流水号、凭证号任一出现不同值,即判为多笔交易,本地与 OCR 一致处理,人工补号也无法解除。同一交易跨页重复仍允许。
|
||||
- R2 外币:本地发现非人民币时,在调用 OCR 前就拦截;OCR 币种与本地不一致记为 `currency_conflict` 并拦截,不再把已知币种清空成“未知”。
|
||||
- R3 OCR 时限:每页发送前检查共同截止时刻,超时后不再发送后续页。
|
||||
- R4 金额:整段值必须是一个合法的人民币金额,千分位错误或超过两位小数记为 `invalid`,不再截断成另一个金额。到账日期同样处理。
|
||||
- AC37 来源变更:保存时携带所选出借人的身份版本和账户版本,识别后出借人被修改即返回 `loan.version_changed`。
|
||||
|
||||
### 真实建行版式适配
|
||||
|
||||
经用户授权,本机只读运行脱敏结构探查:已知标签保留,数字改为 9、字母改为 A、其他汉字改为“某”。不调用云端、不写库、不保存文件,原件摘要前后一致。两份借入回单是“单位客户专用回单”表格版式:PDF 文字顺序与阅读顺序不同,付款人、收款人是竖排栏头,日期在顶行且没有标签,金额分大写和小写,右侧竖排联次说明中也含“收款人”。
|
||||
|
||||
据此新增以下处理(`PARSER_VERSION` 改为 2):
|
||||
|
||||
- PDF 工作线程按需返回文字位置(付款回单分类不使用,结果不变);按纵坐标归行、行内按横坐标排序;竖排文字不并入横向行。
|
||||
- 顶行日期规则;`(小写)` 金额标签。
|
||||
- 付款人、收款人栏头须恰为该词且付款人在左。
|
||||
|
||||
新增同版式的合成生成器 `syntheticCcbIncomingPdf`,内容全部虚构。
|
||||
|
||||
最终代码下两份真实样本:金额、到账日期、流水号、币种、双方名称及账号全部提取,方向为入账,无拦截项,云端调用 0。具体数值是否正确,仍由财务保存前对照原件核对。
|
||||
|
||||
### 验证
|
||||
|
||||
- 单测 46 项(较首轮新增 24 项);回单分类相关测试仍通过。
|
||||
- 隔离 HTTP 9 组,新增建行版式识别、主体建议与保存;浏览器电脑/手机 6 组(`browser-r4.log`)。
|
||||
- 变异 18 个全部捕获(HTTP 12 个、单元 6 个,`mutations-summary.json`)。其中“行内按横坐标排序”首次未捕获,原因是合成文字同一行的基线完全相同,补充基线偏移用例后已捕获。
|
||||
- 借款完整回归 `regression-r8.log` 通过,检查项去重后 189 项(相加为 248)。
|
||||
- r6、r7 均在员工出借人浏览器场景的手机端翻页处超时:等待响应在点击完成前超时,未处理的异常导致进程退出,未留截图。r8 改为点击与等待一起处理并设 30 秒点击时限后通过,但当次没有触发失败分支,无法确认是修复生效还是恰好不需翻页,**根因未定**。
|
||||
- 工作区另有一处测试调整:原对账浏览器场景固定 `timezoneId: 'UTC'`(见首轮记录)。
|
||||
|
||||
### 仍开放
|
||||
|
||||
- 需要再次独立只读复核本节改动(AC39)。
|
||||
- 真实样本只验证了字段能提取出来,没有核对数值。真实云端 OCR 未调用。
|
||||
- 未提交、未推送、未部署。
|
||||
|
||||
## 2026-10-10 N5-P 本地实现与验证完成(首轮,未提交;统计与基线见上节更正)
|
||||
|
||||
用户先听取“暂缓”建议后明确要求实施,选择**完整方案**并指定由 Claude 会话实施。基线为本地 `c1034bbe`(线上 `45737ccb`),已执行 `task.py start`。本节记录实际结果;下方原计划保留规划时点。
|
||||
|
||||
### 实现
|
||||
|
||||
- 服务端:新增 `loanIncomingReceipt.js`。它复用受限 PDF 文字工作线程(从 `inspectReceiptDocument` 抽出 `extractReceiptTexts`,付款回单分类语义不变),按白名单标签逐行读取金额、记账/交易日期、流水号/凭证号、币种、双方名称与账号;同一字段出现不同值时留空并标为“歧义”。多页同一交易可合并,多笔交易、非最终、失败/处理中、非人民币一律拦截。本地缺必填字段时,才调用一次现有腾讯 BankSlipOCR:每页最多一次、总时限 20 秒、不重试,结果只补空缺,不覆盖本地值,也不能解除限制。
|
||||
- 预填上下文用 AES-GCM 字段加密封装,绑定用途、经办人、文件 SHA-256、解析版本和 30 分钟有效期;只经请求体传递,响应 `no-store`,账号只返回遮罩值。
|
||||
- 新接口:`POST /company-loans/incoming-receipt/recognize` 只做识别,不写任何数据;`POST /company-loans/from-incoming-receipt` 先在事务外写入原件,再在同一事务内锁出借人、创建借款、创建 `incoming` 原件并写两条审计,失败或幂等重试时删除本次新写的对象。保存时重验收款主体:公司支出或收款方为其他主体的回单拒绝保存;审计只记录字段来源和被更正的字段名。
|
||||
- 同一出借人的 `incoming` 原件按摘要唯一:新入口与原有附件上传都检查,原上传路径改为先锁出借人、再锁借款,与还款创建的锁序一致。
|
||||
- 前端:新增“上传回单登记借入”入口和 `LoanIncomingReceiptDialog`,复用现有组件。迟到的识别结果不覆盖人工修改或新选的文件;切换账号、卸载时中止请求;原“登记借入”手工入口保持不变。新增 4 个错误码,三处已同步登记。无新增迁移,仍为 82 项。
|
||||
|
||||
### 验证(证据目录 `artifacts/qa-flow/loan-incoming-prefill-20261009/`)
|
||||
|
||||
- 单元测试:新增 22 项;相关付款回单、报销路由及授权策略合计 349 项通过(BIFF8 需指定 `PAYMENT_ORDER_PYTHON_BIN`,不设时有 2 项报环境不可用)。Prisma 校验、前端类型检查与构建、错误码合同、UI 规则均通过。
|
||||
- 隔离 PG HTTP 8 组:识别不落数据、保存后余额待核实且带原件、重试与请求键、跨入口原件重复、上下文绑定(他人、换文件、篡改、过期)、受限/支出/他方收款拦截、无 OCR 时人工补录、审计失败整体回滚并删除对象、并发与新旧入口竞争只成功一个。
|
||||
- 浏览器电脑/手机共 6 组(`browser-r2.log`):预填、人工更正、同账号令牌过期只重试一次后成功、受限文件拦截、迟到结果不覆盖新文件、真实切换账号后迟到 401 被中止且不以新身份重发。
|
||||
- 变异 10 个全部被测试抓到(`mutations-summary*.json`)。首轮“公司支出”未被抓到:原用例同时触发了“收款主体不符”,已改为只写付款方后单独验证。
|
||||
- 借款完整回归 `regression-r5.log`:247 项检查全部通过,含 HTTP 116 组、浏览器 45 组。r1–r4 的失败保留:r1–r3 是原有对账浏览器场景用 UTC 时间填表、浏览器却沿用本机 CST,北京时间 0–8 点运行必然失败,已在该场景显式固定 `timezoneId: 'UTC'`(r3 的补丁实际未生效,r4 才生效);r4 是新场景多建的出借人挤动了员工场景的分页,已把借入回单场景移到完整运行最后。未改任何业务断言。
|
||||
- 既有测试素材调整:同一出借人多笔借款原先上传同一份 `incoming` 测试 PDF,现在每笔各自生成唯一内容,其他材料类型不变。
|
||||
|
||||
### 未完成与边界
|
||||
|
||||
- **独立复核未做**:实施与自检出自同一会话,不能算独立复核(AC39 保持未勾选),需另行只读复核。
|
||||
- **真实建行版式未验证**:解析只在合成的“标签 值”同行版式上验证过。真实借入回单若采用标签与数值不同行的表格版式,本地可能读不出字段:OCR 未启用时转人工填写,启用时会调用云端。两份真实借入回单的本地只读试解析,或真实材料的云端识别,都需另行授权。
|
||||
- 未提交、未推送、未部署;未调用真实 OCR,也未访问生产。
|
||||
|
||||
## 2026-10-09 N5-P 原借入回单识别预填执行计划(规划时点)
|
||||
|
||||
### 当前阶段与授权
|
||||
|
||||
用户已要求规划,复用本任务,当前仅修改 PRD/design/implement。2026-10-09 在 `main`、本地基线 `45737ccb` 核对 `task.py current --source` 为 none;规划阶段不调用 start,不手写运行时指针。父任务的 `in_progress` 代表历史已批准工作,不代表 N5-P 实施已批准。用户转交上一会话的部署结果已在 PRD Current Status 标记来源,本轮未连接生产重验。
|
||||
|
||||
无原生 update_plan,以下为本阶段执行清单。inline 模式由主会话实现和检查,不派 implement/check 子代理;稳定 spec 直接按需加载,不把临时计划、日志或本文件登记进 jsonl。
|
||||
|
||||
### 顺序与验收映射
|
||||
|
||||
- [x] P0 规划:核对入口、文件策略、PDF 工作者、银行 OCR 字段、数据模型与隐私规则;形成 PRD P1–P6 / AC32–AC39、design P-A–P-E 和本清单。保留原阶段结果;实施批准仍待用户给出。
|
||||
- [x] P1 启动:收到本方案实施批准后,官方 `python3 .trellis/scripts/task.py start 09-17-company-loan-principal-repayment`,读回 current 与 phase,再加载 trellis-before-dev。记录批准范围、实际基线及执行位置;若届时 N5 已人工登记,不迁移或重建既有数据。
|
||||
- [x] P2 解析与预填(AC32、34、35):在受限文字提取上加借入字段白名单、金额/日期/引用歧义处理及相对公司方向判断;补扫描 PDF 的受控 OCR 适配及关闭/失败降级。复用底层,保留旧付款凭证分类和归一化结果。完成正常、非最终/失败/支出、多交易、格式/字节限制、OCR 零调用/有界调用的单元反例。
|
||||
- [x] P3 保存合同(AC33、36、37、38):添加无业务持久化识别接口与带同一原件的原子登记接口;实现专用加密上下文、身份/文件/时效绑定、最终主体重验、请求键及摘要去重、旧上传竞争和失败补偿。审计与业务同事务;保存不确认余额。扩展现有真实隔离 HTTP/PG fixture,证明两种顺序、故障回滚和旧路径兼容。
|
||||
- [x] P4 电脑手机入口(AC32、33、35、37):沿原弹窗复用组件实现上传、识别预填、人工更正和一次保存;保留手工登记。覆盖换文件、取消、失败重试、手改保护、来源缺项、迟到401换账号及同身份重试。用户只需选择一次文件,保存后原件已在该笔材料中。
|
||||
- [ ] P5 整体验证与只读复核(AC38、39):运行最小完整检查集和原借款专项回归,按变化范围覆盖共享付款解析。权限与保存流程变化需要独立只读复核;本会话 inline 限制下不派检查代理,准备精确交审范围并标为待外部独立复核,不能沿用 AC28–AC31 的复核结论。有可用且合规的独立只读会话后接收实际结果。
|
||||
- [ ] P6 文档与交接:按实际证据更新各 AC、PRD 状态及本清单;经 trellis-update-spec 判断后补既有借款/附件规约,不提前把提案写成已生效事实。报告本地实现、独立复核、实样/OCR及公网状态。提交、推送、部署、真实业务与真实材料云识别分别按明确授权办理。
|
||||
|
||||
### 预计修改范围
|
||||
|
||||
- Backend:`server/src/routes/companyLoans.js`、`server/src/services/companyLoans.js`,新增借入识别/保存专用服务;必要时对 `paymentReceiptDocument.js` / `paymentReceiptDocumentWorker.js` / `paymentReceiptOcr.js` 做最小底层提取复用。现有 `paymentDocumentReady` 的付款方向、成功及核销规则不改变。错误理由新增时同步注册、客户端映射及既有合同测试。
|
||||
- Frontend:`client/src/pages/CompanyLoansPage.tsx`、`client/src/api/companyLoans.ts`,按可读性抽出与借入登记直接相关的弹窗组件;不重构整页或员工档案。
|
||||
- Tests:现有 `scripts/qa/test-company-loan-db.cjs`、HTTP/browser fixture 接入新增合成场景;共享解析测试与新借入服务测试按实际职责分文件。新增代码文件名在实施时固定,本计划不预称其已经存在。
|
||||
- 不改 schema/migrations、生产配置、付款状态机、公司授权规则或既有员工资料。若证据证明必须改这些边界,先补风险/兼容设计并取得范围批准。
|
||||
|
||||
### 最小验证集(实施后执行)
|
||||
|
||||
1. 后端改动 `node --check`;`cd server && npm run prisma:validate`;运行新增借入服务测试及已有 `tests/unit/services/paymentReceiptDocument.test.js`、`paymentReceiptOcr.test.js`,若碰共享归一化再覆盖对应消费者。使用项目 Vitest 实際脚本,不能以一份针对性测试冒称全量通过。
|
||||
2. `cd client && npm run typecheck && npm run build`;错误理由/UI合同沿现有 `check-error-reason-contract.mjs`、`check-ui-design-rules.mjs` 按影响执行。
|
||||
3. `RUN_ISOLATED_LOAN_DB_TEST=1 RUN_ISOLATED_LOAN_BROWSER=1 node scripts/qa/test-company-loan-db.cjs`:仅新建一次性数据库、合成身份/回单,沿现有受控 Python/浏览器缓存环境;保留原借款/员工/关联方场景。执行前确认适用环境,禁止指向本地业务库或生产库;清理只针对本轮 owner。
|
||||
4. 必须有确定性交错:重复保存、同请求不同文件、相同原件不同请求、识别后出借人资料变更、旧上传与新保存、审计失败/存储失败。读取原件哈希、借款/附件/审计数量及权限结果,不只断言 HTTP 200。必要的权限/绑定守卫做少量定向变异,环境失败不算捕获。
|
||||
5. 浏览器电脑/手机各实际完成上传预填、人工修正、保存后附件与待核实余额;无 OCR 路径可完成手工登记。覆盖多笔/非最终拒绝、迟到响应与换账号取消,且保存未触发余额确认/授权/付款。合成 PDF 和 provider 替身明确标记,不调用真实云端或保留真实银行资料。
|
||||
6. 任务文档检查:`python3 .trellis/scripts/task.py validate 09-17-company-loan-principal-repayment`、`node scripts/qa/check-trellis-governance.mjs`、`node scripts/qa/check-process-contract.mjs --baseline --json`、`node scripts/qa/audit-docs.mjs --check-registry`、`node scripts/qa/check-sensitive-content.mjs`、`git diff --check`。未改历史 Issue CSV,不新建 CSV 执行合同。
|
||||
|
||||
### 规划交付检查
|
||||
|
||||
2026-10-09 规划文档检查:任务 validate、Trellis 治理、流程合同 baseline、文档索引、敏感内容扫描及 `git diff --check` 均通过。文档索引仍有既有 22 份未登记文件的 1 条汇总警告;inline 可选的 implement/check jsonl 缺失按规则跳过,不宣称注入已配置。新增 P1–P6、AC32–AC39 与设计/清单已收敛核对,现有历史验收和开放项保留;只有本阶段实施批准待取得。没有运行产品测试、调用真实 OCR 或实施/部署;不把本节计划命令当作已执行证据。
|
||||
|
||||
## 2026-10-09 N5 员工出借人选择调整(本地实现与验证完成,未提交)
|
||||
|
||||
用户提出从现有员工选择出借人并复用资料,避免重复手录。已只读核对员工主档、敏感银行卡维护接口、借款登记/身份/账户接口及电脑手机共用页面;实现基线为本地 `c1034bbe`,线上仍为 `beb9f02c`。用户已明确确认方案,本轮接续本地实现及针对性验证;不连接生产、不代录真实数据,提交、推送和部署不在本轮授权内。
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# 公司借款台账与本金归还
|
||||
|
||||
## 2026-10-09 N5-P 原借入回单识别预填(2026-10-10 第五轮独立复核通过,待提交授权与最终门禁)
|
||||
|
||||
### 目标与依据
|
||||
|
||||
用户在办理 N5 时提出先识别凭证或流水、再自动填写,并明确要求“规划改进任务”。本阶段复用当前借款任务,减少已持有原借入回单时重复录入金额、日期、凭据号及再次上传同一文件的操作。此次仅授权规划;原任务 `in_progress` 和此前员工选择功能的实施授权不代表本阶段已获实施批准。
|
||||
|
||||
当前页面先手填登记借入、保存后再上传材料(`client/src/pages/CompanyLoansPage.tsx:176`、`:121`);服务端创建借款和上传凭据是分开的动作(`server/src/routes/companyLoans.js:262`、`:314`)。现有本地 PDF 工作者能读取文字,但对外只返回凭证分类;现有银行 OCR 归一化未包含到账日期和双方名称(`server/src/services/paymentReceiptDocument.js:101`、`server/src/services/paymentReceiptMatching.js:41`)。本阶段需要新增借入预填合同,不能把现有付款核验通过条件直接用于借入。
|
||||
|
||||
### 范围与要求
|
||||
|
||||
- **P1 单笔原件优先**:登记借入增加“上传原借入回单”路径,一次处理一份、仅含一笔交易的银行回单 PDF,包括扫描 PDF;新识别入口上限 5 MiB、20 页,多页必须属于同一交易。保留手工登记和既有附件能力。默认“历史借入登记”,财务明确确认登记性质;文件日期不能自行产生当天新增现金流。
|
||||
- **P2 字段预填**:识别原始本金、实际到账日期、原凭据唯一引用、人民币币种与付款/收款方摘要。可靠字段预填,缺失或歧义字段留空并说明;流水号与凭证号分别辨识,优先稳定流水号,无则使用明确凭证号,不以文件名或随机编号补造。公司和出借人只从已有记录中建议匹配并由财务确认;没有出借人登记时沿已实现的员工选择入口办理,不依据回单新建员工或替换收款账户。
|
||||
- **P3 识别与业务确认分开**:回单只支持证明历史收款,不推断借款性质、未还本金、余额核对截止日、代账确认或公司代表授权。财务补充余额字段并明确核对原件及借款性质后,才能保存为“余额待核实”。余额确认仍要求三类材料及代账引用;关联审批、在途控制、还本与核销规则保持现有合同。
|
||||
- **P4 降级与方向**:可读 PDF 优先本地提取;文字缺失或必要字段不足时,在现有银行 OCR 配置与调用边界允许的条件下补充识别。关闭、不可用、超时或额度不足时显示人工核对入口,不伪造字段、不切换提供商、不自动反复调用。公司入账回单可用于借入;已知公司支出、失败/处理中、非最终回执、多交易文件或非人民币不作为此路径的有效借入回单。无法判断与已知矛盾分别显示,未知可核对补录,明确矛盾需更换材料。
|
||||
- **P5 核对后保存一次**:识别本身不创建借款、出借人、付款或正式业务附件。确认保存时,借款与其 `incoming` 原件关联一并成立,原件不要求用户再次选择;附件或审计失败不留下可用的半条借款。双击、重试、重复引用、已有相同原件和并发重复需有明确结果。未保存即取消、换账号或离开时清除预填内容;本期不提供跨设备或关闭后续填草稿。
|
||||
- **P6 保留人工修改与来源**:允许核对后改正预填字段,迟到结果不能覆盖人工已改字段或新选择的文件。保存留存识别方式、原件摘要、修正字段及实际经办审计,不把原始票面/OCR payload或完整银行账号写入日志、任务或浏览器长期存储。既有材料下载权限、用途与审计继续适用。
|
||||
|
||||
### 本阶段不包含
|
||||
|
||||
整份银行流水/Excel 批量导入、多交易拆分、自动建立借还对应、自动会计凭证、银行验真、自动确认余额/授权/付款、新银行或 OCR 提供商、图片直传识别、历史记录批量重识别。既有图片附件上传不受影响。规划和本地合成验收不包含真实票据调用云 OCR、生产配置、生产录入或发布。
|
||||
|
||||
验收新增 AC32–AC39,技术合同及执行清单分别见 design、implement 顶部 N5-P。以上为待批准的完整提案,暂无需单独澄清的阻塞问题;用户批准后才进入实施。
|
||||
|
||||
## 2026-10-09 N5 员工出借人选择调整(本地验证完成,未发布)
|
||||
|
||||
用户要求员工出借人从已有员工中选择,复用系统中已维护的资料,不重复录入姓名和银行卡。已核对:员工主档存在姓名、公司关联、银行机构及加密账号;原借款入口要求手填,再另行核实员工关联。用户已确认本方案,授权本地实现与验证;不包含提交、推送、部署或真实业务录入。
|
||||
@@ -197,6 +220,15 @@ R3 的模式同时作用于付款审批、提交清单、成功/失败归属、
|
||||
- [x] AC30(重复与版本,本地验收完成):重复或并发选择同公司同员工复用明确的已有登记,不产生新员工或无声合并历史记录;预览至确认间来源变更时要求重新核对。已有出借人刷新保持既有锁序、版本、在途限制和审计;员工主档变化不改写已批准付款、已发生银行事实或本金。
|
||||
- [x] AC31(办理验证,本地验收完成):合成数据贯通选择员工、登记历史借入、确认余额、登记公司授权及经理确认;电脑/手机入口和缺资料提示可用。验证来源变更、并发重复、无权限和旧快照保持的反例,不用真实员工资料或付款作为开发测试。
|
||||
|
||||
- [x] AC32(单笔预填):合成可读入账 PDF 正确预填金额、日期和唯一引用,选择公司/出借人后可核对保存;扫描 PDF 的受控 OCR 返回同样合同。文件中有多笔交易、歧义金额/日期或缺字段时,不取第一笔、不取最大金额、不补造值。
|
||||
- [x] AC33(确认边界):识别和退出不产生业务借款或正式附件;保存后仅为余额待核实且带原件。未还本金、截止日、借款性质由财务确认,缺协议/余额核对材料时不能确认余额或申请还本,不生成付款或改写公司授权。
|
||||
- [x] AC34(主体与方向):公司入账可预填,公司支出、明确失败/处理中、非最终回执、非人民币及多交易文件受阻;付款方同名、旧银行账号或主体不唯一时只提示人工核对,不新增身份、不更改员工/出借人账户,不放宽现有还款回单规则。
|
||||
- [x] AC35(失败可继续):OCR 关闭、缺配置、超时、额度不足、字段冲突和本地无法提取文字均有可理解结果;未知可人工补录,已知矛盾不能靠普通确认消除。断言无虚构字段、无自动重试风暴,充分可读 PDF 不调用云 OCR。
|
||||
- [x] AC36(保存一致性):保存成功同时存在一笔借款及对应 `incoming` 原件;相同请求重试仅返回原结果,换文件/字段复用请求键被拒;同公司同出借人的重复引用、相同原件及并发新入口保存不会生成重复可用借款。存储、附件或审计失败时正确补偿,不虚报成功。
|
||||
- [x] AC37(身份与原件绑定):无财务权限、跨身份结果复用、令牌过期/篡改、确认前换文件及来源变更受阻;电脑/手机换文件、卸载及同文档换账号时取消旧请求,迟到响应不覆盖新表单或以新身份重发保存;保留同身份正常会话恢复。
|
||||
- [x] AC38(原流程兼容与隐私):原手工登记、员工关联、非员工登记、三类材料余额确认、关联授权与还款路径仍通过相关回归;原件下载沿既有权限/用途/审计,无完整账号或原始识别 payload外泄。PDF/文件不安全或超限在持久化前拒绝。
|
||||
- [x] AC39(证据边界;第五轮独立复核 PASS_WITH_NOTES,真实样本仅确认字段已提取、未逐值核对,云端 OCR 与发布另行验收):完成单元、真实隔离 HTTP/数据库及电脑/手机浏览器验证,覆盖失败、并发和清理;完成涉及入口权限/保存流程的独立只读复核,受 inline 限制时明确待独立复核,不冒用旧结论。真实样本与云 OCR 可用性、公开发布分别验收,不以合成结果勾 AC5 或 N5/N6 实际业务完成。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 不处理利息、工资/分红、员工向公司借备用金、资本金、债转股、机构借款、外币、一般退款或债务冲销;仅保留本任务已定义的超额还款实际追回,不做冲抵后续本金。
|
||||
@@ -210,9 +242,9 @@ R3 的模式同时作用于付款审批、提交清单、成功/失败归属、
|
||||
|
||||
## Current Status
|
||||
|
||||
N5 办理前新增“员工出借人从已有员工选择并复用资料”的调整要求,方案已列于本任务顶部,尚待批准实施。线上仍为原登记入口;本轮没有真实业务登记,也不重复索取已确认的借款事实。
|
||||
当前工作为顶部 N5-P“原借入回单识别预填”的规划,PRD、设计及执行清单已形成,待用户批准实施。本轮没有修改产品代码、启动任务执行或进行真实业务登记;父任务继续保持 `in_progress`,不把此前授权套用于本阶段。
|
||||
|
||||
当前线上及 `origin/main` 为 `beb9f02c`;N3 独立复核和 N4 本次发布检查已完成,实际发布过程及证据见 implement 顶部。完整门禁为 82 迁移、服务端 4558 通过/1 项既有可选跳过、浏览器 372 全通过且零失败/跳过/重试;生产迁移、部署后复核、公网基础检查及合成 PDF 解析已核对。本轮只更新任务文档,未重跑应用测试或操作生产。
|
||||
员工选择阶段 AC28–AC31 已本地验收。用户在上一会话回报 `45737ccb` 已推送部署、公网检查正常、生产仍为 82 迁移,并明确尚未在线上登录办理新入口;本会话只核对本地版本和转交记录,未重新连接生产或复跑发布检查。下方及前述 `beb9f02c`、“未发布”等记录保留其历史时点,不能作为 N5-P 的验证证据。
|
||||
|
||||
整体任务保持 `in_progress`。真实 OA 台账及关联授权尚未登记,首笔真实还款的网银提交、正式回单、核销及会计交接尚未验收;AC5 及未覆盖的真实材料边界继续开放。无需重新索取已确认的借款事实或要求技术员工参与;真实录入和付款仍按各自授权执行,不从未还本金推定本次全额支付。
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户