├── erpdata.db ├── readme_img ├── img0.png ├── img1.png ├── img2.png ├── img3.png ├── img4.png ├── img5.png ├── img6.png ├── img7.png ├── img8.png ├── img9.png ├── imga.png ├── img10.png ├── img11.png ├── img12.png ├── img13.png ├── img14.png ├── img5.1.png └── data_stream.png ├── requirements.txt ├── .gitignore ├── ERP ├── ERP.py ├── MPS.py └── Tree.py ├── LICENSE ├── connectdb.py ├── sql ├── structure_only.sql └── structure_data.sql ├── templates ├── store.html ├── func.html └── index.html ├── README.md └── erpsys.py /erpdata.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/erpdata.db -------------------------------------------------------------------------------- /readme_img/img0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img0.png -------------------------------------------------------------------------------- /readme_img/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img1.png -------------------------------------------------------------------------------- /readme_img/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img2.png -------------------------------------------------------------------------------- /readme_img/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img3.png -------------------------------------------------------------------------------- /readme_img/img4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img4.png -------------------------------------------------------------------------------- /readme_img/img5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img5.png -------------------------------------------------------------------------------- /readme_img/img6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img6.png -------------------------------------------------------------------------------- /readme_img/img7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img7.png -------------------------------------------------------------------------------- /readme_img/img8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img8.png -------------------------------------------------------------------------------- /readme_img/img9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img9.png -------------------------------------------------------------------------------- /readme_img/imga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/imga.png -------------------------------------------------------------------------------- /readme_img/img10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img10.png -------------------------------------------------------------------------------- /readme_img/img11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img11.png -------------------------------------------------------------------------------- /readme_img/img12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img12.png -------------------------------------------------------------------------------- /readme_img/img13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img13.png -------------------------------------------------------------------------------- /readme_img/img14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img14.png -------------------------------------------------------------------------------- /readme_img/img5.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/img5.1.png -------------------------------------------------------------------------------- /readme_img/data_stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesc/simple-erp-system/HEAD/readme_img/data_stream.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.103.2 2 | jinja2==3.1.2 3 | python-multipart==0.0.6 4 | starlette==0.27.0 5 | uvicorn==0.23.2 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .idea/ 3 | __pycache__/ 4 | 5 | # Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` 6 | # should NOT be excluded as they contain compiler settings and other important 7 | # information for Eclipse / Flash Builder. 8 | -------------------------------------------------------------------------------- /ERP/ERP.py: -------------------------------------------------------------------------------- 1 | from ERP.MPS import MpsList 2 | from ERP.Tree import ComposeTree 3 | 4 | 5 | class ERP: 6 | def __init__(self, mutex_for_store): 7 | self.ans = [] 8 | self.MpsList = MpsList() 9 | self.ComposeTree = ComposeTree(self.MpsList, self.ans, mutex_for_store) 10 | 11 | async def clear(self): 12 | await self.MpsList.clear_mps() 13 | await self.ComposeTree.clear_tree() 14 | self.ans.clear() 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 bytesc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /connectdb.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import sqlite3 3 | mutex_for_store = asyncio.Lock() 4 | 5 | 6 | async def select_from_db(sql): 7 | connect = sqlite3.connect('./erpdata.db') # Connect to the SQLite database file 'erp.db' 8 | cursor = connect.cursor() # Use cursor() to get a cursor 9 | cursor.execute(sql) # Execute the SQL statement 10 | results = cursor.fetchall() # Get all results 11 | 12 | connect.commit() 13 | cursor.close() 14 | connect.close() 15 | return results 16 | 17 | 18 | async def exec_sql(sql): 19 | connect = sqlite3.connect('./erpdata.db') # Connect to the SQLite database file 'erp.db' 20 | cursor = connect.cursor() # Use cursor() to get a cursor 21 | cursor.execute(sql) # Execute the SQL statement 22 | 23 | connect.commit() 24 | cursor.close() 25 | connect.close() 26 | return 27 | 28 | 29 | async def get_supply_available(): 30 | supply_available = [] 31 | sql_res = await select_from_db("""select DISTINCT inventory."父物料名称" from inventory""") 32 | for item in sql_res: 33 | if item[0] != "" and item[0] is not None: 34 | supply_available.append(item) 35 | return supply_available 36 | 37 | 38 | async def get_store_available(): 39 | return await select_from_db("""select * from store""") 40 | -------------------------------------------------------------------------------- /ERP/MPS.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import datetime 3 | 4 | 5 | class MpsObj: 6 | def __init__(self, pname, require, deadline, index): 7 | self.pname = pname 8 | self.require = require 9 | self.deadline = deadline 10 | self.index = index 11 | 12 | 13 | class MpsList: 14 | def __init__(self): 15 | self.MPS_output_que = [] 16 | self.MPS_obj_que = [] 17 | self.MPS_que_index = 0 18 | self.mutex_for_mps = asyncio.Lock() 19 | 20 | async def add_mps(self, pname, require, deadline): 21 | await self.mutex_for_mps.acquire() 22 | try: 23 | if pname != '' and require != '' and deadline != '': 24 | deadline = datetime.datetime.strptime(deadline, '%Y-%m-%d').date() 25 | self.MPS_obj_que.append(MpsObj(pname, require, deadline, self.MPS_que_index)) 26 | self.MPS_output_que.append([pname, require, deadline, self.MPS_que_index]) 27 | self.MPS_que_index = self.MPS_que_index + 1 # 录入一次就+1 28 | 29 | self.MPS_obj_que.sort(key=lambda item: item.deadline) 30 | finally: 31 | self.mutex_for_mps.release() 32 | 33 | async def clear_mps(self): 34 | await self.mutex_for_mps.acquire() 35 | try: 36 | self.MPS_output_que = [] 37 | self.MPS_obj_que = [] 38 | self.MPS_que_index = 0 39 | finally: 40 | self.mutex_for_mps.release() 41 | -------------------------------------------------------------------------------- /sql/structure_only.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : erp 5 | Source Server Type : SQLite 6 | Source Server Version : 3035005 7 | Source Schema : main 8 | 9 | Target Server Type : SQLite 10 | Target Server Version : 3035005 11 | File Encoding : 65001 12 | 13 | Date: 13/10/2023 23:08:27 14 | */ 15 | 16 | PRAGMA foreign_keys = false; 17 | 18 | -- ---------------------------- 19 | -- Table structure for bom 20 | -- ---------------------------- 21 | DROP TABLE IF EXISTS "bom"; 22 | CREATE TABLE "bom" ( 23 | "序号" varchar NOT NULL, 24 | "资产类说明" varchar(255), 25 | "资产类方向" varchar, 26 | "资产类汇总序号" varchar, 27 | "变量名" varchar, 28 | PRIMARY KEY ("序号") 29 | ); 30 | 31 | -- ---------------------------- 32 | -- Table structure for inventory 33 | -- ---------------------------- 34 | DROP TABLE IF EXISTS "inventory"; 35 | CREATE TABLE "inventory" ( 36 | "调配基准编号" varchar NOT NULL, 37 | "父物料名称" varchar, 38 | "子物料名称" varchar, 39 | "构成数" integer, 40 | "配料提前期" integer, 41 | "供应商提前期" integer, 42 | PRIMARY KEY ("调配基准编号"), 43 | FOREIGN KEY ("父物料名称") REFERENCES "supply" ("名称") ON DELETE CASCADE ON UPDATE CASCADE, 44 | FOREIGN KEY ("子物料名称") REFERENCES "supply" ("名称") ON DELETE CASCADE ON UPDATE CASCADE 45 | ); 46 | 47 | -- ---------------------------- 48 | -- Table structure for store 49 | -- ---------------------------- 50 | DROP TABLE IF EXISTS "store"; 51 | CREATE TABLE "store" ( 52 | "物料名称" varchar NOT NULL, 53 | "工序库存" integer, 54 | "资材库存" integer, 55 | PRIMARY KEY ("物料名称"), 56 | FOREIGN KEY ("物料名称") REFERENCES "supply" ("名称") ON DELETE CASCADE ON UPDATE CASCADE 57 | ); 58 | 59 | -- ---------------------------- 60 | -- Table structure for supply 61 | -- ---------------------------- 62 | DROP TABLE IF EXISTS "supply"; 63 | CREATE TABLE "supply" ( 64 | "名称" varchar NOT NULL, 65 | "单位" varchar(255), 66 | "调配方式" varchar(255), 67 | "损耗率" float, 68 | "作业提前期" int, 69 | PRIMARY KEY ("名称") 70 | ); 71 | 72 | PRAGMA foreign_keys = true; 73 | -------------------------------------------------------------------------------- /templates/store.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ERP 6 | 78 | 79 | 80 |
81 | 物料计算 82 | 资产管理 83 | 库存管理 84 |

