Series Article

Day07 · FMS + BI 核心业务面试准备

核心业务流程

平台账单对账完整流程

业务背景

跨境卖家在 Amazon、TikTok Shop、Shopee、Temu、Shopify 等平台开店后,订单是在平台上产生的,但平台最终打给卖家的钱不是订单销售额。

比如买家支付了 29.99 美元,平台可能会扣推荐费、FBA 配送费、广告费、仓储费、退款、赔付和调整项。卖家如果只看 OMS 订单金额,就会误以为自己卖了很多钱;但真正到账时,可能已经被平台扣掉了不少费用。

所以 FMS 里要做平台账单对账:把平台结算报告导入系统,解析成主账单和明细,再和 OMS 订单、PIM SKU、TMS 物流费用进行匹配,最后形成可追溯的财务数据。

数据来源

账单数据主要来自三种方式:

  • 平台 API 拉取:平台开放结算、交易或费用接口时,系统定时拉取
  • 文件导入:财务专员从平台后台下载 CSV 或 Excel 后上传
  • 模板导入:小平台没有标准账单时,财务按系统模板整理后导入

finance_platform_bill 表保存一份结算报告的汇总信息,比如平台、店铺、结算周期、销售总额、退款总额、平台费用、净到账金额。

finance_bill_item 表保存这份账单里的每一行明细,比如某个订单的商品收入、推荐费、FBA 费、退款、广告费、仓储费。

完整流程

flowchart TD
    A[平台生成结算报告] --> B[财务上传账单文件或系统调用平台 API]
    B --> C[创建 finance_platform_bill 主账单]
    C --> D[异步解析 CSV 或 Excel]
    D --> E[逐行写入 finance_bill_item 明细]
    E --> F[汇总销售退款平台费广告费]
    F --> G[根据平台订单号匹配 OMS 订单]
    G --> H{是否全部自动匹配}
    H -->|是| I[账单进入待确认或对账完成]
    H -->|否| J[生成差异清单]
    J --> K[财务处理差异]
    K --> I
    I --> L[触发利润核算]
    L --> M[生成 SKU 利润快照]

关键步骤

上传账单时,系统不会直接在请求线程里解析大文件。正确流程是先把文件上传到 OSS,再插入 finance_platform_bill,状态为待解析,然后把解析任务提交给线程池或 MQ。 上传的文件流 可以直接写入到OSS 因为OSS有文件文件流/网络流的传输 真正的解析是放到异步中执行 异步中执行的时候 还会从OSS下载这个账单 然后再次进行解析

解析账单时,系统按流式方式读取文件,不一次性加载到内存。每解析一行,就识别它是商品收入、退款、推荐费、FBA 费、广告费还是调整项,然后批量写入 finance_bill_item

解析完成后,系统按费用类型汇总账单主表。例如:

  • Principal 汇总为销售收入
  • Refund 汇总为退款金额
  • Referral 汇总为平台推荐费
  • FBAFee 汇总为 FBA 配送费
  • Advertising 汇总为广告费
  • StorageFee 汇总为仓储费

订单匹配时,系统主要通过平台订单号匹配 OMS 的 platform_order_no。匹配成功后,账单明细会记录内部订单 ID;匹配失败则进入差异清单。

差异类型

账单差异主要分三类。

第一类是账单有,系统无订单。比如平台订单同步失败、店铺授权过期、卖家在平台后台手动操作,或者平台产生了没有订单号的调整项。这类差异不能简单删除,因为它会影响真实到账金额。

第二类是系统有订单,账单无记录。常见原因是订单还没进入本期结算,比如订单刚发货、买家还没签收、售后期没过、平台冻结款项,或者跨结算周期。

第三类是金额不符。常见原因是平台费率变化、退款调整、优惠券承担方变化、汇率口径不同、平台后续补扣或补发。

面试时怎么讲

“平台账单对账这个流程,我会先讲业务背景。

跨境平台最终打给卖家的钱,不等于订单销售额。比如一笔 Amazon 订单卖了 29.99 美元,但平台会扣推荐费、FBA 配送费、广告费、仓储费,还可能有退款和调整项。所以我们需要把平台结算报告导入 FMS,和系统内部订单做对账。

我的实现分几步。

第一步是账单导入。财务专员上传 Amazon Settlement Report 这类 CSV 文件,或者系统通过平台 API 拉取账单。上传后不会同步解析,而是先把原始文件放到 OSS,再创建一条 finance_platform_bill 主账单记录,状态是待解析。

第二步是异步解析。解析任务会流式读取 CSV,不会一次性把文件读到内存里。每一行都会解析成一条 finance_bill_item,并识别费用类型,比如商品收入、退款、推荐费、FBA 费、广告费、仓储费。为了减少数据库交互,每 1000 条左右做一次批量插入。

第三步是汇总主账单。明细解析完成后,系统按费用类型汇总销售额、退款、平台费、广告费、仓储费,计算净到账金额,然后回写到 finance_platform_bill

第四步是订单匹配。系统根据平台订单号去匹配 OMS 的内部订单,匹配成功后记录内部订单 ID,匹配失败就进入差异清单。差异主要有三类:账单有系统无、系统有账单无、金额不一致。

第五步是差异处理和确认。系统会自动给出可能原因,但最终需要财务专员确认。对账完成后,才会触发后续 SKU 利润核算,把平台真实扣费纳入利润快照。

这里我会强调一个点,95% 匹配率不是财务准确率,而是自动匹配率阈值。因为平台账单里会有广告费、仓储费、调整项、跨周期退款,这些不一定都有订单号。系统能自动处理大部分明细,剩下的异常交给财务人工确认。这样既保证效率,也保证财务数据可追溯。“


SKU 级利润核算完整流程

业务背景

跨境卖家最常见的问题是:整体销售额看起来不错,但不知道到底哪些商品赚钱,哪些商品亏钱。

一个 SKU 的真实利润不能只看售价和采购价。它还要扣平台推荐费、FBA 配送费、头程物流费、广告费、仓储费、退款损失、VAT 税费、包材和操作费。只有把这些成本拆到 SKU 级别,运营才知道这个商品该加大投放、继续补货,还是应该降价清仓或停止采购。

利润核算数据来源

数据来源模块说明
销售收入OMS / 平台账单订单销售金额,优先使用对账后的平台账单口径
采购成本PMS / WMS采购入库后的加权平均成本
物流费TMS运单实际费用或头程费用分摊
平台费FMS账单明细中的推荐费、FBA 费、平台服务费
广告费FMS / 平台广告账单按 SKU 或销售额进行分摊
仓储费FMS / WMSFBA 或 3PL 仓储费用,按库存占用分摊
退款损失OMS / WMS退款金额减去退货残值
VAT 税费FMS按目的国税率计算或按申报记录分摊

完整流程

flowchart TD
    A[订单完成或账单对账完成] --> B[获取销售收入]
    B --> C[按汇率折算为人民币]
    C --> D[获取采购成本]
    D --> E[获取平台费和 FBA 费]
    E --> F[获取物流费]
    F --> G[分摊广告费和仓储费]
    G --> H[计算退款损失和 VAT]
    H --> I[计算毛利润]
    I --> J[计算净利润]
    J --> K[写入 finance_profit_snapshot]
    K --> L[触发利润异常预警]

核心公式

毛利润 = 销售收入 - 采购成本 - 平台手续费 - 物流费

净利润 = 毛利润 - 广告费 - 仓储费 - 退款损失 - VAT 税费 - 其他成本

净利润率 = 净利润 / 销售收入 * 100%

计算时机

利润核算不是只算一次,而是分阶段计算。

订单完成时,可以先做预估利润。这个时候采购成本、物流费、预估平台费基本能算出来,但广告费、仓储费、平台最终扣费可能还没完全确认。

