plan.md 7.1 KB

Implementation Plan: 订单 ezPay 电子发票开立

Branch: test(不新建分支,在当前分支开发) | Date: 2026-06-16 | Spec: spec.md

Input: Feature specification from specs/010-order-invoice/spec.md

Summary

订单完成后客户在客户端主动申请电子发票(B2C 个人邮箱 / B2B 公司统编 / 电子发票载具),系统读取门店 ezPay 凭证(来自 009 的 pos_store_ezpay),复用已实现的 EzPay.issueInvoice 即时开立,将发票号 / 防伪随机码落库到新表 pos_order_invoice(与订单 1:1),客户可查看发票;支持开票失败重试与发票作废。金额按订单商品实付(amount − freight,已扣全部优惠、不含运费)拆分为销售额 / 税额(台湾营业税 5% 内含),逐商品明细取自订单 food JSON,运费不计入发票。客户端开票/查询 API 供客户端团队对接(客户端 UI 不在本期);商家端、平台后台管理端查看/重试/作废,新增文字四语言 i18n。

Technical Context

Language/Version: Java 17+(Spring Boot,若依 RuoYi 框架;jakarta.servletHexFormat 表明 JDK17+)

Primary Dependencies:

  • 后端:Spring Boot、MyBatis-Plus(@TableName/@TableId/LambdaQueryWrapper)+ XML mapper、Apache HttpClient、fastjson2、hutool、若依通用(AjaxResult/TableDataInfo/BaseController/@PreAuthorize
  • 复用(不改内部):com.ruoyi.app.utils.ezPay.EzPay(issueInvoice/doPost)、EzPayConfigezPayCrypto.EzPayEncryptUtil
  • 复用 009:PosStoreEzpay(门店 ezPay 凭证:merchantId/hashKey/hashIv/companyId、ezpayStatus、isEnabled)、PosStoreEzpayMapper
  • 复用订单:PosOrder(id/ddId/amount/foodAmount/state/payStatus/food 等)、UserOrderController(客户端订单 /system/userOrder
  • 前端:Vue.js + Element UI(平台后台 foodie-admin-vue、商家端 foodie-store,vue-i18n 四语言);客户端前端 UI 不在本期,本期交付开票/查询后端 API 供客户端团队对接

Storage: MySQL(新增表 pos_order_invoice,与 pos_order 1:1)

Testing: 轻量单测(开票金额含税拆分 + ezPay 回应判读,mock EzPay.issueInvoice)+ 手测(cinv.ezpay.com.tw 测试环境真凭证开票 / 作废)

Target Platform: Windows 开发 / Linux 部署的 Spring Boot 服务 + Vue 后台/商家端 + 客户端

Project Type: web-service(Spring Boot 后端)+ 多个 Vue / 小程序前端

Performance Goals: 开票调用(含 ezPay 网络往返)< 10s 超时;订单详情接口携带发票状态无显著额外开销

Constraints: 复用 ezPay 工具类不改其内部;不改订单状态机(订单「完成」由现有 state=3 判定);发票作废以 ezPay invoice_invalid 接口返回为准,不在本地做时限计算;本期不含折让 / 字轨 / 批次 / 中奖通知

Scale/Scope: 1 新表;后端 1 domain + 1 mapper(含XML) + 1 service + 2 controller 域(客户端开票 + 管理端查看/重试/作废);管理端两前端(平台后台 + 商家端)最小入口;4 语言 i18n ×2 前端

Constitution Check

GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.

.specify/memory/constitution.md 为未填充模板(占位符未替换),项目未定义具体宪法原则与门槛。无实质门槛需评估,设计遵循项目既有约定(若依分层、MyBatis-Plus、全栈字段清单、四语言 i18n、SQL 写 updatesql/sql.md)。Phase 1 后复核:无违反。

Project Structure

Documentation (this feature)

specs/010-order-invoice/
├── spec.md              # 需求规格
├── plan.md              # 本文件(技术方案)
├── research.md          # Phase 0:关键技术决策与依据
├── data-model.md        # Phase 1:表结构、字段、状态机
├── quickstart.md        # Phase 1:跑通验证步骤
├── contracts/
│   └── api.md           # Phase 1:REST 接口契约
├── checklists/
│   └── requirements.md  # spec 质量检查
└── tasks.md             # /speckit-tasks 生成(本期 plan 不创建)

Source Code (repository root)

# ===== 后端 foodie_server =====
ruoyi-system/src/main/java/com/ruoyi/system/
├── domain/
│   ├── PosOrderInvoice.java                 # 新增:订单发票实体(与 pos_order 1:1)
│   ├── dto/ApplyInvoiceDto.java             # 新增:客户申请开票入参
│   └── vo/PosOrderInvoiceVo.java            # 新增:发票列表/详情 VO
├── mapper/
│   └── PosOrderInvoiceMapper.java           # 新增
└── resources/mapper/chanting/
    └── PosOrderInvoiceMapper.xml            # 新增

ruoyi-admin/src/main/java/com/ruoyi/app/
├── order/
│   ├── OrderInvoiceService.java             # 新增:开票/作废/查询业务(注入 EzPay + 订单 + 009凭证)
│   └── UserOrderController.java             # 已有:新增客户端开票接口 /applyInvoice、/getInvoice
├── mendian/
│   └── PosOrderInvoiceController.java       # 新增:管理端(商家+平台)查看/重试/作废 /system/orderInvoice
└── utils/ezPay/                              # 已有,不改内部:EzPay/EzPayConfig/EzPayEncryptUtil

# ===== SQL =====
updatesql/sql.md                             # 追加建 pos_order_invoice 表

# ===== 平台后台前端 foodie-admin-vue =====
src/api/chanting/orderInvoice.js             # 新增:接口封装
src/views/mendian/orderInvoice/index.vue     # 新增:订单发票管理(查看/重试/作废)
src/api/language/{zh_CN,zh_TW,en_US,vi}.js   # 四语言加 orderInvoice:{} key

# ===== 商家端前端 foodie-store =====
门店订单详情                                 # 显示发票状态/号
src/lang/{zh,tw,en,vi}.js                    # 四语言加 key

Structure Decision:

  • 后端遵循既有若依分层:domain/mapperruoyi-system(被各处依赖);开票业务 service 放 ruoyi-admin.app.order.OrderInvoiceService,因为它要调用 EzPay(在 ruoyi-admin)+ 读订单 + 读 009 凭证——与 009「ezPay 联网放 Controller、Service 仅持久化」不同,本期开票业务较重,单独 service 更内聚,调用链:Controller → OrderInvoiceService → EzPay.issueInvoice。
  • 客户端开票接口挂在既有 UserOrderController/system/userOrder)下,与订单详情同域;管理端(商家+平台)查看/重试/作废用独立 PosOrderInvoiceController/system/orderInvoice,权限键 chanting:orderInvoice:*)。
  • 发票与订单 1:1:pos_order_invoice 当前态表(uk_order_id 唯一约束防重复开票),作废后状态置「作废」允许重开(不开历史明细表,作废记录保留在同一行)。
  • 管理端两前端(平台后台 + 商家端)各自最小入口,严格四语言 i18n;客户端 UI 不在本期。

Complexity Tracking

无宪法违反项,无需填写。

Violation Why Needed Simpler Alternative Rejected Because