# 开发票操作时序图

```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: 计算金额<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
```