平台账单对账完成后,要做确认利润。因为此时平台实际扣了多少推荐费、广告费、FBA 费已经明确,系统要把预估数据修正成最终数据。

每天凌晨或月初,定时任务会把前一天或上个月的 SKU 利润汇总到 finance_profit_snapshot,方便 BI 报表快速查询。

面试时怎么讲

“SKU 级利润核算这个点,我会先讲它解决的业务问题。

很多卖家只看销售额,会觉得某个商品卖得很好。但实际扣掉广告费、FBA 费、退款和 VAT 后,可能是亏损的。所以我在 FMS 里做了 SKU 级利润快照,把收入和成本都拆到 SKU 维度。

我的计算流程是这样的。

第一步获取销售收入。收入可以来自 OMS 订单,也可以来自平台账单。最终确认利润时,我更倾向于使用平台账单口径,因为平台账单代表平台实际认可并结算的数据。

第二步统一币种。Amazon 美国站是 USD,欧洲站是 EUR,日本站是 JPY,所以所有收入和费用都会保存原币金额,同时按照交易日或结算日汇率折算成人民币。

第三步获取成本。采购成本来自 WMS 的加权平均成本,乘以销售数量;物流费来自 TMS 运单费用;平台推荐费、FBA 费、广告费来自 finance_bill_item;退款损失来自 OMS 退款单和 WMS 退货质检结果;VAT 来自目的国税率和申报数据。

第四步做分摊。不是所有费用都有订单号,比如广告费和仓储费通常是周期费用,所以要按 SKU 的销量、销售额或库存占用进行分摊。分摊规则要固定下来,不能每次随意变,否则利润报表前后不可比。

第五步计算毛利润和净利润。毛利润主要看商品本身和履约成本,净利润再扣广告、仓储、退款、VAT 和其他成本。最后写入 finance_profit_snapshot

第六步是异常预警。如果净利润率小于 0,就标记为亏损 SKU;如果小于 10%,标记为低利润 SKU;如果利润率很高,则可以提示运营加大备货或广告投放。

这个设计的好处是把利润从店铺级、平台级下钻到了 SKU 级。运营不只是知道这个月赚了多少钱,还能知道是哪几个 SKU 在赚钱,哪几个 SKU 看起来卖得多但实际亏钱。“


BI 指标看板与智能补货完整流程

业务背景

BI 不是简单做图表,而是把 OMS、WMS、PMS、TMS、FMS、SRM 这些模块的数据汇总起来,变成经营指标和决策建议。

比如老板关心 GMV、净利润、现金流;运营关心 SKU 利润和广告 ACOS;采购关心补货建议和供应商准时率;仓库关心库存周转、缺货率和积压率;财务关心账单差异、应收应付和 VAT。

BI 数据处理流程

flowchart TD
    A[业务系统原始数据] --> B[ETL 定时聚合]
    B --> C[清洗和转换]
    C --> D[计算 KPI 指标]
    D --> E[写入 BI 汇总表]
    E --> F[写入 Redis 热点报表缓存]
    F --> G[Dashboard 展示]
    D --> H[判断 KPI 阈值]
    H --> I[生成预警通知]
    D --> J[计算销售预测]
    J --> K[生成补货建议]

ETL 的含义

ETL 就是抽取、转换、加载。

抽取是从订单、库存、采购、物流、财务这些业务表里取数据。

转换是清洗数据、统一时间口径、统一币种、计算 GMV、净利润率、库存周转率、缺货率、OTD 等指标。

加载是把结果写入 BI 汇总表或 Redis 缓存,给前端 Dashboard 快速查询。

智能补货流程

智能补货依赖销售预测。系统会先统计 SKU 最近 30 天或 90 天销量,再用加权移动平均预测未来日均销量。

预测日均销量 = 近 7 天日均 * 50% + 近 8 到 14 天日均 * 30% + 近 15 到 30 天日均 * 20%

建议补货量 = 预测日均销量 * (Lead Time + 安全天数) - (可用库存 + 在途库存)

如果建议补货量大于 0,系统生成补货建议单,推送给采购专员。采购专员确认后,可以一键转成 PMS 采购申请。

面试时怎么讲

“BI 模块我不会只说做了几个 ECharts 图表,因为图表只是展示层,真正有价值的是指标计算和决策建议。

我的理解是,BI 是供应链系统的大脑。OMS 负责订单,WMS 负责库存,PMS 负责采购,TMS 负责物流,FMS 负责财务,但管理层和运营人员需要的是一个统一视角:现在卖得怎么样,赚不赚钱,会不会断货,哪些指标异常。

所以我做了一个 BI 数据处理流程。每天定时任务从订单、库存、采购、物流、财务表里抽取数据,经过清洗和转换后,计算 GMV、订单量、净利润率、库存周转率、缺货率、物流异常率、采购准时到货率等 KPI。计算结果写入 BI 汇总表,高频看板数据再放到 Redis 缓存。

这里有两个重点。

第一个是 KPI 预警。系统会有 bi_kpi_threshold 阈值配置表,每个租户可以配置自己的预警阈值。比如缺货率超过 2% 黄色预警,超过 5% 红色预警;净利润率低于 10% 预警,低于 0 说明亏损。定时任务计算完 KPI 后,会和阈值表对比,超过阈值就生成站内信或企业微信通知。

第二个是智能补货。系统会根据历史销量预测未来日均销量,再结合当前可用库存、在途库存、供应商 Lead Time 和安全库存天数计算建议补货量。比如一个 SKU 预测每天卖 10 件,供应商交期 7 天,安全库存 15 天,那么系统至少要覆盖 22 天销量。如果现有库存加在途只有 100 件,就会建议采购 120 件左右。

这个模块的价值是把数据从事后报表变成事前预警和建议。不是等断货了才发现,也不是等月底才知道亏钱,而是提前通过 KPI 和预测把问题暴露出来。“


技术亮点

大文件账单解析与自动对账

为什么需要这个设计

平台账单文件可能有几万行,甚至几十万行。如果用户上传后在 HTTP 请求线程里同步解析,会出现三个问题:

  • 请求超时,用户一直等待
  • 文件一次性读入内存,容易 OOM
  • 单条插入数据库,性能很差

所以账单解析要做成异步、流式、批量、可追踪状态的流程。

技术方案

核心方案是:OSS 存储原始文件 + 账单状态机 + 线程池或 MQ 异步解析 + EasyExcel 或 CSV 流式读取 + 批量插入 + 自动匹配。

账单状态建议设计为:

  • 待解析
  • 解析中
  • 已解析
  • 对账完成
  • 有差异
  • 解析失败

关键代码

@Service
public class PlatformBillImportService {

    private final Executor billParseExecutor;
    private final PlatformBillMapper platformBillMapper;
    private final BillParseTaskService billParseTaskService;

    @Transactional(rollbackFor = Exception.class)
    public Long uploadBill(BillUploadCommand command) {
        String fileUrl = command.getFileUrl();

        PlatformBill bill = new PlatformBill();
        bill.setTenantId(command.getTenantId());
        bill.setPlatform(command.getPlatform());
        bill.setStoreId(command.getStoreId());
        bill.setSourceFileUrl(fileUrl);
        bill.setStatus(BillStatus.PENDING_PARSE.getCode());
        platformBillMapper.insert(bill);
		
		// 此处使用的是线程池的方式
		// 我们也可以使用MQ的方式
        billParseExecutor.execute(() -> billParseTaskService.parseBill(bill.getId()));
        return bill.getId();
    }
}
@Service
public class BillParseTaskService {

    private static final int BATCH_SIZE = 1000;

