Files
th-hotel-simple/docs/project/requirements/M009-manual-invoice-generation-v1.md

20 KiB
Raw Blame History

M009 Manual Invoice 手工开票生成 V1

项目 内容
文档状态 CP3 前端 V1 已完成,后续继续推进订单 / 任务预填、客户目录接口和历史查询
适用范围 无订单 / 无任务场景下的手工 Invoice 创建,以及后续从任务或订单预填 Invoice
当前目标 第一阶段已基于现有 invoice.html 原型落地 Manual Invoice Mode前端页面已接入正式业务接口并按原型三段式结构展示
依赖能力 M008 Excel 转 PDF 平台转换能力、阿里云 OSS、登录权限和酒店上下文

1. 背景

当前系统的主业务数据来自 AgentBus、SuperAgent、订单和任务。但实际运营中可能出现极端情况

  • 邮件还没有被 SuperAgent 正确识别成订单 / 任务。
  • 订单或任务尚未入库,用户仍需要先出一张 Proforma Invoice。
  • 历史、线下、电话或临时沟通产生的开票需求没有可关联的任务。
  • SuperAgent 或主链路故障时,需要人工兜底生成 PDF。

因此第一阶段需要支持一个独立的 Manual Invoice Mode:用户可以不选择订单、不选择任务,直接填写或选择 Invoice 字段,由后端生成 Excel再通过 LibreOffice 转为 PDF 并上传 OSS。

2. 第一阶段定位

第一阶段不是完整财务发票系统,而是酒店预订业务中的 Proforma Invoice 生成能力。

第一阶段应做到:

  • 页面可从独立入口进入,例如 /reservation/invoices/new/invoices/new
  • 不要求 task_idorder_id
  • 用户手工填写 / 选择字段后,后端生成 PDF。
  • 后端重新计算金额、税额和合计,不信任前端提交的计算结果。
  • 酒店、法人、税号、银行账户、Logo 和固定文案优先来自模板或酒店发票配置。
  • 生成行为必须有权限、酒店隔离和审计记录。

第一阶段不做:

  • 不接真实 OPERA / OHIP。
  • 不要求必须挂靠订单或任务。
  • 不做应收账款、收款核销、发票税务编号或财务系统入账。
  • 不把 SuperAgent AI 原始 JSON 直接渲染成 Invoice 表单。
  • 不允许前端直接调用 M008 调试上传接口来伪造业务开票。

3. 与现有流程关系

3.1 手工模式

用户打开手工 Invoice 页面
  -> 前端读取发票表单配置和默认值
  -> 用户填写 / 选择字段
  -> 前端提交 invoice_payload
  -> 后端校验、计算金额和税额
  -> 后端填充 Excel 模板
  -> 调用 M008 内部转换能力生成 PDF
  -> PDF 上传 OSS
  -> 写入 Invoice 生成记录和审计
  -> 返回 PDF URL 和生成摘要

3.2 任务或订单预填模式

后续可以支持从任务或订单进入:

任务详情 / 订单详情
  -> 打开 Invoice 页面并带 task_id 或 order_id
  -> 后端按 task_id / order_id 查询可用上下文
  -> 返回同一套 invoice_payload 默认值
  -> 用户确认或修改
  -> 后续生成流程与手工模式相同

中文说明:

  • task_idorder_id 只是可选上下文,不是第一阶段生成 Invoice 的必要条件。
  • 如果从任务进入,应优先使用任务详情 fields[]draft_payload_jsonconfirmed_payload_json 中已确认的数据。
  • 如果从订单进入,应由用户选择具体任务或由页面显式展示“仅使用订单摘要生成”,避免一个订单多任务时字段来源不清。

4. 字段来源和控件分类

基于现有 invoice.html 原型和 Excel 模板,字段分为四类。

分类 中文说明 示例字段 第一阶段控件建议
用户输入字段 每张 Invoice 都可能不同,需要用户填写或修改 Group Name、Arrival Date、Departure Date、Due Date、Room Rate Note、明细 Description input / date / textarea
选择 + 可手填字段 有标准目录,但业务上允许人工覆盖 Company、Attention、Address、Tel、Email、Booking Date、Room Type、Room Rate、Extra Bed select + Manual 或后续 combobox
模板 / 配置字段 不是每张 Invoice 的业务输入,随酒店、法人或模板版本变化 酒店名称、Tax ID、法人公司、银行账号、Logo、固定付款文案、页脚 第一阶段可在模板或酒店发票配置中维护
系统计算字段 不应由用户手填,后端或 Excel 公式计算 Amount、Sub-Total、VAT、Total、Night(s) 汇总、Room(s) 汇总 前端只读预览,后端重新计算

