开发票操作时序图
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: 计算金额<br/>(payAmount + elecMoney + serviceMoney)
Svc->>DB: 创建 Invoice 记录<br/>状态=待开票(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()<br/>行项1: 充电电费 @13%<br/>行项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: 返回发票信息<br/>(发票代码/号码/PDF/OFD/XML)
HPClient-->>HPSvc: InvoiceInfo
alt 开票成功
HPSvc->>DB: 写入 InvoiceDetail<br/>(发票代码/号码/下载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