    public void parseBill(Long billId) {
	    // 账单主表的状态 默认是 待解析 现在更新为 解析中 
        updateStatus(billId, BillStatus.PARSING);
		// 从OSS下载账单源文件 然后开始进行逐行解析
        try (InputStream inputStream = downloadBillFile(billId)) {
        
	        // 把解析的每条数据 都存储到这个集合中 然后批量写入到MySQL
            List<FinanceBillItem> batch = new ArrayList<>(BATCH_SIZE);
			// 创建表格的解析器
            CsvReader reader = CsvReader.builder().build(inputStream);
            // Excel 的解析分为 DOM 解析 SAX 解析 
            // DOM 解析的方式是 把整个DOM树 一次性加入到内存中 容易引起内存的溢出 OOM  --> 可以操作DOM节点 --> 节点搜索
            // SAX 一行一行进行解析 读取一行 释放一行内存 对应内存非常友好 不会导致内存的溢出 --> 不能进行节点的搜索
            for (CsvRow row : reader) {
	            // row 就是读取到的Excel表格中的每行记录
	            // 把这个读取的每行的记录数据 转成 数据库中的Bean 因为字段可能是不对应的 所以需要做字段的匹配
                FinanceBillItem item = convertToBillItem(billId, row);
                // 把拼凑好的表对应的Bean对象 存储到集合中
                batch.add(item);
				// 如果这个集合中的元素的数量 已经达到了我们指定的1000 就批量的插入到数据库表中
                if (batch.size() >= BATCH_SIZE) {
		            // 批量插入数据库 一次性插入1000
                    billItemMapper.insertBatch(batch);
                    // 把插入到数据库中的数据 清空 然后装下一批数据
                    batch.clear();
                }
            }
			// 如果整个Excel已经读完了 那么上面的for循环就结束了
			// 如果集合中还有数据 但是不满1000条 再次插入到数据库中
            if (!batch.isEmpty()) {
                billItemMapper.insertBatch(batch);
            }

            calculateSummary(billId);
            matchOrders(billId);
            // 把解析中 --> 解析完成
            updateStatus(billId, BillStatus.PARSED);
        } catch (Exception e) {
            updateStatus(billId, BillStatus.PARSE_FAILED);
            throw new BusinessException("账单解析失败");
        }
    }
}

自动对账 SQL 思路

UPDATE finance_bill_item bi
JOIN order_main om
  ON bi.tenant_id = om.tenant_id
 AND bi.order_no = om.platform_order_no
SET bi.is_matched = 1,
    bi.match_order_id = om.id
WHERE bi.bill_id = #{billId}
  AND bi.order_no IS NOT NULL
  AND bi.is_matched = 0;

面试时怎么讲

“账单解析这个技术点,我会从大文件和财务可追溯两个角度讲。

平台账单不是普通的小表单,而是一个结算周期内所有收入和扣费的明细文件,可能几万行。如果同步解析,用户请求会超时;如果一次性读入内存,文件大了会 OOM;如果一条一条插入数据库,数据库压力也很大。

所以我的实现是上传和解析分离。用户上传文件后,系统先把原始文件存到 OSS,再创建主账单记录,状态是待解析。真正解析由线程池异步执行。

解析时我用流式读取,不把整个文件加载到内存。每读取一行,就转换成 finance_bill_item,识别它是销售收入、退款、推荐费、FBA 费还是广告费。数据不是单条插入,而是每 1000 条批量入库一次。解析完以后再统一汇总主账单。

对账这块,我先用平台订单号和 OMS 订单做自动匹配。能匹配上的明细直接更新 is_matched 和内部订单 ID。匹配不上的进入差异清单。差异分为账单有系统无、系统有账单无和金额不一致三类。

这个方案的好处是用户上传后可以立即返回账单 ID,前端轮询解析状态;后端解析过程可追踪、失败可重试、原始文件可审计。它不是简单读个 CSV,而是把外部平台账单变成系统内可核对、可追溯、可参与利润核算的财务数据。“


SKU 级利润快照与成本分摊

为什么这是核心亮点

FMS 最能体现业务深度的地方就是利润核算。

如果只做销售额统计,面试官很容易觉得这是普通报表。但如果能讲清楚 SKU 级全成本拆解、费用分摊、预估利润和确认利润,就能体现真实供应链财务系统的复杂度。

快照设计

利润快照表按租户、SKU、平台、店铺、日期维度存储结果:

  • snapshot_type 区分日快照和月快照
  • gross_revenue_cny 存销售收入人民币金额
  • purchase_cost 存采购成本
  • logistics_fee 存物流费用
  • platform_fee 存平台手续费
  • advertising_fee 存广告费
  • refund_loss 存退款损失
  • vat_fee 存 VAT 税费
  • gross_profit 存毛利润
  • net_profit 存净利润
  • net_margin 存净利润率

成本分摊原则

有些费用可以直接归属到订单,比如平台推荐费、FBA 费、退款。

有些费用不能直接归属到订单,比如月度广告费、仓储费、平台月租。这类费用要按规则分摊。常见规则有:

  • 按销量分摊
  • 按销售额分摊
  • 按库存占用天数分摊
  • 按 SKU 毛利贡献分摊

分摊规则必须固定,并且要在系统里留痕,否则财务报表前后口径不一致。

关键代码

public ProfitResult calculateSkuProfit(SkuProfitContext context) {
    BigDecimal revenue = context.getRevenueCny();
    BigDecimal purchaseCost = context.getPurchaseCost();
    BigDecimal logisticsFee = context.getLogisticsFee();
    BigDecimal platformFee = context.getPlatformFee();
    BigDecimal advertisingFee = context.getAdvertisingFee();
    BigDecimal storageFee = context.getStorageFee();
    BigDecimal refundLoss = context.getRefundLoss();
    BigDecimal vatFee = context.getVatFee();

    BigDecimal grossProfit = revenue
        .subtract(purchaseCost)
        .subtract(logisticsFee)
        .subtract(platformFee);

    BigDecimal netProfit = grossProfit
        .subtract(advertisingFee)
        .subtract(storageFee)
        .subtract(refundLoss)
        .subtract(vatFee)
        .subtract(context.getOtherCost());

    BigDecimal netMargin = BigDecimal.ZERO;
    if (revenue.compareTo(BigDecimal.ZERO) > 0) {
        netMargin = netProfit
            .divide(revenue, 4, RoundingMode.HALF_UP)
            .multiply(BigDecimal.valueOf(100));
    }

    return new ProfitResult(grossProfit, netProfit, netMargin);
}

幂等写入

INSERT INTO finance_profit_snapshot (
    tenant_id, snapshot_type, snapshot_date, sku_id, platform, store_id,
    gross_revenue_cny, purchase_cost, logistics_fee, platform_fee,
    advertising_fee, refund_loss, vat_fee, gross_profit, net_profit, net_margin
) VALUES (
    #{tenantId}, #{snapshotType}, #{snapshotDate}, #{skuId}, #{platform}, #{storeId},
    #{grossRevenueCny}, #{purchaseCost}, #{logisticsFee}, #{platformFee},
    #{advertisingFee}, #{refundLoss}, #{vatFee}, #{grossProfit}, #{netProfit}, #{netMargin}
)  
-- 利润快照表 只能新增 不能修改 不能删除
-- 如果数据不存在 就进行插入 如果数据存在 就进行更新
-- 在业务层中 不需要执行 delete update 相关的操作 只能执行 insert 操作
ON DUPLICATE KEY UPDATE
    gross_revenue_cny = VALUES(gross_revenue_cny),
    purchase_cost = VALUES(purchase_cost),
    logistics_fee = VALUES(logistics_fee),
    platform_fee = VALUES(platform_fee),
    advertising_fee = VALUES(advertising_fee),
    refund_loss = VALUES(refund_loss),
    vat_fee = VALUES(vat_fee),
    gross_profit = VALUES(gross_profit),
    net_profit = VALUES(net_profit),
    net_margin = VALUES(net_margin);