4.1 Basic Information

字段 控件 后续来源
Company 选择 + 可手填 客户 / 旅行社目录,第一阶段可使用后端配置或静态种子
Attention 选择 + 可手填 Company 下联系人目录
Address 选择 + 可手填 联系人或公司账单地址
Tel 选择 + 可手填 联系人电话
Email 选择 + 可手填 联系人邮箱
Booking Date 选择 + 可手填 默认使用当前酒店本地日期,也可用户调整

4.1.1 Recipient Directory 第一阶段种子数据

CompanyAttentionAddressTelEmail 不是五个互相独立的普通字段,而是一组收件方 / 旅行社联系人档案。第一阶段页面可以允许 Manual 覆盖,但默认选择逻辑必须保持关联关系:

  • 选择 Company 后,刷新该公司下可选 Attention 列表。
  • 选择 Attention 后,同步带出该联系人的 AddressTelEmail
  • 公司只有一个联系人时,可以默认选中该联系人并自动带出地址、电话和邮箱。
  • Address 可在公司级和联系人级之间复用;如果同一公司多个联系人共用地址,不应在每个联系人手工重复维护多份无关数据。
  • 用户选择 Manual 时,才允许脱离目录手工填写;提交时仍保存最终文本值,目录 code 仅用于审计和后续追溯。

第一阶段确认的种子数据:

company_code Company contact_id Attention Address Tel Email
LIAN_TAI LIAN TAI TRAVEL (THAILAND) CO., LTD. LIAN_TAI_KHUN_ANN Khun Ann 2/86 Rajpattana Road, Rajpattana, Sapansoong, Bangkok, TH, 10240 061-397-2675 op.liantaitravel@gmail.com
QBD Q.B.D. TRAVEL GROUP CO., LTD QBD_JITDANUN_PANAPHUCHONG Jitdanun Panaphuchong 2/90 Rajpattana,Rajpattana,Sapansoong, Bangkok, TH, 10240 089-032 0176 op.qbdtravel@gmail.com
HANATOUR HANATOUR TD CO., LTD. HANATOUR_WICHIENPRAKARN Wichienprakarn, Nuanphae, Khun. HanaTour Bldg, 41,Insadong 5-gil, Jongno-gu, KR 066 124 - 7297 HI219@hanatour.com
HANATOUR HANATOUR TD CO., LTD. HANATOUR_PHONGBUPPA Phongbuppa, Buppachat HanaTour Bldg, 41,Insadong 5-gil, Jongno-gu, KR 096 051 3587 HI223@hanatour.com
HANATOUR HANATOUR TD CO., LTD. HANATOUR_KANG_SUNG_HWA Kang,Sung Hwa HanaTour Bldg, 41,Insadong 5-gil, Jongno-gu, KR 82 051 804 0707 k8040707@nave.com
HANATOUR HANATOUR TD CO., LTD. HANATOUR_MINYOUNG_KIM Minyoung Kim HanaTour Bldg, 41,Insadong 5-gil, Jongno-gu, KR 82 010 6638 3345 mykim1220@hanayour.com
HANATOUR HANATOUR TD CO., LTD. HANATOUR_BOLAM_JO Bolam Jo HanaTour Bldg, 41,Insadong 5-gil, Jongno-gu, KR 82 010 4182 4615 melissa0609@hanatour.com
HANATOUR HANATOUR TD CO., LTD. HANATOUR_HWANG_SEONGSEOP Hwang Seongseop HanaTour Bldg, 41,Insadong 5-gil, Jongno-gu, KR 82 010 3167 8648 hwangpilot59@naver.com
HANATOUR HANATOUR TD CO., LTD. HANATOUR_SONG_SAE_HWA Song Sae Hwa HanaTour Bldg, 41,Insadong 5-gil, Jongno-gu, KR 82 010 2400 6801 sh-tour2016@naver.com

