仿金蝶,物料库存系统设计与实现思路

5,447 阅读10分钟

公司最近也在开发库存系统,就拿业界比较成功的金蝶来分析学习,提升自身的业务水平 库存系统最重要的就是入库与出库,成本与利润的计算

入库操作核心流程

入库操作时序图
出库操作核心流程
出库操作时序图

参与的角色

出入库操作参与的角色可以在金蝶系统里的核算项目里看到,这个角色非常重要,涉及到后面的表结构设计

可以看到有很多的角色,因为金蝶不仅仅是个库存系统,同时也是一个完整财务系统,找出库存系统不可缺少的角色有以下几个

  1. 供应商:库存系统把物料出库给供应商,供应商供货给系统就入库
  2. 物料:参与库存计算的最小单位
  3. 仓库:同一个物料可以同时出现在多个仓库中,但是出入库的对象一次只能选择一个仓库

在计量单位里可以管理物料的单位

特别注意这个计量单位里的换算率,比如上面的盒的换算率是10,个是默认单位,就表示1盒可以换成10个

还有一个发起出入库方的角色(一般是一个公司发起出入库),这里出入库都是作者,表结构上暂时忽略

出入库

打开仓库管理->采购入库/销售出库,拿采购入库举例

可以看到入库单核心的字段有这些

  1. 供应商(这些物料从哪里采购的)
  2. 入库日期
  3. 编号(不可修改,自动生成)
  4. 入库的物料
  5. 入库的仓库
  6. 实收的数量
  7. 实收的数量的单位
  8. 单价
  9. 金额

计价方式

在金蝶的新增物料界面,可以查看当前系统支持的计价方法

目前业界广泛使用有:

  1. 移动平均法
  2. 先进先出法 其它的计价方式目前使用得比较少了,有兴趣的可以去了解下会计知识 目前对先进先出法比较熟悉,就拿先进先出的计价方法举例了

表结构设计

  1. 出入库单据表
  2. 出入库单据里关联的物料表
  3. 批次物料库存金额结存表
  4. 批次物料库存出入库记录表
  5. 供应商表
  6. 物料表
  7. 仓库表
  8. 单位表
  9. 库存表 可以看到这个最简单的批次库存表也有9张表了 下面由作者认为的从简单到复杂的顺序来设计表,设计一个满足出入库的最小表结构

单位表

目前不考虑换算率,也就不参与库存的换算,所以单位表的字段就比较简单了 sql如下:

CREATE TABLE `unit` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `code` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '物品编码',
  `name` varchar(128) NOT NULL DEFAULT '' COMMENT '名称',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `name` (`name`) USING BTREE,
  UNIQUE KEY `code` (`code`) USING BTREE COMMENT '`'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='计量单位';

供应商表

供应商在系统里是出入库的对象,入库的物料是从供应商那里买来的,出库的物料是退货给供应商

CREATE TABLE `supply` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL DEFAULT '' COMMENT '名称',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='供应商表';

仓库表

仓库表用来标识物料存在在那个仓库,同一个物料可能存在在多个仓库,入库时必须选择物料入到那个库