面试时怎么讲

“利润核算这个亮点,我会重点讲全成本和快照。

首先,SKU 利润不能只用售价减采购价。跨境场景里,成本特别多,包括平台推荐费、FBA 配送费、头程物流费、广告费、仓储费、退款损失、VAT 税费。少算任何一项,利润都会失真。

所以我设计的是 SKU 级利润快照。每天或每月按 SKU、平台、店铺维度生成一条快照,里面保存收入、采购成本、物流费、平台费、广告费、退款、VAT、毛利润、净利润和净利润率。

这里最难的是费用分摊。像平台推荐费一般能通过订单号直接匹配,但广告费、仓储费往往是周期性费用,不一定能直接落到某一笔订单。所以我会根据业务口径做分摊,比如广告费按 SKU 销售额或销量分摊,仓储费按库存占用天数分摊。分摊规则一旦确定,要保持稳定,并且在系统里留痕。

另外我会区分预估利润和确认利润。订单完成时先算预估利润,方便运营及时看数据;平台账单对账完成后,再用真实平台扣费重算确认利润。写入快照时用唯一索引保证幂等,定时任务重复执行也不会产生重复数据。

这个亮点的价值是,运营能看到每个 SKU 的真实利润率。比如某个商品 GMV 很高,但广告费和退款率也高,净利润率可能是负的,系统会自动标记为亏损 SKU。这样业务不再只看销量,而是看真实利润。“


多货币汇率、汇兑损益与现金流预测

业务问题

跨境卖家会同时面对 USD、EUR、GBP、JPY 等货币。订单发生时一个汇率,平台结算时一个汇率,实际换汇时可能又是另一个汇率。

如果系统只存人民币金额,后续无法解释差异;如果只存外币金额,报表又无法统一比较。

双字段存储

财务金额建议同时保存:

  • 原币金额:amount_origin
  • 原币币种:currency
  • 折算人民币金额:amount_cny
  • 使用汇率:exchange_rate
  • 汇率日期:rate_date

这样既能保留原始业务事实,又能支撑人民币口径报表。

汇率同步任务

@XxlJob("syncExchangeRateJob")
public void syncExchangeRateJob() {
    LocalDate rateDate = LocalDate.now();
    Map<String, BigDecimal> latestRates = exchangeRateClient.queryLatestRates();

    for (Map.Entry<String, BigDecimal> entry : latestRates.entrySet()) {
        ExchangeRate rate = new ExchangeRate();
        rate.setRateDate(rateDate);
        rate.setCurrency(entry.getKey());
        rate.setRateToCny(entry.getValue());
        rate.setRateSource("OpenExchange");
        exchangeRateMapper.upsert(rate);
    }

    exchangeRateAlertService.checkFluctuation(rateDate);
}

汇兑损益

汇兑损益不是订单错误,而是汇率变化导致的财务差异。

比如 1 月 1 日平台有 10000 美元待结算,当天汇率 7.20,账面价值是 72000 元。

1 月 15 日平台打款时汇率变成 7.15,实际折算是 71500 元,那么发生 500 元汇兑损失。

这笔损失一般记录到财务损益里,不会修改原订单金额。

现金流预测

现金流预测不是利润预测,而是预测未来账户会进多少钱、出多少钱。

预计收入来自平台待打款账单,预计支出来自供应商应付、物流应付、VAT 缴纳、广告充值计划。

SELECT
    flow_date,
    SUM(CASE WHEN flow_type = 'INCOME' THEN amount_cny ELSE 0 END) AS income,
    SUM(CASE WHEN flow_type = 'PAYMENT' THEN amount_cny ELSE 0 END) AS payment,
    SUM(CASE WHEN flow_type = 'INCOME' THEN amount_cny ELSE -amount_cny END) AS net_cash_flow
FROM finance_cash_flow_forecast
WHERE tenant_id = #{tenantId}
  AND flow_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY)
GROUP BY flow_date
ORDER BY flow_date;

面试时怎么讲

“多货币这个点,我会从双字段存储、汇率快照和汇兑损益三个方面讲。

跨境卖家可能美国站收美元,欧洲站收欧元,日本站收日元。如果系统只存人民币,后续对账时解释不清楚原始金额;如果只存外币,老板看经营报表又没办法统一比较。所以我设计的是双字段存储,原币金额和人民币金额都保存,同时保存使用的汇率和汇率日期。

汇率通过 XXL-Job 每天定时同步。同步成功后写入 finance_exchange_rate,用日期和币种做唯一索引,保证同一天同一币种只有一条汇率。同步后还会和昨日汇率比较,如果波动超过 2%,通知财务专员。这个告警的意义不是让财务改汇率,而是提醒今日利润核算、外币应收和换汇决策可能受影响。

汇兑损益这块,我会举例讲。比如平台 1 月 1 日形成 10000 美元应收,按 7.20 记账是 72000 元。1 月 15 日实际到账时汇率是 7.15,折算只有 71500 元,这 500 元就是汇兑损失。它不是订单少收了,也不是系统算错了,而是汇率变化导致的财务损益。

最后我会补充现金流预测。利润高不代表现金流安全,因为平台可能还没打款,但供应商款马上要付。所以系统会根据平台账单预计到账日、供应商应付到期日、物流账单和 VAT 缴纳计划,预测未来 30 天每天的资金流入流出。如果某天净现金流为负,就提前预警。“


BI ETL、KPI 预警与 Redis 报表缓存

为什么 BI 不能直接查业务表

业务表是给交易流程用的,比如订单表、库存表、采购单、运单、账单明细。BI 看板要做的是跨模块聚合,如果每次打开首页都实时 JOIN 多张大表,性能会很差。

所以 BI 适合用 ETL 定时聚合,把常用指标提前算好。

KPI 指标体系

常见 KPI 包括:

  • 销售类:GMV、订单量、客单价
  • 库存类:库存周转率、库存天数、缺货率、积压率
  • 采购类:准时到货率、质量合格率
  • 物流类:正常签收率、物流异常率、平均配送时效
  • 财务类:毛利润率、净利润率、退款率、广告 ACOS

KPI 预警流程

预警的阈值可以让租户自己进行配置 SaaS系统在进行KPI的指标计算后 需要去匹配预警的阈值 看是否达到了阈值 如果达到了阈值就需要进行预警(通知相关的工作人员)

flowchart TD
    A[定时任务计算 KPI] --> B[读取 bi_kpi_threshold]
    B --> C{是否超过阈值}
    C -->|否| D[记录正常指标]
    C -->|是| E[生成 KPI 预警记录]
    E --> F[查询通知角色]
    F --> G[发送站内信或企业微信]
    G --> H[负责人处理预警]

Redis 缓存策略

适合缓存的数据:

  • 首页经营总览
  • 近 30 天销售趋势
  • SKU 利润排行榜
  • 库存健康统计
  • KPI 看板
  • 现金流预测结果

缓存更新策略:

  • 实时看板设置短 TTL,比如 5 分钟
  • 日报表设置 1 小时 TTL
  • 月报表设置 24 小时 TTL
  • 订单完成、账单对账完成、库存变动后主动删除相关缓存

面试时怎么讲

“BI 这块我会重点讲 ETL、KPI 和缓存。

首先,BI 不是直接在前端画几个图。真正的难点是数据来源分散。销售数据在 OMS,库存数据在 WMS,采购数据在 PMS,物流数据在 TMS,财务数据在 FMS。如果每次打开看板都实时查这些业务表,SQL 会很复杂,响应也会很慢。

所以我做的是 ETL 聚合。每天或者每小时定时从业务表抽取数据,清洗后统一口径,比如统一时间、统一币种、统一租户隔离,然后计算 GMV、净利润率、库存周转率、缺货率、物流异常率、采购准时率等指标,写入 BI 汇总表。

