Feature Branch: 不新建分支(当前 test 分支开发)
Created: 2026-07-24
Status: Draft
Input: User description: "开发票时用户每次都要手填手机码(载具)等信息,做一个像收货地址管理一样的功能,让用户保存常用发票抬头、开票时直接选用、不必重填。"
010(订单 ezPay 发票开立)已实现订单级即时开票,客户每次开票需在 ApplyInvoiceDto 手填买方名称 / 统编 / 邮箱 / 载具等。本期新增用户级发票抬头 CRUD(镜像 InfoAddress 收货地址管理模式),客户保存常用 B2C / B2B 抬头,开票时由客户端选用并预填,后端开票链路(010)完全不改。
范围 = 后端 App 端 CRUD 接口;客户端 App 的「我的抬头」管理页 + 订单开票时的「选用抬头」入口由客户端团队后续对接(沿用 010 分工)。本期无任何前端页面,故无 i18n 改动。
客户在 App 新增 / 查看 / 编辑 / 删除个人发票抬头,含姓名 + 载具(手机条码 / 自然人凭证 / ezPay 会员,必填)+ 邮箱(会员载具时必填)。
Why this priority: 个人发票是外卖/餐饮场景最高频的开票类型,与 B2B 同为核心 CRUD 路径。
Independent Test: 调 POST /system/invoice/invoice(B2C + 手机条码载具)→ 成功 → GET /getinvoice 列出该条 → GET /deleinvoice?id= 删除成功。
Acceptance Scenarios:
客户保存公司抬头,含公司名 + 统编(8 位)+ 邮箱。
Why this priority: B2B 报账是刚需(统编标配),与个人开票同为核心路径。
Independent Test: POST /invoice(B2B + 公司名 + 合法统编 + 邮箱)→ 成功;非法统编 → 拦截。
Acceptance Scenarios:
/ 开头、自然人凭证非 2 字母 + 14 数字)→ 保存时拦截。category(B2C / B2B)区分;字段随类型(见 data-model.md、contracts/api.md 校验矩阵)。^\d{8}$ + 载具号码按类型正则(手机条码以 / 开头、自然人凭证 ^[A-Z]{2}\d{14}$、ezPay 会员非空)+ 邮箱格式。JwtUtil.getusid(token) 取 userId,写库前 setUserId 强制覆盖、查/改/删按 user_id 过滤;越权操作拒绝。applyInvoice / getInvoice;抬头仅为客户端开票时的输入快捷方式。updatesql/sql.md,不直接执行(项目规范)。info_invoice):titleName / category / buyerName / buyerUbn / buyerEmail / carrierType / carrierNum + userId + 审计列。id)。ApplyInvoiceDto(不改):开票时客户端从选中抬头取字段填入。ApplyInvoiceDto(UI 由客户端团队对接)。InfoAddress(收货地址)用户级 CRUD 模式:实体即 DTO/VO、JWT 隔离、无默认、无上限、硬删除、App 端接口风格(@Anonymous @Auth + @RequestHeader token)。applyInvoice 规则:B2C 载具必填,0/1/2 三选一;ezPay 会员载具(2) 还须带邮箱)。applyInvoice 接收显式字段并做最终校验兜底,即使抬头存了也会在开票时再校验一次。