中文说明:

  • 以上数据第一阶段可以作为后端种子配置、数据库初始化数据或前端原型静态数据,但正式业务接口生成 PDF 时应以请求中的最终文本值为准。
  • 后续如果接客户 / 联系人维护后台,company_codecontact_id 应保持稳定,不使用 Company 或 Attention 展示文案做业务主键。
  • Booking Date 虽然展示在 Basic Information 区域,但它不是联系人档案的一部分,应独立保存在 invoice_payload.document.booking_date

4.2 Reservation

字段 控件 后续来源
Group Name / Group Code 输入框 手工填写;任务预填时来自 case_keys.group_code 或订单 group_code
Arrival Date 日期输入 手工填写;任务预填时来自任务字段
Departure Date 日期输入 手工填写;任务预填时来自任务字段
Due Date 日期输入 手工填写;可按酒店付款规则默认生成
Room Rate 选择 + 可手填 第一阶段可手填;必填且必须是数字,允许 0,不能小于 0;后续接 Rate Code / 房价配置
Room Rate Note 输入框 例如 includingBF
Extra bed 选择 + 可手填 第一阶段可手填;后续接加床价格配置
No. of Room(s) 输入框 / 只读派生 可由第一条明细同步,也允许手工覆盖
No. of Night(s) 输入框 / 日期派生 前端默认按 Departure Date - Arrival Date 自动填入第一条明细 Night(s),用户手工修改后不再覆盖

4.3 Description & Amount

字段 控件 后续来源
Description 输入框 手工填写;默认可使用 Group Name 或 Booking No
Room Types 选择 + 可手填 第一阶段可手填;后续接 PMS 房型目录
Quantity(s) 数字输入 后端要求正数
Rate 数字输入 后端要求必填、数字且不能小于 0,允许 0
Night(s) 数字输入 后端要求正数
Amount 只读 quantity * rate * nights
Sub-Total 只读 后端按 VAT 规则计算
VAT 7% 只读 后端按配置税率计算
Total amount 只读 明细金额合计

5. 建议请求模型

第一阶段建议新增业务接口,不复用 M008 的调试上传接口:

POST /api/reservation/invoices/manual-generations
Authorization: Bearer <access_token>
Content-Type: application/json

请求体草案:

{
  "hotel_id": "HOTEL-TEST",
  "source_type": "MANUAL",
  "task_id": null,
  "order_id": null,
  "template_code": "PROFORMA_INVOICE_V1",
  "invoice_payload": {
    "document": {
      "invoice_date": "2026-07-17",
      "booking_date": "2026-07-12",
      "due_date": "2026-07-22"
    },
    "recipient": {
      "company_code": "LIAN_TAI",
      "contact_id": "LIAN_TAI_KHUN_ANN",
      "company": "LIAN TAI TRAVEL (THAILAND) CO., LTD.",
      "attention": "Khun Ann",
      "address": "2/86 Rajpattana Road, Rajpattana, Sapansoong, Bangkok, TH, 10240",
      "telephone": "061-397-2675",
      "email": "op.liantaitravel@gmail.com"
    },
    "booking": {
      "group_name": "GRP-DEMO-0802",
      "arrival_date": "2026-08-02",
      "departure_date": "2026-08-05",
      "room_rate_note": "includingBF",
      "extra_bed_rate": 1200
    },
    "charges": [
      {
        "description": "GRP-DEMO-0802",
        "room_type": "Deluxe Room",
        "quantity": 2,
        "rate": 3000,
        "nights": 3
      }
    ]
  }
}

中文说明:

  • source_type=MANUAL 表示无订单 / 无任务的手工开票。
  • task_idorder_id 第一阶段允许为空。
  • 如果同时传入 task_idorder_id,后端会校验该任务必须挂靠在该订单下;不一致时返回 RESERVATION_INVOICE_CONTEXT_MISMATCH
  • invoice_payload 中的日期是酒店本地业务日期,使用 yyyy-MM-dd
  • 前端可以传金额预览,但后端不得信任;最终金额以服务端计算和模板公式为准。
  • 客户目录选择码和手填文本可以同时传;后端第一阶段以文本值生成 PDFcompany_codecontact_id 用于审计和后续配置追溯。