第二个重点是 KPI 预警。我们有 bi_kpi_threshold 表,每个租户可以配置自己的阈值。比如缺货率大于 2% 预警,大于 5% 危险;净利润率小于 10% 预警,小于 0 就是亏损。定时任务算完 KPI 后和阈值表对比,超过阈值就生成预警,并通知对应角色。

第三个重点是 Redis 缓存。经营总览、销售趋势、利润排行这些报表访问频繁,但短时间内不要求秒级实时,所以可以缓存。缓存策略用 Cache Aside,先查 Redis,没有再查汇总表,查询完成写入 Redis。数据变更时,比如账单对账完成、订单完成、库存变动,就删除相关缓存。

这个方案解决的是 BI 报表慢、指标口径不统一、问题发现滞后的问题。上线后,管理层打开首页可以快速看到经营状态,运营和采购也能通过 KPI 预警提前处理缺货、低利润和物流异常。“


销售预测与智能补货建议

为什么需要智能补货

补货太少会断货,补货太多会积压库存,占用现金流。跨境供应链还有 Lead Time,今天下采购单可能 7 天、15 天甚至 30 天后才到货,所以不能等库存快没了才补。

智能补货要回答三个问题:

  • 未来每天大概能卖多少
  • 当前库存还能支撑多少天
  • 现在应该补多少货

加权移动平均算法

public BigDecimal calculateForecastDailySales(SalesWindow window) {
    BigDecimal avg7 = window.getAvgLast7Days();
    BigDecimal avg14 = window.getAvgLast8To14Days();
    BigDecimal avg30 = window.getAvgLast15To30Days();

    return avg7.multiply(new BigDecimal("0.50"))
        .add(avg14.multiply(new BigDecimal("0.30")))
        .add(avg30.multiply(new BigDecimal("0.20")))
        .setScale(2, RoundingMode.HALF_UP);
}

补货建议计算

public ReplenishmentSuggestion calculateSuggestion(ReplenishmentContext context) {
    BigDecimal forecastDailySales = context.getForecastDailySales();
    int leadTime = context.getLeadTimeDays();
    int safetyDays = context.getSafetyStockDays();

    BigDecimal targetStock = forecastDailySales
        .multiply(BigDecimal.valueOf(leadTime + safetyDays));

    BigDecimal currentStock = BigDecimal.valueOf(context.getAvailableQty() + context.getInTransitQty());
    BigDecimal suggestionQty = targetStock.subtract(currentStock);

    if (suggestionQty.compareTo(BigDecimal.ZERO) <= 0) {
        return ReplenishmentSuggestion.noNeed(context.getSkuId());
    }

    return ReplenishmentSuggestion.needPurchase(
        context.getSkuId(),
        suggestionQty.setScale(0, RoundingMode.CEILING).intValue()
    );
}

ABC 分类联动

A 类商品贡献高,断货影响大,安全天数可以高一些。

B 类商品按正常安全库存管理。

C 类商品贡献低,安全库存可以低一些,避免资金占用。

ABC 分类会影响补货公式里的安全天数。

ABC的分类会影响到 库存的存储的安全天数不一样 盘点的频率也不一样

ABC 怎么进行判断的 使用定时任务 根据每个SKU的销量总额进行排序 前80% 是A类 中间10% 是B类 末尾的10% 是C类

面试时怎么讲

“智能补货这个点,我会从预测销量和补货公式讲。

跨境供应链不是库存少了马上就能补,因为供应商有生产周期,物流有运输周期,入库还要质检。所以系统必须提前预测未来销量,再结合 Lead Time 算建议补货量。

销量预测我用的是加权移动平均。近 7 天权重最高,比如 50%;近 8 到 14 天权重 30%;近 15 到 30 天权重 20%。这样既能参考历史,又能让最近的销量变化对预测更敏感。

补货公式是:预测日均销量乘以 Lead Time 加安全天数,再减去可用库存和在途库存。比如预测每天卖 10 件,供应商 Lead Time 是 7 天,安全天数是 15 天,那么目标库存就是 220 件。如果当前可用库存 80 件,在途库存 30 件,就建议补 110 件。

这里还会联动 ABC 分类。A 类 SKU 销售贡献高,断货损失大,所以安全天数更高;C 类 SKU 是长尾商品,安全天数更低,避免积压。

最后系统不会直接自动下采购单,而是生成补货建议推送给采购专员。采购专员确认后可以一键转 PMS 采购申请。这样既有智能建议,也保留了人工决策。“


VAT 申报数据生成与合规留痕

业务背景

VAT 是欧洲等市场常见的增值税。跨境卖家把商品卖给欧洲消费者时,需要按照目的国税率申报销售额和税额。

系统不是替税务机关决定交多少税,而是把订单、退款、目的国、税率和平台代扣代缴情形整理出来,生成申报数据和留痕。

数据来源

  • 平台账单销售明细
  • OMS 订单目的国
  • 退款记录
  • VAT 税率配置
  • 平台是否代扣代缴标识
  • OSS 注册号和申报期

流程

flowchart TD
    A[申报期结束] --> B[查询欧洲目的国订单和账单]
    B --> C[扣除退款和不应重复申报金额]
    C --> D[按目的国汇总应税销售额]
    D --> E[查询 VAT 税率]
    E --> F[计算应缴 VAT]
    F --> G[生成 finance_vat_record]
    G --> H[导出 OSS 申报文件]
    H --> I[财务或税务顾问提交申报]
    I --> J[缴税完成后写入资金流水]

面试时怎么讲

“VAT 这块我会讲清楚系统边界。

我们的 SaaS 系统不是直接替税务局算最终税单,而是帮助卖家生成申报所需的数据。系统会按申报期查询欧洲目的国订单和平台账单,扣除退款,排除平台已代扣代缴的数据,然后按国家汇总应税销售额。

接着系统根据国家和申报期查询 VAT 税率,计算应缴税额,写入 finance_vat_record。财务专员可以导出 OSS 申报格式文件,再由财务或税务顾问登录官方系统提交。

这里的技术重点是数据留痕。每个 VAT 申报记录都要能追溯到哪些订单、哪些退款、使用了哪个税率、生成时间是什么。这样后续审计或复核时,系统能解释为什么这个国家这个季度申报了这些销售额和税额。

缴税完成后,VAT 记录状态从待申报变成已申报,再变成已缴纳,同时写入 finance_cash_flow。这样税务数据和现金流能闭环。“


简历怎么写

项目描述

项目:跨境电商 SaaS 供应链管理平台 - 财务结算与数据分析模块
时间:2024.06 - 2024.12
角色:核心开发
技术:Spring Boot 3.2、MyBatis-Plus、MySQL 8.0、Redis、RocketMQ、XXL-Job、EasyExcel、ECharts

这是一个面向跨境电商卖家的 SaaS 供应链平台,我负责 FMS 财务结算和 BI 数据分析模块。
系统支持多平台账单导入与自动对账、SKU 级利润核算、多货币汇率折算、汇兑损益、
VAT 申报数据导出、现金流预测、KPI 预警、销售预测和智能补货建议。

日均处理平台账单 100+ 份,账单明细 30 万+ 行,覆盖 5000+ SKU 的利润核算,
支持 Amazon、TikTok Shop、Shopee、Temu、Shopify 等多平台结算数据接入。

核心难点:
1. 平台账单文件大,解析慢,且需要保留原始文件和解析状态
2. 平台账单和内部订单存在跨周期、缺失、金额不符等差异
3. SKU 利润核算需要整合订单、采购、库存、物流、平台费用和 VAT
4. 多平台多币种结算,需要处理汇率快照和汇兑损益
5. BI 看板要跨模块聚合数据,不能每次实时 JOIN 大表
6. KPI 预警和智能补货需要从事后报表升级为事前决策建议

