财务ERP - 进销存模块(采购与台账)详细开发文档

版本历史



版本 日期 修改内容 作者
v1.0 2026-06-25 基于20260618需求梳理,定义采购模块与台账功能 架构组

1. 模块定位与范围

1.1 模块目标

实现采购业务全流程的财务核算与台账管理,打通采购订单 → 付款 → 入库 → 发票的业务闭环,并提供多维度的台账查询与对账功能。本模块作为现有 wimoor-finance 微服务的扩展,复用已有凭证、科目、辅助核算等基础能力。

1.2 涉及功能清单

1.3 依赖关系


2. 数据库设计(新增表)

2.1 采购账户表 fin_purchase_account

已在原文档中定义,此处补充字段说明:



字段 类型 说明
id bigint unsigned 主键
groupid bigint unsigned 租户ID(账套)
account_name varchar(100) 账户名称,如“公户现金”“跨境直采”
account_type tinyint 1-现金,2-账期
related_subject_id bigint unsigned 对应会计科目ID(如“银行存款-浦发”)
config_json json 完整配置:费用类型列表、归属标记、借贷科目映射、退款设置、支持的付款账户等
is_enabled tinyint 是否启用
created_time datetime  
updated_time datetime  

config_json 结构示例:

json
{
  "feeTypes": [
    { "code": "GOODS", "name": "货款", "isSupplierRelated": true },
    { "code": "FREIGHT", "name": "运费", "isSupplierRelated": true },
    { "code": "SERVICE", "name": "跨境直采手续费", "isSupplierRelated": false }
  ],
  "paySubjectMapping": {
    "GOODS": { "debitSubjectId": 101, "creditSubjectId": 201 },
    "FREIGHT": { "debitSubjectId": 102, "creditSubjectId": 201 },
    "SERVICE": { "debitSubjectId": 501, "creditSubjectId": 201 }
  },
  "refundMapping": {
    "GOODS": { "debitSubjectId": 201, "creditSubjectId": 101 },
    "FREIGHT": { "debitSubjectId": 201, "creditSubjectId": 102 }
  },
  "creditPayAccounts": [  // 仅账期类型有效,可选的现金账户列表
    { "cashAccountId": 1, "subjectMapping": { "debit": 301, "credit": 201 } }
  ]
}

2.2 采购订单表 fin_purchase_order

已在原文档中定义,补充以下索引和约束:

sql
ALTER TABLE `fin_purchase_order` 
ADD INDEX `idx_status_pay` (`status_pay`),
ADD INDEX `idx_status_inv` (`status_inv`),
ADD INDEX `idx_status_stock` (`status_stock`);

2.3 采购付款明细表 fin_purchase_payment

已在原文档定义,确保与 purchase_order_id 和 voucher_id 关联。

2.4 供应商收款账户表 fin_supplier_bank

(新增)用于维护供应商的多个收款账户信息:

sql
CREATE TABLE `fin_supplier_bank` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `groupid` bigint unsigned NOT NULL,
  `supplier_id` bigint unsigned NOT NULL,
  `bank_name` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL,
  `account_name` varchar(100) COLLATE utf8mb4_bin NOT NULL,
  `account_no` varchar(50) COLLATE utf8mb4_bin NOT NULL,
  `is_default` tinyint DEFAULT '0',
  `is_enabled` tinyint DEFAULT '1',
  `created_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_supplier` (`supplier_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='供应商收款账户';

2.5 库存余额表 fin_warehouse_stock

已在原文档定义,新增 warehouse_type 字段区分本地/FBA:

sql
ALTER TABLE `fin_warehouse_stock` ADD COLUMN `warehouse_type` tinyint DEFAULT '1' COMMENT '1-本地,2-FBA';

2.6 库存变动明细表 fin_inventory_transaction

已在原文档定义。

2.7 采购发票关联表(可选)

为支持“一张发票对应多笔采购订单”,可新增中间表,但当前简化设计,发票直接关联凭证,通过凭证分录的辅助核算追踪订单,暂不建中间表。


3. 核心业务逻辑

3.1 采购付款凭证生成规则(完整版)

根据需求,采购付款涉及三个环节:付款在途库存确认发票入账。系统按以下规则自动生成凭证。

3.1.1 现金类账户付款(公户、跨境直采等)

3.1.2 账期类账户付款(先采后付、公户请款等)

3.1.3 退款处理

退款时,根据已付款、已入库、已开票的不同状态,系统自动生成红字冲销凭证(方向相反),并更新台账。具体规则已在需求中列出,此处不再赘述。

3.1.4 辅助核算要求

所有涉及供应商的科目必须启用“供应商”辅助核算;涉及存货的科目启用“存货”辅助核算(即SKU)。系统在生成凭证时自动填充 fin_voucher_entries_auxiliary 表。


3.2 移动加权平均成本计算

在每次采购入库、盘盈入库时,重新计算库存单价。出库时,出库成本 = 出库数量 × 当前移动单价。

Java 核心实现:

java
@Component
public class MovingAverageCostCalculator {

    /**
     * 计算新移动平均单价
     * @param currentQty 当前数量
     * @param currentAmount 当前金额
     * @param inQty 入库数量(正数)
     * @param inAmount 入库金额(正数)
     * @return 新单价,保留4位小数
     */
    public BigDecimal calculateNewUnitCost(BigDecimal currentQty, BigDecimal currentAmount,
                                           BigDecimal inQty, BigDecimal inAmount) {
        if (inQty.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal newQty = currentQty.add(inQty);
        if (newQty.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal newAmount = currentAmount.add(inAmount);
        return newAmount.divide(newQty, 4, RoundingMode.HALF_UP);
    }

    /**
     * 计算出库成本(金额)
     */
    public BigDecimal calculateOutCost(BigDecimal qty, BigDecimal unitCost) {
        return qty.multiply(unitCost).setScale(2, RoundingMode.HALF_UP);
    }
}

库存余额更新(确保不反算):

java
@Transactional
public void updateStockForTransaction(InventoryTransactionDTO dto) {
    // 1. 获取当前期间库存
    WarehouseStock stock = stockMapper.selectBySkuPeriod(...);
    BigDecimal oldQty = stock.getCurrentQty();
    BigDecimal oldAmount = stock.getCurrentAmount();
    BigDecimal oldUnitCost = stock.getUnitCost();

    if (dto.getTransType() == PURCHASE_IN) {
        // 入库:重新计算单价
        BigDecimal newUnitCost = calculateNewUnitCost(oldQty, oldAmount, dto.getQty(), dto.getAmount());
        stock.setUnitCost(newUnitCost);
        stock.setCurrentQty(oldQty.add(dto.getQty()));
        stock.setCurrentAmount(oldAmount.add(dto.getAmount()));
    } else if (dto.getTransType() == SALE_OUT) {
        // 出库:使用当前单价计算成本
        BigDecimal cost = calculateOutCost(dto.getQty().abs(), oldUnitCost);
        dto.setAmountChange(cost.negate()); // 记录变动金额
        stock.setCurrentQty(oldQty.subtract(dto.getQty().abs()));
        stock.setCurrentAmount(oldAmount.subtract(cost));
        // 单价不变
    }
    // 其他类型(调拨、盘点等)类似...
    stockMapper.updateById(stock);
    // 插入变动明细
    inventoryTransactionMapper.insert(buildTransaction(dto, oldUnitCost));
}

3.3 采购账户台账查询与批量付款

3.3.1 台账列表查询

支持多维度筛选(采购账户、账套、时间、状态),返回以下核心字段:

SQL 查询核心(MyBatis XML):

xml
<select id="queryLedgerList" resultType="com.wimoor.finance.vo.PurchaseLedgerVO">
    SELECT 
        po.groupid,
        po.id as orderId,
        po.sku,
        s.name as supplierName,
        sb.account_no as bankAccount,
        po.total_amount,
        po.paid_amount,
        po.inventory_value,
        po.invoiced_amount,
        -- 计算待付、待入库、待开票
        (po.total_amount - po.paid_amount) as pending_pay,
        (po.inventory_value - IFNULL(SUM(ir.received_value),0)) as pending_stock,
        (po.total_amount - po.invoiced_amount) as pending_invoice,
        po.status_pay,
        po.status_stock,
        po.status_inv
    FROM fin_purchase_order po
    LEFT JOIN t_erp_supplier s ON po.supplier_id = s.id
    LEFT JOIN fin_supplier_bank sb ON s.id = sb.supplier_id AND sb.is_default=1
    LEFT JOIN fin_inventory_transaction ir ON po.id = ir.source_doc_id AND ir.trans_type='RECEIVE'
    WHERE po.groupid = #{groupid}
    <if test="accountIds != null and accountIds.size()>0">
        AND po.purchase_account_id IN 
    </if>
    <if test="startDate != null"> AND po.created_time >= #{startDate} </if>
    <if test="endDate != null"> AND po.created_time < #{endDate} </if>
    <if test="payStatus != null"> AND po.status_pay IN ... </if>
    GROUP BY po.id
    ORDER BY po.created_time DESC
</select>

3.3.2 台账付款操作

批量上传对账单(Excel)流程:

  1. 下载模板(含列:采购订单号、SKU、应付金额、本次付款金额、费用类型)

  2. 用户填写后上传

  3. 后端解析,校验订单号有效、未超额付款

  4. 预览确认,用户提交

  5. 批量生成付款记录和凭证


3.4 供应商台账与发票入账

3.4.1 供应商台账查询

与采购账户台账类似,但按供应商维度汇总,展示:

3.4.2 发票入账操作


3.5 发票台账同步与入账

3.5.1 发票数据同步

3.5.2 发票台账查询

提供多维度筛选(账簿、销方、日期、入账状态、发票状态),展示发票号码、开票日期、价税合计、税额、入账状态等。

3.5.3 手工入账(未匹配自动入账)

用户可在发票台账页面勾选发票,选择“入账”,系统生成凭证(借:预付账款-ERP在途发票 贷:预付账款-ERP采购供应商),并关联供应商(若之前未匹配)。


3.6 本地仓库台账(移动加权平均核算)

3.6.1 汇总账

按“期间 + 账簿 + SKU + 仓库”展示期初、入库、出库、期末的数量和金额。

勾稽校验:期末金额应与对应会计科目(库存商品)余额一致,若不一致,系统提示差异。

3.6.2 明细账

展示每笔库存变动明细:操作时间、类型、数量、单价、金额变动、关联单据号、凭证号。支持按SKU、仓库、时间筛选。


4. API 接口详细设计

4.1 采购账户管理



方法 路径 说明 请求参数
POST /api/finance/purchase/account/create 创建采购账户 PurchaseAccountSaveDTO
PUT /api/finance/purchase/account/update 更新配置 PurchaseAccountUpdateDTO
GET /api/finance/purchase/account/list 查询可用账户 groupid
GET /api/finance/purchase/account/detail/{id} 获取详情 id

4.2 采购订单(会计版)



方法 路径 说明 请求参数
POST /api/finance/purchase/order/create 创建采购订单(从ERP业务单转换) PurchaseOrderSaveDTO
POST /api/finance/purchase/order/pay 付款(生成凭证) PurchasePayDTO(含付款账户、费用明细)
POST /api/finance/purchase/order/refund 退款 RefundDTO
POST /api/finance/purchase/order/receive 入库 ReceiveDTO(收货数量/金额)
GET /api/finance/purchase/order/page 订单列表 PageQuery + 筛选条件

4.3 采购账户台账



方法 路径 说明
GET /api/finance/ledger/purchase/page 台账列表(分页)
POST /api/finance/ledger/purchase/ledger-pay 台账付款(批量)
POST /api/finance/ledger/purchase/upload-pay 上传对账单批量付款
GET /api/finance/ledger/purchase/statistics 统计图表数据

4.4 供应商台账



方法 路径 说明
GET /api/finance/ledger/supplier/page 供应商台账列表
POST /api/finance/ledger/supplier/invoice-post 发票入账
GET /api/finance/ledger/supplier/export-uninvoiced 导出未开票明细

4.5 发票台账



方法 路径 说明
GET /api/finance/ledger/invoice/page 发票台账列表
POST /api/finance/ledger/invoice/sync 手动触发同步(定时任务自动)
POST /api/finance/ledger/invoice/post 发票入账(勾选后)
GET /api/finance/ledger/invoice/statistics 发票统计

4.6 库存台账



方法 路径 说明
GET /api/finance/ledger/stock/summary 汇总账(按期间)
GET /api/finance/ledger/stock/detail 明细账(按SKU)
GET /api/finance/ledger/stock/check 勾稽校验(与总账对比)

5. 服务层实现要点

5.1 采购付款服务(PurchasePaymentService

5.2 库存服务(InventoryService

5.3 发票服务(InvoiceService

5.4 台账查询服务(LedgerQueryService


6. 前端页面交互设计

6.1 采购单(会计版)页面

6.2 采购账户台账页面

6.3 供应商台账页面

6.4 发票台账页面

6.5 本地仓库台账


7. 定时任务与消息队列

7.1 定时任务(XXL-JOB)



任务名称 Cron 说明
InvoiceSyncJob 0 0 2 * * ? 同步税局发票数据
StockCheckJob 0 0 3 1 * ? 每月1号执行库存与总账勾稽校验
AutoPostInvoiceJob 0 0/30 9-18 * * ? 工作时间内自动匹配未入账发票(可选)

7.2 消息队列(RabbitMQ)


8. 异常处理与日志

8.1 异常场景

8.2 日志规范


9. 测试策略

9.1 单元测试(JUnit + Mockito)

9.2 集成测试(@SpringBootTest + Testcontainers)

9.3 前端测试(Vue Test Utils)


10. 部署与配置

10.1 依赖环境

10.2 配置示例(application.yml)

yaml
wimoor:
  finance:
    inventory:
      cost-method: moving_average   # 仅支持移动加权
    invoice:
      sync-api: https://api.tax.gov/invoice
      sync-cron: 0 0 2 * * ?
    ledger:
      batch-size: 1000   # 批量导入单次最大行数

11. 后续扩展规划


开发周期估计

  • 数据库设计与基础服务:3天

  • 核心业务逻辑(凭证生成、成本计算、台账查询):5天

  • 接口开发与联调:4天

  • 前端页面开发:5天

  • 测试与修复:3天
    总计约20人天


版本号 #2
由 Admin 创建于 2026-06-25 09:19:35 CST
由 Admin 更新于 2026-06-25 10:52:06 CST