☰
餐饮采购系统数据库建设:从原材料清单到自动补货的建库实战
2026/10/3 1:01:59 网站建设 项目流程

简介:这份资源面向餐饮企业信息化建设者、后端开发与数据库设计初学者,提供食品采购系统原材料清单的建库参考。内容围绕食材名称、分类、规格与计量单位展开,涵盖蔬菜、菌菇、豆类等常见品类,并延伸至数据标准化、库存跟踪、供应商管理、采购计划、质量控制、报表分析、接口集成、安全备份与可扩展性等设计要点,帮助读者理解如何把零散食材信息整理成结构清晰、便于维护的数据库模型。资源包共1个doc文件,约864KB,以文档形式集中呈现原材料清单与字段组织方式,适合直接对照建表或作为数据字典底稿。目前已有101人学习下载,可作为餐饮采购系统数据库搭建、库存管理模块开发与食材主数据梳理的实用参考资料。

1. 餐饮食品采购系统数据库建设:从一张原材料清单.doc 说起

很多餐饮老板或后厨管理者第一次找我聊数字化,手里都攥着一份《餐饮食品采购系统数据库建设原材料清单.doc》。这份文档通常长这样:几十行 Excel 风格的表格,列着“土豆、5 斤、根茎类、供应商老张、周一送”,再往下是“冻品虾仁、20 斤、冷冻、李姐、周三送”。它看着像一张普通采购单,但真正要把它变成一套能跑起来的采购系统数据库,核心难点不在写 SQL,而在把这份“人话清单”翻译成机器能稳定查询、能自动补货、能对账的结构化数据。这篇文章面向的是正打算把餐饮采购从微信接龙、纸质单子搬到数据库里的开发者或门店 IT 负责人,我会按“先立数据模型、再建表、再灌数据、最后避坑”的顺序,把这份原材料清单.doc 拆成可复现的建库路径。你不需要先成为 DBA,但需要愿意动手改字段、调参数。

2. 原材料清单.doc 里的字段怎么映射成数据库表

2.1 先别急着建表:把清单里的“人话”拆成实体

打开那份原材料清单.doc,你看到的每一行其实混了四类信息:食材本身(土豆、虾仁)、规格单位(5 斤、20 斤)、供应商(老张、李姐)、配送周期(周一送、周三送)。如果直接建一张大宽表,后面改一个供应商电话就要全表更新,查询“本周所有冻品供应商”也会变得很别扭。常见做法是拆成四张核心表:食材表(ingredient)、供应商表(supplier)、采购订单表(purchase_order)、订单明细表(order_item)。食材表存名称、分类、默认单位、存储条件;供应商表存名称、联系方式、结算周期;采购订单表存下单日期、供应商、总金额、状态;订单明细表存订单 ID、食材 ID、数量、单价。这样拆的好处是,当老张不再送土豆时,你只需要改食材表里的默认供应商字段,历史订单不受影响。

2.2 字段类型和约束:别让“5 斤”变成字符串

很多新手会把数量字段设成 VARCHAR,因为清单里写的是“5 斤”。但一旦你要算“本周土豆总采购量”,字符串就没法 SUM。正确做法是数量用 DECIMAL(10,2),单位单独用 ENUM 或字典表存“斤/公斤/箱/袋”。下面这段 MySQL 建表语句是我在门店项目里常用的最小可用版本,你可以直接抄:

-- 食材表:存基础信息,不存库存 CREATE TABLE ingredient ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL COMMENT '食材名称,如土豆', category VARCHAR(32) NOT NULL COMMENT '根茎类/叶菜类/冻品', default_unit VARCHAR(8) NOT NULL DEFAULT '斤' COMMENT '默认采购单位', storage_type TINYINT NOT NULL DEFAULT 1 COMMENT '1常温 2冷藏 3冷冻', default_supplier_id INT COMMENT '默认供应商ID', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_name (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 供应商表:结算周期用天数存,别存“月结30天”这种文本 CREATE TABLE supplier ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, phone VARCHAR(20), settle_days INT NOT NULL DEFAULT 0 COMMENT '结算天数,0为现结', status TINYINT DEFAULT 1 COMMENT '1合作中 0停用' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

逻辑说明:ingredient 表的 name 加了唯一索引,防止“土豆”和“马铃薯”被当成两种食材重复录入;storage_type 用数字而不是文本,是为了后面做“冷冻食材单独生成采购单”时查询更快。参数上,DECIMAL(10,2) 能存到小数点后两位,足够应对“1.5 斤”这种场景;settle_days 存整数,对账时直接用 DATE_ADD 算到期日,比解析文本可靠得多。

2.3 采购订单和明细:为什么必须分两张表

如果只建一张 purchase 表,字段会是“订单号、食材、数量、单价、供应商”,那一个订单买五样菜就要插五行,订单号重复,改一个供应商电话要改五行。拆成 purchase_order 和 order_item 后,订单头存一次供应商和日期,明细存多行食材。查询“老张本周送了多少钱”只需要 JOIN 两张表。这里有个参数要注意:order_item 里的 unit_price 要存下单时的价格,不要关联食材表的当前价,否则历史订单金额会变。我一般还会加一个 snapshot_unit 字段,把当时的单位也存下来,防止食材表后来把“斤”改成“公斤”导致对账混乱。

3. 把清单.doc 灌进数据库:解析、清洗、入库三步走

3.1 用 Python 读 .doc 并转成结构化行

.doc 是老二进制格式,直接 open 会乱码。常见做法是先用 LibreOffice 命令行转成 .docx 或 .csv,再用 python-docx 或 pandas 读。下面这段脚本假设你已经把清单另存为 CSV,列名为“食材名称、数量、单位、分类、供应商、配送日”:

import pandas as pd import pymysql # 读 CSV,注意编码,餐饮清单常从 WPS 导出,gbk 居多 df = pd.read_csv('原材料清单.csv', encoding='gbk') # 去掉全空行和表头重复行 df = df.dropna(how='all') df = df[df['食材名称'] != '食材名称'] # 清洗数量:把“5斤”“5 斤”“约5斤”统一成 5.0 def clean_qty(val): import re if pd.isna(val): return 0.0 m = re.search(r'(\d+(\.\d+)?)', str(val)) return float(m.group(1)) if m else 0.0 df['数量'] = df['数量'].apply(clean_qty) # 单位统一:斤/公斤/箱/袋,其他归为“斤” df['单位'] = df['单位'].apply(lambda x: x if x in ['斤','公斤','箱','袋'] else '斤') print(df.head())

逻辑说明:clean_qty 用正则抓第一个数字,能处理“约5斤”“5斤左右”这类写法;单位归一化是为了后面入库时不会因为“KG”和“公斤”并存导致统计错误。参数上,encoding='gbk' 是针对国内 WPS 导出的常见编码,如果你用 UTF-8 打开报错,就换回来。

3.2 入库前去重:同名食材只留一条

清单里经常出现“土豆”写两遍,供应商不同。入库时不能直接 INSERT,否则 ingredient 表的唯一索引会报错。我一般用 INSERT ... ON DUPLICATE KEY UPDATE 来兜底:

conn = pymysql.connect(host='localhost', user='root', password='yourpass', database='catering', charset='utf8mb4') cursor = conn.cursor() for _, row in df.iterrows(): # 先插食材,忽略重复 cursor.execute(""" INSERT INTO ingredient (name, category, default_unit, storage_type) VALUES (%s, %s, %s, %s) ON DUPLICATE KEY UPDATE category=VALUES(category) """, (row['食材名称'], row['分类'], row['单位'], 1)) # 再查供应商ID,没有就插 cursor.execute("SELECT id FROM supplier WHERE name=%s", (row['供应商'],)) sup = cursor.fetchone() if not sup: cursor.execute("INSERT INTO supplier (name) VALUES (%s)", (row['供应商'],)) sup_id = cursor.lastrowid else: sup_id = sup[0] # 这里省略订单插入,实际按配送日分组生成订单 conn.commit()

逻辑说明:ON DUPLICATE KEY UPDATE 只更新分类,不更新单位,因为单位一旦被历史订单引用就不该变。供应商查询用 SELECT 再 INSERT,虽然效率不高,但清单通常只有几十行,够用。参数上,charset='utf8mb4' 必须加,否则食材名里的生僻字会变问号。

3.3 验证数据:三条 SQL 查有没有灌歪

入库后别急着写业务代码,先跑三条查询验证。第一,查食材总数和分类分布:SELECT category, COUNT(*) FROM ingredient GROUP BY category;如果“根茎类”只有一条,说明分类字段没读对。第二,查有没有数量为 0 的明细:SELECT * FROM order_item WHERE qty = 0;有的话回去看 clean_qty 是不是没匹配到数字。第三,查供应商重复:SELECT name, COUNT(*) FROM supplier GROUP BY name HAVING COUNT(*) > 1;正常应该为空。这三条能挡住八成低级错误。

4. 采购系统数据库避坑:五个让我半夜爬起来改表的教训

4.1 现象:月底对账金额对不上,差几毛钱

原因:unit_price 用了 FLOAT 而不是 DECIMAL,累加时出现浮点误差。解决:所有金额字段改 DECIMAL(10,2),Python 里用 Decimal 类型,不要用 float 算钱。

4.2 现象:查询“本周冻品采购量”特别慢,三秒才出结果

原因:storage_type 和 created_at 没建索引,全表扫描。解决:ALTER TABLE ingredient ADD INDEX idx_storage (storage_type);订单表按 created_at 建索引。注意索引不要乱加,写多读少的表加索引会拖慢插入。

4.3 现象:供应商改名后,历史订单里的供应商名也跟着变了

原因:order 表里存了 supplier_name 文本,而不是 supplier_id。解决:订单表只存 supplier_id,显示时 JOIN supplier 表。如果业务要求保留历史名称,加一个 supplier_name_snapshot 字段,下单时写入,之后不更新。

4.4 现象:清单里的“冻品虾仁”和“虾仁”被当成两种食材

原因:没有做名称归一化。解决:入库前用映射表把“冻品虾仁”统一成“虾仁”,storage_type 设为 3。我一般会维护一个 alias 表,记录“冻品虾仁→虾仁”“土豆→马铃薯”这类同义词。

4.5 现象:配送日“周一送”存成字符串,没法自动生成下周订单

原因:配送周期没有结构化。解决:在 supplier 表加 delivery_weekday 字段,存 1-7 的数字,1 代表周一。生成订单时用 Python 的 date.weekday() 匹配,自动算出下周日期。

5. 进阶:用视图和定时任务把清单变成自动补货建议

5.1 建一个“安全库存”视图,低于阈值就提醒

原材料清单.doc 只告诉你买什么,不告诉你什么时候该买。我一般会加一张 stock 表记录当前库存,再建一个视图算缺口:

CREATE VIEW v_reorder AS SELECT i.name, i.default_unit, s.qty AS current_qty, i.safe_stock, (i.safe_stock - s.qty) AS gap FROM ingredient i JOIN stock s ON s.ingredient_id = i.id WHERE s.qty < i.safe_stock;

逻辑说明:safe_stock 字段需要你在 ingredient 表里补上,默认给 0。这个视图查出来的就是“需要补货的食材”。参数上,gap 为负数表示库存充足,正数表示缺口。你可以每天早八点用 cron 跑一次,把结果发到门店群。

5.2 用事件调度器自动生成采购草稿

MySQL 从 5.1 开始支持 EVENT,但很多云数据库默认关闭。我一般用 Python 的 APScheduler 更可控:

from apscheduler.schedulers.blocking import BlockingScheduler import pymysql def gen_draft(): conn = pymysql.connect(host='localhost', user='root', password='yourpass', database='catering') cursor = conn.cursor() cursor.execute("SELECT * FROM v_reorder") for row in cursor.fetchall(): cursor.execute(""" INSERT INTO purchase_order (supplier_id, order_date, status) VALUES ((SELECT default_supplier_id FROM ingredient WHERE name=%s), CURDATE(), 'draft') """, (row[0],)) conn.commit() sched = BlockingScheduler() sched.add_job(gen_draft, 'cron', hour=8, minute=0) sched.start()

逻辑说明:这里只生成草稿订单,不直接发给供应商,避免自动下单买错。参数上,hour=8 是门店上班时间,你可以改成 6 点让店长一开门就看到。注意 default_supplier_id 可能为空,实际项目里要加 if 判断。

5.3 一个我坚持了三年的习惯

每次改完表结构,我一定先在一个测试库跑一遍全量清单导入,再用SELECT COUNT(*)对比源文件行数。差一行都不行。这个习惯帮我挡过至少两次“字段截断导致食材名变空”的事故。数据库建设没有后悔药,原材料清单.doc 可以改十版,但线上表结构改一次就要想清楚三个月后会不会翻车。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询