成功响应草案:

{
  "invoice_generation_id": "2080000000000000001",
  "generation_status": "SUCCEEDED",
  "source_type": "MANUAL",
  "hotel_id": "HOTEL-TEST",
  "template_code": "PROFORMA_INVOICE_V1",
  "pdf_url": "https://oss.example.test/reservation-invoices/HOTEL-TEST/2026-07-17/.../proforma-invoice.pdf",
  "pdf_object_key": "reservation-invoices/HOTEL-TEST/2026-07-17/.../proforma-invoice.pdf",
  "generated_excel_object_key": "reservation-invoices/HOTEL-TEST/2026-07-17/.../proforma-invoice.xlsx",
  "totals": {
    "subtotal": 16822.43,
    "vat": 1177.57,
    "total": 18000.00,
    "currency": "THB"
  },
  "created_at": "2026-07-17T03:30:00Z"
}

6. 建议数据模型

第一阶段建议持久化生成记录,便于审计、排查和后续列表查询。

建议新增表:

workflow_reservation_invoice_generation

核心字段:

字段 中文含义
id Invoice 生成记录 ID
hotel_id 酒店 ID
source_type 来源类型:MANUALTASKORDER
order_id 可选订单 ID
task_id 可选任务 ID
source_message_id 可选来源邮件 ID
template_code 模板编码
template_version 模板版本
invoice_payload_json 用户提交并经后端归一化后的业务字段
calculated_totals_json 后端计算后的金额、税额和合计
generated_excel_object_key 生成后的 Excel OSS 对象 Key
pdf_object_key PDF OSS 对象 Key
pdf_url PDF 访问 URL
generation_status 生成状态
safe_error_code 安全错误码
safe_error_summary 安全错误摘要
created_by 创建人
created_at / updated_at UTC 时间点

中文说明:

  • generated_excel_object_keypdf_object_keypdf_url 不只在成功记录中出现;如果流程在后续步骤失败,后端也会保留已经成功上传的对象定位,避免 OSS 生成物变成不可追踪的孤立文件。
  • SUCCEEDED 只在模板填充、Excel 上传、PDF 转换、PDF 上传和业务审计均完成后写入;如果业务审计失败,不会先标记成功再覆盖为失败。

建议状态:

状态 中文含义
PENDING 已接收,等待生成
RUNNING 正在填模板或转换
SUCCEEDED 生成成功
FAILED 生成失败

第一阶段也可以同步生成并直接返回,但仍建议落库,因为这属于业务文档生成,不是一次性调试转换。

7. 后端模块边界

建议放在 Reservation 工作流下,而不是平台文件转换模块:

server/src/main/java/cn/nianxx/thhotel/workflows/reservation/invoice
├── control
├── service
│   └── impl
├── domain
├── mapper
├── repository
└── common
    ├── dto
    ├── request
    ├── result
    └── enums

中文说明:

  • reservation.invoice 负责业务字段校验、模板字段映射、发票生成记录、权限和审计。
  • platform.documentconversion 继续作为底层文件转换能力,负责 Excel 到 PDF。
  • integrations.storage.aliyunoss 继续作为 OSS 能力。
  • 前端不能直接调用 POST /api/system/document-conversions/excel-to-pdf 来完成业务开票;那是调试 / 后台工具接口。

8. 模板和字段映射

第一阶段建议先使用一个受控模板:

server/src/main/resources/templates/reservation-invoice/proforma-invoice-v1.xlsx

后续可迁移到 OSS + 数据库模板版本管理。

模板字段映射原则:

  • 模板固定文案、Logo、银行信息、税号可以先保留在模板内。
  • 当前 PROFORMA_INVOICE_V1 使用 A4 纵向单页模板,打印区域固定为 A1:H52,后端渲染时会再次写入纸张、缩放和打印区域,避免 LibreOffice 转 PDF 时分页漂移。
  • 可动态变更但不属于单张 Invoice 的配置,后续迁移到酒店发票配置。
  • 用户输入字段通过后端模板填充器写入指定单元格。
  • 第一版明细行固定支持 1 到 10 行;超过模板默认行数时,后续 checkpoint 再做复制行样式和公式。
  • 金额公式由后端统一写入或由模板公式统一生成,不复制历史案例中不一致的公式。

