配套面试准备:完成本篇后,可以继续阅读:Day07_FMS+BI核心业务面试准备 Day07_FMS+BI财务结算+数据分析完整面试指南
课程目标:完整实现跨境供应链的财务闭环——从多平台收款对账到 SKU 级精准利润核算,并构建以数据驱动决策的智能 BI 分析系统,掌握供应链 KPI 体系、销售预测补货算法和 AI 自然语言查询的后续规划。
今日交付物:
- 财务模块 7 张数据库表完整 SQL
- 多平台结算报告解析(亚马逊 Settlement Report)
- SKU 级完整利润核算模型
- 多货币处理与汇兑损益
- 欧洲 VAT 申报数据导出
- BI 核心 KPI 指标体系设计
- 销售预测与智能补货算法
- 数据可视化 Dashboard 设计
- AI 自然语言查询功能规划(V2 版本实现)
- 两模块完整接口清单与错误码
第一节 财务结算业务全解析
1.1 跨境卖家最头疼的财务问题
graph TD
A["跨境卖家财务困境"] --> B["钱收了,不知道<br>赚了多少<br>(成本算不清)"]
A --> C["多个平台多币种<br>折算人民币总搞混<br>(汇率处理混乱)"]
A --> D["平台扣的手续费/仓储费<br>对不上账单数字<br>(对账全靠人工)"]
A --> E["某款产品卖得很好<br>但整体还是亏钱<br>(利润黑洞找不到)"]
A --> F["欧洲站要交VAT<br>不知道交多少<br>报什么数据<br>(合规风险)"]
1.2 FMS 的核心价值
graph LR
subgraph FMS财务管理系统
A["💰 收款对账<br>平台结算报告自动解析"]
B["📊 利润核算<br>SKU级精准成本拆解"]
C["💱 多货币<br>汇率自动同步折算"]
D["📋 VAT申报<br>自动生成申报数据"]
E["💳 应付账款<br>供应商款项管控"]
end
subgraph 数据来源
F["OMS 订单数据"]
G["WMS 库存成本"]
H["TMS 物流费用"]
I["PMS 采购成本"]
J["平台结算报告"]
end
F & G & H & I & J --> A & B & C & D & E
1.3 跨境财务的特殊性
| 复杂维度 | 说明 | 国内电商对比 |
|---|---|---|
| 多货币结算 | 美国站收 USD,欧洲站收 EUR,日本站收 JPY | 国内统一人民币 |
| 平台手续费复杂 | 亚马逊手续费 8%-15%,按品类不同,还有 FBA 费、广告费等多项 | 通常只有1-2%服务费 |
| 结算周期长 | 亚马逊每两周结算一次,Shopee 每周结算 | 通常 T+1 或 T+2 |
| 费用多且分散 | 物流费/仓储费/广告费/平台手续费/退款损失,每项都影响利润 | 费用结构简单 |
| VAT 合规 | 欧洲多国需申报,税率各异 | 国内增值税相对统一 |
| 汇兑损益 | 汇率波动导致同一笔销售的实际收益不同 | 无此问题 |
1.4 财务核心参与角色
租户/卖家 方面的人员角色
| 角色 | 职责 |
|---|---|
| 财务专员 | 导入对账报告、核对差异、发起付款 |
| 财务负责人 | 审批大额付款、查看整体财务报表 |
| 运营专员 | 查看自己负责商品/店铺的利润数据 |
| 管理层 | 查看经营全貌、毛利润/净利润趋势 |
第二节 财务数据库表设计
2.1 财务模块表结构总览
erDiagram
finance_exchange_rate {
bigint id PK
date rate_date "汇率日期"
char currency "货币代码"
decimal rate_to_cny "兑人民币汇率"
}
finance_platform_bill {
bigint id PK
bigint tenant_id
varchar bill_no "账单编号"
varchar platform "平台"
varchar store_id "店铺ID"
date settlement_start "结算开始日"
date settlement_end "结算结束日"
decimal total_sales "销售总额"
decimal total_fees "平台费用合计"
decimal net_amount "净到账金额"
tinyint status "状态"
}
finance_bill_item {
bigint id PK
bigint bill_id FK
varchar item_type "费用类型"
varchar order_no "关联订单号"
decimal amount "金额"
char currency "货币"
}
finance_profit_snapshot {
bigint id PK
bigint tenant_id
bigint sku_id FK
varchar store_id "店铺"
date snapshot_date "快照日期"
decimal revenue "销售收入"
decimal platform_fee "平台手续费"
decimal logistics_fee "物流费用"
decimal purchase_cost "采购成本"
decimal advertising_fee "广告费"
decimal refund_loss "退款损失"
decimal gross_profit "毛利润"
decimal net_profit "净利润"
decimal profit_rate "净利润率"
}
finance_vat_record {
bigint id PK
bigint tenant_id
char country_code "申报国"
varchar period "申报期YYYYMM"
decimal taxable_amount "应税销售额"
decimal vat_amount "应缴VAT"
tinyint status "申报状态"
}
finance_payment_apply {
bigint id PK
bigint tenant_id
varchar apply_no "付款申请单号"
bigint payable_id FK
decimal amount "付款金额"
tinyint status "状态"
bigint audit_user_id "审批人"
}
finance_cash_flow {
bigint id PK
bigint tenant_id
date flow_date "资金流水日期"
tinyint flow_type "类型收入支出"
varchar source_type "来源类型"
decimal amount "金额CNY"
varchar remark "说明"
}
finance_platform_bill ||--o{ finance_bill_item : "账单含多条明细"
finance_profit_snapshot }|--|| product_sku : "利润快照关联SKU"
finance_vat_record }|--|| finance_platform_bill : "VAT来自账单数据"
先理解这几张表的数据来源:
| 表名 | 数据从哪里来 | 谁触发生成 | 主要用途 |
|---|---|---|---|
finance_platform_bill | 外部电商平台的结算报告,如 Amazon Settlement Report、TikTok Shop 结算账单、Shopee 收款账单;也可以来自平台 API 拉取结果 | 财务专员上传账单文件,或系统定时调用平台接口拉取 | 保存“一个结算周期”的账单汇总,例如本期销售总额、退款、平台佣金、广告费、净打款金额 |
finance_bill_item | 同一份平台结算报告中的明细行 | 账单解析任务自动逐行写入 | 保存账单里的每一笔收入或扣费,例如某个订单的商品收入、推荐费、FBA 费、退款、广告费 |
finance_profit_snapshot | OMS 订单、PMS 采购成本、WMS 库存成本、TMS 物流费、FMS 平台账单费用共同计算出来 | 定时任务每日/月末生成,也可在账单对账完成后重算 | 固化某天或某月每个 SKU 的利润结果,供报表和 BI 查询使用 |
finance_vat_record | 平台账单明细、订单目的国、VAT 税率配置计算出来 | 财务专员点击“生成申报数据”,或季度末定时任务生成草稿 | 记录某租户某国家某申报期的 VAT 应税销售额、税率、应缴金额和申报状态 |
finance_cash_flow | 平台实际打款、供应商付款、物流付款、VAT 缴纳、广告充值等真实资金动作 | 财务确认到账/付款后写入,也可由银行流水导入后匹配生成 | 记录现金真的进出账户的时间和金额,用于现金流报表和资金预测 |
这里要特别区分两个概念:账单数据不等于资金流水。平台账单代表平台告诉卖家“这一期应该结算多少钱”,资金流水代表银行账户里“实际到账或实际付款多少钱”。账单可能先生成,钱可能几天后才到账;账单金额和到账金额也可能因为汇率、手续费、平台调整而存在差异。
2.2 汇率表
-- ============================================================
-- 汇率表
-- 每日同步最新汇率,以 CNY(人民币)为基准货币
-- ============================================================
CREATE TABLE `finance_exchange_rate`
(
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键ID',
`rate_date` DATE NOT NULL COMMENT '汇率日期(该汇率适用的日期)',
`currency` CHAR(3) NOT NULL COMMENT '外币货币代码,如 USD/EUR/GBP/JPY',
`rate_to_cny` DECIMAL(12, 6) NOT NULL COMMENT '1单位外币 = 多少人民币,如 USD: 7.2300',
`rate_source` VARCHAR(32) NOT NULL DEFAULT 'OpenExchange' COMMENT '汇率数据来源',
`is_official` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否官方汇率(0=估算)',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_date_currency` (`rate_date`, `currency`) COMMENT '同一天同一货币只有一条记录',
KEY `idx_currency_date` (`currency`, `rate_date`) COMMENT '查某货币某日汇率'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
COMMENT = '汇率表(每日更新)';
2.3 平台账单主表与明细表
-- ============================================================
-- 平台结算账单主表
-- 记录每个结算周期从平台收到的对账数据
-- ============================================================
CREATE TABLE `finance_platform_bill`
(
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键ID',
`tenant_id` BIGINT NOT NULL COMMENT '租户ID',
`bill_no` VARCHAR(32) NOT NULL COMMENT '内部账单号,格式 BILL-YYYYMMDD-XXXX',
`platform` VARCHAR(32) NOT NULL COMMENT '平台:AMAZON/TIKTOK/SHOPEE/TEMU',
`store_id` VARCHAR(64) NOT NULL COMMENT '店铺ID(同一平台可有多个店铺)',
`store_name` VARCHAR(128) NULL COMMENT '店铺名称',
`platform_bill_id` VARCHAR(128) NULL COMMENT '平台侧账单ID(如亚马逊 Settlement ID)',
`settlement_start` DATE NOT NULL COMMENT '结算周期开始日期',
`settlement_end` DATE NOT NULL COMMENT '结算周期结束日期',
-- 金额汇总
`currency` CHAR(3) NOT NULL COMMENT '账单货币',
`total_sales` DECIMAL(14, 2) NOT NULL DEFAULT 0 COMMENT '销售收入总额(未扣任何费用)',
`total_refund` DECIMAL(14, 2) NOT NULL DEFAULT 0 COMMENT '退款退费总额',
`referral_fee` DECIMAL(14, 2) NOT NULL DEFAULT 0 COMMENT '平台佣金/推荐费',
`fba_fee` DECIMAL(14, 2) NOT NULL DEFAULT 0 COMMENT 'FBA 配送费(亚马逊专项)',
`storage_fee` DECIMAL(14, 2) NOT NULL DEFAULT 0 COMMENT '仓储费',
`advertising_fee` DECIMAL(14, 2) NOT NULL DEFAULT 0 COMMENT '广告费',
`other_fee` DECIMAL(14, 2) NOT NULL DEFAULT 0 COMMENT '其他杂费(账单处理费等)',
`net_amount` DECIMAL(14, 2) NOT NULL DEFAULT 0 COMMENT '净到账金额 = 销售 - 退款 - 所有费用',
`cny_amount` DECIMAL(14, 2) NOT NULL DEFAULT 0 COMMENT '折合人民币净到账金额',
`exchange_rate` DECIMAL(10, 6) NOT NULL DEFAULT 1 COMMENT '结算时使用的汇率',
-- 状态与文件
`status` TINYINT NOT NULL DEFAULT 0
COMMENT '状态:0=待解析 1=解析中 2=已解析(待对账) 3=对账完成 4=有差异(需处理)',
`source_file_url` VARCHAR(512) NULL COMMENT '原始账单文件URL(CSV/Excel)',
`import_time` DATETIME NULL COMMENT '导入时间',
`import_user_id` BIGINT NULL COMMENT '导入操作人',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`create_by` BIGINT NULL,
`is_deleted` TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_bill_no` (`tenant_id`, `bill_no`),
UNIQUE KEY `uk_platform_bill_id` (`tenant_id`, `platform`, `store_id`, `platform_bill_id`),
KEY `idx_tenant_platform_period` (`tenant_id`, `platform`, `settlement_start`),
KEY `idx_status` (`status`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
COMMENT = '平台结算账单主表';
-- ============================================================
-- 账单明细表
-- 账单中每一笔费用收入的明细记录
-- ============================================================
CREATE TABLE `finance_bill_item`
(
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键ID',
`tenant_id` BIGINT NOT NULL COMMENT '租户ID',
`bill_id` BIGINT NOT NULL COMMENT '关联账单主表ID',
`item_type` VARCHAR(64) NOT NULL
COMMENT '费用类型:Principal=商品本金 Referral=推荐费 FBAFee=FBA费
Shipping=配送费 StorageFee=仓储费 Advertising=广告费 Refund=退款 Adjustment=调整',
`order_no` VARCHAR(128) NULL COMMENT '关联平台订单号(如有)',
`sku_id` BIGINT NULL COMMENT '关联SKU ID(解析时匹配)',
`platform_sku` VARCHAR(128) NULL COMMENT '平台侧SKU标识(如ASIN)',
`amount` DECIMAL(12, 4) NOT NULL COMMENT '金额(负数为扣费,正数为收入)',
`currency` CHAR(3) NOT NULL COMMENT '货币',
`description` VARCHAR(512) NULL COMMENT '明细说明',
`transaction_date` DATE NULL COMMENT '交易发生日期',
`is_matched` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否已与系统内部数据匹配:0=未匹配 1=已匹配',
`match_order_id` BIGINT NULL COMMENT '匹配到的内部订单ID',
PRIMARY KEY (`id`),
KEY `idx_bill_id` (`bill_id`),
KEY `idx_order_no` (`order_no`),
KEY `idx_sku_id` (`sku_id`),
KEY `idx_item_type` (`item_type`),
KEY `idx_is_matched` (`is_matched`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
COMMENT = '账单明细表';
这两张表为什么要拆成主表和明细表?
平台结算报告通常是“一份报告 + 很多行明细”。例如亚马逊一个结算周期可能包含几千个订单收入、退款、FBA 配送费、推荐费、仓储费、广告扣费和平台调整项。finance_platform_bill 只保存这份报告的汇总信息,类似“账单封面”;finance_bill_item 保存报告里每一行明细,类似“账单清单”。
导入流程通常是这样的:
- 财务专员从平台后台下载结算报告,或系统通过平台 API 自动拉取报告。
- 系统先创建一条
finance_platform_bill,状态为“待解析”或“解析中”,并保存原始文件地址。 - 异步解析任务逐行读取 CSV/Excel,把每一行收入或扣费写入
finance_bill_item。 - 解析完成后,系统按
item_type汇总销售额、退款、平台费、广告费等字段,回写到finance_platform_bill。 - 系统根据
order_no、platform_sku匹配 OMS 订单和 PIM 商品,匹配成功后更新is_matched和match_order_id。
所以这两张表的核心来源不是财务专员手工录入,而是外部平台账单导入或 API 拉取。财务专员主要负责上传文件、处理异常、确认对账结果;系统负责解析、匹配和汇总。
2.4 利润快照表
-- ============================================================
-- SKU 利润快照表
-- 每日/每月计算并存储各 SKU 在各店铺的利润数据
-- 快照设计:一旦写入不修改,历史数据永久保留
-- ============================================================
CREATE TABLE `finance_profit_snapshot`
(
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键ID',
`tenant_id` BIGINT NOT NULL COMMENT '租户ID',
`snapshot_type` TINYINT NOT NULL COMMENT '快照类型:1=日快照 2=月快照',
`snapshot_date` DATE NOT NULL COMMENT '快照日期(日快照=当天,月快照=月末最后一天)',
`sku_id` BIGINT NOT NULL COMMENT 'SKU ID',
`sku_code` VARCHAR(64) NOT NULL COMMENT 'SKU编码(冗余)',
`platform` VARCHAR(32) NOT NULL COMMENT '平台',
`store_id` VARCHAR(64) NOT NULL COMMENT '店铺ID',
`country_code` CHAR(2) NULL COMMENT '目的国',
`currency` CHAR(3) NOT NULL COMMENT '原始货币',
`exchange_rate` DECIMAL(10, 6) NOT NULL COMMENT '当日汇率',
-- 销售数据
`order_count` INT NOT NULL DEFAULT 0 COMMENT '出单量(笔数)',
`sales_qty` INT NOT NULL DEFAULT 0 COMMENT '销售数量(件数)',
`gross_revenue` DECIMAL(14, 4) NOT NULL DEFAULT 0 COMMENT '销售总收入(原币)',
`gross_revenue_cny` DECIMAL(14, 2) NOT NULL DEFAULT 0 COMMENT '销售总收入(人民币)',
-- 成本拆解(人民币,方便统一核算)
`purchase_cost` DECIMAL(14, 4) NOT NULL DEFAULT 0 COMMENT '采购成本 = 销售数量 × SKU加权均价',
`logistics_fee` DECIMAL(14, 4) NOT NULL DEFAULT 0 COMMENT '物流运费(国际段)',
`platform_fee` DECIMAL(14, 4) NOT NULL DEFAULT 0 COMMENT '平台手续费(推荐费+FBA费等)',
`fba_storage_fee` DECIMAL(14, 4) NOT NULL DEFAULT 0 COMMENT 'FBA 仓储费(按库存时间分摊)',
`advertising_fee` DECIMAL(14, 4) NOT NULL DEFAULT 0 COMMENT '广告费(该SKU的广告投入)',
`refund_loss` DECIMAL(14, 4) NOT NULL DEFAULT 0 COMMENT '退款损失(退款金额 - 回收商品价值)',
`vat_fee` DECIMAL(14, 4) NOT NULL DEFAULT 0 COMMENT 'VAT 税费(欧洲市场)',
`other_cost` DECIMAL(14, 4) NOT NULL DEFAULT 0 COMMENT '其他成本(包材/操作费等)',
-- 利润汇总
`total_cost` DECIMAL(14, 4) NOT NULL DEFAULT 0 COMMENT '总成本 = 以上所有成本之和',
`gross_profit` DECIMAL(14, 4) NOT NULL DEFAULT 0 COMMENT '毛利润 = 收入 - 采购成本 - 物流费 - 平台费',
`net_profit` DECIMAL(14, 4) NOT NULL DEFAULT 0 COMMENT '净利润 = 毛利润 - 广告费 - 退款损失 - VAT - 其他',
`gross_margin` DECIMAL(8, 4) NOT NULL DEFAULT 0 COMMENT '毛利润率 = 毛利润 / 收入',
`net_margin` DECIMAL(8, 4) NOT NULL DEFAULT 0 COMMENT '净利润率 = 净利润 / 收入',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_snapshot` (`tenant_id`, `snapshot_type`, `snapshot_date`, `sku_id`, `platform`, `store_id`),
KEY `idx_sku_id` (`sku_id`),
KEY `idx_snapshot_date` (`snapshot_date`),
KEY `idx_platform_date` (`platform`, `snapshot_date`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
COMMENT = 'SKU利润快照表(只增不改,历史永久保留)';
利润快照不是人工录入的数据,而是系统计算出来的数据。
这张表一般由定时任务生成:
- 日快照:每天凌晨统计前一天已完成或已发货的订单,按 SKU、平台、店铺汇总收入和成本。
- 月快照:月末或次月初统计整月数据,用于月度经营报表。
- 重算快照:当平台账单对账完成后,如果确认了更准确的平台费、广告费、FBA 费,可以触发重算,把利润从“预估”修正为“确认”。
为什么不每次打开报表都实时扫订单表计算?因为利润核算要关联 OMS、PMS、WMS、TMS、FMS 多个模块,SQL 很复杂,数据量大时实时计算会非常慢。快照表的作用就是把复杂计算提前算好,报表查询时直接读取结果。
利润快照采用“只增不改,历史永久保留”的设计时,更适合审计型场景;如果业务要求同一天同 SKU 允许重算,也可以采用 INSERT ... ON DUPLICATE KEY UPDATE 覆盖当天快照,并额外保存重算日志。实际系统中通常会区分“预估利润”和“最终确认利润”。
2.5 VAT 申报记录与资金流水表
-- VAT 申报记录表
CREATE TABLE `finance_vat_record`
(
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键ID',
`tenant_id` BIGINT NOT NULL COMMENT '租户ID',
`country_code` CHAR(2) NOT NULL COMMENT '申报国家代码,如 DE/FR/GB',
`vat_no` VARCHAR(32) NULL COMMENT 'VAT 登记号',
`period` VARCHAR(7) NOT NULL COMMENT '申报期,格式 YYYY-MM,如 2025-01',
`taxable_amount` DECIMAL(14, 2) NOT NULL DEFAULT 0 COMMENT '应税销售额(当地货币)',
`vat_rate` DECIMAL(6, 4) NOT NULL COMMENT '适用VAT税率',
`vat_amount` DECIMAL(14, 2) NOT NULL DEFAULT 0 COMMENT '应缴VAT金额 = 应税额 × 税率',
`local_currency` CHAR(3) NOT NULL COMMENT '当地货币',
`cny_amount` DECIMAL(14, 2) NOT NULL DEFAULT 0 COMMENT '折合人民币',
`status` TINYINT NOT NULL DEFAULT 0
COMMENT '状态:0=待申报 1=已申报 2=已缴纳 3=申报异常',
`file_url` VARCHAR(512) NULL COMMENT '申报文件URL(导出的报表)',
`declare_time` DATETIME NULL COMMENT '实际申报时间',
`pay_time` DATETIME NULL COMMENT '实际缴纳时间',
`remark` VARCHAR(512) NULL COMMENT '备注',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`create_by` BIGINT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_country_period` (`tenant_id`, `country_code`, `period`),
KEY `idx_status` (`status`),
KEY `idx_period` (`period`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
COMMENT = 'VAT申报记录表';
-- 资金流水记录表(现金流管理)
CREATE TABLE `finance_cash_flow`
(
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键ID',
`tenant_id` BIGINT NOT NULL COMMENT '租户ID',
`flow_date` DATE NOT NULL COMMENT '资金流水日期',
`flow_type` TINYINT NOT NULL COMMENT '类型:1=收入 2=支出',
`source_type` VARCHAR(32) NOT NULL
COMMENT '来源类型:PLATFORM_INCOME=平台收款 PURCHASE_PAYMENT=采购付款
LOGISTICS_FEE=物流费 VAT_PAYMENT=VAT缴纳 ADVERTISING=广告费',
`source_id` BIGINT NULL COMMENT '来源单据ID(账单ID/采购单ID等)',
`source_no` VARCHAR(64) NULL COMMENT '来源单据编号',
`amount_cny` DECIMAL(14, 2) NOT NULL COMMENT '金额(人民币)',
`amount_origin` DECIMAL(14, 2) NULL COMMENT '原币金额',
`currency` CHAR(3) NULL COMMENT '原币货币',
`exchange_rate` DECIMAL(10, 6) NULL COMMENT '当日汇率',
`remark` VARCHAR(256) NULL COMMENT '说明',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`create_by` BIGINT NULL,
PRIMARY KEY (`id`),
KEY `idx_tenant_date` (`tenant_id`, `flow_date`),
KEY `idx_source_type` (`source_type`),
KEY `idx_flow_type` (`flow_type`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
COMMENT = '资金流水记录表';
VAT 记录和资金流水的生成时机:
finance_vat_record 不是每发生一笔订单就插一条,而是按申报期生成。例如欧盟 OSS 常见是按季度申报,系统会在季度末按国家汇总销售额、退款额和适用税率,生成一条或多条 VAT 申报草稿。触发方式可以是财务专员点击“生成申报数据”,也可以是季度末定时任务自动生成待确认记录。财务专员确认后,状态从“待申报”变成“已申报”,缴税完成后再变成“已缴纳”。
finance_cash_flow 记录的是“钱真的动了”的事件,来源更复杂:
- 平台打款到账:财务确认平台款项到账后,写入收入流水。
- 供应商付款:采购应付账款审批并付款后,写入支出流水。
- 物流商扣款:物流账单确认付款后,写入支出流水。
- VAT 缴纳:税款支付完成后,写入支出流水。
- 银行流水导入:如果系统对接银行或导入银行流水,也可以先导入银行记录,再与平台账单、付款申请做匹配。
所以资金流水表不是单纯定时任务生成,也不是完全人工新增,而是由“业务单据状态变化 + 财务确认 + 外部银行流水”共同驱动。
第三节 多平台收款对账
3.1 亚马逊结算报告解析
亚马逊每两周生成一份 Settlement Report(结算报告),是一个 CSV 文件,包含该周期内所有的收入和扣费记录:
flowchart TD
A["亚马逊后台<br>下载 Settlement Report<br>(CSV格式,通常几MB-几十MB)"] --> B["财务专员上传文件到系统<br>POST /api/fms/bills/upload"]
B --> C["后端接收文件<br>存储到 OSS"]
C --> D["异步解析任务启动<br>逐行读取CSV"]
D --> E["识别行数据类型:<br>order=商品销售<br>refund=退款<br>other-transaction=其他费用"]
E --> F["提取关键字段:<br>transaction-type/amount/order-id/<br>sku/marketplace-name/quantity"]
F --> G["写入 finance_bill_item 表<br>按类型分类存储"]
G --> H["汇总计算账单总览:<br>销售合计/退款合计/各项费用合计<br>净到账金额"]
H --> I["更新 finance_platform_bill<br>status = 2(已解析)"]
I --> J["订单匹配:<br>根据 order-id 匹配内部订单<br>is_matched = 1"]
J --> K{"匹配率 < 95%?"}
K -- 是 --> L["发送提醒:<br>有部分订单无法匹配<br>需要人工核查"]
K -- 否 --> M["自动对账完成<br>status = 3(对账完成)"]
L --> N["财务专员核查差异单"]
M & N --> O(["对账完成"])
亚马逊 Settlement Report 字段映射:
| CSV 字段名 | 含义 | 映射到我们的字段 |
|---|---|---|
settlement-id | 结算ID | platform_bill_id |
settlement-start-date | 结算开始日 | settlement_start |
settlement-end-date | 结算结束日 | settlement_end |
transaction-type | 交易类型 | item_type |
order-id | 平台订单号 | order_no |
sku | 平台SKU | platform_sku |
quantity | 数量 | 计算用 |
amount | 金额(可为负) | amount |
amount-type | 金额细项类型 | description |
amount-description | 金额描述 | description |
这里的“导入账单再对账”到底是在对什么?
SaaS 系统自己的 OMS 里有订单数据,知道卖家卖出了什么商品、卖了多少钱、发货状态如何;但是平台最终打给卖家的钱,不一定等于订单销售额。平台会扣推荐费、支付手续费、广告费、仓储费、退款、赔付、调整项等。因此必须把平台结算报告导入系统,再和 OMS 订单、TMS 物流费、PMS 采购成本进行核对。
95% 不是说系统只能对账 95%,而是一个自动匹配率阈值。真实平台账单经常会出现一些无法自动匹配的明细:
- 平台账单里有广告费、仓储费、赔付、调整项,这些费用没有直接的订单号。
- 订单刚同步到系统时状态不完整,账单先到了,订单还没同步成功。
- 卖家在电商平台后台手动改过订单、退款或赔付,系统没有收到这次变更。
- 平台账单里的 SKU 编码和系统 SKU 编码不一致,需要做映射。
- 跨周期退款、跨周期调整会出现在本期账单,但订单发生在上一个周期。
所以系统通常会设置一个阈值:如果绝大多数订单类明细都能自动匹配,就允许进入“待确认”或“自动对账完成”;剩余少量异常交给财务专员处理。这个阈值不是财务准确率,而是自动化处理率。
如果平台不支持账单导出怎么办?
不能假设所有平台都有标准账单导出,平台接入可以按优先级设计三种方式:
- API 拉取:平台提供结算、交易、费用接口时,系统定时拉取并落库。 就算是支持导出 如果有API拉取的接口 我们也应该先使用API拉取 出错的概率会更小
- 文件导入:平台只支持下载 CSV/Excel 时,由财务专员上传文件。
- 人工录入或模板导入:小平台没有账单导出时,提供标准 Excel 模板,让财务按平台后台数据整理后导入。
如果一个平台既没有 API,也没有可下载账单,那么就无法做到完全自动对账,只能做到“系统内部账 + 人工确认外部到账”。SaaS 系统提供的是对账工具和数据承载能力,无法凭空获取平台没有开放的数据。
3.2 对账差异处理机制
flowchart TD
A["自动匹配完成后<br>仍有未匹配明细"] --> B{"差异原因分析"}
B -- "账单有,系统无此订单" --> C["可能原因:<br>1.订单未同步到系统<br>2.手动在平台操作但系统未记录<br>3.测试订单"]
B -- "系统有,账单无" --> D["可能原因:<br>1.该订单未进入本期结算<br>2.跨结算周期订单<br>3.被平台冻结待复查"]
B -- "金额不符" --> E["可能原因:<br>1.汇率差异<br>2.平台手续费率变化<br>3.退款调整"]
C --> F["财务专员手动处理:<br>补录缺失订单 / 标注忽略原因"]
D --> G["标注为待下期核对<br>记录跨期说明"]
E --> H["核实差额来源<br>调整系统记录"]
F & G & H --> I["人工确认后<br>账单状态→对账完成"]
差异场景一:账单有,系统无订单
平台账单里出现了某个 order_no,但是 OMS 订单表查不到。这通常说明外部平台发生了交易,但系统没有完整记录。常见原因包括:平台订单同步接口失败、订单被人工在平台后台处理、店铺授权过期导致订单漏同步、历史订单在接入系统前已经产生、测试订单或平台调整单没有对应业务订单。
处理方式不是简单删除账单明细,而是先判断它是否影响钱。如果是实际收入或扣费,就必须保留账单明细,并由财务或运营补录订单、修复平台订单映射,或者标记为“平台调整项”。这样后续利润核算才不会少算收入或费用。
差异场景二:系统有订单,账单无记录
系统里已经有订单,但本期平台结算报告没有它。这里的“未进入本期结算”指的是:订单虽然已经下单或发货,但平台还没有把它纳入本期打款。例如亚马逊通常按自己的结算周期结算,订单可能发生在 1 月 14 日,但平台在 1 月 15 日结算时仍处于未签收、未过售后期、风控审核、退款处理中或资金冻结状态,因此要到下一个结算周期才会出现在账单中。
“跨结算周期订单”就是订单发生时间、发货时间、签收时间、结算时间不在同一个周期。例如 1 月 30 日下单,2 月 2 日签收,2 月中旬才结算;系统按订单时间看到它属于 1 月,但平台账单可能在 2 月才出现。
“被平台冻结待复查”指平台因为风控、买家投诉、退款争议、账号审核、赔付调查等原因暂时不结算这笔钱。系统不能把它当成丢单,而要标记为“待下期核对”或“平台冻结”,后续继续跟踪。
差异场景三:账单金额和系统金额不一致
同一个订单在系统里查得到,平台账单也有记录,但金额不一样。常见原因包括:平台扣费规则变化、促销券由卖家承担、买家部分退款、平台补扣广告费、汇率取值不同、税费或运费承担方变化。处理时要区分“平台最终金额可信”和“系统成本口径可信”:平台账单决定实际收入和扣费,系统内部决定采购、物流、库存等成本。对账完成后,平台费用类字段应以平台账单为准,系统内部成本则以 PMS/WMS/TMS 为准。
差异处理的业务闭环:
- 系统自动生成差异清单,按差异类型分组。
- 财务专员查看原始账单明细、系统订单、平台订单链接和历史同步日志。
- 能自动修复的,触发订单补同步或 SKU 映射修复。
- 无法自动修复的,财务选择差异原因并填写说明。
- 差异处理完成后,账单状态变为“对账完成”,利润快照才允许进入最终确认。
3.3 多平台对账统计报表
-- 各平台各月净收入汇总对账报表
SELECT
pb.platform,
pb.store_name,
DATE_FORMAT(pb.settlement_start, '%Y-%m') AS period,
pb.currency,
SUM(pb.total_sales) AS total_sales,
SUM(pb.total_refund) AS total_refund,
SUM(pb.referral_fee) AS referral_fee,
SUM(pb.fba_fee) AS fba_fee,
SUM(pb.storage_fee) AS storage_fee,
SUM(pb.advertising_fee) AS advertising_fee,
SUM(pb.net_amount) AS net_amount,
SUM(pb.cny_amount) AS net_cny_amount,
COUNT(pb.id) AS bill_count,
SUM(CASE WHEN pb.status = 4 THEN 1 ELSE 0 END) AS diff_bill_count
FROM finance_platform_bill pb
WHERE
pb.tenant_id = #{tenantId}
AND pb.is_deleted = 0
AND pb.settlement_start BETWEEN #{startDate} AND #{endDate}
GROUP BY pb.platform, pb.store_name, pb.store_id,
DATE_FORMAT(pb.settlement_start, '%Y-%m'), pb.currency
ORDER BY pb.platform, period;
第四节 SKU 级利润核算模型
4.1 利润核算的全成本拆解
graph TD
A["SKU 销售收入(GMV)<br>买家实际支付金额"] --> B["- 平台推荐费<br>亚马逊 8-15%,TikTok 5-8%"]
B --> C["- FBA/配送费<br>亚马逊按重量/体积收费"]
C --> D["- 物流头程费<br>国内备货→FBA仓的物流费"]
D --> E["= 毛收入"]
E --> F["- 采购成本<br>商品采购均价 × 销量"]
F --> G["= 毛利润<br>Gross Profit"]
G --> H["- 广告费<br>站内广告ACOS投入"]
H --> I["- 仓储费<br>FBA月度仓储费分摊"]
I --> J["- 退款损失<br>退款金额 - 回收商品残值"]
J --> K["- VAT 税费<br>欧洲市场增值税"]
K --> L["- 其他杂费<br>包材/操作费/平台注册费分摊"]
L --> M["= 净利润<br>Net Profit"]
M --> N["净利润率 = 净利润 / GMV"]
style G fill:#e8f5e9
style M fill:#c8e6c9
全成本拆解里的专业词汇说明:
| 成本项 | 通俗解释 | 数据来源 |
|---|---|---|
| GMV | 商品销售总额,买家在平台上实际支付的商品金额。注意 GMV 不等于利润,也不等于最终到账金额 | OMS 订单、平台账单销售明细 |
| 平台推荐费 | 平台向卖家收的佣金。亚马逊叫 Referral Fee,通常按品类和销售额比例扣费 | finance_bill_item 中的 Referral 明细 |
| FBA/配送费 | 平台仓或平台物流帮卖家完成拣货、包装、配送产生的费用 | 平台结算账单中的 FBAFee、Fulfillment Fee |
| 物流头程费 | 商品从国内工厂或国内仓发到海外仓、FBA 仓、3PL 仓的运输费用 | TMS 物流费用、头程运费分摊 |
| 采购成本 | 卖家从供应商采购商品的成本,通常按 SKU 加权平均成本计算 | PMS 采购单、WMS 入库成本 |
| 广告费 | 卖家在平台站内投广告产生的费用。ACOS 是广告花费占广告销售额的比例,ACOS 越高,说明广告越贵 | 平台广告账单、finance_bill_item 广告明细 |
| 仓储费 | 商品放在 FBA、3PL 或自建仓里产生的仓储费用 | 平台账单、WMS 仓储计费、3PL 账单 |
| 退款损失 | 买家退款后造成的损失,不一定等于退款金额。如果货退回来还能二次销售,就要扣除残值 | OMS 退款单、WMS 退货质检结果 |
| VAT 税费 | 欧洲等市场销售产生的增值税义务 | VAT 税率配置、订单目的国、销售额 |
| 其他杂费 | 包材、贴标、平台月租、操作费等无法直接归到某一类的成本 | 财务手工分摊或费用导入 |
利润核算的核心思想是:不能只看销售额,要把每个 SKU 真正承担的费用尽量拆出来。很多跨境卖家会遇到“卖得越多亏得越多”的情况,本质就是广告、物流、退款、平台费没有被精确分摊到 SKU 上。
4.2 利润核算的计算时机
flowchart TD
A(["触发利润核算的事件"]) --> B["触发1:订单完成<br>(已签收且过了售后期)<br>计算单笔订单利润"]
A --> C["触发2:账单对账完成<br>平台手续费确认后<br>重新计算该周期利润"]
A --> D["触发3:定时任务<br>每日零点计算前一天<br>月末计算整月汇总"]
B & C & D --> E["利润核算引擎执行"]
E --> F["Step 1:获取销售收入<br>从 order_item 取 amount(原币)<br>× 当日汇率 → CNY"]
F --> G["Step 2:获取各项成本<br>平台费:来自 finance_bill_item<br>物流费:来自 logistics_fee_record<br>采购成本:来自 inventory.avg_cost × qty<br>广告费:来自账单广告明细<br>仓储费:来自账单仓储费按天分摊<br>退款损失:来自 order_refund 已完成记录"]
G --> H["Step 3:计算汇总<br>毛利润 = 收入 - 平台费 - 物流费 - 采购成本<br>净利润 = 毛利润 - 广告 - 仓储 - 退款 - VAT - 其他"]
H --> I["Step 4:写入快照<br>INSERT finance_profit_snapshot<br>(已存在则更新)"]
为什么利润要分多个时机计算?
一笔订单刚卖出去时,只能得到销售收入、SKU、数量、平台和店铺等信息,很多成本还不确定。例如平台最终扣多少手续费、是否退款、广告费如何分摊、物流账单是否确认,都可能要等几天甚至一个结算周期后才知道。因此利润核算通常分成两层:
- 预估利润:订单完成后先用规则和历史费率估算,用于运营快速判断是否赚钱。
- 确认利润:平台账单对账完成、物流费用确认、退款结束后再重算,用于财务报表。
图中的三个触发点分别解决不同问题:
- 订单完成触发:买家签收并过售后期后,说明这笔订单大概率不会退款,可以先计算单笔订单利润。
- 账单对账完成触发:平台实际扣费已经确认,此时要把预估的平台费、广告费、FBA 费替换成账单里的真实费用。
- 定时任务触发:每天或每月做一次汇总,生成
finance_profit_snapshot,让 BI 报表不用实时扫描所有订单和费用明细。
计算时要注意数据口径:销售收入以平台账单或 OMS 实收为准,采购成本以 WMS 加权成本为准,物流费以 TMS 费用记录或分摊结果为准,平台费以对账后的平台账单为准。不同来源的数据先统一折算成人民币,再写入利润快照。
4.3 利润核算 SQL 实现
-- 查询指定 SKU 在指定时间段的利润汇总
SELECT
ps.sku_code,
ps.platform,
ps.store_id,
SUM(ps.order_count) AS total_orders,
SUM(ps.sales_qty) AS total_qty,
ROUND(SUM(ps.gross_revenue_cny), 2) AS total_revenue_cny,
ROUND(SUM(ps.purchase_cost), 2) AS total_purchase_cost,
ROUND(SUM(ps.logistics_fee), 2) AS total_logistics_fee,
ROUND(SUM(ps.platform_fee), 2) AS total_platform_fee,
ROUND(SUM(ps.advertising_fee), 2) AS total_advertising_fee,
ROUND(SUM(ps.refund_loss), 2) AS total_refund_loss,
ROUND(SUM(ps.vat_fee), 2) AS total_vat_fee,
ROUND(SUM(ps.gross_profit), 2) AS total_gross_profit,
ROUND(SUM(ps.net_profit), 2) AS total_net_profit,
ROUND(SUM(ps.net_profit) * 100.0
/ NULLIF(SUM(ps.gross_revenue_cny), 0), 2) AS net_margin_pct
FROM finance_profit_snapshot ps
WHERE
ps.tenant_id = #{tenantId}
AND ps.snapshot_type = 1 -- 日快照
AND ps.snapshot_date BETWEEN #{startDate} AND #{endDate}
AND (#{skuId} IS NULL OR ps.sku_id = #{skuId})
AND (#{platform} IS NULL OR ps.platform = #{platform})
GROUP BY ps.sku_code, ps.platform, ps.store_id
ORDER BY total_net_profit DESC;
4.4 利润异常预警
flowchart TD
A(["月度利润计算完成"]) --> B["遍历所有 SKU 利润快照"]
B --> C{"净利润率 < 0?<br>(亏损)"}
C -- 是 --> D["标记为亏损SKU<br>发送告警:某SKU净利润为负X元<br>主要亏损原因:广告费占比过高/退款率高"]
C -- 否 --> E{"净利润率 < 10%?<br>(微利)"}
E -- 是 --> F["标记为低利润SKU<br>建议:降低广告投入/重新谈判采购价/提高售价"]
E -- 否 --> G{"净利润率 > 40%?<br>(高利润)"}
G -- 是 --> H["标记为高利润SKU<br>建议:加大备货/增加广告投入/开拓新市场"]
G -- 否 --> I["正常利润区间<br>继续监控"]
D & F & H & I --> J["生成月度利润分析报告"]
第五节 多货币处理与汇兑损益
5.1 汇率同步机制
flowchart TD
A(["定时任务:每天 01:00 执行"]) --> B["调用汇率 API<br>OpenExchangeRates / 中国人民银行接口"]
B --> C["获取今日最新汇率<br>以 CNY 为基准<br>各主要货币兑换率"]
C --> D{"API 调用是否成功?"}
D -- 成功 --> E["写入 finance_exchange_rate 表<br>rate_date = 今天"]
D -- "失败(API故障/网络超时)" --> F["告警通知财务专员<br>使用昨日汇率作为备用<br>写入记录并标注 is_official=0(估算)"]
E & F --> G["检查汇率波动:<br>与昨日相比波动 > 2%?"]
G -- 是 --> H["发送汇率大幅波动告警<br>如:USD汇率较昨日上涨2.3%<br>影响今日利润核算"]
G -- 否 --> I["正常完成"]
汇率波动告警发给谁?收到后要做什么?
告警一般发给租户侧的财务专员、财务负责人,也可以抄送运营负责人。它的目的不是让财务专员去“修改汇率”,而是提醒本日利润、报价、采购付款和换汇决策可能受到影响。
收到告警后的典型处理动作:
- 财务确认汇率来源是否正常,排除 API 数据异常。
- 如果是 API 失败导致使用昨日汇率,要手动刷新或录入官方汇率。
- 如果汇率真实大幅波动,财务需要关注待结算外币余额是否会产生较大汇兑损益。
- 运营可以根据汇率变化调整定价、促销和广告预算,避免利润被汇率吃掉。
- 管理层可以决定是否提前换汇、延迟换汇,或调整采购付款节奏。
所以汇率同步不只是一个技术定时任务,它会影响利润核算、现金流预测和经营决策。
5.2 货币折算规则
所有财务数据以双字段方式存储:原始货币金额 + 折合人民币金额
graph LR
A["亚马逊账单 $1,000 USD"] --> B["使用结算当日汇率<br>2025-01-15 USD汇率 7.23"]
B --> C["折算结果:¥7,230 CNY<br>存储:amount=1000, amount_cny=7230<br>exchange_rate=7.23"]
D["日后汇率变化"] --> E["历史记录不变<br>仍然是 ¥7,230<br>(使用当时汇率快照)"]
D --> F["汇兑损益单独计算:<br>如今日汇率变为 7.30<br>1000 USD 现值 ¥7,300<br>汇兑收益 = ¥70"]
5.3 汇兑损益计算
什么是汇兑损益?
1月1日:亚马逊有 $10,000 待打款,当日汇率 7.20,账面价值 ¥72,000 1月15日:亚马逊实际打款,当日汇率 7.15,实际到账 ¥71,500 汇兑损失 = ¥72,000 - ¥71,500 = ¥500
为什么不在 1 月 1 日立刻兑换成人民币?因为 1 月 1 日只是平台账面上形成了“待结算金额”,钱还在平台账户或结算周期里,卖家并不一定已经收到外币现金。很多平台会按自己的结算周期打款,卖家只有在平台实际打款到账后,才能决定是否换汇。
汇兑损益本质上是会计记录:同一笔外币收入,在确认收入时按一个汇率入账,在实际到账或换汇时按另一个汇率折算,两个人民币金额之间的差额就是汇兑收益或损失。出现汇兑损失后,一般不是“追回”或“修正订单”,而是记录到财务损益里,作为经营成本的一部分。企业可以通过提前换汇、外币账户管理、价格调整等方式降低风险,但已经发生的汇兑损益需要如实记录。
-- 计算某时间段内的汇兑损益
SELECT
cf.currency,
SUM(CASE WHEN cf.flow_type = 1 THEN cf.amount_origin ELSE 0 END) AS total_received_foreign,
SUM(CASE WHEN cf.flow_type = 1 THEN cf.amount_cny ELSE 0 END) AS total_received_cny,
-- 以当前最新汇率重新计算理论价值
SUM(CASE WHEN cf.flow_type = 1 THEN cf.amount_origin ELSE 0 END)
* (SELECT er.rate_to_cny FROM finance_exchange_rate er
WHERE er.currency = cf.currency ORDER BY er.rate_date DESC LIMIT 1)
AS current_value_cny,
-- 汇兑损益 = 当前理论价值 - 实际收到的CNY
SUM(CASE WHEN cf.flow_type = 1 THEN cf.amount_origin ELSE 0 END)
* (SELECT er.rate_to_cny FROM finance_exchange_rate er
WHERE er.currency = cf.currency ORDER BY er.rate_date DESC LIMIT 1)
- SUM(CASE WHEN cf.flow_type = 1 THEN cf.amount_cny ELSE 0 END) AS exchange_gain_loss
FROM finance_cash_flow cf
WHERE
cf.tenant_id = #{tenantId}
AND cf.currency != 'CNY'
AND cf.flow_date BETWEEN #{startDate} AND #{endDate}
GROUP BY cf.currency;
第六节 应收应付账款管理
6.1 应收账款(平台待打款)
flowchart TD
A["平台结算周期结束"] --> B["账单解析完成<br>net_amount = 净应收款项"]
B --> C["创建应收账款记录<br>(来自 finance_platform_bill)"]
C --> D["预计打款日期:<br>亚马逊:结算结束后7天内<br>Shopee:结算后3-5个工作日"]
D --> E{"实际打款日期到达?"}
E -- "未到期" --> F["正常等待"]
E -- "已到期但未到账" --> G["🔴 发送逾期告警<br>通知财务专员跟进"]
E -- "资金到账" --> H["财务专员确认到账<br>填写实际到账金额"]
H --> I{"实际到账 vs 账单净额"}
I -- "一致" --> J["应收账款关闭<br>生成资金流水记录"]
I -- "有差异" --> K["差异原因分析<br>补充差异说明"]
K --> J
G --> L["联系平台客服查询"]
什么是平台结算周期?
平台结算周期就是电商平台按固定规则汇总一段时间内的销售、退款、平台扣费,然后给卖家生成结算账单并安排打款的周期。不同平台规则不完全一样:有的平台按周,有的平台按双周,有的平台会根据店铺等级、账户风险、订单签收状态和售后期设置不同的打款节奏。
在系统里,应收账款不是订单创建时就确认,而是在平台账单解析完成后确认。finance_platform_bill.net_amount 就是“账单净额”,含义是平台计算后预计要打给卖家的金额,通常等于销售收入减退款、佣金、配送费、广告费、仓储费、调整项等。
实际到账可能与账单净额不同,常见原因包括:
- 打款当天汇率和账单折算汇率不同。
- 银行或收款服务商扣了转账手续费。
- 平台临时冻结部分款项,用于处理退款、争议或风控。
- 上期差异在本期进行了补扣或补发。
- 财务确认到账时选择的币种和账单币种不同。
完整流程是:平台结算周期结束 → 平台生成结算报告 → 财务导入或系统拉取账单 → 系统解析出净应收金额 → 生成应收记录和预计到账日 → 财务等待平台打款 → 实际到账后确认金额 → 如果有差异,记录原因 → 写入 finance_cash_flow。
6.2 应付账款复用设计(与 PMS 联动)
应付账款表(
finance_payable)已在第三天 PMS 模块完整设计,这里重点关注 FMS 侧的财务管理能力。
graph TD
A["应付账款来源"] --> B["采购完成后<br>PMS自动生成应付记录<br>(第三天已设计)"]
A --> C["账单差异补缴<br>物流商账单差额<br>需补付的运费"]
A --> D["VAT 税款缴纳<br>欧洲VAT季度申报<br>需向税务机关缴款"]
B & C & D --> E["统一在 FMS 管理:<br>查看未付/即将到期/已逾期<br>发起付款申请→审批→付款确认"]
三类应付场景的完整业务说明:
第一类是采购应付。PMS 中采购单完成收货、质检和对账后,会生成供应商应付账款。例如供应商发了 1000 件货,仓库验收合格 980 件,双方确认按 980 件结算,系统生成一笔应付。财务在 FMS 里看到这笔应付后,按付款条件发起付款申请,负责人审批后付款,付款完成后写入资金流水。
第二类是物流应付。TMS 中可能先用预估运费创建运单,月底物流商提供正式账单后,发现实际费用和预估费用存在差异。如果实际费用更高,就需要生成补付应付;如果实际费用更低,则可能形成抵扣或退款。FMS 负责统一管理这些物流账单差异,避免费用散落在 TMS 里无人跟进。
第三类是税费应付。VAT 申报数据确认后,系统知道某个国家或 OSS 申报期应缴多少税款,这笔税款也需要进入应付管理。它的收款方不是供应商,而是税务机关或税务代理机构。付款完成后,finance_vat_record 状态更新为“已缴纳”,同时写入 finance_cash_flow。
这样设计的好处是:无论钱要付给供应商、物流商还是税务机关,财务都在同一个 FMS 流程里处理,审批、付款、流水、报表口径统一。
6.3 现金流预测
现金流预测不是算利润,而是预测未来一段时间“账户里可能进多少钱、出多少钱、会不会缺钱”。利润高不代表现金流安全,因为平台可能还没打款,供应商付款却马上到期;反过来,某个月利润一般,但如果平台集中回款,现金流也可能很充足。
本系统的现金流预测主要使用两类数据:
- 预计收入:已解析或已对账的平台账单,根据平台结算规则推算预计打款日。
- 预计支出:未结清的供应商应付、物流应付、VAT 税款、广告充值计划等,根据到期日推算付款日。
下面这段 SQL 的含义是:先查询未来 30 天预计会收到的平台款,再查询未来 30 天预计要支付的供应商款,然后按日期合并到一张时间轴上,计算每天的净现金流。如果某一天净现金流为负,并且账户余额不足,就要提前预警,提醒财务准备资金。
-- 未来30天现金流预测(收入 - 支出)
WITH future_income AS (
-- 预计收入:未打款的平台账单(按预计打款日预测)
SELECT
DATE_ADD(settlement_end, INTERVAL 7 DAY) AS flow_date,
SUM(cny_amount) AS amount_cny,
'平台收款' AS flow_type
FROM finance_platform_bill
WHERE tenant_id = #{tenantId}
AND status IN (2, 3) -- 已解析但未到账
AND settlement_end >= CURDATE()
AND settlement_end <= DATE_ADD(CURDATE(), INTERVAL 30 DAY)
GROUP BY DATE_ADD(settlement_end, INTERVAL 7 DAY)
),
future_payment AS (
-- 预计支出:未结清的应付账款(按到期日预测)
SELECT
due_date AS flow_date,
SUM(remaining_amount) AS amount_cny,
'供应商付款' AS flow_type
FROM finance_payable
WHERE tenant_id = #{tenantId}
AND status IN (0, 1) -- 未结清
AND due_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY)
GROUP BY due_date
)
SELECT
flow_date,
SUM(CASE WHEN flow_type = '平台收款' THEN amount_cny ELSE 0 END) AS income,
SUM(CASE WHEN flow_type = '供应商付款' THEN amount_cny ELSE 0 END) AS payment,
SUM(CASE WHEN flow_type = '平台收款' THEN amount_cny ELSE 0 END)
- SUM(CASE WHEN flow_type = '供应商付款' THEN amount_cny ELSE 0 END) AS net_cash_flow
FROM (
SELECT * FROM future_income
UNION ALL
SELECT * FROM future_payment
) t
GROUP BY flow_date
ORDER BY flow_date;
现金流预测还可以继续扩展:把广告充值计划、物流商账单、VAT 到期缴纳、银行贷款还款都纳入预测。预测结果一般展示为未来 7 天、30 天、90 天现金流曲线,并标记“资金缺口日期”。
第七节 VAT 合规申报
VAT 可以先理解为欧洲很多国家对商品销售征收的增值税。跨境卖家把商品卖给欧洲消费者时,通常需要按照目的国税率申报销售额和应缴税额。这里的系统不是“替官方决定交多少税”,而是帮助卖家把订单、退款、目的国、税率整理成申报所需的数据,供财务、税务顾问或官方申报平台使用。
真实申报中,税务机关不会每天给卖家推送一张“你该交多少”的账单。更多情况下是卖家或税务代理根据销售数据自行申报,官方后续可以通过平台报送数据、海关记录、支付记录、抽查审计等方式核对。如果少报、漏报,可能产生补税、罚款、滞纳金,严重时影响店铺合规经营。
7.1 VAT 数据收集流程
flowchart TD
A(["季度末执行VAT申报计算"]) --> B["查询本季度各欧洲国家的销售数据"]
B --> C["从 finance_bill_item 提取:<br>平台=AMAZON/SHOPEE等<br>目的国=DE/FR/GB等<br>transaction-type=Principal(销售)/<br>Refund(退款)"]
C --> D["按国家分组汇总:<br>应税销售额 = 销售总额 - 退款额"]
D --> E["查询当国VAT税率:<br>SELECT standard_rate FROM tax_vat_rate<br>WHERE country_code=? AND effective_date<=季度末<br>ORDER BY effective_date DESC LIMIT 1"]
E --> F["计算应缴VAT:<br>vat_amount = taxable_amount × vat_rate"]
F --> G["写入 finance_vat_record<br>status = 0(待申报)"]
G --> H["生成 VAT 申报汇总报告(Excel导出):<br>国家 / 应税额 / 税率 / 应缴VAT / 申报期"]
H --> I["财务专员提交给税务顾问<br>或直接通过 OSS 平台申报"]
这张流程图的目的是什么?
它不是在说“系统查出来多少就一定交多少”,而是在说 SaaS 系统要把申报所需的基础数据准备好。财务专员或税务顾问会基于这些数据进行复核,再通过对应国家税务网站、OSS 申报平台或税务代理系统提交。
VAT 数据收集通常要关注以下字段:
- 订单销售金额:买家实际支付的商品金额。
- 退款金额:本期已经发生并确认的退款。
- 目的国:商品最终卖给哪个国家的消费者。
- 税率:该目的国在申报期内适用的 VAT 税率。
- 币种:申报时可能需要当地货币或欧元口径。
- 平台代扣代缴标识:有些平台、国家、交易模式下,平台可能已经代扣代缴,系统需要避免重复申报。
所以 VAT 模块的核心价值是降低人工整理申报数据的成本和错误率,不是替代税务机关,也不是替代专业税务顾问。
7.2 OSS 申报数据格式
欧盟 OSS(One Stop Shop)要求按固定格式提交申报数据:
OSS 申报数据(2025年Q1)
报告期:2025-01-01 至 2025-03-31
申报人:[公司名称] VAT号:[OSS注册号]
按目的国汇总:
┌─────────┬──────────┬───────────┬───────────┬───────────┐
│ 目的国 │ 国家代码 │ 应税销售额 │ VAT税率 │ 应缴VAT │
├─────────┼──────────┼───────────┼───────────┼───────────┤
│ 德国 │ DE │ €8,423.50 │ 19% │ €1,600.47 │
│ 法国 │ FR │ €5,218.30 │ 20% │ €1,043.66 │
│ 意大利 │ IT │ €3,102.80 │ 22% │ €682.62 │
│ 西班牙 │ ES │ €2,845.90 │ 21% │ €597.64 │
│ 合计 │ │€19,590.50 │ │ €3,924.39 │
└─────────┴──────────┴───────────┴───────────┴───────────┘
OSS 是怎么简化申报的?
如果卖家在多个欧盟国家向消费者销售商品,传统方式可能需要分别在多个国家注册、申报和缴税。OSS 的思路是:卖家可以在一个欧盟成员国注册 OSS,然后在一个入口申报对多个欧盟目的国消费者的销售数据,系统按目的国拆分税额,后续由欧盟内部再做分配。
这并不意味着“只按一个国家税率交税”。销售到德国消费者通常按德国税率,销售到法国消费者通常按法国税率。OSS 简化的是申报入口和流程,不是取消按目的国计算税额。
官方如何判断申报是否准确?
官方并不是完全没有参考依据。平台、支付机构、海关、物流、税务审计都可能形成外部记录;大型平台在很多地区也有交易数据报送义务。卖家少报短期内不一定马上被发现,但后续审计时可能通过平台销售记录、银行收款、库存流转、物流记录进行交叉核对。因此系统生成 VAT 申报数据时要保留明细来源,方便追溯每个国家、每个申报期的销售额是从哪些订单汇总出来的。
系统边界:
本系统实现的是 VAT 申报数据的计算、留痕和导出,不直接对接各国税务局完成在线申报。企业通常会由财务专员或税务顾问登录官方申报系统提交数据并完成缴款。系统在缴款完成后更新 finance_vat_record 状态,并写入资金流水。
第八节 数据分析业务全解析(BI)
8.1 为什么 BI 是供应链的”大脑”
BI 是 Business Intelligence,中文通常叫“商业智能”或“数据分析系统”。它不是一个单独的业务流程,而是把订单、库存、采购、物流、财务等模块的数据汇总起来,转化成管理层和运营人员能看懂的指标、图表、预警和建议。
如果没有 BI,卖家只能凭感觉判断:哪个商品该补货、哪个供应商有问题、哪个平台赚钱、哪个 SKU 看着卖得多其实亏钱。有了 BI,系统可以基于真实数据告诉用户:哪些商品缺货风险高、哪些 SKU 利润率低、哪些物流渠道异常率高、未来 30 天现金是否紧张。
graph TD
A["没有BI系统时的运营状态"] --> B["完全靠经验和感觉决策"]
A --> C["补货多少?问老采购感觉补100个吧"]
A --> D["哪个产品该放弃?感觉这个卖不动"]
A --> E["广告投多少?先投1000元试试"]
A --> F["结果:频繁断货/积压/浪费/亏损"]
G["有BI系统后的运营状态"] --> H["数据驱动决策"]
G --> I["系统:基于30天销量和安全库存<br>建议补货220件"]
G --> J["系统:SKU-XYZ近90天利润率-5%<br>已亏损¥2,300 建议停售"]
G --> K["系统:广告ACOS=35%超过阈值30%<br>建议降低出价"]
G --> L["结果:精准补货/及时止损/高效投放"]
可以把 BI 理解成“把各模块的业务数据变成经营决策”。它不负责创建订单、不负责采购、不负责发货,但它会告诉业务人员这些流程做得好不好,以及下一步应该重点处理什么。
BI: 数据的分析 —> 给出分析结果 —> 建议 —> 工作人员来决策
8.2 BI 模块的数据架构
graph TD
subgraph 数据来源层
A1["OMS 订单数据"]
A2["WMS 库存数据"]
A3["PMS 采购数据"]
A4["TMS 物流数据"]
A5["FMS 财务数据"]
A6["SRM 供应商数据"]
end
subgraph 数据处理层
B["ETL 数据聚合<br>每日定时计算<br>指标汇总存储"]
end
subgraph 分析展示层
C1["KPI 大盘看板<br>核心指标实时展示"]
C2["利润分析报表<br>SKU/店铺/时间多维度"]
C3["库存健康分析<br>预警/积压/周转率"]
C4["智能补货建议<br>销量预测 + 规则引擎"]
C5["AI自然语言查询<br>V2规划:用中文问数据"]
end
A1 & A2 & A3 & A4 & A5 & A6 --> B
B --> C1 & C2 & C3 & C4 & C5
ETL 是什么?
ETL 是 Extract、Transform、Load 的缩写:
- Extract 抽取:从 OMS、WMS、PMS、TMS、FMS、SRM 等业务表中读取数据。
- Transform 转换:清洗字段、统一币种、统一时间口径、计算指标、处理异常值。
- Load 加载:把计算好的结果写入 BI 汇总表、报表缓存或数据看板接口。
为什么不直接让 BI 每次都查业务表?因为业务表是为“交易流程”设计的,字段分散、关联复杂、数据量大。BI 更关注“统计分析”,适合提前把结果聚合成日报、月报、SKU 维度、店铺维度、平台维度等汇总数据。
三层架构的业务含义:
- 数据来源层负责提供事实数据,例如订单销量、库存数量、采购到货、物流签收、账单费用、供应商评分。
- 数据处理层负责把这些事实数据加工成指标,例如 GMV、净利润率、缺货率、库存周转率、准时到货率。
- 分析展示层负责把指标展示给不同角色:老板看经营总览,运营看 SKU 利润和广告效果,采购看补货建议,仓库看库存健康,财务看现金流和账单差异。
当前版本重点实现 KPI、利润报表、库存健康、智能补货和 Dashboard。AI 自然语言查询作为 V2 规划保留设计思路,当前版本不落地实现。
第九节 供应链核心 KPI 体系
KPI 是 Key Performance Indicator,中文叫“关键绩效指标”。它不是普通报表字段,而是用来判断业务是否健康的核心指标。例如缺货率高,说明库存管理有问题;物流异常率高,说明渠道或承运商有问题;净利润率低,说明看起来卖得多但可能不赚钱。
做 KPI 的目的有三个:
- 看现状:让管理层快速知道销售、库存、采购、物流、财务是否健康。
- 找问题:通过指标定位是缺货、发货慢、退款高、广告贵还是采购成本高。
- 驱动动作:指标超过阈值后触发预警,提醒对应负责人处理,而不是等问题扩大。
所以 KPI 不是为了“写几条 SQL”,而是为了把复杂业务变成可监控、可比较、可追责、可优化的数字。
9.1 KPI 全景图
graph TD
A["供应链核心KPI体系"] --> B["销售效率"]
A --> C["库存管理"]
A --> D["采购效率"]
A --> E["物流效率"]
A --> F["财务健康"]
B --> B1["GMV 商品交易总额"]
B --> B2["订单量 / 日均单量"]
B --> B3["客单价"]
B --> B4["平台转化率(点击→购买)"]
C --> C1["库存周转率"]
C --> C2["库存天数(Days of Supply)"]
C --> C3["缺货率"]
C --> C4["积压率"]
D --> D1["采购准时到货率 OTD"]
D --> D2["质量合格率"]
D --> D3["采购响应速度"]
E --> E1["物流正常签收率"]
E --> E2["平均配送时效"]
E --> E3["物流异常率"]
F --> F1["毛利润率"]
F --> F2["净利润率"]
F --> F3["ACOS 广告成本占比"]
F --> F4["退款率"]
这张图把供应链 KPI 分成五类:销售效率看卖得怎么样,库存管理看货是否健康,采购效率看供应商是否稳定,物流效率看履约是否顺畅,财务健康看最终是否赚钱。不同角色关注的 KPI 不同:老板看 GMV、净利润、现金流;运营看转化率、广告 ACOS、退款率;采购看准时到货率和质量合格率;仓库看库存周转和缺货率;财务看利润率和账单差异。
9.2 核心 KPI 计算 SQL
下面的 SQL 演示 KPI 如何从业务表计算出来。线上系统通常不会让每个看板请求都实时执行这些复杂 SQL,而是由定时任务提前计算到 BI 汇总表,前端看板直接查询汇总结果,必要时再下钻到明细。
① 库存周转率(最重要的库存 KPI)
-- 库存周转率 = 本期销售成本 / 平均库存价值
-- 库存周转天数 = 统计天数 / 库存周转率
-- 解读:周转率越高,说明库存流转越快,资金利用率越高
SELECT
p.sku_code,
p.spu_name,
SUM(ps.sales_qty) AS period_qty,
SUM(ps.purchase_cost) AS period_cogs, -- 本期销售成本
i.quantity * i.avg_cost AS current_inv_value, -- 当前库存价值
-- 简化版库存周转率(用当前库存代替平均库存)
ROUND(SUM(ps.purchase_cost)
/ NULLIF(i.quantity * i.avg_cost, 0), 2) AS turnover_rate,
-- 库存天数 = 统计天数 / 周转率
ROUND(#{dayCount}
/ NULLIF(SUM(ps.purchase_cost)
/ NULLIF(i.quantity * i.avg_cost, 0), 0)
, 1) AS days_of_supply,
CASE
WHEN ROUND(SUM(ps.purchase_cost)
/ NULLIF(i.quantity * i.avg_cost, 0), 2) >= 12
THEN '✅ 优秀(年化12次以上)'
WHEN ROUND(SUM(ps.purchase_cost)
/ NULLIF(i.quantity * i.avg_cost, 0), 2) >= 6
THEN '🟡 正常(年化6-12次)'
ELSE '🔴 偏低(积压风险)'
END AS turnover_status
FROM finance_profit_snapshot ps
JOIN product_sku p ON ps.sku_id = p.id
JOIN inventory i ON i.sku_id = ps.sku_id
AND i.location_id IS NULL -- 仓库汇总级
WHERE
ps.tenant_id = #{tenantId}
AND ps.snapshot_type = 1
AND ps.snapshot_date BETWEEN #{startDate} AND #{endDate}
AND i.tenant_id = #{tenantId}
GROUP BY p.sku_id, p.sku_code, p.spu_name, i.quantity, i.avg_cost
ORDER BY turnover_rate ASC;
② 订单履约准时率(OTD)
-- OTD = 按时发货的订单数 / 总应发货订单数
SELECT
DATE_FORMAT(o.platform_order_time, '%Y-%m') AS month,
COUNT(o.id) AS total_orders,
SUM(CASE
WHEN o.ship_time IS NOT NULL
AND DATE(o.ship_time) <= o.delivery_deadline
THEN 1 ELSE 0 END) AS on_time_orders,
ROUND(SUM(CASE
WHEN o.ship_time IS NOT NULL
AND DATE(o.ship_time) <= o.delivery_deadline
THEN 1 ELSE 0 END) * 100.0 / COUNT(o.id), 2) AS otd_rate,
SUM(CASE
WHEN o.delivery_deadline IS NOT NULL
AND o.ship_time IS NULL
AND o.delivery_deadline < CURDATE()
THEN 1 ELSE 0 END) AS overdue_not_shipped
FROM order_main o
WHERE
o.tenant_id = #{tenantId}
AND o.is_deleted = 0
AND o.status NOT IN (0, 1, 10) -- 排除待处理/风控中/已取消
AND o.platform_order_time >= #{startDate}
GROUP BY DATE_FORMAT(o.platform_order_time, '%Y-%m')
ORDER BY month;
③ 缺货率统计
-- 缺货率 = 因库存不足导致挂起的订单数 / 总订单数
SELECT
DATE_FORMAT(o.create_time, '%Y-%m') AS month,
COUNT(o.id) AS total_orders,
SUM(CASE WHEN o.is_abnormal = 1
AND o.abnormal_reason LIKE '%库存不足%'
THEN 1 ELSE 0 END) AS stockout_orders,
ROUND(SUM(CASE WHEN o.is_abnormal = 1
AND o.abnormal_reason LIKE '%库存不足%'
THEN 1 ELSE 0 END) * 100.0
/ NULLIF(COUNT(o.id), 0), 2) AS stockout_rate
FROM order_main o
WHERE o.tenant_id = #{tenantId} AND o.is_deleted = 0
GROUP BY DATE_FORMAT(o.create_time, '%Y-%m')
ORDER BY month;
9.3 KPI 预警阈值配置
-- KPI 预警配置表(每个租户可自定义预警阈值)
CREATE TABLE `bi_kpi_threshold`
(
`id` BIGINT UNSIGNED NOT NULL,
`tenant_id` BIGINT NOT NULL,
`kpi_code` VARCHAR(64) NOT NULL COMMENT 'KPI编码:STOCKOUT_RATE/OTD/TURNOVER等',
`kpi_name` VARCHAR(128) NOT NULL COMMENT 'KPI名称',
`warning_value` DECIMAL(12, 4) NOT NULL COMMENT '预警阈值(达到此值触发黄色预警)',
`danger_value` DECIMAL(12, 4) NOT NULL COMMENT '危险阈值(达到此值触发红色预警)',
`compare_type` TINYINT NOT NULL COMMENT '比较类型:1=小于触发 2=大于触发',
`notify_roles` JSON NULL COMMENT '通知角色列表,如["ROLE_PURCHASE","ROLE_TENANT_ADMIN"]',
`is_enabled` TINYINT(1) NOT NULL DEFAULT 1,
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_kpi` (`tenant_id`, `kpi_code`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
COMMENT = 'KPI预警阈值配置表';
-- 插入默认预警阈值
INSERT INTO `bi_kpi_threshold`
(`id`, `tenant_id`, `kpi_code`, `kpi_name`, `warning_value`, `danger_value`, `compare_type`, `is_enabled`)
VALUES
(1, 0, 'STOCKOUT_RATE', '缺货率', 2.00, 5.00, 2, 1), -- 大于2%预警,大于5%危险
(2, 0, 'RETURN_RATE', '退款率', 5.00, 10.00, 2, 1), -- 大于5%预警
(3, 0, 'OTD_RATE', '准时发货率', 95.00, 90.00, 1, 1), -- 小于95%预警
(4, 0, 'SIGN_RATE', '物流签收率', 95.00, 90.00, 1, 1),
(5, 0, 'EXCEPTION_RATE', '物流异常率', 3.00, 8.00, 2, 1),
(6, 0, 'NET_MARGIN', '净利润率', 10.00, 0.00, 1, 1), -- 小于10%预警,小于0%危险(亏损)
(7, 0, 'TURNOVER_RATE', '库存周转率(年化)', 6.00, 3.00, 1, 1), -- 小于6次/年预警
(8, 0, 'DELIVERY_RATE', '采购准时到货率', 90.00, 80.00, 1, 1);
KPI 预警阈值配置到底做什么?
这张表保存的是“什么时候该提醒谁”。例如缺货率大于 2% 发黄色预警,大于 5% 发红色预警;准时发货率低于 95% 发黄色预警,低于 90% 发红色预警。compare_type 决定是“大于阈值触发”还是“小于阈值触发”。
完整业务流程如下:
- SaaS 平台初始化一套默认阈值,
tenant_id = 0表示系统默认配置。 - 租户管理员可以在后台复制默认配置并调整成自己的阈值,例如高周转卖家可以把缺货率阈值设置得更严格。
- BI 定时任务每天计算核心 KPI,并把结果写入 KPI 汇总表或缓存。
- 预警任务读取 KPI 结果和
bi_kpi_threshold,判断是否达到黄色或红色阈值。 - 触发预警后,系统按
notify_roles找到对应角色,通过站内信、企业微信、短信或邮件通知。 - 负责人处理后,预警可以标记为已读、处理中、已解决,并保留处理记录。
例如本月缺货率为 3.5%,配置为大于 2% 黄色、大于 5% 红色,那么当前是黄色预警。系统应该通知采购负责人、运营负责人或租户管理员,提醒他们检查补货建议、在途库存和采购交期。
第十节 销售预测与智能补货
10.1 销售预测算法
智能补货的核心是准确预测未来销量,我们实现三种预测算法并取加权平均:
graph TD
A["历史销量数据<br>近90天每日销量"] --> B["数据预处理"]
B --> C["异常值识别:<br>某天销量超过 均值+2倍标准差<br>标记为异常日(促销/节日)"]
C --> D["用相邻日期均值填充异常值<br>避免异常数据影响预测"]
D --> E["三种预测算法并行计算"]
E --> F["算法1:简单移动平均<br>取近30天日均销量<br>预测未来30天日均<br>权重:30%"]
E --> G["算法2:加权移动平均<br>近7天权重50%<br>8-14天权重30%<br>15-30天权重20%<br>权重:40%"]
E --> H["算法3:指数平滑<br>α=0.3(平滑系数)<br>对趋势更敏感<br>权重:30%"]
F & G & H --> I["加权平均得到最终预测:<br>预测日均销量 = F×30% + G×40% + H×30%"]
I --> J["季节性调整:<br>根据历史同期数据<br>调整节假日前后的预测值"]
J --> K["输出:未来30天预计销量"]
销售预测算法是用来做什么的?
它的目的不是为了追求数学模型多高级,而是为了回答一个非常实际的问题:未来一段时间大概会卖多少件,我现在要不要补货。如果预测过低,可能断货;预测过高,可能积压库存,占用资金。
图中的几个步骤可以这样理解:
- 历史销量数据:从 OMS 订单明细里拿到某个 SKU 每天卖了多少件。
- 异常值识别:如果某天销量突然远高于平时,例如平时每天卖 10 件,某天直播促销卖了 200 件,这一天不能直接代表日常销量。
- 相邻日期均值填充:把异常日销量替换成前后几天的平均值,避免一次促销把未来补货量拉得过高。
- 简单移动平均:直接取最近一段时间的平均销量,优点是简单稳定,缺点是对最近趋势不够敏感。
- 加权移动平均:越近的数据权重越高,适合销量最近明显变好或变差的商品。
- 指数平滑:把历史趋势平滑到当前预测中,比简单平均更能反映趋势变化。
- 季节性调整:节假日、开学季、黑五、圣诞等特殊时期销量会明显变化,系统需要参考历史同期数据进行修正。
当前系统可以先实现加权移动平均,另外两种算法作为后续扩展。这样既能满足补货建议的核心需求,又不会把系统复杂度过早拉高。
10.2 加权移动平均算法 SQL
-- 计算某SKU的加权移动平均日销量
WITH daily_sales AS (
-- 从订单数据聚合每日销量
SELECT
DATE(o.platform_order_time) AS sale_date,
SUM(oi.quantity) AS daily_qty
FROM order_main o
JOIN order_item oi ON o.id = oi.order_id
WHERE
o.tenant_id = #{tenantId}
AND oi.sku_id = #{skuId}
AND o.status NOT IN (10) -- 排除已取消
AND o.platform_order_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY DATE(o.platform_order_time)
),
weighted_avg AS (
SELECT
-- 近7天(高权重 50%)
AVG(CASE WHEN sale_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
THEN daily_qty ELSE NULL END) AS avg_7d,
-- 近8-14天(中权重 30%)
AVG(CASE WHEN sale_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 14 DAY)
AND DATE_SUB(CURDATE(), INTERVAL 8 DAY)
THEN daily_qty ELSE NULL END) AS avg_8_14d,
-- 近15-30天(低权重 20%)
AVG(CASE WHEN sale_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 30 DAY)
AND DATE_SUB(CURDATE(), INTERVAL 15 DAY)
THEN daily_qty ELSE NULL END) AS avg_15_30d
FROM daily_sales
)
SELECT
ROUND(COALESCE(avg_7d, 0) * 0.50
+ COALESCE(avg_8_14d, 0) * 0.30
+ COALESCE(avg_15_30d, 0) * 0.20, 2) AS weighted_daily_avg
FROM weighted_avg;
10.3 智能补货建议计算
flowchart TD
A["输入:SKU + 目标仓库"] --> B["获取当前库存状态:<br>可用库存 / 在途库存"]
B --> C["计算预测日销量<br>(加权移动平均)"]
C --> D["获取供应商 Lead Time<br>(交货周期天数)"]
D --> E["计算公式:<br>建议补货量 = 预测日销量 × (Lead_Time + 安全天数)<br> - (可用库存 + 在途库存)"]
E --> F{"建议补货量 > 0?"}
F -- "否(库存充足)" --> G["无需补货<br>显示预计售罄天数"]
F -- "是(需要补货)" --> H["生成补货建议单:<br>SKU / 建议数量 / 推荐供应商 / 紧急程度"]
H --> I{"紧急程度判断"}
I -- "可用库存 ≤ 7天销量" --> J["紧急:立即补货<br>优先处理"]
I -- "可用库存 ≤ 14天销量" --> K["较急:本周内处理"]
I -- "可用库存 ≤ 30天销量" --> L["正常:计划内补货"]
J & K & L --> M["推送给采购专员<br>一键转化为采购申请单"]
智能补货建议依赖上一节的销售预测结果。销售预测回答“未来每天大概卖多少”,补货建议回答“现在应该补多少、什么时候补、找谁补”。
关键字段说明:
- 可用库存:当前仓库里可以立即发货的库存,不包含锁定库存、残次品和待质检库存。
- 在途库存:已经下采购单或已经发货,但还没入库的库存。
- Lead Time:供应商从接到采购单到货物入库可售的总周期,可能包括生产、发货、运输、清关、入库质检。
- 安全天数:为了防止销量波动、物流延迟和供应商延期而额外准备的库存天数。
- 建议补货量:不是简单补到 0,而是覆盖 Lead Time 加安全天数内预计会卖掉的数量。
系统生成补货建议后,不应该直接自动下采购单,而是推送给采购专员确认。采购专员可以调整数量、选择供应商、合并多个 SKU 的采购需求,然后一键转成 PMS 采购申请单。
补货建议计算示例:
SKU:蓝牙耳机Pro-黑色
当前状态:
可用库存:45件
在途库存:0件(无在途)
预测计算:
近7天日均销量:8.2件/天(权重50%)
近8-14天日均:6.8件/天(权重30%)
近15-30天日均:5.5件/天(权重20%)
加权日均 = 8.2×0.5 + 6.8×0.3 + 5.5×0.2 = 4.1+2.04+1.1 = 7.24件/天
供应商 Lead Time:7天
安全天数:15天(配置)
建议补货量 = 7.24 × (7 + 15) - (45 + 0)
= 7.24 × 22 - 45
= 159.28 - 45
= 114.28 → 取整为 115件
当前库存可用天数 = 45 / 7.24 = 6.2天
紧急程度:紧急(可用库存仅剩6.2天,即将断货!)
建议:立即向广州鑫源(A级供应商)下采购单 115件
按当前Lead Time 7天,须今天下单,预计到货:2025-01-24
10.4 ABC 库存分类与动态调整
flowchart TD
A(["定时任务:每月1日执行"]) --> B["计算每个SKU近90天的销售额占比"]
B --> C["按销售额降序排列所有SKU"]
C --> D["分类规则:<br>累计占总销售额前80%的SKU → A类<br>累计占80%-95%的SKU → B类<br>剩余SKU → C类"]
D --> E["更新 product_sku.abc_class 字段"]
E --> F["联动更新库存安全天数:<br>A类:安全天数=15天(高保障)<br>B类:安全天数=10天<br>C类:安全天数=7天(少备货)"]
F --> G["联动更新盘点频率:<br>A类:每月盘点<br>B类:每季度盘点<br>C类:每半年盘点<br>(写入循环盘点计划)"]
G --> H["生成ABC分类变动报告:<br>本月新晋A类/降级B类/降级C类的SKU列表"]
ABC 分类是一种库存管理方法,用来区分哪些 SKU 最重要。它通常按销售额、利润贡献或出库频率排序,把少数贡献最大的 SKU 划为 A 类,把中间部分划为 B 类,把长尾低贡献 SKU 划为 C 类。
- A 类 SKU:贡献大、断货影响大,要重点保障库存,安全库存更高,盘点更频繁。
- B 类 SKU:中等贡献,按正常策略管理。
- C 类 SKU:贡献低、长尾商品,不宜占用太多库存资金,安全库存可以低一些。
动态调整指的是 SKU 分类不是永远不变。一个新品如果销量快速上升,可能从 C 类升到 B 类甚至 A 类;一个老品销量下滑,也可能从 A 类降到 B 类或 C 类。系统一般每月或每周重算一次分类,并生成变动报告给运营和采购。
它和前面的智能补货有关:A 类商品安全天数更高,补货建议会更积极;C 类商品安全天数更低,系统会避免过度补货。这样可以把有限资金优先放在最重要的商品上。
第十一节 数据可视化 Dashboard
11.1 经营总览 Dashboard 设计
📊 FlexChain 经营总览 📅 2025年1月(截止今日 01-17)
┌─────────────┬─────────────┬─────────────┬─────────────┐
│ 本月 GMV │ 净利润 │ 净利润率 │ 总订单量 │
│ ¥286,420 │ ¥58,340 │ 20.4% │ 1,842单 │
│ ↑15% vs上月 │ ↑8% vs上月 │ ↓1.2% vs上月│ ↑12% vs上月│
└─────────────┴─────────────┴─────────────┴─────────────┘
┌────────────────────────┬─────────────────────────────┐
│ 销售额趋势(近30天) │ 平台销售占比 │
│(折线图) │(饼图) │
│ 1.5万 ___ │ ■ 亚马逊 62% │
│ 1.0万/ \ __/ │ ■ TikTok 21% │
│ 0.5万 │ ■ Shopee 11% │
│ 1/1 ... 1/17 │ ■ 其他 6% │
└────────────────────────┴─────────────────────────────┘
┌────────────────────────┬─────────────────────────────┐
│ 利润 Top 5 SKU │ 亏损 SKU 预警 │
│ 1. 蓝牙耳机Pro-黑 +¥21,230 │ 🔴 手机壳-透明 -¥340 │
│ 2. 手机支架-铝合金 +¥15,860 │ 广告费占比62%,建议暂停 │
│ 3. 数据线-1m +¥12,450 │ 🔴 电风扇-小型 -¥1,820 │
│ 4. 充电宝-10000mAh+¥8,920 │ 退款率高达18%,质量问题 │
│ 5. 耳机-有线版 +¥7,340 │ 🟡 蓝牙音箱-白 利润率2.1% │
└────────────────────────┴─────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 库存健康状态 │
│ 🟢 正常:156个SKU 🟡 预警:12个SKU 🔴 零库存:3个SKU│
│ 📦 库存总价值:¥1,248,560 📈 周转率:8.2次/年 │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 今日待处理 │
│ 📋 待发货订单 87单(其中 🔴 即将超期 3单) │
│ 📊 待对账账单 2份(亚马逊 + Shopee) │
│ 💰 本周到期应付账款 ¥28,500(广州鑫源 2月16日) │
│ 🚚 物流异常运单 5票(停滞超7天) │
└─────────────────────────────────────────────────────┘
11.2 前端图表技术选型
系统使用 ECharts(百度开源图表库)实现数据可视化:
| 图表类型 | 使用场景 | ECharts 组件 |
|---|---|---|
| 折线图 | 销售额/利润率趋势 | line |
| 柱状图 | 平台对比/KPI对比 | bar |
| 饼图 | 平台占比/成本构成 | pie |
| 散点图 | SKU 利润率 vs 销量分布 | scatter |
| 热力图 | 销售时间分布(小时×星期) | heatmap |
| 仪表盘 | 单一KPI达成率展示 | gauge |
| 地图 | 各国家/地区销售额分布 | map |
11.3 报表数据缓存策略
因为报表数据的计算 都是比较复杂的 数据的扫描 数据的分组 数据的计算 … 如果每次查询 报表都要重新走一下这个流程 那么这个响应的时间就会很长 很慢 通常的做法是:
- 提前把统计好的数据 缓存起来 当进行查询的时候 就直接进行展示(走缓存)
- 把相关的数据 也就是需要进行统计和计算的数据 同步到 列存储 数据库中 当请求过来的时候 走列存储数据库进行统计
flowchart TD
A["前端请求报表数据"] --> B{"Redis 缓存是否存在?"}
B -- "存在(未过期)" --> C["直接返回缓存数据<br>响应时间 < 50ms"]
B -- "不存在或已过期" --> D["查询数据库<br>执行复杂聚合 SQL<br>可能需要 1-5 秒"]
D --> E["计算完成<br>写入 Redis 缓存"]
E --> F["设置缓存过期时间:<br>实时看板:5分钟<br>日报表:1小时<br>月报表:24小时"]
F --> G["返回数据给前端"]
C --> G
H["数据变化时主动清除缓存:<br>新订单完成→清除销售报表缓存<br>账单对账完成→清除利润报表缓存"]
哪些报表适合缓存?
适合缓存的是查询慢、访问频繁、短时间内不要求绝对实时的数据,例如经营总览、近 30 天销售趋势、SKU 利润排行、库存健康统计、KPI 看板、现金流预测结果。
不适合长时间缓存的是订单明细、付款审批、库存扣减这类强实时数据。
常见缓存策略有两种:
- 懒加载缓存:用户第一次访问报表时查询数据库并写入 Redis,后续用户直接读缓存。适合访问不固定的筛选条件。
- 预热缓存:定时任务提前把常用报表算好写入 Redis,例如每天凌晨生成昨日经营日报、每小时刷新 KPI 大盘。适合首页大盘和高频报表。
缓存更新也有三种方式:
- 一种是设置 TTL 到期自动失效
- 一种是在关键业务变化时主动删除相关缓存
- 新的缓存数据 替代 旧的缓存数据 (也是根据业务来)
例如订单完成后清销售趋势缓存,账单对账完成后清利润报表缓存,库存变动后清库存健康缓存。系统通常会组合使用:短 TTL 兜底 + 业务事件主动失效。
第十二节 AI 自然语言查询(V2 版本规划)
当前版本不实现 AI 自然语言查询。原因是这个功能需要额外集成大模型、权限控制、SQL 安全校验、查询结果脱敏和成本控制,复杂度已经超出 FMS + BI 的核心范围。该能力作为 V2 版本规划,后续可以在现有 BI 报表能力之上继续扩展。
12.1 AI 查询功能设计思路
flowchart TD
A["用户输入自然语言问题:<br>'上个月哪些SKU在亚马逊亏损了?'"] --> B["意图识别:<br>时间范围:上个月<br>分析维度:SKU + 平台<br>筛选条件:净利润 < 0<br>平台:AMAZON"]
B --> C["转换为 SQL 查询条件"]
C --> D["执行 SQL 查询<br>FROM finance_profit_snapshot<br>WHERE platform='AMAZON'<br>AND snapshot_date BETWEEN...<br>AND net_profit < 0"]
D --> E["获取查询结果数据"]
E --> F["将数据传给 AI 大模型<br>让其生成自然语言分析结论"]
F --> G["返回给用户:<br>表格数据 + AI分析文字 + 可视化图表"]
这个功能的目标是让业务人员不用写 SQL,也能用中文问数据。例如“最近 30 天哪些 SKU 亏损最多”“哪个供应商准时率最低”“本月库存积压金额是多少”。系统不是让 AI 直接自由查询数据库,而是要经过一层严格的查询控制。
V2 实现时建议分成四步:
- 意图识别:识别用户要查什么指标、什么时间范围、什么维度和过滤条件。
- 安全 SQL 生成:只允许查询预设的数据集或视图,不允许 AI 拼接任意表名和危险 SQL。
- 数据查询:后端执行经过校验的查询,返回表格数据。
- AI 解读:把查询结果交给大模型生成业务解释和建议。
这样设计可以避免 AI 乱查敏感表、生成慢 SQL、泄露其他租户数据。当前版本先用“预设分析模板”满足大多数报表需求,AI 查询留到后续版本。
12.2 AI 查询的 Prompt 设计
系统提示词(System Prompt):
你是一位专业的跨境电商数据分析师。
你将收到用户的业务问题和从数据库查询到的结果数据。
请用清晰的中文回答问题,并给出针对性的运营建议。
回答格式:
1. 直接回答问题(1-2句话结论)
2. 关键数据列表(3-5条最重要的数据点)
3. 可能原因分析(1-3个可能原因)
4. 运营建议(2-3条具体可执行的建议)
用户问题:上个月哪些SKU在亚马逊亏损了?
数据查询结果:
[
{"sku_code":"SKU-PHONE-CASE","sku_name":"手机壳-透明","net_profit":-340,"net_margin":-12.5%,
"advertising_fee":890,"refund_loss":120},
{"sku_code":"SKU-FAN-SMALL","sku_name":"电风扇-小型","net_profit":-1820,"net_margin":-28.3%,
"return_rate":18.2%,"purchase_cost":4500}
]
请根据以上数据生成分析报告。
AI 返回示例:
📊 亏损 SKU 分析报告(2025年1月)
【结论】上个月亚马逊有2个SKU处于亏损状态,合计亏损¥2,160元。
【关键数据】
• 手机壳-透明:亏损¥340,净利润率-12.5%,主要原因是广告费¥890过高
• 电风扇-小型:亏损¥1,820,净利润率-28.3%,退款率高达18.2%
【可能原因分析】
• 手机壳广告投放过激进,ACOS可能超过50%,广告费已超过商品利润
• 电风扇退款率18%远超正常水平(行业均值5%),极可能存在质量问题
【运营建议】
1. 🔴 手机壳-透明:立即降低广告出价,ACOS控制在30%以内,或暂停广告
2. 🔴 电风扇-小型:紧急联系供应商排查质量问题,暂停新采购,清仓促销
3. 💡 如电风扇质量确有问题,考虑向供应商追责,在评分系统中降低该供应商质量分
12.3 预设常用分析模板
在当前版本中,系统优先提供预设的常用分析模板,让用户一键生成固定报表;自然语言查询留到 V2 再实现:
| 模板名称 | 功能描述 | 核心 SQL |
|---|---|---|
| 利润分析报表 | 指定时间范围内 SKU/店铺利润全貌 | finance_profit_snapshot 聚合 |
| 库存健康报告 | 各SKU库存状态、周转率、预警 | inventory JOIN product_sku |
| 销售趋势报告 | 日/周/月销售量和金额趋势 | order_main + order_item 聚合 |
| 供应商绩效报告 | 采购准时率/质量合格率/价格趋势 | supplier_score_log |
| 物流效率报告 | 各渠道时效/签收率/异常率 | logistics_waybill 聚合 |
| 广告效果报告 | ACOS/ROI/点击量分析 | finance_bill_item 广告明细 |
版本边界:
当前版本只实现预设模板、固定接口和固定 SQL,不实现 /api/bi/ai/query 的真实大模型调用。BI 的核心价值仍然完整覆盖:看板、报表、KPI、预警、预测和补货。AI 自然语言查询属于系统后续规划,不作为当前版本的已完成功能。
第十三节 接口设计规范
13.1 FMS 财务模块接口清单
基础路径前缀: /api/fms
| 模块 | 接口名称 | HTTP方法 | 路径 | 权限标识 |
|---|---|---|---|---|
| 汇率 | 汇率列表 | GET | /exchange-rates | fms:rate:list |
| 汇率 | 手动刷新汇率 | POST | /exchange-rates/refresh | fms:rate:refresh |
| 账单 | 账单列表 | GET | /bills | fms:bill:list |
| 账单 | 账单详情 | GET | /bills/{id} | fms:bill:list |
| 账单 | 上传账单 | POST | /bills/upload | fms:bill:import |
| 账单 | 手动触发解析 | POST | /bills/{id}/parse | fms:bill:import |
| 账单 | 账单明细列表 | GET | /bills/{id}/items | fms:bill:list |
| 账单 | 标记对账完成 | PUT | /bills/{id}/confirm | fms:bill:audit |
| 账单 | 账单汇总报表 | GET | /bills/summary | fms:bill:list |
| 利润 | SKU利润报表 | GET | /profit/sku | fms:profit:view |
| 利润 | 店铺利润报表 | GET | /profit/store | fms:profit:view |
| 利润 | 利润趋势 | GET | /profit/trend | fms:profit:view |
| 利润 | 亏损预警列表 | GET | /profit/loss-warning | fms:profit:view |
| 应收 | 应收账款列表 | GET | /receivables | fms:receivable:list |
| 应付 | 应付账款列表 | GET | /payables | fms:payable:list |
| 应付 | 发起付款申请 | POST | /payables/{id}/apply | fms:payable:apply |
| 应付 | 审批付款 | PUT | /payables/{id}/approve | fms:payable:approve |
| 应付 | 确认已付款 | PUT | /payables/{id}/paid | fms:payable:pay |
| VAT | VAT申报列表 | GET | /vat | fms:vat:list |
| VAT | 生成VAT数据 | POST | /vat/generate | fms:vat:generate |
| VAT | 导出申报表 | GET | /vat/{id}/export | fms:vat:export |
| 现金流 | 现金流列表 | GET | /cash-flow | fms:cashflow:list |
| 现金流 | 现金流预测 | GET | /cash-flow/forecast | fms:cashflow:list |
13.2 BI 数据分析模块接口清单
基础路径前缀: /api/bi
| 模块 | 接口名称 | HTTP方法 | 路径 | 权限标识 |
|---|---|---|---|---|
| 大盘 | 经营总览 | GET | /dashboard/overview | bi:dashboard:view |
| 大盘 | 今日实时 | GET | /dashboard/realtime | bi:dashboard:view |
| 销售 | 销售趋势 | GET | /sales/trend | bi:sales:view |
| 销售 | 平台销售对比 | GET | /sales/platform-compare | bi:sales:view |
| 库存 | 库存健康报告 | GET | /inventory/health | bi:inventory:view |
| 库存 | 库存周转率 | GET | /inventory/turnover | bi:inventory:view |
| 补货 | 补货建议列表 | GET | /reorder/suggestions | bi:reorder:view |
| 补货 | 一键转采购申请 | POST | /reorder/to-purchase | bi:reorder:apply |
| 补货 | 预测日销量 | GET | /reorder/forecast/{skuId} | bi:reorder:view |
| KPI | KPI看板 | GET | /kpi/dashboard | bi:kpi:view |
| KPI | KPI趋势 | GET | /kpi/trend | bi:kpi:view |
| KPI | 预警阈值配置 | GET/PUT | /kpi/thresholds | bi:kpi:config |
| 报表 | 报表导出(Excel) | POST | /export | bi:report:export |
AI 自然语言查询接口属于 V2 规划,当前版本不作为必须实现接口。当前版本用固定报表接口和预设分析模板满足 BI 查询需求。
13.3 关键接口示例
① SKU 利润报表接口:
GET /api/fms/profit/sku?startDate=2025-01-01&endDate=2025-01-17&platform=AMAZON&page=1&size=20
响应:
{
"code": 200,
"data": {
"total": 48,
"records": [
{
"skuId": "1000001",
"skuCode": "SKU-EARPHONE-BK",
"skuName": "蓝牙耳机Pro-黑色",
"platform": "AMAZON",
"storeId": "store001",
"orderCount": 124,
"salesQty": 124,
"grossRevenueCny": 21659.28,
"purchaseCost": 4340.00,
"logisticsFee": 2318.20,
"platformFee": 3248.89,
"advertisingFee": 1083.00,
"refundLoss": 0.00,
"vatFee": 0.00,
"grossProfit": 11752.19,
"netProfit": 10669.19,
"grossMargin": 54.26,
"netMargin": 49.27,
"profitStatus": "HIGH",
"profitStatusName": "高利润"
},
{
"skuId": "1000099",
"skuCode": "SKU-PHONE-CASE",
"skuName": "手机壳-透明",
"platform": "AMAZON",
"storeId": "store001",
"orderCount": 89,
"salesQty": 89,
"grossRevenueCny": 2720.00,
"purchaseCost": 267.00,
"logisticsFee": 541.80,
"platformFee": 408.00,
"advertisingFee": 890.00,
"refundLoss": 120.00,
"vatFee": 0.00,
"grossProfit": 1503.20,
"netProfit": -340.00,
"grossMargin": 55.26,
"netMargin": -12.50,
"profitStatus": "LOSS",
"profitStatusName": "亏损"
}
],
"summary": {
"totalRevenue": 286420.00,
"totalNetProfit": 58340.00,
"avgNetMargin": 20.37,
"lossSkuCount": 2,
"highProfitSkuCount": 8
}
}
}
② V2 规划:AI 自然语言查询接口(当前版本不实现):
POST /api/bi/ai/query
Content-Type: application/json
{
"question": "最近30天哪个供应商的采购准时率最低?"
}
响应:
{
"code": 200,
"data": {
"question": "最近30天哪个供应商的采购准时率最低?",
"sqlGenerated": "SELECT supplier_name, SUM(CASE WHEN...) ...",
"tableData": {
"columns": ["供应商名称", "采购单数", "准时到货数", "准时率%"],
"rows": [
["义乌精品小商品", 8, 5, 62.5],
["深圳博通贸易", 12, 9, 75.0],
["广州鑫源电子", 25, 24, 96.0]
]
},
"aiAnalysis": "最近30天准时率最低的供应商是义乌精品小商品(62.5%),远低于安全阈值90%。...建议:1.与该供应商沟通改进计划 2.暂停向其下新采购单 3.寻找替代供应商",
"chartType": "bar",
"processingTimeMs": 1240
}
}
13.4 模块专用错误码
FMS 财务模块(17000-17099):
| 错误码 | 含义 |
|---|---|
| 17001 | 账单文件格式不支持(仅支持CSV/Excel) |
| 17002 | 账单解析失败,文件格式不符合平台规范 |
| 17003 | 该结算周期账单已存在,请勿重复导入 |
| 17004 | 汇率数据不存在,无法计算折合金额 |
| 17005 | VAT 申报记录已存在,请勿重复生成 |
| 17006 | 付款金额超过应付余额 |
| 17007 | 账单尚未对账完成,无法生成利润快照 |
| 17008 | 付款申请待审批中,请勿重复提交 |
BI 分析模块(18000-18099):
| 错误码 | 含义 |
|---|---|
| 18001 | 时间范围超过最大限制(最多查询365天) |
| 18002 | AI查询服务暂时不可用,请稍后重试(V2 预留) |
| 18003 | 历史数据不足,无法进行预测(至少需要30天数据) |
| 18004 | 报表导出数据量过大(建议缩小查询范围) |
| 18005 | KPI 指标编码不存在 |
今日总结与作业
今日知识点回顾
FMS 财务管理:
- 跨境财务的 6 大复杂维度(多货币/多平台费用/长结算周期/分散费用/VAT/汇兑损益)
- 亚马逊 Settlement Report 解析:CSV 逐行解析→字段映射→订单匹配→差异处理
- SKU 级完整利润核算:8 项成本拆解(采购/物流/平台费/FBA仓储/广告/退款/VAT/其他)
- 毛利润 vs 净利润的区别与计算公式
- 多货币双字段存储策略(原币 + CNY)
- 汇兑损益计算:按结算当日汇率快照,事后汇率变化产生的浮动损益
- 现金流预测 SQL(应收 + 应付的时间轴预测)
- VAT 申报:税率配置表 + 按月计算 + OSS 格式导出
- 利润快照设计:只增不改,历史数据永久保留
BI 智能分析:
- 供应链 8 大核心 KPI(缺货率/OTD/退款率/签收率/异常率/净利润率/周转率/采购准时率)
- KPI 预警阈值配置(可按租户定制,黄/红双阈值)
- 销售预测三种算法(简单移动平均/加权移动平均/指数平滑)及加权融合
- 补货建议计算公式:预测日销量 × (Lead Time + 安全天数) - 现有可用库存
- ABC 分类:按销售额占比(80%/15%/5%分三类),联动安全库存和盘点频率
- 报表数据缓存策略:Redis + 按数据变化频率设置不同过期时间
- AI 自然语言查询作为 V2 规划:意图识别→安全 SQL→数据查询→AI 分析,当前版本不作为必做功能
数据库: 7 张表(汇率/平台账单主/明细/利润快照/VAT申报/资金流水/KPI阈值配置)
今日作业
作业 1:数据库执行(必做)
- 执行本课所有建表 SQL(7 张表)
- 插入测试数据:
-- 插入汇率数据(近7天)
INSERT INTO `finance_exchange_rate` (`id`, `rate_date`, `currency`, `rate_to_cny`, `rate_source`) VALUES
(1, CURDATE(), 'USD', 7.2300, 'OpenExchange'),
(2, CURDATE(), 'EUR', 7.8900, 'OpenExchange'),
(3, CURDATE(), 'GBP', 9.1500, 'OpenExchange'),
(4, CURDATE(), 'JPY', 0.0481, 'OpenExchange'),
(5, DATE_SUB(CURDATE(), INTERVAL 1 DAY), 'USD', 7.2150, 'OpenExchange'),
(6, DATE_SUB(CURDATE(), INTERVAL 1 DAY), 'EUR', 7.8750, 'OpenExchange');
-- 插入平台账单(亚马逊1月前两周结算)
INSERT INTO `finance_platform_bill` (
`id`, `tenant_id`, `bill_no`, `platform`, `store_id`, `store_name`,
`platform_bill_id`, `settlement_start`, `settlement_end`,
`currency`, `total_sales`, `total_refund`, `referral_fee`, `fba_fee`,
`storage_fee`, `advertising_fee`, `net_amount`, `cny_amount`, `exchange_rate`,
`status`
) VALUES (
11001, 101, 'BILL-20250117-0001', 'AMAZON', 'ATVPDKIKX0DER', '亚马逊美国店',
'SETTLEMENT-20250115', '2025-01-01', '2025-01-15',
'USD', 3982.85, 89.97, 597.43, 428.10, 45.20, 381.50, 2440.65, 17645.90, 7.23,
3 -- 对账完成
);
-- 插入账单明细
INSERT INTO `finance_bill_item` (
`id`, `tenant_id`, `bill_id`, `item_type`, `order_no`, `platform_sku`,
`amount`, `currency`, `description`, `transaction_date`, `is_matched`
) VALUES
(12001, 101, 11001, 'Principal', '112-3456789-0123456', 'B0XXXXXX', 29.99, 'USD', '商品销售收入', '2025-01-17', 1),
(12002, 101, 11001, 'Referral', '112-3456789-0123456', 'B0XXXXXX', -4.50, 'USD', '推荐费 15%', '2025-01-17', 1),
(12003, 101, 11001, 'FBAFee', '112-3456789-0123456', 'B0XXXXXX', -3.22, 'USD', 'FBA配送费', '2025-01-17', 1);
-- 插入利润快照
INSERT INTO `finance_profit_snapshot` (
`id`, `tenant_id`, `snapshot_type`, `snapshot_date`,
`sku_id`, `sku_code`, `platform`, `store_id`, `country_code`,
`currency`, `exchange_rate`,
`order_count`, `sales_qty`, `gross_revenue`, `gross_revenue_cny`,
`purchase_cost`, `logistics_fee`, `platform_fee`, `advertising_fee`,
`refund_loss`, `vat_fee`, `other_cost`, `total_cost`,
`gross_profit`, `net_profit`, `gross_margin`, `net_margin`
) VALUES (
13001, 101, 2, '2025-01-17',
1000001, 'SKU-EARPHONE-BK', 'AMAZON', 'store001', 'US',
'USD', 7.23,
124, 124, 3000.00, 21690.00,
4340.00, 2318.20, 3253.50, 1083.00,
0.00, 0.00, 0.00, 10994.70,
12038.30, 10695.30, 55.50, 49.31
);
-- 验证查询:SKU利润汇总
SELECT
ps.sku_code,
ps.platform,
ps.order_count,
ROUND(ps.gross_revenue_cny, 2) AS revenue_cny,
ROUND(ps.purchase_cost, 2) AS purchase_cost,
ROUND(ps.platform_fee, 2) AS platform_fee,
ROUND(ps.logistics_fee, 2) AS logistics_fee,
ROUND(ps.advertising_fee, 2) AS advertising_fee,
ROUND(ps.net_profit, 2) AS net_profit,
CONCAT(ROUND(ps.net_margin, 2), '%') AS net_margin
FROM finance_profit_snapshot ps
WHERE ps.tenant_id = 101
ORDER BY ps.net_profit DESC;
-- 验证查询:账单费用构成分析
SELECT
pb.platform,
ROUND(pb.total_sales, 2) AS total_sales,
ROUND(pb.total_refund, 2) AS total_refund,
ROUND(pb.referral_fee, 2) AS referral_fee,
ROUND(pb.fba_fee, 2) AS fba_fee,
ROUND(pb.advertising_fee, 2) AS advertising_fee,
ROUND(pb.net_amount, 2) AS net_amount,
ROUND(pb.referral_fee / pb.total_sales * 100, 2) AS referral_rate_pct
FROM finance_platform_bill pb
WHERE pb.tenant_id = 101 AND pb.is_deleted = 0;
作业 2:业务分析题(必做)
-
利润核算理解:假设某 SKU 在 1 月份:
- 亚马逊销售了 100 件,美元售价 $29.99/件,汇率 7.23
- 采购成本:¥35/件
- 物流费:¥15/件(头程分摊)
- 亚马逊推荐费:15%
- FBA 配送费:$3.22/件(汇率 7.23 折算)
- 广告费:¥1,200(该 SKU 当月广告总投入)
- 退款 3 件(退款金额 $29.99/件,退回商品残值 ¥10/件)
请计算:毛利润、净利润、净利润率。(过程要完整写出)
-
预测补货设计:某 SKU 过去 30 天每天销量为:
- 近7天平均:12件/天
- 近8-14天平均:9件/天
- 近15-30天平均:7件/天
- 当前可用库存:80件
- 在途库存:30件
- 供应商 Lead Time:10天
- 安全库存天数:15天
请用加权移动平均法(近7天50%/近8-14天30%/近15-30天20%)计算:
- 预测日均销量是多少?
- 建议补货量是多少?
- 当前库存可支撑多少天?紧急程度是什么?
-
汇兑损益场景:1月1日,亚马逊账上有待结算的 $5,000,当日汇率 7.20,系统账面价值 ¥36,000。1月15日亚马逊打款,当日汇率已变为 7.10,实际到账 $5,000(折合 ¥35,500)。请问:发生了汇兑盈利还是损失?金额是多少?如何在 finance_cash_flow 表中记录这个差异?
-
KPI 预警机制:根据本课的 KPI 预警阈值配置表,如果本月”缺货率”为 3.5%,请判断:当前是黄色预警还是红色预警?系统应该通知哪些角色?并描述系统应该如何自动触发这个通知(通知的来源数据从哪张表里来?)。
参考答案:
-
销售收入为
100 × 29.99 × 7.23 = ¥21,682.77。采购成本100 × 35 = ¥3,500,头程物流费100 × 15 = ¥1,500,推荐费21,682.77 × 15% = ¥3,252.42,FBA 配送费100 × 3.22 × 7.23 = ¥2,328.06。毛利润可以按不扣广告和退款前计算:21,682.77 - 3,500 - 1,500 - 3,252.42 - 2,328.06 = ¥11,102.29。退款 3 件损失为3 × 29.99 × 7.23 - 3 × 10 = ¥620.48,再扣广告费 ¥1,200,净利润为11,102.29 - 620.48 - 1,200 = ¥9,281.81。净利润率按扣除退款后的有效销售额计算:9,281.81 / (21,682.77 - 650.48) = 44.13%。 -
加权预测日均销量为
12 × 50% + 9 × 30% + 7 × 20% = 10.1 件/天。补货需要覆盖供应商交期 10 天和安全库存 15 天,所以目标库存为10.1 × (10 + 15) = 252.5,向上取整为 253 件。当前可用库存 80 件,在途库存 30 件,建议补货量为253 - 80 - 30 = 143 件。当前可用库存可支撑80 / 10.1 = 7.9 天,小于供应商 Lead Time 10 天,属于比较紧急,需要尽快下采购单。 -
1 月 1 日账面价值是
5000 × 7.20 = ¥36,000,1 月 15 日实际到账折合5000 × 7.10 = ¥35,500,发生汇兑损失¥500。在finance_cash_flow中应记录一笔平台回款现金流,金额为 ¥35,500;同时记录汇兑损益字段或单独记录一笔“汇兑损失”现金流/财务调整,金额为 -¥500,用于利润核算和财务报表展示。 -
缺货率 3.5%,如果阈值配置为大于 2% 黄色预警、大于 5% 红色预警,则当前是黄色预警。系统应通知采购负责人、运营负责人和租户管理员。自动触发流程是:BI 定时任务从
order_main中统计订单总量和因缺货挂起/取消的订单数,计算缺货率;再读取bi_kpi_threshold表中的阈值配置;如果达到阈值,就写入预警记录并通过站内信、邮件或企业微信通知相关角色。
作业 3:SQL 练习(必做)
-- 练习1:查询当月各平台的净收入(CNY)汇总
-- 要求:显示平台、结算账单数、总净收入(CNY),按净收入降序
-- 你的答案:
SELECT
platform,
COUNT(*) AS bill_count,
SUM(cny_amount) AS total_net_income_cny
FROM finance_platform_bill
WHERE tenant_id = 101
AND is_deleted = 0
AND settlement_start >= DATE_FORMAT(CURDATE(), '%Y-%m-01')
AND settlement_start < DATE_ADD(DATE_FORMAT(CURDATE(), '%Y-%m-01'), INTERVAL 1 MONTH)
GROUP BY platform
ORDER BY total_net_income_cny DESC;
-- 练习2:查询净利润率 < 10% 的SKU(低利润预警)
-- 要求:显示 sku_code、平台、销量、净利润、净利润率,按净利润率升序
-- 数据范围:最近的月快照(snapshot_type=2)
-- 你的答案:
SELECT
sku_code,
platform,
sales_qty,
net_profit,
net_margin
FROM finance_profit_snapshot
WHERE tenant_id = 101
AND snapshot_type = 2
AND snapshot_date = (
SELECT MAX(snapshot_date)
FROM finance_profit_snapshot
WHERE tenant_id = 101 AND snapshot_type = 2
)
AND net_margin < 10
ORDER BY net_margin ASC;
-- 练习3:计算各币种的汇兑变化趋势(近7天汇率对比今日)
-- 要求:显示货币、7天前汇率、今日汇率、变动幅度(%)
-- 你的答案:
SELECT
today.currency,
old.rate_to_cny AS rate_7_days_ago,
today.rate_to_cny AS rate_today,
ROUND((today.rate_to_cny - old.rate_to_cny) / old.rate_to_cny * 100, 2) AS change_pct
FROM finance_exchange_rate today
JOIN finance_exchange_rate old ON today.currency = old.currency
WHERE today.rate_date = CURDATE()
AND old.rate_date = DATE_SUB(CURDATE(), INTERVAL 7 DAY)
ORDER BY ABS(change_pct) DESC;
-- 练习4:找出近30天日销量标准差最大的Top5 SKU
-- (标准差大说明销量波动大,预测难度高,补货风险高)
-- 提示:MySQL使用 STDDEV() 函数计算标准差
-- 你的答案:
SELECT
sku_id,
sku_code,
STDDEV(daily_qty) AS sales_stddev
FROM (
SELECT
oi.sku_id,
oi.sku_code,
DATE(o.platform_order_time) AS sale_date,
SUM(oi.quantity) AS daily_qty
FROM order_item oi
JOIN order_main o ON oi.order_id = o.id
WHERE o.tenant_id = 101
AND o.is_deleted = 0
AND o.platform_order_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY oi.sku_id, oi.sku_code, DATE(o.platform_order_time)
) t
GROUP BY sku_id, sku_code
ORDER BY sales_stddev DESC
LIMIT 5;
作业 4:系统设计题(选做)
新需求:支持”利润模拟器”。运营专员在做定价决策时,可以输入:
- 调整后售价(美元)
- 调整后广告出价(ACOS目标值)
- 是否开启 DDP(需要额外承担关税成本)
系统实时计算并展示调整后的预期利润变化。
请设计:
- 利润模拟器的核心计算逻辑(哪些成本是固定的,哪些会随定价/广告/DDP变化?)
- 接口设计(请求参数和响应结构)
- 前端如何实现”滑动条拖动售价,利润率实时刷新”的交互效果(从接口调用频率角度考虑,如何避免频繁请求)
- 模拟器的结果是否需要存储到数据库?为什么?
参考答案:
-
利润模拟器的核心是根据“新售价”和“成本结构”重新计算预期利润。固定成本包括采购成本、包装成本、部分头程物流费;随定价变化的成本包括平台推荐费、VAT、支付手续费,因为它们通常按售价百分比计算;随广告策略变化的成本包括广告费,ACOS 越高广告成本越高;开启 DDP 后,卖家还要承担关税、VAT 或清关费用。计算公式可以简化为:
预期净利润 = 售价收入 - 采购成本 - 物流成本 - 平台佣金 - 广告成本 - VAT/DDP成本 - 退款损耗。 -
接口可以设计为:
POST /api/fms/profit/simulate
请求参数包括:skuId、platform、countryCode、salePrice、currency、acosTarget、enableDdp、expectedSalesQty。响应结构包括:grossRevenue、purchaseCost、platformFee、logisticsFee、advertisingFee、vatFee、ddpCost、netProfit、netMargin、profitChangeAmount、profitChangeRate、suggestion。
-
前端滑动条不要每拖动一次都请求后端。可以使用防抖,例如用户停止拖动 300ms 后再调用接口;也可以前端先用上一次返回的成本参数做本地即时预估,等用户停止拖动后再请求后端精确计算。这样既保证交互流畅,又避免接口被高频调用。
-
模拟结果默认不需要每次都存数据库,因为拖动滑动条会产生大量临时结果,全部保存没有意义。只有当运营点击“保存方案”或“生成调价建议”时,才需要写入
finance_profit_simulation表,方便后续审批、复盘和对比真实利润。
明日预告
第八天:权限安全系统 + SaaS 运营管理 + 项目部署上线
最后一天将完成系统的安全防护和运营管理,并完成整个项目的部署上线:
- RBAC 权限体系完整实现(用户/角色/菜单/数据权限)
- Sa-Token 权限框架集成(Redis 分布式登录态认证)
- 接口安全防护(限流/SQL注入/XSS防护/审计日志)
- SaaS 租户管理(注册/套餐/到期处理)
- Docker 容器化部署(Dockerfile + docker-compose)
- CI/CD 自动化流水线(GitHub Actions)
- Nginx 反向代理与 HTTPS 配置
- 项目简历亮点总结与面试准备
预习建议:
- 了解 Token、JWT 与 Sa-Token 的区别,重点掌握随机 Token + Redis 登录态方案
- 了解 Docker 的基本概念(镜像/容器/Dockerfile/docker-compose)
- 思考:整个 8 天项目中,你认为技术难度最高的 3 个设计点是什么?为什么?(面试常问)
今日学习总结:今天完成了供应链系统最”烧脑”的两个模块。
FMS 的核心价值在于”把钱算清楚”——跨境卖家最大的痛点之一就是不知道自己到底赚了多少,FMS 通过多平台对账 + SKU 级成本拆解,让每一分钱的来龙去脉都清晰可见。
BI 的核心价值在于”用数据指导决策”——从补货靠感觉到系统自动计算建议补货量,从凭经验判断哪个产品该放弃到系统用利润率数据告诉你,这是整个 SaaS 平台最能体现智能化价值的模块。
面试重点回顾:
- 利润核算的 8 项成本拆解(务必能脱口而出)
- 补货建议计算公式(含完整示例数字,能当场手算)
- ABC 分类的划分标准和联动效果
- AI 查询可以作为后续规划说明,本期项目核心仍是 FMS + BI 报表、KPI、预测补货