核心亮点

亮点1:异步解析平台账单大文件

问题:Amazon、Shopee 等平台账单文件可能有几万行,直接同步解析会导致接口超时和内存压力。

方案:采用 OSS 原始文件存储 + 线程池异步解析 + 流式读取 + 批量入库。

具体实现:

  • 上传文件后先保存 OSS,并创建 finance_platform_bill
  • 使用账单状态机跟踪待解析、解析中、已解析、解析失败
  • 使用流式读取逐行解析 CSV 或 Excel,避免一次性加载到内存
  • 每 1000 条 finance_bill_item 批量插入一次
  • 解析完成后自动汇总账单主表并触发订单匹配

效果:

  • 上传接口从分钟级等待变成秒级返回
  • 支持 100MB 级账单文件解析
  • 解析失败可重试,原始文件可审计

亮点2:平台账单自动对账与差异分析

问题:平台账单金额和系统订单金额经常不一致,人工逐条核对效率低。

方案:基于平台订单号、SKU、金额和结算周期做自动匹配,并生成差异清单。

具体实现:

  • 通过 platform_order_no 匹配 OMS 内部订单
  • 自动识别账单有系统无、系统有账单无、金额不符三类差异
  • 设置自动匹配率阈值,低于阈值进入人工处理
  • 对账完成后触发利润重算,保证最终利润使用真实平台扣费

效果:

  • 大部分订单类账单明细可自动匹配
  • 财务只处理异常差异,减少人工核对工作量
  • 对账过程可追溯,支撑后续利润核算和审计

亮点3:SKU 级全成本利润核算

问题:卖家只看 GMV 无法判断哪些 SKU 真正赚钱。

方案:设计 SKU 级利润快照,整合销售收入、采购成本、物流费、平台费、广告费、仓储费、退款损失和 VAT。

具体实现:

  • 销售收入按平台账单或 OMS 订单获取
  • 采购成本使用 WMS 加权平均成本
  • 物流费来自 TMS 实际运费
  • 平台费、广告费来自 finance_bill_item
  • 广告费、仓储费按销量、销售额或库存占用进行分摊
  • 日快照和月快照写入 finance_profit_snapshot

效果:

  • 支持 SKU、平台、店铺、日期多维度利润分析
  • 自动识别亏损 SKU 和低利润 SKU
  • 帮助运营从看销量转向看利润

亮点4:多货币汇率快照与汇兑损益

问题:跨境平台多币种结算,汇率变化会影响实际收益。

方案:采用原币金额 + 人民币金额 + 汇率快照的双字段存储模式。

具体实现:

  • XXL-Job 每天同步 USD、EUR、GBP、JPY 等汇率
  • rate_date + currency 唯一索引保证幂等
  • 财务数据保存原币金额、折算人民币金额和使用汇率
  • 汇率波动超过阈值时通知财务
  • 实际到账时计算汇兑收益或损失

效果:

  • 历史财务记录不会因汇率变化被污染
  • 汇兑损益可单独统计
  • 多平台多币种报表能统一折算成人民币口径

亮点5:BI ETL 聚合与 Redis 报表缓存

问题:BI 看板需要跨订单、库存、采购、物流、财务多模块统计,实时查询慢。

方案:采用定时 ETL 聚合 + BI 汇总表 + Redis 热点报表缓存。

具体实现:

  • 定时从 OMS、WMS、PMS、TMS、FMS 抽取数据
  • 清洗时间、币种、租户等口径
  • 计算 GMV、净利润率、库存周转率、缺货率、物流异常率等 KPI
  • 结果写入 BI 汇总表
  • 首页看板、趋势图、利润排行写入 Redis 缓存
  • 订单完成、账单对账完成、库存变动时主动删除缓存

效果:

  • 首页看板响应从秒级降低到毫秒级
  • 指标口径统一,避免各模块重复计算
  • 报表查询压力从业务表转移到汇总表和缓存

亮点6:KPI 阈值预警机制

问题:报表只能告诉用户已经发生了什么,不能主动提醒业务风险。

方案:设计 bi_kpi_threshold 阈值配置表,支持租户级 KPI 预警。

具体实现:

  • 每个 KPI 配置黄色预警和红色危险阈值
  • 支持大于触发和小于触发两种比较方式
  • 定时任务计算 KPI 后和阈值表比较
  • 超过阈值生成预警记录,并通知对应角色
  • 支持缺货率、净利润率、物流异常率、采购准时率等指标

效果:

  • 缺货、亏损、物流异常可以提前暴露
  • 不同租户可以配置自己的经营阈值
  • BI 从被动报表升级为主动预警

亮点7:销售预测与智能补货

问题:人工补货容易过量或不足,断货和积压都会影响利润和现金流。

方案:用加权移动平均预测日销量,再结合 Lead Time、安全库存、可用库存和在途库存计算补货建议。

具体实现:

  • 近 7 天销量权重 50%,近 8 到 14 天权重 30%,近 15 到 30 天权重 20%
  • 计算预测日均销量
  • 建议补货量 = 预测日均销量 * (Lead Time + 安全天数) - 可用库存 - 在途库存
  • ABC 分类影响安全天数
  • 补货建议可一键转 PMS 采购申请

效果:

  • 提前发现断货风险
  • 减少经验式补货带来的库存积压
  • 采购计划和销售预测形成闭环

亮点8:VAT 申报数据生成与资金闭环

问题:欧洲 VAT 申报需要按目的国汇总销售额、退款和税率,人工整理容易出错。

方案:系统按申报期自动生成 VAT 申报记录和导出文件。

具体实现:

  • 查询欧洲目的国订单和平台账单
  • 扣除退款和平台代扣代缴金额
  • 按国家汇总应税销售额
  • 查询 VAT 税率配置并计算应缴税额
  • 写入 finance_vat_record
  • 缴税完成后写入 finance_cash_flow

效果:

  • VAT 申报数据可追溯到订单明细
  • 减少人工整理税务数据的错误
  • 申报、缴税、资金流水形成闭环

面试高频问题

Q1:平台账单主表和账单明细表是什么关系?

一对多关系。

finance_platform_bill 是账单主表,表示某个平台、某个店铺、某个结算周期的一份结算报告。它保存汇总信息,比如销售总额、退款总额、平台费用、净到账金额、结算开始和结束日期。

finance_bill_item 是账单明细表,保存这份账单里的每一行收入或扣费,比如某个订单的商品收入、推荐费、FBA 费、退款、广告费、仓储费。

主表像账单封面,明细表像账单清单。

追问:这些数据是人工录入的吗?

答:通常不是人工逐条录入。主表和明细表主要来自平台账单文件导入或平台 API 拉取。财务专员负责上传、确认和处理异常,系统负责解析、匹配和汇总。

Q2:为什么账单匹配率不是百分之百?

因为平台账单里不是所有明细都能和系统订单一一对应。

比如广告费、仓储费、平台调整项可能没有订单号;跨周期退款可能发生在本期账单,但订单属于上期;有些平台订单可能因为同步失败没有进入 OMS;还有些费用是平台临时补扣或补发。

所以 95% 是自动匹配率阈值,不是财务准确率。系统自动匹配大部分订单类明细,剩余异常进入差异清单,由财务确认。

追问:匹配不上怎么办?

答:分三类处理。账单有系统无,要补同步或标记平台调整;系统有账单无,要标记为待下期核对;金额不符,要分析手续费、退款、汇率或平台调整原因。

Q3:如何解析几十万行账单文件?

核心是异步、流式、批量。

上传文件后先存 OSS,插入主账单记录,然后异步解析。解析时用流式读取,不一次性加载到内存;每行解析成账单明细;每 1000 条左右批量插入数据库。

