整体流程图
Sales 销售
Customer 客户
分支判断
需人工 Confirm
自动 confirmed
关键合流节点
Commission lane (Finance)
Supplier lane (Finance)
1 销售出单 Sales
-
1.1
-
1.2
-
1.3
建 booking
/bookings/new3-Step 向导,系统自动生成 EKOK No. (例 000017)
-
1.4
Step 1 上传
OCR Uploader 自动解析 PDF 行程单,或手工 Manual Form
-
1.5
Step 2 Review
检查乘客 / 航段 / 金额,字段校验
-
1.6
Step 3 Collect Payment
大 QR 显示 + 复制付款链接(链接自动到剪贴板)
完成后状态
bookings.payment_status = 'unpaid' ·
branch_id = 销售所属 ·
sales_id = 销售 user.id ·
commission_amount = 0(未付款不算提成)
2 客户付款 Customer
-
2.1
-
2.2
点开公开付款页
/pay/:token显示 branch 配置的方式(最多 6 种)
-
2.3
6 种付款方式
需上传截图
Bank Transfer
手机网银手工转账 → 截图上传
pending 等 Sales Confirm
自动
WeChat Pay
扫 QR → 第三方回调
confirmed
自动
Alipay
扫 QR → 第三方回调
confirmed
自动
UnionPay
跳转网关
confirmed
自动
Credit Card
Stripe / 2C2P 流程
confirmed
自动
Online Banking
跳转网银
confirmed
关键差异
银行转账 = 真正的线下流程,商家没法主动知道钱到账 → 必须客户截图 + 人工 confirm。
其他方式 = 第三方网关已验证 → 直接 confirmed,跳过人工审核。
3 银行转账 Confirm Sales
业务规则
bank-transfer 的 Confirm 是销售员自己的活,不是财务。销售员持有自家门店的银行 App,对照流水核对客户上传的截图。
-
3.1
看到自家这单 pending
/paymentsbooking 详情 Payment Received tab自动 scope 到 sales_id
-
3.2
对照自家门店银行流水
自家网银 App,看钱有没有真的到账
-
3.3
点 Confirm
操作列 ✓ 按钮 → payment.status='confirmed'
-
3.4
(金额对不上)Reject
操作列 ✗ 按钮 → status='rejected' + 必填原因,客户能看到
权限
| 角色 | 能 Confirm / Reject 谁的 |
| Sales | 只能 confirm/reject 自己 booking 的 payment(service 层 BadRequestException 拦截跨人) |
| Finance / Admin | 任何 payment(Payment Audit 页保留作为兜底 / 跨员工审计路径) |
Confirm 后系统自动
1. 累计所有 confirmed payments
2. 若 ≥ totalAmount → payment_status='paid'
3. 若 paid 且产品有 commission → commissionAmount = profit × rate(机票 50% / 旅游定额 / 保险按 product.commPercent)
4. 触发 audit log payment.confirm
4 月底结算 Commission Finance
4.1 创建 Payout
- 4.1.1
看本月 Commission Payable
/finance/commission-payable选时间范围 → Summary 看总额
- 4.1.2
- 4.1.3
勾选同一销售的行
系统强制只能勾同一销售(disable 其他人的 checkbox)
- 4.1.4
点 Create Payout
顶部蓝色按钮 → 跳到 /finance/commission-payouts
生成的数据
commission_payouts 多一条 draft ·
commission_payout_items 关联多张 booking + 当时的 commissionAmount 快照(防止后续改单影响历史)
4.2 实际打款
- 4.2.1
Export Bank Batch CSV (draft)
一次性导出所有 draft 的 NZ Direct Credit 5 列 CSV
- 4.2.2
上传到网银
ANZ Direct / ASB FastNet 批量代发
- 4.2.3
钱到账后逐条 Mark Paid
弹窗填 reference + 时间
Mark Paid 后系统自动
1. commission_payouts.status='paid',记 paid_at / paid_by / reference
2. 关联的所有 booking 🔒 LOCKED
3. 改这些 booking 的 totalAmount / costAmount / commissionRate / commissionAmount / grossAmount / taxAmount → 400 Booking is locked
4. 触发 audit log payout.paid
4.3 Sales 拿到提成
- Sales 在 /my-commission 页看到这单进入 Settled
- Booking 详情 My Commission 卡显示「Settled via Payout #N · 2026-05-20 · ref ANZ-001」
- Sales 银行账户实际收到对应金额
5 供应商账单结算 Finance
5.1 收到账单 + 导入
- 5.1.1
BSP / 航司发来周/月结清单
邮件 / 网站下载
- 5.1.2
进 Supplier Statements 页
/finance/supplier-statements
- 5.1.3
点 Import Statement
选 supplier / 填 period / reference
- 5.1.4
- 5.1.5
提交
系统按 ticket# 自动匹配 booking
5 种 line 状态
🟢 matched(金额一致)· 🟠 amount_diff(找到 booking 但金额不对)· 🔵 paid_in_full(已付过)· 🔴 no_booking / no_amount
5.2 逐行核对
- 5.2.1
点行 View → Modal
展开 lines 表
- 5.2.2
matched 行 Confirm
line.matchStatus → confirmed
- 5.2.3
amount_diff / no_booking 处理
Dispute + note → line.disputeStatus = pending
- 5.2.4
dispute 谈妥 Resolve
disputeStatus → resolved
- 5.2.5
ADM / ACM 标记
type Dropdown:ticket / ADM / ACM / fee / other
- 5.2.6
全部处理完 → Approve
statement.status → approved(pending dispute 阻塞)
5.3 Pay Supplier
- 5.3.1
去 Pay Supplier
/finance/pay-purchase选 supplier
- 5.3.2
勾选要付的 booking
自动列出 unsettled
- 5.3.3
- 5.3.4
提交
生成 supplier_payment + supplier_payment_applications
- 5.3.5
5.4 Bank Reconciliation 对账
- 5.4.1
- 5.4.2
上传到 Bank Reconciliation
/finance/bank-recon
- 5.4.3
- 5.4.4
红色不匹配行手动配对
标记成「无对应订单」或配对到 payment
单张 EKOK 完整生命周期
[Sales 出单]
booking: { paymentStatus: 'unpaid', commissionAmount: 0 }
[客户银行转账上传截图]
+ payment: { status: 'pending', source: 'customer', method: 'bank_transfer' }
[Sales Confirm]
payment: { status: 'confirmed' }
booking: { paymentStatus: 'paid', commissionAmount: 150 }
audit_log: 'payment.confirm'
[客户 WeChat/Alipay 等付款](替代路径)
+ payment: { status: 'confirmed', source: 'customer', confirmedById: null }
booking: 同上
[财务 Create Payout]
commission_payout: { status: 'draft', total: 150 }
commission_payout_item: { booking_id: 16, amount: 150 }
audit_log: 'create'
[财务 Mark Paid]
commission_payout: { status: 'paid', paidAt: ..., reference: 'ANZ-001' }
booking: 🔒 financial fields locked
audit_log: 'payout.paid'
[供应商账单到]
supplier_statement_line: { booking_id: 16, match_status: 'matched' }
[Confirm + Approve]
supplier_statement: { status: 'approved' }
[Pay Supplier]
supplier_payment + supplier_payment_application: { booking_id: 16, amount: 1000 }
[Bank Recon]
✓ 系统已收 1300,已付 1000,提成 150,留存 150
业务红线(系统强制)
| 规则 | 实现位置 |
| 销售只能看 / 改自己的 booking | bookings.findAll/findOne + salesId scope,findOne 403 拦截 |
| 销售只能 Confirm / Reject 自己 booking 的 payment | payments.service.confirm/reject 内 userRole==='sales' 校验 |
| 销售只能删 Unpaid booking | bookings.controller.delete 校验 |
| 全款付清才算 commission | recomputeBookingPaymentStatus 内置 |
| Payout 已发后 booking 锁定 | BookingsService.update 检测 isLockedByPayout |
| Supplier Statement 有 pending dispute 不能 Approve | approve() 内置检查 |
| Payout 一旦 PAID 不能取消 | cancel() 仅允许 DRAFT |
| 同一 booking 只能进一个 paid payout | commission_payout_items.booking_id UNIQUE |
| 万国签证机票都是虚订 | Sales 自觉走 IEPJ → XI 流程,系统不强制 |
痛点 → 页面 速查表
| 痛点 | 去哪 | 谁 |
| 「我要催款」 | /follow-up | Sales |
| 「客户银行转账要审」 | /payments · booking 详情 | Sales |
| 「这单提成发了没」 | booking 详情 My Commission 卡 /finance/commission-payouts 搜 EKOK | Sales / Finance |
| 「钱该催谁」 | /finance/ar-aging | Finance |
| 「钱到账了系统没记」 | /finance/bank-recon | Finance |
| 「这单赚多少 / 钱链路」 | /finance/payment-trace?bookingId=N booking 详情顶部 Trace 按钮 | Finance |
| 「今天付哪个供应商」 | /finance/ap-aging → /finance/pay-purchase | Finance |
| 「BSP 账单来了」 | /finance/supplier-statements | Finance |
| 「月底发提成」 | /finance/commission-payable → /finance/commission-payouts | Finance |
| 「老板要月报」 | /finance/reports · /finance/branch-pl | Finance |
| 「税务季报」 | /finance/gst | Finance |
| 「客户/供应商对账争议」 | /finance/payment-trace | Finance |
| 「财务一天的入口」 | /finance/dashboard(finance 登录默认页) | Finance |
主要数据模型
| 表 | 主要字段 | 关系 |
bookings | ekok_number / sales_id / branch_id / payment_status / commission_amount / total_amount / cost_amount | → customer / product / branch / payments |
payments | booking_id / amount / status (pending / confirmed / rejected) / source (customer/sales) / payment_method / confirmed_by | → booking |
commission_payouts | sales_id / status (draft / paid / cancelled) / total_amount / paid_at / reference | → items |
commission_payout_items | payout_id / booking_id UNIQUE / amount | → booking |
supplier_statements | supplier_id / period / status (reconciling / approved / paid / disputed / closed) / total_amount | → lines |
supplier_statement_lines | statement_id / ticket_no / amount / type (ticket/ADM/ACM/fee) / match_status / dispute_status | → booking |
supplier_payments + supplier_payment_applications | 一付多核销关联表 | → booking |
audit_logs | entity_type / entity_id / action / field / old_value / new_value / user_id | — |
branches | name / code / bankName / accountName / accountNumber / bankSwift / wechatQrUrl / alipayQrUrl / 等 | → bookings / users |
ai_usage | provider / model / purpose / tokens / cost_usd / cache hits | — |
未做(待用户提)
- Item-level mark-paid:允许财务对单个 EKOK 单独确认(拆分 payout)— 当前是整个 payout 整体 PAID
- Supplier Statement → Pay Supplier 自动衔接:approved statement 一键转 supplier_payment(当前要手工去 Pay Supplier)
- PDF 解析(Supplier Statement):单航司 PDF 账单自动解析(当前只支持 BSP 文本)
- Bank Recon 自动匹配增强:当前是金额 + 日期模糊匹配,可以加 ML
- Stripe / WeChat Open API 集成:让 WeChat/Alipay/Credit Card 真的走 gateway 回调(当前系统假设是 auto-confirmed,但实际还没接 SDK)
- Refund / 改签流程:完整退款(含反扣提成 / 关联 ADM)
- Credit Note / Invoice Adjustment:已开发票后的修订
- 期间关账(Lock Period):月末关账后那批数据 read-only