# 开发票操作时序图 ```mermaid sequenceDiagram actor User as 用户(小程序) actor Admin as 管理员(后台) participant Ctrl as InvoiceController participant Svc as InvoiceServiceImpl participant DB as 数据库 participant FinCtrl as FinanceController participant HPSvc as HuapiaoerInvoiceService participant HPClient as HuapiaoerClient participant HPAPI as 航信API(票儿) participant Job as InvoiceStatusJob(定时任务) participant Mail as 邮件服务 %% ===== 阶段1: 用户申请开票 ===== rect rgb(230, 245, 255) Note over User,DB: 阶段1: 用户申请开票 User->>Ctrl: POST /invoice/applyInvoice Ctrl->>Svc: applyInvoice(params) Svc->>DB: 查询 ChargeOrder (by orderSn) DB-->>Svc: 返回订单信息 alt 订单不存在或已开票 Svc-->>Ctrl: 返回错误 Ctrl-->>User: 申请失败 else 订单有效 Svc->>Svc: 计算金额
(payAmount + elecMoney + serviceMoney) Svc->>DB: 创建 Invoice 记录
状态=待开票(0) Svc->>DB: 自动保存 InvoiceTitle(如需要) Svc->>DB: 更新 ChargeOrder.invoiceStatus=开票中(3) Svc-->>Ctrl: 返回 Invoice Ctrl-->>User: 申请成功 end end %% ===== 阶段2: 管理员开票 ===== rect rgb(255, 245, 230) Note over Admin,HPAPI: 阶段2: 管理员开票 Admin->>FinCtrl: GET /finance/handleInvoice/{invoiceId} FinCtrl->>DB: 查询 Invoice DB-->>FinCtrl: 返回 Invoice alt 状态不是待开票(0) FinCtrl-->>Admin: 状态异常,无法开票 else 状态正常 FinCtrl->>DB: 记录开票人(biller) FinCtrl->>HPSvc: openBlueInvoice(invoice) HPSvc->>HPSvc: buildOpenBlueInvoiceRequest()
行项1: 充电电费 @13%
行项2: 充电服务费 @6% HPSvc->>HPClient: openBlueInvoice(request) HPClient->>HPAPI: POST /outopenapis/openBlueInvoice HPAPI-->>HPClient: 返回 {code, data} HPClient-->>HPSvc: OpenInvoiceResponse alt code == "200" HPSvc-->>FinCtrl: 成功 FinCtrl->>DB: 更新 Invoice.status=开票中(3) FinCtrl-->>Admin: 开票请求已提交 else code != "200" HPSvc-->>FinCtrl: 失败 FinCtrl-->>Admin: 开票失败 end end end %% ===== 阶段3: 定时轮询开票结果 ===== rect rgb(230, 255, 230) Note over Job,Mail: 阶段3: 定时轮询开票结果 (每5分钟) loop 每5分钟执行 Job->>DB: 查询 status=开票中(3) 的 Invoice DB-->>Job: 返回待查询发票列表 loop 每张发票 Job->>HPSvc: queryInvoiceResult(applyId, 1) HPSvc->>HPClient: getInvoice(orderId, invoiceType) HPClient->>HPAPI: POST /outopenapis/getInvoice HPAPI-->>HPClient: 返回发票信息
(发票代码/号码/PDF/OFD/XML) HPClient-->>HPSvc: InvoiceInfo alt 开票成功 HPSvc->>DB: 写入 InvoiceDetail
(发票代码/号码/下载URL等) HPSvc-->>Job: 返回发票详情 Job->>DB: 更新 ChargeOrder.invoiceStatus=已开票(1) Job->>DB: 更新 Invoice.status=已开票(1) opt 用户填写了邮箱 Job->>HPSvc: sendInvoiceEmail(orderId, email) HPSvc->>HPClient: sendMail(orderId, invoiceType, email) HPClient->>HPAPI: POST /outopenapis/sendMail HPAPI-->>HPClient: 发送结果 end else 开票处理中 HPSvc-->>Job: 仍在处理,等待下次轮询 else 开票失败 HPSvc-->>Job: 开票失败 Note over Job: 保持原状态,等待人工处理 end end end end %% ===== 阶段4: 手动确认开票(备用) ===== rect rgb(255, 230, 230) Note over Admin,DB: 阶段4: 手动确认开票 (备用路径) Admin->>FinCtrl: POST /finance/confirmManualInvoice/{id} FinCtrl->>Svc: confirmManualInvoice(invoiceId) Svc->>DB: 查询 Invoice alt 状态为开票中(3) 或 待开票(0) Svc->>DB: 更新 ChargeOrder.invoiceStatus=已开票(1) Svc->>DB: 更新 Invoice.status=已开票(1) Svc->>DB: 创建 InvoiceDetail(status=ISSUED) Svc-->>FinCtrl: 确认成功 FinCtrl-->>Admin: 手动确认完成 else 状态不符 Svc-->>FinCtrl: 状态异常 FinCtrl-->>Admin: 操作失败 end end %% ===== 阶段5: 用户下载发票 ===== rect rgb(245, 230, 255) Note over User,DB: 阶段5: 用户查看/下载发票 User->>Ctrl: GET /invoice/list?status=1 Ctrl->>Svc: listInvoiceForApp(userId, status) Svc->>DB: 查询 Invoice 列表 DB-->>Svc: 返回已开票列表 Svc-->>Ctrl: 返回发票列表 Ctrl-->>User: 展示发票列表 Admin->>FinCtrl: GET /finance/downloadInvoice/{id} FinCtrl->>DB: 查询 InvoiceDetail DB-->>FinCtrl: 返回详情(含PDF/OFD/XML URL) FinCtrl->>HPSvc: getInvoiceDownloadUrls(id, detail) HPSvc-->>FinCtrl: 返回下载URL FinCtrl-->>Admin: 返回下载链接 end ```