同时用账单状态记录解析进度和结果,比如待解析、解析中、已解析、解析失败。失败后可以重新触发解析。

Q4:为什么利润核算要做成 SKU 级?

因为店铺整体赚钱不代表每个 SKU 都赚钱。

有些 SKU 销量高,但广告费高、退款率高、FBA 费高,最后可能亏损。如果只看店铺总利润,很难发现这些利润黑洞。

SKU 级利润核算可以告诉运营:哪个 SKU 赚钱,哪个 SKU 亏钱,亏损原因是采购成本高、广告费高、退款高还是物流费高。

Q5:广告费和仓储费如何分摊到 SKU?

要看费用类型和业务口径。

广告费如果平台能提供 SKU 级广告数据,就直接按 SKU 归集。如果只有店铺或广告活动级别,可以按 SKU 销售额、销量或广告归因销售额分摊。

仓储费通常按库存占用分摊,比如 SKU 库存体积、数量、占用天数。

关键是分摊规则要固定,并且要留痕。否则同一份利润报表今天按销量分摊,明天按销售额分摊,前后就不可比。

Q6:预估利润和确认利润有什么区别?

预估利润是在订单完成后先算一版,用于运营快速判断商品是否赚钱。

确认利润是在平台账单对账完成后再算一版,用真实的平台扣费、广告费、FBA 费替换预估值。

预估利润适合及时看趋势,确认利润适合财务报表和月度经营分析。

Q7:多货币金额为什么要双字段存储?

因为跨境系统既要保留原始金额,又要统一报表口径。

原始金额用于对账,比如平台账单是 1000 USD,就必须保留 1000 USD。人民币金额用于统一统计,比如老板要看所有平台总收入,就要把 USD、EUR、JPY 统一折算。

所以金额表里通常保存原币金额、币种、人民币金额、汇率和汇率日期。

Q8:汇兑损益是怎么产生的?

汇兑损益来自汇率变化。

比如平台 1 月 1 日形成 5000 美元应收,当天汇率 7.20,账面价值 36000 元。1 月 15 日实际到账时汇率 7.10,折算只有 35500 元,这 500 元就是汇兑损失。

它不是订单错误,而是汇率变化导致的财务损益。系统应该单独记录,不应该回改原订单金额。

Q9:现金流预测和利润预测有什么区别?

利润看的是赚不赚钱,现金流看的是钱什么时候进、什么时候出。

比如这个月利润很高,但平台 14 天后才打款,供应商货款明天就要付,这时现金流可能很紧张。

现金流预测会根据平台预计到账日、供应商应付到期日、物流账单、VAT 缴纳计划,预测未来 30 天每天的资金流入和流出。

Q10:BI 为什么要做 ETL?

因为 BI 看板要跨多个模块聚合数据。

如果每次打开看板都实时 JOIN 订单表、库存表、采购表、物流表、财务表,SQL 会很复杂,响应会很慢,还会影响业务库性能。

ETL 的做法是定时抽取业务数据,清洗后提前计算指标,写入 BI 汇总表。前端看板查汇总表或 Redis 缓存,性能更稳定。

Q11:KPI 阈值预警怎么实现?

先定义 KPI 阈值配置表,比如 bi_kpi_threshold,里面有 KPI 编码、黄色预警值、红色危险值、比较方式、通知角色。

定时任务计算 KPI 后,和阈值表比较。如果超过阈值,就生成预警记录,并通知对应角色。

比如缺货率超过 2% 黄色预警,超过 5% 红色预警;净利润率低于 10% 预警,低于 0 说明亏损。

Q12:报表缓存怎么设计?

高频、查询慢、短时间不要求绝对实时的报表适合缓存,比如经营总览、销售趋势、利润排行、库存健康、KPI 看板。

缓存策略可以用 Cache Aside。先查 Redis,命中直接返回;没有命中查 BI 汇总表,查完写入 Redis。

更新策略是 TTL + 主动失效。比如订单完成删除销售报表缓存,账单对账完成删除利润报表缓存,库存变动删除库存健康缓存。

Q13:智能补货建议怎么计算?

先预测日均销量,再结合库存和供应商交期。

预测日均销量可以用加权移动平均:近 7 天权重 50%,近 8 到 14 天权重 30%,近 15 到 30 天权重 20%。

建议补货量 = 预测日均销量 * (Lead Time + 安全天数) - 可用库存 - 在途库存。

如果结果大于 0,就生成补货建议;如果小于等于 0,说明当前库存足够。

Q14:ABC 库存分类和补货有什么关系?

ABC 分类决定不同 SKU 的库存管理策略。

A 类 SKU 贡献高,断货影响大,安全库存天数更高,补货更积极。

B 类 SKU 正常管理。

C 类 SKU 贡献低,安全库存天数更低,避免积压资金。

所以 ABC 分类会影响补货公式里的安全天数。

Q15:VAT 申报数据是系统直接交税吗?

不是。

系统负责计算、整理、导出申报数据。比如按国家汇总应税销售额,扣除退款,查询 VAT 税率,生成 finance_vat_record 和 OSS 格式导出文件。

实际申报和缴税通常由财务专员或税务顾问登录官方系统完成。缴税完成后,系统更新 VAT 记录状态,并写入资金流水。

Q16:FMS 和其他模块如何交互?

FMS 不是孤立模块。

它从 OMS 拿订单收入和退款,从 PMS 拿采购应付,从 WMS 拿库存成本和退货残值,从 TMS 拿物流费用,从平台账单拿真实扣费。

对外输出给 BI 利润快照、现金流、应收应付和 KPI 指标。

模块交互可以同步查询,也可以通过 MQ 解耦。比如订单完成后 OMS 发消息给 FMS,FMS 触发利润预估;物流费用确认后 TMS 发消息给 FMS 生成物流应付。

Q17:财务数据如何保证可追溯?

三个方面。

第一,保留原始账单文件地址,任何解析结果都能回到原始文件。

第二,账单主表和明细表分开保存,既有汇总,也有每一笔明细。

第三,利润快照、VAT 申报、资金流水都保存来源单据 ID,比如订单 ID、账单 ID、付款申请 ID。

财务系统不能只给结果,必须能解释结果从哪里来。

Q18:AI 自然语言查询为什么放到 V2?

因为它不是简单调用大模型。

AI 查询涉及意图识别、SQL 生成、权限控制、租户隔离、敏感字段脱敏、慢 SQL 防护和大模型成本控制。如果没有这些保护,让 AI 直接查数据库风险很高。

当前版本先用固定报表和预设分析模板满足核心 BI 查询。AI 查询作为 V2 规划,在已有指标体系和数据权限成熟后再接入。


面试准备建议

必须能画的图

平台账单对账流程:

flowchart TD
    A[导入平台账单] --> B[异步解析明细]
    B --> C[汇总主账单]
    C --> D[匹配 OMS 订单]
    D --> E[生成差异清单]
    E --> F[财务确认]
    F --> G[触发利润核算]

SKU 利润核算流程:

flowchart TD
    A[销售收入] --> B[采购成本]
    B --> C[平台费用]
    C --> D[物流费用]
    D --> E[广告仓储退款 VAT]
    E --> F[毛利润和净利润]
    F --> G[利润快照]

BI 数据处理流程:

flowchart TD
    A[OMS WMS PMS TMS FMS] --> B[ETL 聚合]
    B --> C[BI 汇总表]
    C --> D[Redis 缓存]
    D --> E[Dashboard]
    C --> F[KPI 预警]
    C --> G[补货建议]

必须能讲清楚的

  • 平台账单主表和明细表的数据来源
  • 账单为什么不能百分之百自动匹配
  • SKU 级利润为什么要做全成本拆解
  • 广告费、仓储费这类周期费用怎么分摊
  • 多货币为什么要保存原币和人民币两个金额
  • 汇兑损益和订单金额差异的区别
  • BI 为什么需要 ETL 和 Redis 缓存
  • KPI 阈值预警怎么触发
  • 智能补货公式怎么计算
  • VAT 模块的系统边界是什么

