拓冰建站拓冰建站
首页 / 资讯中心 / 正文

进销存系统设计与成本核算:从需求说明书到数据模型落地

简介这是一份进销存系统需求说明书文档适合产品经理、需求分析师、开发人员及企业信息化主管参考用于梳理采购、销售、库存和财务对账等核心业务需求。文档先界定客户群体、信息化现状、应用范围、运行环境再对性能、稳定性、扩展性及支撑平台提出明确要求业务功能章节作为重点覆盖建立帐套、基础资料录入、建帐期初数据、进货管理、销售管理、存货管理等模块并细化到客户档案、员工档案、仓库档案、资金账户、商品库存等基础数据设计。资源为单个doc文件共182KB属于文档资料便于直接阅读或二次编辑。目前已有274人在CSDN学习下载可作为进销存项目立项前的需求调研模板、功能设计依据及验收评审检查清单能够帮助读者快速搭建规范的需求框架减少遗漏。1. 从一份进销存说明书里读出业务闭环的门道拿到一份进销存系统需求说明书.doc多数人先翻目录看到客户群体、运行环境、业务功能要求觉得不过是一个模板文档。但真正有价值的往往藏在细节里比如文档里用“移动加权平均法”举的那个例子——货品A期初结存10个、金额100元先销售11个再采购10个单价11元销售时成本按期初加权价10元结转导致结存数量变成-1、结存金额-10元采购后结存单价变成11.11元。这个例子说明系统必须允许负库存出库并且在负库存状态下仍能正确计算移动加权平均价否则单据顺序稍作调整成本就会算错。再比如付款业务中“结余款”的设定预付货款时付款类型为“付预付款”金额自动转入供应商结余款后续收到货物填写入库单后用“支付欠款”类型付款此时可以调用结余款冲抵本次入库金额。这套机制把预付款、应收应付、单据核销串成了一个闭环比单纯记录一笔应付账款要复杂得多。这篇文档对正在做进销存需求分析、系统设计或要写自己版本需求说明书的人来说是一个很完整的框架样本。2. 账套、档案与编码设计把业务主数据拆成可落地的数据模型2.1 账套是业务隔离的边界不只是数据库名文档第九章把“建立帐套”作为业务功能的第一项强调建立初期就要初始化员工、客户、仓库、资金账户等基础数据。这里其实隐含了一个多组织维度同一套系统可能跑多个独立核算的贸易主体每个账套拥有独立的商品、往来单位、期初库存和财务数据。从数据库设计角度账套号应该作为绝大多数业务表的主键前缀或分区键而不是简单用一个字段标识。常见做法是设计一个 sys_account账套表包含账套编号、账套名称、启用日期、会计期间配置、成本核算方法加权平均/移动加权平均/先进先出/分批认定法。所有后续表以 account_id 字段关联。注意成本核算方法应该存在账套级别因为同一个企业不太可能对两种商品采用不同核算方法而不同账套之间完全可能不同。2.2 客户档案、商品档案、仓库档案的字段级拆分文档对档案的描述已经细化到字段级别这是最容易被忽略的部分。客户档案需要编号、简称、名称、拼音编码、开户银行、账号、开户姓名、税号、省份、单位性质和备注每个客户还有多个联系人联系人字段含电话、传真、移动电话、住宅电话、部门、职务。这说明需求方要求主档与子表分离一个单位对多个联系人这在实现上需要 base_customer 和 base_customer_contact 两张表。商品档案的字段更复杂商品名称、条码、规格型号、拼音编码、单位、分类、预设售价、最低售价、预设进价、最高进价、库存上下限还有几个特殊的业务开关——停止采购、停止销售、允许负库存。这几个开关直接影响业务流程允许负库存控制商品出库时是否检查库存充足性。文档中的移动加权平均示例说明系统必须支持负库存因为先销售后采购的情况下销售单先扣库存会产生负值。如果业务上不允许负库存下单顺序就受限这也解释了为什么需要这个开关。库存上下限下采购订单时系统自动计算最佳订货量公式是库存上限减去当前实际库存量。注意这个公式不是安全库存减去当前库存而是直接用上限做目标值。停止采购/停止销售比删除商品更稳妥的停用方式。商品档案一旦参与历史单据就不能物理删除只能通过开关控制。2.2.1 商品档案表结构示例CREATE TABLE base_commodity ( id BIGINT PRIMARY KEY AUTO_INCREMENT, account_id BIGINT NOT NULL COMMENT 账套ID, category_id BIGINT COMMENT 商品分类ID, commodity_no VARCHAR(32) NOT NULL COMMENT 商品编号/条码, commodity_name VARCHAR(128) NOT NULL COMMENT 商品名称, spec_model VARCHAR(64) COMMENT 规格型号, pinyin_code VARCHAR(32) COMMENT 拼音助记码用于快速检索, unit VARCHAR(16) COMMENT 计量单位, stop_purchase TINYINT DEFAULT 0 COMMENT 停止采购1是 0否, stop_sale TINYINT DEFAULT 0 COMMENT 停止销售, allow_negative TINYINT DEFAULT 0 COMMENT 允许负库存, stock_lower DECIMAL(12,2) DEFAULT 0 COMMENT 库存下限, stock_upper DECIMAL(12,2) DEFAULT 0 COMMENT 库存上限, preset_purchase_price DECIMAL(12,2) COMMENT 预设进价, max_purchase_price DECIMAL(12,2) COMMENT 最高进价超过需审批, preset_sale_price DECIMAL(12,2) COMMENT 预设售价, min_sale_price DECIMAL(12,2) COMMENT 最低售价低于需审批, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_account_no (account_id, commodity_no) ) COMMENT 商品基础档案;逻辑说明account_id 在所有基础表都存在保证不同账套的数据隔离stop_purchase、allow_negative 这类开关用 tinyint 而不是 varchar避免在代码里做字符串判断价格字段用 decimal(12,2)不要用 float/double否则累计金额多次计算后会出现精度误差。商品档案还承载着价格跟踪的职责。文档要求显示预设进价、预设售价、平均价、平均售价、最后一次进价、最后一次售价并且能从价格明细追溯到采购入库单和销售出库单。这个设计意味着商品表只是入口真实价格轨迹要落在流水表里——每次采购入库更新“最后一次进价”每次销售出库更新“最后一次售价”而平均价则需要实时按成本核算方法计算或定时落表。2.3 仓库档案与资金账户的初始化规则仓库档案要求初始化数据为主仓库、管理员是初始化系统管理员。开发时要注意一个陷阱如果只建一个“主仓库”后续做仓库调拨、盘点、分仓核算时所有商品都必须能归属到某个仓库。建议在表设计中增加 is_main 字段标识默认仓库期初库存和现采现卖的商品如果未指定仓库自动归入主仓库。资金账户明确了“现金”是默认账户后续银行存取、收款退款都从这些账户发生额。别把资金账户和会计科目混为一谈这里是出纳口径的账户不是总账科目。一个账户余额 期初余额 收款项 - 付款项 - 银行存取差额。文档里单独设置了 bank_save_take银行存取功能说明账户间的资金划转也算一笔业务单据需要留痕。3. 进货、销售与收付款链路结余款核销与成本更新规则3.1 进货管理三步走订货、入库、退货文档把进货流程分成采购订货、采购入库、采购退货三张单据每一步都有明确的业务规则。采购订单的关键在于执行状态的跟踪哪些订单已收完货、哪些只收了一部分、哪些已到交货日期但供应商还没发货。反应到表设计上订单头、订单行需要冗余一个“已入库数量/已入库金额”字段每次采购入库单保存或审核时累加这两个字段而不是实时 LEFT JOIN 汇总。最佳订货量的计算公式是库存上限减去当前实际库存这是文档明确给出的规则。但补充一个要注意的点如果商品设置了“允许负库存”则当前实际库存可能为负数此时最佳订货量会大于库存上限。实现的时候可以在计算时先 clamp 到 0 以上。采购入库完成后的核心动作是更新商品库存——数量、平均进价、总价三个值都要变。这里的分仓逻辑值得注意文档明确说“总仓的总数量、平均价、总价不发生变化”因为这批货只是从一个仓库移到另一个仓库总账没变。但在业务上单据上的商品可能入库到 A 仓后续发现放错了要改到 B 仓此时就要通过“仓库调拨”来单独立单而不是直接改原单据。3.1.1 采购入库时更新移动加权成本的实现-- 以 MySQL 为例在保存采购入库单后执行 UPDATE base_warehouse_stock ws JOIN ( SELECT i.account_id, i.warehouse_id, i.commodity_id, i.quantity, i.amount FROM purchase_inbound_item i WHERE i.bill_id #{billId} ) src ON ws.account_id src.account_id AND ws.warehouse_id src.warehouse_id AND ws.commodity_id src.commodity_id SET ws.quantity ws.quantity src.quantity, ws.amount ws.amount src.amount, ws.avg_cost CASE WHEN ws.quantity src.quantity 0 THEN 0 ELSE (ws.amount src.amount) / (ws.quantity src.quantity) END;逻辑说明这段 SQL 模拟的是移动加权平均法。ws 表记录每个仓库存放每个商品的数量、总金额和平均成本。采购入库时把入库数量加到库存数量上入库金额加到库存总金额上然后用新总金额除以新数量得到新的平均成本。参数说明{billId} 是采购入库单 IDsrc 子查询查出该单所有商品行的数量与金额quantity 与 amount 之和是否允许为 0取决于账套是否开放负库存。如果入库的是一种此前从未有库存的商品ws 表中还没有对应记录要先做一次 INSERT ... ON DUPLICATE KEY UPDATE 或先初始化一条零记录。3.2 付款业务的“结余款”机制预付款、欠款、冲抵文档中关于预付款的说明是全文最有产品深度的部分。业务场景是向供应商预付 70% 货款此时商品还没到不能关联入库单预付款自动转入供应商结余款货到后填采购入库单再付尾款时将预付款作为结余款冲抵入库金额。这套机制要求系统把“付款”和“单据核销”分成两个动作。表结构上payment_bill付款单记录供应商、付款类型付预付款/支付欠款、付款账户、金额、经办人等基本信息payment_alloc付款分配表记录这笔付款核销了哪张入库单、核销金额多少。预付款不分配任何入库单只增加该供应商的“余额”支付欠款时付款单主体做分配同时自动优先使用结余款。3.2.1 结余款冲抵的伪代码逻辑供应商应付余额 应付账款合计 - 已核销付款合计 - 预付款余额 支付欠款时 核销金额 应收到的入库单总金额 使用预付款余额 MIN(该供应商可用结余款, 核销金额) 本次实际支付 核销金额 - 使用预付款余额 若本次实际支付 0则付款账户余额减少本次实际支付 供应商结余款减少使用预付款余额 入库单已核销金额增加核销金额 当已核销金额 入库单金额时该入库单标记为已结清逻辑说明结余款是供应商维度余额不是单据维度余额因此付款单上不直接写死核销到哪张入库单而是先冲抵结余款再在分配表里登记对应入库单号和核销金额。这样做的好处是灵活但代价是供应商余额的实时性完全依赖每次付款操作的原子性如果中途异常必须先回滚整个事务。文档特意举了一个数字例子采购总额 53200 元先付预付款 37240 元货到当天支付剩余 15960 元。实现时可以给“本次付款”字段填入 53200系统自动拆成“预付款冲抵 37240 本次实付 15960”。从使用者视角这样更直观也验证了付款单必须记录“单据原值、冲抵金额、实付金额”三个独立字段。3.3 销售管理与收款退款的边界条件销售管理的单据结构与进货类似但多了两个关键业务规则客商下拉框里默认“散客”以及销售退货后按原销售价或当时加权价退款。散客的存在意味着客户表里至少要有一条初始化客户系统里所有现款销售都落到这个虚拟客户上这样销售明细表、业绩统计、应收报表都不需要单独处理“无客户”的情况。销售单对库存的影响是立即扣减还是审核后扣减是需求说明书没有写死、但实现时必须决策的点。我一般会建议做“保存草稿不动库存审核才动库存”的双状态设计因为销售开单经常填一半发现商品价格或数量需要修改如果每次保存都扣库存取消单子时就要反向补货容易出错。收款退款环节与进货付款对称。预收客户货款时增加客户结余款开销售单后允许用结余款冲抵应收这与供应商预付款逻辑完全一致。一个值得注意的处理是文档提到“付预付款时不能指定付某一张入库单”对应到销售收款侧也一样“收预收款”不应指定到具体销售单只能在后续“收欠款”时做核销分配。两套对称逻辑同时实现时最好抽象成统一的结算引擎避免在采购模块和销售模块写两遍。4. 期初数据与成本核算加权平均、先进先出的落地差异4.1 期初数据的六张表和录入顺序需求说明书列出了六类期初数据期初商品库存、期初应付款、期初应收款、期初现金银行、期初借入款余额、期初借出款余额。这不是让用户随便填的录入顺序有依赖关系先有仓库档案才能录期初库存先有客户档案供应商类型才能录期初应付款、应收款先有资金账户才能录现金银行期初余额。期初库存单的表结构可以直接复用采购入库单的“商品行仓库”结构但单据类型标识是“期初建账单”而不是真正的进货单。这样处理的好处是期初数据也能参加库存账表查询后续仓库盘点时这些数量会被纳入计算。期初应付款、应收款表各冗余一张往来单位余额表初始化后该供应商或客户的余额直接写入而不是通过汇总单据生成。借入款和借出款在很多进销存系统里容易被忽略。文档单独列出了两张表含义是企业向外部单位借入现金形成负债或借出现金形成资产。这类业务不产生商品流动只做资金账户和往来单位余额的变化。建表时需要注意这里的往来单位既可以是供应商也可以是客户所以往来单位字段应关联到统一的“往来单位档案”而不是复用客户档案或供应商档案单表。4.2 四种成本核算方法从公式到实现文档九.1 词汇解释把四种核算方法全讲了一遍这是整份需求说明书含金量最高的部分。做一个进销存系统成本核算方法是账套的基础参数必须在建账套时选定之后不允许修改因为历史成本已经按某种方法结转切换规则会导致账目核不平。4.2.1 移动加权平均法的 SQL 计算逻辑移动加权平均法的核心难点不是公式本身而是多线程并发下库存被同时扣减时的计算顺序。下面是一段按时间顺序处理出入库流水并计算结存的存储过程逻辑-- 计算某一商品在指定时间点的移动加权平均成本和结存数量 -- 以流水表 transaction_flow 为输入按发生时间排序 SELECT t.id, t.bill_type, -- INBOUND 采购入库 / OUTBOUND 销售出库 t.quantity, t.amount, prev_qty : prev_qty t.quantity AS current_qty, prev_amt : prev_amt t.amount AS current_amount, CASE WHEN prev_qty 0 THEN 0 ELSE prev_amt / prev_qty END AS current_avg_cost FROM transaction_flow t JOIN (SELECT prev_qty : 0, prev_amt : 0) r WHERE t.account_id #{accountId} AND t.commodity_id #{commodityId} AND t.biz_date #{targetDate} ORDER BY t.biz_date, t.id;逻辑说明prev_qty 和 prev_amt 是 MySQL 的会话变量在一条 SQL 中按时间顺序累加数量和金额。每处理一行都用当前总金额除以当前总数量得出一行的加权平均单价。注意这里的 amount 对采购入库是正数对销售出库是负数因此出库金额按当时计算的平均价结转形成负数量的结存后单价依然有效。参数说明bill_type 决定金额取正值还是负值这在复杂业务里经常出错——采购退货也是一条 INBOUND 流水但数量是负数销售退货则是 OUTBOUND 且数量为负数。建议把每种单据类型的方向在配置表里定义清楚不要靠代码里 if-else 硬编码。4.2.2 先进先出法与分批认定法先进先出法的实现思路完全不同。移动加权平均把成本摊平了而先进先出要求按批次消耗库存。数据模型上需要增加批号表batch_stock每批入库记录批次号、数量、单价出库时按时间顺序从最早批次开始扣减。FIFO 出库成本计算 输入商品 A出库数量 100 逻辑 剩余待扣数量 100 累计出库成本 0 按批次时间从早到晚遍历 batch_stock 中该商品的未消耗数量 当前批次可扣数量 MIN(该批次剩余数量, 剩余待扣数量) 累计出库成本 当前批次可扣数量 * 该批次单价 该批次剩余数量 - 当前批次可扣数量 剩余待扣数量 - 当前批次可扣数量 若剩余待扣数量 0停止遍历 输出出库总成本逻辑说明先进先出的前提是库存批次数据完整且不允许负库存出库。如果允许负库存FIFO 就会算不下去——因为负库存意味着后面卖出的货在时间上早于它对应的进货批次此时只能退化为移动加权平均或强制不允许负库存。文档里允许负库存的开关在这个意义上是与成本核算方法联动校验的账套选择先进先出法时应禁止负库存移动加权平均法可以允许。分批认定法相对简单要求入库时给每一批货一个唯一标识如生产批次号或采购订单号出库时先指定从哪批出。这种方法的数据库实现最简单商品库存表增加 batch_id 字段即可。但业务使用成本高所以文档限定为“原材料数量不多、使用不频繁或者贵重物品”场景。在产品设计中可以把三种方法做成账套参数界面只展示可选值由实施人员按企业实际选定。4.3 组装与拆卸的成本结转文档九.7.7 提到“组装与拆卸”这是很多小进销存系统不做的功能。组装是把多个子商品合成为一个成品拆卸反之。成本处理规则是组装单发生后子商品库存减少、成品库存增加成品成本等于子商品总成本可加上组装费用拆卸单则反向操作。如果成品此前已有库存这里又遇到成本和按批次选择的问题。稳妥做法是让用户在组装单上手工填写成品成本价系统给出默认值按各子商品加权均价累加这样既灵活又避免强行自动计算引发的争议。5. 查询报表与单据联查把需求文档里的每个“要求”变成可验收的功能点5.1 查询条件的颗粒度决定报表可用性文档第十章把查询报表分成采购进货、商品销售、存货、财务四个模块覆盖了订单、入库单、付款单、供应商/客户档案、库存流水等对象。但需求方在文档里没有逐一写出查询条件字段真正做设计时要按“单据头单据行”两级来规划筛选条件。单据头级单号、日期区间、往来单位、经办人、单据状态草稿/已审核/已作废单据行级商品编码/名称/拼音码、仓库。两级合起来基本能覆盖日常找单场景。一个容易踩坑的点是“单据联查”。文档提到从进价明细表可以查看采购入库单整套单据从售价明细表查看销售出库单整套单据。这里需要关联到原单的行 ID而不是仅仅显示一个单号。前端实现时明细行上必须带 source_bill_id 和 source_bill_type 字段点击“联查”才能直接跳到源头单据。如果最初表设计遗漏了这两个字段后期只能靠冗余的单号去模糊匹配会非常被动。5.2 订货执行状态与单据作废策略采购订单是否存在未执行完的行这是仓库人员最关心的查询维度。实现时建议在采购订单行上维护 ordered_qty、received_qty、returned_qty 三个冗余字段查询时用 received_qty - returned_qty ordered_qty 判定未完成。返回未执行完的订单列表时再把“已到交货日期仍未收完货”作为一个独立筛选标签。作废策略是另一个文档里提到但没有展开、又必须提前定的规则。订单可以终止终止后的业务流程如何处理废单是否保留我建议采用软删除单据头加 status 字段草稿/已审核/已作废三态。作废单保留全部明细报表默认过滤但允许通过“包含作废单”的开关查看。这样审计时能找到历史记录又不干扰日常业务统计。5.3 价格跟踪与常用商品过滤商品档案里的价格跟踪需要在商品主列表的详情抽屉中提供两个 tab进价明细表和售价明细表各按时间倒序列出历史价格并链接到对应的入库单或出库单。实现上可以单独建一张价格流水表字段为商品 ID、业务类型采购/销售、价格、关联单据 ID、业务日期。每次单据审核时写一条记录而不是实时去明细表里查 MAX(date)。这样查询速度更稳定也方便后续做价格趋势折线图。常用商品过滤是一个很实用的细节。录入单据时商品选择器默认展示常用商品常用之外的需要通过搜索框查找。设计上是对商品档案表增加 is_frequent 字段每个账套下各自维护。别把这个字段做成全局的不同账套经营的品类不同全局设置会让推荐列表失真。报表设计这里建议直接把文档第九章所有“要求”逐条映射成功能点卡片贴在研发团队的迭代看板上。一条需求说明书里写的功能没有对应的查询入口就无法验收在交付时很容易被需求方追着问“这个功能做了为什么看不到”。先列需求再列对应的菜单路径、查询条件、展示字段一页清单就能把这个文档翻译成开发任务和测试用例整个交付过程会顺畅很多。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门