ERP 库存

85 |

物料库存

86 | {% for item in store %} 87 |
88 | 91 | 95 | 99 | 100 |
101 | {% endfor %} 102 |
103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-erp-system 2 | 3 | ✨ **基于 Python, FastAPI, sqlite3 的简单 ERP 系统**:企业资源计划系统(Enterprise Resource Planning System),实现 MPS(Master Production Schedule) 的计算,按时间分段计划企业应生产的最终产品的数量和交货期;资产负债表公式查询。 4 | 5 | [个人网站:www.bytesc.top](http://www.bytesc.top) 包含项目 📌 [在线演示](http://www.bytesc.top#erp) 📌 6 | 7 | 🔔 如有项目相关问题,欢迎在本项目提出`issue`,我一般会在 24 小时内回复。 8 | 9 | 🚩 欢迎参考、复用代码,但请遵守`LICENSE`文件中的开源协议(参考翻译见本文档末尾)。对本仓库内的实质性内容(包括但不限于数据,图片,文档)保留著作权。 10 | 11 | ## 功能介绍 12 | 13 | - 根据 MPS(Master Production Schedule) 按时间分段计划企业应生产的最终产品的数量和交货期 14 | - 实现资产负债表公式查询 15 | - 注释详细,易复现 16 | - 极简的项目架构 17 | - 极简的页面布局,轻量化的前端代码 18 | 19 | ## 功能优点 20 | - **根据库存动态更新计划日期**:根据工序库存和资材库存的余量,判断是否需要配料提前期和供应商提前期,根据库存更新计划日期。 21 | - **不会处理不需要的子节点**:当某个节点库存够用,实际需求量(要生产/采购的数量)为 0 时,不会处理其子节点。 22 | - **底层的物料先分配库存,得到最优方案**:搜索时按照子树深度对子节点排序,优先遍历子树深度较大的子节点,尽可能让底层的物料先拿到库存。 23 | 24 | ## 基本原理 25 | 26 | 🚩 系统架构: 27 | 28 | ![](./readme_img/img0.png) 29 | 30 | 🚩 数据流图: 31 | 32 | ![](./readme_img/data_stream.png) 33 | 34 | 🚩 DPS 算法流程: 35 | 36 | ![](./readme_img/imga.png) 37 | 38 | - 通过读取数据库中的物料的子父关系表,建立物料合成关系树,并用 DFS(Deep First Search) 深度优先搜索算法遍历物料关系树,并标记节点深度和每个节点的子树深度。 39 | - 按照 MPS 队列和物料库存数量,深度优先搜索,计算生产和采购计划。 40 | * 根据工序库存和资材库存,判断是否需要配料提前期和供应商提前期,动态更新计划日期。 41 | * 按照子树深度对子节点排序,优先遍历子树深的子节点,尽可能让底层的物料先分配库存。 42 | * 当实际需求量(要生产/采购的数量)为 0 时返回,防止生产/采购无需处理的子节点。 43 | 44 | ## 功能展示 45 | 46 | 添加 MPS 记录 47 | ![](./readme_img/img1.png) 48 | 产品名称从数据库中获取,用户可选择 49 | ![](./readme_img/img2.png) 50 | 点击计算,可一次性计算多条计划,按照时间优先级分配库存 51 | ![](./readme_img/img3.png) 52 | 资产负债公式变量同意从数据库中获取 53 | ![](./readme_img/img4.png) 54 | 可以一输出计算多个变量的公式 55 | ![](./readme_img/img5.png) 56 | 可手动修改库存信息(点击计算也会根据消耗自动更新库存) 57 | ![](./readme_img/img5.1.png) 58 | 59 | ## 数据库结构 60 | 61 | `erpdata.db` 为 sqlite3 数据文件,`sql` 文件夹下包含数据库备份sql代码。 62 | 63 | 数据库结构 64 | ![](./readme_img/img6.png) 65 | 66 | `supply`物料信息表 67 | ![](./readme_img/img7.png) 68 | ![](./readme_img/img8.png) 69 | 70 | `inventory`物料构成清单 71 | ![](./readme_img/img9.png) 72 | ![](./readme_img/img10.png) 73 | 74 | `store`库存表 75 | ![](./readme_img/img11.png) 76 | ![](./readme_img/img12.png) 77 | 78 | `bom`资产负债信息表 79 | ![](./readme_img/img13.png) 80 | ![](./readme_img/img14.png) 81 | 82 | ## 项目运行 83 | 84 | 环境`python 3.9` 85 | 86 | 安装依赖 87 | ```bash 88 | pip install -r requirements.txt 89 | ``` 90 | 运行 91 | ```bash 92 | python erpsys.py 93 | ``` 94 | 浏览器访问 [http://127.0.0.1:8080](http://127.0.0.1:8080)即可进入系统 95 | 96 | 如果8080端口被占用,修改`erpsys.py`文件末尾的端口配置 (port) 即可。 97 | ```python 98 | if __name__ == "__main__": 99 | import uvicorn 100 | uvicorn.run(app, host="127.0.0.1", port=8081) 101 | ``` 102 | 注意,如果这里 `host="0.0.0.0"` 代表允许所有host访问,请依然浏览器输入 [http://127.0.0.1:8080](http://127.0.0.1:8080) 而不是 [http://0.0.0.0:8080](http://0.0.0.0:8080) 103 | 104 | ## 项目结构 105 | 106 | ```txt 107 | │ connectdb.py 108 | ├─ERP 109 | │ ERP.py 110 | │ MPS.py 111 | │ Tree.py 112 | ├─templates 113 | │ func.html 114 | │ index.html 115 | ├─readme_img 116 | ├─sql 117 | │ structure_data.sql 118 | │ structure_only.sql 119 | │ erpdata.db 120 | │ erpsys.py 121 | │ LICENSE 122 | │ README.md 123 | │ requirements.txt 124 | ``` 125 | 126 | - `erpsys.py` 是源代码文件,包含服务端代码,也是项目的运行入口 127 | - `ERP` 文件夹下存放源代码文件,包含核心算法代码 128 | - `connectdb.py` 包含数据库配置,连接,查询程序 129 | - `erpdata.db` sqlite3 数据库文件 130 | - `templates` 文件夹存放待渲染的 html 模板文件 131 | - `sql` 文件夹存放创建数据库的 sql 代码 132 | 133 | 134 | # 开源许可证 135 | 136 | 此翻译版本仅供参考,以 LICENSE 文件中的英文版本为准 137 | 138 | MIT 开源许可证: 139 | 140 | 版权所有 (c) 2023 bytesc 141 | 142 | 特此授权,免费向任何获得本软件及相关文档文件(以下简称“软件”)副本的人提供使用、复制、修改、合并、出版、发行、再许可和/或销售软件的权利,但须遵守以下条件: 143 | 144 | 上述版权声明和本许可声明应包含在所有副本或实质性部分中。 145 | 146 | 本软件按“原样”提供,不作任何明示或暗示的保证,包括但不限于适销性、特定用途适用性和非侵权性。在任何情况下,作者或版权持有人均不对因使用本软件而产生的任何索赔、损害或其他责任负责,无论是在合同、侵权或其他方面。 147 | -------------------------------------------------------------------------------- /templates/func.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ERP 6 | 7 | 95 | 96 | 97 |
98 | 物料计算 99 | 资产管理 100 | 库存管理 101 |

ERP 资产负债计算

102 |
103 | 111 | 112 |
113 |

公式列表

114 | 计算 115 | 清空 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | {% for item in func_output %} 124 | 125 | 126 | 127 | {% endfor %} 128 | 129 |
公式
{{ item }}
130 |
131 | 132 | -------------------------------------------------------------------------------- /sql/structure_data.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : erp 5 | Source Server Type : SQLite 6 | Source Server Version : 3035005 7 | Source Schema : main 8 | 9 | Target Server Type : SQLite 10 | Target Server Version : 3035005 11 | File Encoding : 65001 12 | 13 | Date: 13/10/2023 23:08:07 14 | */ 15 | 16 | PRAGMA foreign_keys = false; 17 | 18 | -- ---------------------------- 19 | -- Table structure for bom 20 | -- ---------------------------- 21 | DROP TABLE IF EXISTS "bom"; 22 | CREATE TABLE "bom" ( 23 | "序号" varchar NOT NULL, 24 | "资产类说明" varchar(255), 25 | "资产类方向" varchar, 26 | "资产类汇总序号" varchar, 27 | "变量名" varchar, 28 | PRIMARY KEY ("序号") 29 | ); 30 | 31 | -- ---------------------------- 32 | -- Records of bom 33 | -- ---------------------------- 34 | INSERT INTO "bom" VALUES ('1', '流动资产', NULL, '', ''); 35 | INSERT INTO "bom" VALUES ('2', '货币资金', '+', '16', 'a1'); 36 | INSERT INTO "bom" VALUES ('3', '短期投资', '+', '16', ' a3'); 37 | INSERT INTO "bom" VALUES ('4', '应收票据', '+', '16', 'a5'); 38 | INSERT INTO "bom" VALUES ('5', '应收账款', '+', '7', 'a7'); 39 | INSERT INTO "bom" VALUES ('6', '减,坏账准备', '+', '7', 'a9'); 40 | INSERT INTO "bom" VALUES ('7', '应收账款净额', '+', '16', 'b1'); 41 | INSERT INTO "bom" VALUES ('8', '预付账款', '+', '16', 'a12'); 42 | INSERT INTO "bom" VALUES ('9', '应收补贴款', '+', '16', 'a14'); 43 | INSERT INTO "bom" VALUES ('10', '其他应收款', '+', '16', 'a16'); 44 | INSERT INTO "bom" VALUES ('11', '存货', '+', '16', 'a18'); 45 | INSERT INTO "bom" VALUES ('12', '待摊费用', '+', '16', 'a20'); 46 | INSERT INTO "bom" VALUES ('13', '待处理流动资产净损失', '+', '16', 'a22'); 47 | INSERT INTO "bom" VALUES ('14', '一年内到期的长期债券投资', '+', '16', 'a24'); 48 | INSERT INTO "bom" VALUES ('15', '其他流动资产', '+', '16', 'a26'); 49 | INSERT INTO "bom" VALUES ('16', '流动资产合计', '+', '35', 'b3'); 50 | 51 | -- ---------------------------- 52 | -- Table structure for inventory 53 | -- ---------------------------- 54 | DROP TABLE IF EXISTS "inventory"; 55 | CREATE TABLE "inventory" ( 56 | "调配基准编号" varchar NOT NULL, 57 | "父物料名称" varchar, 58 | "子物料名称" varchar, 59 | "构成数" integer, 60 | "配料提前期" integer, 61 | "供应商提前期" integer, 62 | PRIMARY KEY ("调配基准编号"), 63 | FOREIGN KEY ("父物料名称") REFERENCES "supply" ("名称") ON DELETE CASCADE ON UPDATE CASCADE, 64 | FOREIGN KEY ("子物料名称") REFERENCES "supply" ("名称") ON DELETE CASCADE ON UPDATE CASCADE 65 | ); 66 | 67 | -- ---------------------------- 68 | -- Records of inventory 69 | -- ---------------------------- 70 | INSERT INTO "inventory" VALUES ('000001', '眼镜', '镜框', 1, 0, 0); 71 | INSERT INTO "inventory" VALUES ('000002', '眼镜', '镜片', 2, 1, 20); 72 | INSERT INTO "inventory" VALUES ('000003', '眼镜', '螺钉', 2, 1, 10); 73 | INSERT INTO "inventory" VALUES ('000004', '镜框', '镜架', 1, 1, 20); 74 | INSERT INTO "inventory" VALUES ('000005', '镜框', '镜腿', 2, 1, 10); 75 | INSERT INTO "inventory" VALUES ('000006', '镜框', '鼻托', 2, 1, 18); 76 | INSERT INTO "inventory" VALUES ('000007', '镜框', '螺钉', 4, 1, 10); 77 | INSERT INTO "inventory" VALUES ('000008', NULL, '眼镜', 1, 0, 0); 78 | 79 | -- ---------------------------- 80 | -- Table structure for store 81 | -- ---------------------------- 82 | DROP TABLE IF EXISTS "store"; 83 | CREATE TABLE "store" ( 84 | "物料名称" varchar NOT NULL, 85 | "工序库存" integer, 86 | "资材库存" integer, 87 | PRIMARY KEY ("物料名称"), 88 | FOREIGN KEY ("物料名称") REFERENCES "supply" ("名称") ON DELETE CASCADE ON UPDATE CASCADE 89 | ); 90 | 91 | -- ---------------------------- 92 | -- Records of store 93 | -- ---------------------------- 94 | INSERT INTO "store" VALUES ('眼镜', 0, 0); 95 | INSERT INTO "store" VALUES ('螺钉', 10, 50); 96 | INSERT INTO "store" VALUES ('镜框', 0, 0); 97 | INSERT INTO "store" VALUES ('镜架', 0, 0); 98 | INSERT INTO "store" VALUES ('镜腿', 10, 20); 99 | INSERT INTO "store" VALUES ('鼻托', 0, 0); 100 | INSERT INTO "store" VALUES ('镜片', 0, 0); 101 | 102 | -- ---------------------------- 103 | -- Table structure for supply 104 | -- ---------------------------- 105 | DROP TABLE IF EXISTS "supply"; 106 | CREATE TABLE "supply" ( 107 | "名称" varchar NOT NULL, 108 | "单位" varchar(255), 109 | "调配方式" varchar(255), 110 | "损耗率" float, 111 | "作业提前期" int, 112 | PRIMARY KEY ("名称") 113 | ); 114 | 115 | -- ---------------------------- 116 | -- Records of supply 117 | -- ---------------------------- 118 | INSERT INTO "supply" VALUES ('眼镜', '副', '生产', 0.0, 1); 119 | INSERT INTO "supply" VALUES ('螺钉', '个', '采购', 0.1, 0); 120 | INSERT INTO "supply" VALUES ('镜框', '副', '生产', 0.0, 2); 121 | INSERT INTO "supply" VALUES ('镜架', '个', '采购', 0.0, 0); 122 | INSERT INTO "supply" VALUES ('镜腿', '个', '采购', 0.0, 0); 123 | INSERT INTO "supply" VALUES ('鼻托', '个', '采购', 0.0, 0); 124 | INSERT INTO "supply" VALUES ('镜片', '片', '采购', 0.0, 0); 125 | 126 | PRAGMA foreign_keys = true; 127 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ERP 6 | 94 | 95 | 96 |
97 | 物料计算 98 | 资产管理 99 | 库存管理 100 |

ERP 物料管理计算

101 |
102 | 110 | 113 | 116 | 117 |
118 |

MPS 计划队列

119 | 计算 120 | 清空 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | {% for item in que %} 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | {% endfor %} 141 | 142 |
产品数量完成日期任务序号
{{ item[0] }}{{ item[1] }}{{ item[2] }}{{ item[3] }}{{ item[4] }}{{ item[5] }}
143 |

生产和采购计划

144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | {% for item in ans %} 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | {% endfor %} 165 | 166 |
产品数量调配方式下达日期完成日期
{{ item[0] }}{{ item[1] }}{{ item[2] }}{{ item[3] }}{{ item[4] }}{{ item[5] }}
167 |
168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /ERP/Tree.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import datetime 3 | import math 4 | 5 | from connectdb import select_from_db, exec_sql 6 | 7 | 8 | class ComposeItem: 9 | def __init__(self, father, child, way, comp_num, 10 | loss_rate, store_1, store_2, adv_work, adv_make, adv_supply): 11 | self.father = father 12 | self.pname = child 13 | self.way = way # 调配方式 14 | self.comp_num = comp_num 15 | self.loss_rate = loss_rate 16 | self.store_1 = store_1 # 工序库存 17 | self.store_2 = store_2 # 资材库存 18 | self.adv_work = adv_work # 作业提前期 19 | self.adv_make = adv_make # 配料提前期 20 | self.adv_supply = adv_supply # 供应商提前期 21 | 22 | 23 | class Node: 24 | def __init__(self, compose_item: ComposeItem): 25 | self.pname = compose_item.pname 26 | self.father = compose_item.father 27 | self.depth = -1 # 节点深度 28 | self.child_depth = -1 # 子树的深度 29 | 30 | self.children = [] 31 | 32 | 33 | class ComposeTree: 34 | def __init__(self, MpsList, ans, mutex_for_store): 35 | self.mutex_for_store = mutex_for_store 36 | self.MpsList = MpsList 37 | self.ans = ans 38 | self.compose = [] 39 | 40 | def build_tree(self, root: Node, depth): 41 | root.depth = depth 42 | for item in self.compose: 43 | if item.father == root.pname: 44 | root.children.append(Node(item)) 45 | if len(root.children) == 0: 46 | return 47 | for node in root.children: 48 | self.build_tree(node, depth + 1) 49 | 50 | def mark_child_depth(self, root: Node): # 标记所有节点子树最大深度 51 | if len(root.children) == 0: 52 | root.child_depth = root.depth 53 | return 54 | for child in root.children: 55 | self.mark_child_depth(child) 56 | for child in root.children: 57 | root.child_depth = max(root.child_depth, child.child_depth) 58 | 59 | def refresh_store(self, item: Node, store_1, store_2): # 刷新库存 60 | for i in self.compose: 61 | if i.pname == item.pname: 62 | i.store_1 -= store_1 63 | i.store_2 -= store_2 64 | 65 | async def refresh_db(self): 66 | for item in self.compose: 67 | sql_statement = """UPDATE store SET""" 68 | sql_statement += " 工序库存=" + str(item.store_1) 69 | sql_statement += " where 物料名称='" + str(item.pname) + "'" 70 | # print(sql_statement) 71 | await exec_sql(sql_statement) 72 | sql_statement = """UPDATE store SET""" 73 | sql_statement += " 资材库存=" + str(item.store_2) 74 | sql_statement += " where 物料名称='" + str(item.pname) + "'" 75 | # print(sql_statement) 76 | await exec_sql(sql_statement) 77 | return 78 | 79 | def main_dfs(self, node: Node, need_num, ans, end_time): 80 | item = None 81 | for i in self.compose: 82 | if i.pname == node.pname and i.father == node.father: 83 | item = i 84 | if need_num <= 0: 85 | return 86 | need_num = math.ceil(need_num / (1 - item.loss_rate)) # 损耗 87 | if need_num <= item.store_1 + item.store_2: # 库存够 88 | if need_num <= item.store_1: # 工序够用 89 | start_time = end_time - datetime.timedelta(days=item.adv_supply) 90 | ans.append([item.pname, 0, item.way, start_time, end_time]) 91 | real_need_num = 0 92 | self.refresh_store(item, need_num, 0) 93 | else: # 工序不够,但加上资材库存够用 94 | start_time = end_time - datetime.timedelta(days=item.adv_supply + item.adv_make) 95 | ans.append([item.pname, need_num - item.store_1, item.way, start_time, end_time]) 96 | real_need_num = 0 97 | self.refresh_store(item, item.store_1, need_num - item.store_1) 98 | else: # 库存不够(工序和资材库存加起来都不够用) 99 | start_time = end_time - datetime.timedelta(days=item.adv_supply + item.adv_make + item.adv_work) 100 | ans.append([item.pname, need_num - item.store_2 - item.store_1, item.way, start_time, end_time]) 101 | real_need_num = need_num - item.store_1 - item.store_2 102 | self.refresh_store(item, item.store_1, item.store_2) 103 | 104 | if len(node.children) == 0: 105 | return 106 | else: 107 | node.children.sort(key=lambda x: -x.child_depth) # 按子树深度倒序,先遍历深的 108 | for child in node.children: 109 | comp_num = 1 110 | for i in self.compose: 111 | if i.pname == child.pname and i.father == child.father: 112 | comp_num = i.comp_num 113 | self.main_dfs(child, real_need_num * comp_num, ans, start_time) 114 | 115 | async def show_result(self): 116 | await self.mutex_for_store.acquire() 117 | await self.MpsList.mutex_for_mps.acquire() 118 | try: 119 | self.compose.clear() 120 | self.ans.clear() # 这里不能ans=[],这样会让ans指向另一个内存空间上的另一个新数组,而不是init传来的数组 121 | sql_state = """ 122 | SELECT inventory."父物料名称", inventory."子物料名称", supply."调配方式", inventory."构成数", 123 | supply."损耗率", store."工序库存",store."资材库存",supply."作业提前期",inventory."配料提前期", 124 | inventory."供应商提前期" 125 | FROM inventory,supply,store 126 | WHERE inventory."子物料名称"=supply."名称" AND inventory."子物料名称"=store."物料名称"; 127 | """ 128 | sql_res = await select_from_db(sql_state) 129 | # print(sql_res) 130 | 131 | for i in sql_res: 132 | self.compose.append(ComposeItem(*i)) 133 | 134 | for mps in self.MpsList.MPS_obj_que: # 遍历 mps 队列计算结果 135 | for item in self.compose: 136 | if item.pname == mps.pname: 137 | root = Node(item) 138 | self.build_tree(root, 0) 139 | self.mark_child_depth(root) 140 | self.main_dfs(root, mps.require, self.ans, mps.deadline) 141 | 142 | await self.refresh_db() 143 | finally: 144 | self.mutex_for_store.release() 145 | self.MpsList.mutex_for_mps.release() 146 | 147 | async def clear_tree(self): 148 | pass 149 | 150 | 151 | -------------------------------------------------------------------------------- /erpsys.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | ############################################################################################## 4 | 5 | from fastapi import FastAPI, Form # 导入FastAPI和Form 6 | from starlette.requests import Request # 导入Request类 7 | from starlette.templating import Jinja2Templates # 导入Jinja2Templates类 8 | 9 | app = FastAPI() # 创建FastAPI应用实例 10 | templates = Jinja2Templates(directory="templates") # 创建Jinja2Templates实例,并指定模板目录为"templates" 11 | 12 | ############################################################################################## 13 | 14 | from connectdb import select_from_db, exec_sql 15 | from connectdb import get_store_available, get_supply_available 16 | from connectdb import mutex_for_store # 只允许一个修改库存 17 | 18 | from ERP.ERP import ERP 19 | ERPobj = ERP(mutex_for_store) 20 | 21 | ############################################################################################## 22 | 23 | 24 | @app.get("/") 25 | async def root(request: Request, 26 | action: str = ""): # 定义根路由处理函数,接受Request对象作为参数 27 | if action == "show": 28 | await ERPobj.ComposeTree.show_result() 29 | if action == "clear": 30 | await ERPobj.clear() # 调用clear函数 31 | supply_available = await get_supply_available() 32 | return templates.TemplateResponse("index.html", {"request": request, "ans": ERPobj.ans, 33 | "que": ERPobj.MpsList.MPS_output_que, 34 | "supply_available": supply_available}) # 返回使用模板"index.html"渲染的响应,传递request、ans和que作为模板变量 35 | 36 | 37 | @app.post("/") 38 | async def root(request: Request, 39 | pname: str = Form("default"), 40 | num: str = Form("0"), 41 | date: str = Form("2002-11-13")): # 定义根路由下的POST请求处理函数,接受Request对象和表单数据作为参数 42 | # print(pname, num, date) # 打印表单数据 43 | await ERPobj.MpsList.add_mps(pname, int(num), date) # 调用add函数处理表单数据 44 | supply_available = await get_supply_available() 45 | return templates.TemplateResponse("index.html", {"request": request, "ans": ERPobj.ans, 46 | "que": ERPobj.MpsList.MPS_output_que, 47 | "supply_available": supply_available}) # 返回使用模板"index.html"渲染的响应,传递request、ans和que作为模板变量 48 | 49 | 50 | @app.get("/store/") 51 | async def root(request: Request): 52 | store_list = await get_store_available() 53 | return templates.TemplateResponse("store.html", {"request": request, 54 | "store": store_list}) 55 | 56 | 57 | @app.post("/store/") 58 | async def root(request: Request, 59 | pname: str = Form("default"), 60 | num1: str = Form("0"), 61 | num2: str = Form("0")): 62 | sql_statement = """update store set 工序库存={num1:d}, 资材库存={num2:d} 63 | where 物料名称='{pname:s}'""".format(pname=pname, num1=int(num1), num2=int(num2)) 64 | 65 | await mutex_for_store.acquire() 66 | try: 67 | await exec_sql(sql_statement) 68 | finally: 69 | mutex_for_store.release() 70 | store_list = await get_store_available() 71 | return templates.TemplateResponse("store.html", {"request": request, 72 | "store": store_list}) 73 | 74 | 75 | ######################################################################################### 76 | ######################################################################################### 77 | ######################################################################################### 78 | 79 | 80 | func_index = 0 81 | func_obj_que = [] 82 | func_que = [] 83 | func_ans_que = [] 84 | 85 | 86 | class collect(): 87 | def __init__(self, name, index): 88 | self.name = name 89 | self.index = index 90 | self.outp = [name, '='] 91 | 92 | 93 | async def add_func_x(name): 94 | global func_obj_que 95 | global func_index 96 | if name != '': 97 | for it in func_obj_que: 98 | it.outp = [it.name, '='] 99 | func_obj_que.append(collect(name, func_index)) 100 | func_que.append(name) 101 | func_index += 1 102 | 103 | 104 | async def show_func(): 105 | global func_ans_que 106 | func_ans_que=[] 107 | global func_index 108 | global func_obj_que 109 | from connectdb import select_from_db 110 | bom = await select_from_db("select * from bom") 111 | 112 | for x in func_obj_que: 113 | flag_x_exist = 0 114 | """先寻找对应的序号""" 115 | for y in bom: 116 | if y[4] == x.name: 117 | num = y[0] 118 | flag_x_exist = 1 119 | if flag_x_exist == 0: 120 | x.outp.clear() 121 | x.outp.append("未找到对应的变量") 122 | continue 123 | 124 | # 再拼接对应变量 125 | flag_x_source = 0 126 | for y in bom: 127 | if y[3] == num: 128 | flag_x_source = 1 129 | if flag_x_exist == 0: 130 | x.outp.append('+') 131 | x.outp.append(y[4]) 132 | # print(y[3], num, x.outp) 133 | flag_x_exist = 0 134 | if flag_x_source == 0: 135 | x.outp = ["未找到资产流入来源"] 136 | for x in func_obj_que: 137 | s = ''.join(x.outp) 138 | func_ans_que.append(x.name+" "+s) 139 | 140 | 141 | async def func_clear(): 142 | global func_index 143 | global func_obj_que 144 | global func_que 145 | global func_ans_que 146 | func_index = 0 147 | func_obj_que = [] 148 | func_que = [] 149 | func_ans_que = [] 150 | 151 | 152 | async def get_x_available(): 153 | from connectdb import select_from_db 154 | sql_x_res = await select_from_db("""SELECT bom."变量名" from bom 155 | WHERE bom."变量名" IS NOT NULL and bom."变量名" != ''""") 156 | x_available = [] 157 | for item in sql_x_res: 158 | x_available.append(item[0]) 159 | return x_available 160 | 161 | 162 | @app.get("/func/") 163 | async def root(request: Request, 164 | action: str = ""): 165 | out = [] 166 | if action == "show": 167 | await show_func() 168 | out = func_ans_que 169 | if action == "clear": 170 | await func_clear() 171 | out = func_que 172 | x_available = await get_x_available() 173 | return templates.TemplateResponse("func.html", {"request": request, 174 | "func_output": out, 175 | "x_available": x_available}) 176 | 177 | 178 | @app.post("/func/") 179 | async def root(request: Request, 180 | x=Form("")): 181 | await add_func_x(x) 182 | # print(func_que) 183 | x_available=await get_x_available() 184 | return templates.TemplateResponse("func.html", {"request": request, 185 | "func_output": func_que, 186 | "x_available": x_available}) 187 | 188 | 189 | ######################################################################################### 190 | ######################################################################################### 191 | ######################################################################################### 192 | 193 | 194 | if __name__ == "__main__": 195 | import uvicorn 196 | uvicorn.run(app, host="0.0.0.0", port=8080) # 运行FastAPI应用,监听本地主机的8080端口 197 | --------------------------------------------------------------------------------