必须能写的 SQL 和代码

  • 平台账单明细批量匹配订单的 SQL
  • SKU 利润核算公式代码
  • 汇率同步定时任务
  • KPI 阈值判断逻辑
  • 智能补货建议计算代码
  • 报表缓存 Cache Aside 伪代码

表达技巧

面试时先讲业务痛点,再讲表设计和技术方案。

比如讲 FMS,不要直接说“我写了账单表和利润表”,而要说“跨境卖家最大的问题是不知道平台到底扣了哪些钱,也不知道每个 SKU 到底赚不赚钱,所以我做了平台账单对账和 SKU 级利润核算”。

讲 BI 时,不要只说“我用了 ECharts”,而要说“BI 的价值不是画图,而是把订单、库存、采购、物流、财务数据聚合成 KPI、预警和补货建议”。

可能的追问和回答

追问:如果平台不支持账单导出怎么办?

回答:优先 API 拉取,其次文件导入,最后提供标准模板人工导入。如果平台完全不开放数据,就只能做系统内部账和人工确认外部到账,不能凭空实现全自动对账。

追问:平台账单和系统订单金额不一致,以谁为准?

回答:平台实际结算收入和扣费以平台账单为准,系统内部成本以 PMS、WMS、TMS 为准。两者职责不同,不能混在一起。

追问:为什么不用实时 SQL 计算所有 BI 报表?

回答:实时 SQL 会跨多模块 JOIN 大表,性能不稳定,还会影响业务库。BI 更适合提前聚合到汇总表,再配合 Redis 缓存。

追问:AI 查询能不能直接让大模型生成 SQL?

回答:不能直接裸用。必须限制可查询的数据集、校验 SQL、做租户隔离和权限控制,否则有数据泄露和慢 SQL 风险。


完整简历版

项目经历

项目名称:跨境电商 SaaS 柔性供应链管理平台
负责模块:FMS 财务结算系统 + BI 数据分析系统
项目时间:2024.06 - 2024.12
技术栈:Spring Boot 3.2、Spring Cloud、MyBatis-Plus、MySQL 8.0、Redis、RocketMQ、XXL-Job、EasyExcel、ECharts

项目描述:
该项目面向跨境电商卖家,提供从供应商、采购、仓储、商品、订单、物流到财务结算和数据分析的一体化管理能力。
我主要负责 FMS 财务结算和 BI 数据分析模块,支持多平台账单导入、自动对账、SKU 级利润核算、多货币汇率折算、
汇兑损益、VAT 申报数据导出、现金流预测、KPI 预警、销售预测和智能补货建议。

业务规模:
日均处理平台账单 100+ 份,账单明细 30 万+ 行,覆盖 5000+ SKU 利润核算;
支持 5 个主流电商平台、多个币种和多个店铺维度的财务分析。

核心业绩

使用异步解析和批量入库实现了平台账单大文件处理,支持 100MB 级账单文件稳定解析。

  • 上传文件后先存 OSS,并创建账单主表记录
  • 使用线程池异步解析,避免 HTTP 请求阻塞
  • 采用流式读取和每 1000 条批量插入,降低内存和数据库压力
  • 通过账单状态机记录解析进度和失败原因

使用自动匹配算法实现了平台账单与 OMS 订单对账,减少财务人工核对成本。

  • 基于平台订单号匹配内部订单
  • 自动识别账单有系统无、系统有账单无、金额不符三类差异
  • 差异清单支持人工确认和原因标注
  • 对账完成后触发利润重算

使用 SKU 级利润快照实现了全成本利润核算,支持运营定位亏损商品。

  • 整合销售收入、采购成本、物流费、平台费、广告费、仓储费、退款损失和 VAT
  • 支持日快照和月快照
  • 支持 SKU、平台、店铺、日期多维分析
  • 自动识别亏损 SKU、低利润 SKU 和高利润 SKU

使用费用分摊模型解决了广告费和仓储费无法直接归属订单的问题。

  • 订单级费用直接按订单归集
  • 周期性广告费按销售额或销量分摊
  • 仓储费按库存占用和占用天数分摊
  • 分摊规则固定留痕,保证报表口径一致

使用汇率快照和双字段金额设计实现了多货币财务核算。

  • 保存原币金额、币种、人民币金额、汇率和汇率日期
  • XXL-Job 每日同步汇率
  • 汇率波动超过阈值自动告警
  • 实际到账时单独计算汇兑损益

使用现金流预测模型实现了未来 30 天资金流入流出预估。

  • 平台待打款账单作为预计收入
  • 供应商应付、物流应付、VAT 缴纳作为预计支出
  • 按日期生成现金流时间轴
  • 资金缺口提前预警

使用 ETL 汇总表实现了 BI 指标聚合,降低跨模块报表查询压力。

  • 定时抽取 OMS、WMS、PMS、TMS、FMS 数据
  • 统一时间、币种、租户和指标口径
  • 计算 GMV、净利润率、库存周转率、缺货率、物流异常率等 KPI
  • 前端 Dashboard 直接查询汇总表

使用 Redis 缓存优化高频报表查询,提升 Dashboard 响应速度。

  • 缓存经营总览、销售趋势、利润排行、库存健康和 KPI 看板
  • 使用 Cache Aside 模式
  • 通过 TTL 和业务事件主动失效保证缓存更新
  • 避免复杂聚合 SQL 高频访问业务库

使用 KPI 阈值配置实现了供应链指标自动预警。

  • 设计 bi_kpi_threshold 表支持租户级阈值
  • 支持大于触发和小于触发两种比较方式
  • 支持黄色预警和红色危险阈值
  • 超过阈值后通知运营、采购、仓库或财务负责人

使用加权移动平均算法实现了销售预测和智能补货建议。

  • 按近 7 天、8 到 14 天、15 到 30 天销量加权计算预测日均销量
  • 结合可用库存、在途库存、供应商 Lead Time 和安全库存天数计算建议补货量
  • ABC 分类影响安全库存策略
  • 补货建议可转 PMS 采购申请

简历优化建议

简历上不要把 FMS 和 BI 写成普通报表模块。可以突出三个关键词:对账、利润、决策。

推荐写法:

负责 FMS 财务结算与 BI 数据分析模块,设计并实现多平台账单导入解析、自动对账、SKU 级利润核算、
多货币汇率折算、现金流预测、KPI 预警和智能补货建议。通过异步解析、批量入库、利润快照、
ETL 汇总表和 Redis 报表缓存,支撑日均 30 万+ 账单明细处理和 5000+ SKU 利润分析,
帮助运营从销售额分析升级到 SKU 级利润和库存决策分析。

面试展开时,可以按 STAR 方式讲:

  • Situation:跨境卖家多平台、多币种、多费用,账单和订单对不上,SKU 是否赚钱不清楚
  • Task:实现 FMS + BI,完成对账、利润核算、指标分析和补货建议
  • Action:异步解析账单、自动匹配订单、利润快照、ETL 汇总、KPI 阈值、Redis 缓存
  • Result:提升对账效率,支撑 SKU 级利润分析,提前发现缺货、亏损和现金流风险

面试加分表达

可以这样收尾:

“FMS 和 BI 这两个模块我认为是供应链系统里最能体现业务闭环的部分。前面的 OMS、WMS、PMS、TMS 解决的是订单、库存、采购和物流怎么流转,FMS 解决的是钱有没有算清楚,BI 解决的是数据能不能指导下一步决策。所以我在设计时不是简单做 CRUD,而是围绕平台对账、SKU 利润、现金流、KPI 预警和智能补货,把业务数据真正变成经营决策。”