CREATE TABLE `warehouse` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `organization_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '门店id',
  `name` varchar(32) NOT NULL DEFAULT '' COMMENT '库房名称',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='库房信息表';

库存表

用来记录物料在不同仓库的库存,入库和出库时需要把库存数量从批次里同步到库存表里

CREATE TABLE `inventory` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `warehouse_id` bigint(20) unsigned NOT NULL COMMENT '库房ID',
  `material_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '物料id',
  `count` decimal(16,2) NOT NULL DEFAULT '0.00' COMMENT '总库存',
  `lock_count` decimal(16,2) NOT NULL DEFAULT '0.00' COMMENT '占用库存',
  `available_count` decimal(16,2) NOT NULL DEFAULT '0.00' COMMENT '可用库存',
  `cost_amount` decimal(16,4) NOT NULL DEFAULT '0.0000' COMMENT '成本总金额',
  `updated_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
  `created_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
  `deleted_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '删除时间',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_group_id_label_id_relation_id_type` (`warehouse_id`,`material_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='仓库库存表';

物料表

物料是库存操作的基本对象

CREATE TABLE `material` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `material_code` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '物品编码',
  `name` varchar(45) NOT NULL COMMENT '名称',
  `category_id` int(11) unsigned NOT NULL COMMENT '所属分类',
  `category_name` varchar(45) NOT NULL COMMENT '分类名称',
  `unit_weight` decimal(10,4) unsigned NOT NULL DEFAULT '0.0000' COMMENT '单位重量',
  `base_unit_id` bigint(20) DEFAULT NULL COMMENT '主单位id',
  `base_unit_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '主单位名称',
  `updated_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新日期',
  `created_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时期',
  `deleted_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '删除时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='物料';

出入库单据表

单据表用来记录有多少个出入库单,和单据的一些元信息,比如这个单据的发起的供应商,操作人,单据金额,单据里的物料的数量等

CREATE TABLE `in_out` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `type` tinyint(2) unsigned NOT NULL DEFAULT '0' COMMENT '1:采购订单 2:采购收货单 3:采购退货单',
  `in_out_code` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '单号',
  `warehouse_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '仓库ID',
  `warehouse_name` varchar(64) NOT NULL DEFAULT '' COMMENT '仓库名称',
  `supply_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '供应商id',
  `supply_name` varchar(64) NOT NULL DEFAULT '' COMMENT '供应商名称',
  `address` varchar(45) NOT NULL DEFAULT '' COMMENT '交货地址',
  `count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '物料数量',
  `amount_total` decimal(18,8) unsigned NOT NULL DEFAULT '0.00000000' COMMENT '当前单据总金额',
  `amount_count` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '当前单据总数量',
  `created_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
  `updated_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
  `deleted_at` int(11) NOT NULL DEFAULT '0' COMMENT '删除时间',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_group_id_expense_sn` (`in_out_code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='单据表';

出入库单据物料关联表

记录出入库里的物料的详细信息,比如物料的名称,编码,单位,数量,单价,金额,税额等参与库存计算的核心字段

CREATE TABLE `in_out_detail` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `warehouse_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '仓库ID',
  `warehouse_name` varchar(62) NOT NULL DEFAULT '' COMMENT '仓库名称',
  `in_out_code` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '单号',
  `in_out_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '单号ID',
  `in_out_type` tinyint(4) DEFAULT NULL COMMENT '单据类型',
  `material_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '关联ID',
  `material_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '关联物料名称',
  `material_code` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '关联物料sn',
  `unit_basic_name` varchar(32) NOT NULL DEFAULT '' COMMENT '基本单位名称',
  `unit_basic_value` decimal(10,3) NOT NULL DEFAULT '1.000' COMMENT '基本单位数值',
  `unit_basic_count` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '主单位数量',
  `unit_basic_price` decimal(14,4) unsigned NOT NULL DEFAULT '0.0000' COMMENT '主单位不含税单价',
  `unit_basic_tax_price` decimal(14,4) unsigned NOT NULL DEFAULT '0.0000' COMMENT '主单位的含税单价',
  `unit_basic_total` decimal(18,8) unsigned NOT NULL DEFAULT '0.00000000' COMMENT '主单位不含税金额',
  `unit_basic_tax_total` decimal(18,8) NOT NULL DEFAULT '0.00000000' COMMENT '主单位含税金额',
  `tax` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '税率',
  `tax_total` decimal(18,8) unsigned NOT NULL DEFAULT '0.00000000' COMMENT '税额',
  `production_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '生产日期',
  `created_at` int(11) unsigned NOT NULL DEFAULT '0',
  `updated_at` int(11) unsigned NOT NULL DEFAULT '0',
  `deleted_at` int(11) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_in_out_id_material_id` (`in_out_id`,`material_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1110 DEFAULT CHARSET=utf8 COMMENT='单据物料关联表';

物料批次表

这个批次表就是记录物料的所有的批次当前的库存状态,物料一个批次的库存理论上最多用到0库存.

CREATE TABLE `batch` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `code` int(11) DEFAULT NULL COMMENT '批次代码',
  `warehouse_id` bigint(20) DEFAULT NULL COMMENT '仓库id',
  `in_type` int(4) DEFAULT NULL COMMENT '入库单类型',
  `in_id` bigint(20) DEFAULT NULL COMMENT '入库单的id',
  `material_id` bigint(20) DEFAULT NULL COMMENT '物料、菜品、套餐关联id',
  `end_tax_price` decimal(14,4) DEFAULT '0.0000' COMMENT '当前含税单价',
  `end_price` decimal(14,4) DEFAULT '0.0000' COMMENT '当前不含税单价',
  `end_count` decimal(10,2) DEFAULT '0.00' COMMENT '当前 主单位库存数量 要减去占用数量',
  `end_total_amount` decimal(18,8) DEFAULT '0.00000000' COMMENT '当前不含税总金额(发生单据后这个值会发生变化)',
  `end_tax_total_amount` decimal(18,8) DEFAULT '0.00000000' COMMENT '当前含税总金额',
  `end_tax_total` decimal(18,8) DEFAULT '0.00000000' COMMENT '当前结存税额',
  `cost_tax_price` decimal(14,4) DEFAULT '0.0000' COMMENT '原含税单价',
  `cost_price` decimal(14,4) DEFAULT '0.0000' COMMENT '原不含税单价',
  `cost_count` decimal(10,2) DEFAULT '0.00' COMMENT '原主单位库存数量',
  `cost_total_amount` decimal(18,8) DEFAULT '0.00000000' COMMENT '原不含税总金额',
  `cost_tax_total_amount` decimal(18,8) DEFAULT '0.00000000' COMMENT '原含税总金额',
  `cost_tax_total` decimal(18,8) DEFAULT '0.00000000' COMMENT '原税额',
  `gross_profit` decimal(18,8) DEFAULT '0.00000000' COMMENT '利润金额(含税:入-成本/出-成本)',
  `lock_count` decimal(10,2) DEFAULT '0.00' COMMENT '占用/锁定数量',
  `production_at` int(11) DEFAULT NULL COMMENT '生产日期',
  `status` tinyint(1) DEFAULT '1' COMMENT '冻结为0 非冻结为1',
  `remark` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
  `updated_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
  `created_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
  `deleted_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '删除时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='批次表';

物料批次快照表

记录每次批次物料表发生变化后的快照

CREATE TABLE `batch_snapshot` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `batch_id` bigint(20) DEFAULT NULL,
  `in_out_detail_type` int(4) DEFAULT NULL COMMENT '单据详情类型',
  `in_out_detail_id` bigint(20) DEFAULT NULL COMMENT '入库单价',
  `in_count` decimal(12,4) DEFAULT '0.0000' COMMENT '入库数量',
  `in_total_amount` decimal(18,8) DEFAULT NULL,
  `in_tax_total` decimal(18,8) DEFAULT '0.00000000' COMMENT '税额',
  `in_tax_total_amount` decimal(18,8) DEFAULT '0.00000000' COMMENT '入库含税金额',
  `out_price` decimal(14,4) DEFAULT '0.0000' COMMENT '出库单价',
  `out_count` decimal(10,2) DEFAULT '0.00' COMMENT '出库数量',
  `out_total_amount` decimal(18,8) DEFAULT '0.00000000' COMMENT '出库未含税金额',
  `out_tax_total` decimal(18,8) DEFAULT '0.00000000' COMMENT '出库税额',
  `out_tax_total_amount` decimal(18,8) DEFAULT '0.00000000' COMMENT '出库含税金额',
  `out_cost_tax_price` decimal(14,4) DEFAULT '0.0000' COMMENT '出库成本单价(含税)',
  `out_cost_price` decimal(14,4) DEFAULT '0.0000' COMMENT '出库成本单价(不含税)',
  `out_cost_total_amount` decimal(18,8) DEFAULT '0.00000000' COMMENT '出库成本含税金额',
  `out_cost_tax_total_amount` decimal(18,8) DEFAULT '0.00000000' COMMENT '出库成本不含税金额',
  `out_cost_tax_total` decimal(18,8) DEFAULT '0.00000000' COMMENT '出库成本税额',
  `end_price` decimal(14,4) DEFAULT '0.0000' COMMENT '当前未含税单价',
  `end_tax_price` decimal(14,4) DEFAULT '0.0000' COMMENT '当前含税单价',
  `end_count` decimal(10,2) DEFAULT '0.00' COMMENT '当前结存数量',
  `end_total_amount` decimal(18,8) DEFAULT '0.00000000' COMMENT '当前结存未含税金额',
  `end_tax_total_amount` decimal(18,8) unsigned DEFAULT '0000000000.00000000' COMMENT '当前结存含税金额',
  `end_tax_total` decimal(18,8) DEFAULT '0.00000000' COMMENT '当前结存税额',
  `updated_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
  `created_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
  `deleted_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '删除时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='批次快照表';

核心算法

库存系统里只要影响库存和金额的操作都可以算作库存的核心算法,其中最重要的就是物料的计价方法了,因为每次出入库都会用到这个物料的计价方法,而不同的计价方法又会直接影响到库存的成本和利润

成本计算:成本是含税的,所以计算成本时需要把税也计算在内 1. 用户输入系统的成本是不含税的,那么系统需要用税点算出税额加到成本里 2. 用户输入系统的成本是含税的,那么系统需要减掉税点得到不含税的成本

先进先出的出库算法:先入库的先出库

一些注意的点

金额在mysql使用decimal类型存储,在java中使用BigDecimal类型表示 批次快照表只是记录批次物料的库存快照,不参与库存和成本的计算。

持续更新...