9. 权限、安全和审计

第一阶段建议:

  • 接口分类:FRONTEND_USER,不是 FRONTEND_DEBUG
  • 必须 Bearer 登录。
  • 权限码建议:RESERVATION_INVOICE_GENERATE
  • 必须校验用户对 hotel_id 的访问权。
  • 如果传入 task_idorder_id,必须反查对象所属酒店并校验与 hotel_id 一致;如果两者同时传入,必须校验任务所属订单与 order_id 一致。
  • 写入业务审计,记录创建人、酒店、来源类型、可选订单 / 任务、模板版本和生成结果。
  • 日志不得输出完整 invoice_payload_json、客户邮箱、电话、OSS 签名 URL 或 PDF 内容。

10. Checkpoint 规划

CP1文档和页面方向确认

  • 落地本文档。
  • 明确当前 invoice.html 原型可作为前端交互和控件参考。
  • 确认第一阶段是手工模式,不强依赖订单和任务。

CP2后端手工生成接口

  • 已完成:新增业务接口 POST /api/reservation/invoices/manual-generations
  • 已完成:新增生成记录表 workflow_reservation_invoice_generation 和业务审计。
  • 已完成:根据请求 payload 填充受控 Excel 模板 templates/reservation-invoice/proforma-invoice-v1.xlsx
  • 已完成:模板切换为接近酒店历史样张的 A4 单页版式,后端渲染器固定 A1:H52 打印区域,避免生成 PDF 被拆成多页。
  • 已完成:复用平台 Excel 转 PDF Adapter 生成 PDF并上传阿里云 OSS。
  • 已完成:返回 PDF URL、对象 Key、金额摘要和生成状态。
  • 已完成:失败记录保留已上传成功的 Excel / PDF object key业务审计完成后才标记 SUCCEEDED
  • 第一版限制:只支持 source_type=MANUALtemplate_code=PROFORMA_INVOICE_V1、最多 10 条费用明细;暂不提供历史列表、详情查询、任务 / 订单预填和客户联系人目录查询接口。

CP3前端 Manual Invoice 页面(已完成 V1

  • 已完成:基于当前 invoice.html 原型改造成前端项目内 /reservation/invoices/new 页面。
  • 已完成:页面按 01 Basic Information02 Please refer to the following reservation03 Description&Amount 三段式结构展示;标题、关键输入占位符和主要操作按钮已接入中 / 英 / 泰 i18n其余原型字段标签后续按需求统一处理。
  • 已完成:字段控件按“输入 / 选择 + 可手填 / 只读计算 / 模板配置”实现,第一条费用明细和预订摘要区保持联动。
  • 已完成:提交到后端业务接口,不调用调试上传接口。
  • 已完成:展示 PDF 生成结果、错误提示和下载 / 预览入口。

CP4任务 / 订单预填

  • 从任务详情进入时,使用任务详情 fields[]、草稿或确认 payload 预填。
  • 从订单详情进入时,要求用户选择具体任务或确认仅使用订单摘要。
  • 保持用户可以修改字段后再生成 PDF。

CP5配置化和历史查询

  • 客户 / 联系人目录、房型、Rate、Extra Bed、银行账户和模板版本逐步配置化。
  • 增加 Invoice 生成历史列表和详情。
  • 明确 OSS 生命周期、重新生成、作废和审计策略。

11. 当前待确认事项

已确认并落地:

  • 第一阶段接口路径采用 POST /api/reservation/invoices/manual-generations
  • PDF 生成记录第一阶段必须落库。
  • 生成后的 Excel 同步上传 OSS 并保存对象 Key便于排查。
  • Invoice Date 第一阶段允许前端提交,后端按酒店本地业务日期字段处理。
  • 发票模板初始文件使用当前 Invoice Templete.xlsx 改造成受控模板资源。
  • VAT 第一阶段固定按 7% 含税反算。

仍待后续确认:

  • 客户 / 联系人目录是否进入后端配置 / 数据库维护,还是继续由前端临时静态维护。
  • 是否需要 Invoice 历史列表、详情、重新生成和作废接口。
  • 是否需要支持超过 10 条费用明细、复制模板行样式和分页。
  • 任务 / 订单预填时字段优先级和冲突提示规则。