├── 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 |
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 | 
29 |
30 | 🚩 数据流图:
31 |
32 | 
33 |
34 | 🚩 DPS 算法流程:
35 |
36 | 
37 |
38 | - 通过读取数据库中的物料的子父关系表,建立物料合成关系树,并用 DFS(Deep First Search) 深度优先搜索算法遍历物料关系树,并标记节点深度和每个节点的子树深度。
39 | - 按照 MPS 队列和物料库存数量,深度优先搜索,计算生产和采购计划。
40 | * 根据工序库存和资材库存,判断是否需要配料提前期和供应商提前期,动态更新计划日期。
41 | * 按照子树深度对子节点排序,优先遍历子树深的子节点,尽可能让底层的物料先分配库存。
42 | * 当实际需求量(要生产/采购的数量)为 0 时返回,防止生产/采购无需处理的子节点。
43 |
44 | ## 功能展示
45 |
46 | 添加 MPS 记录
47 | 
48 | 产品名称从数据库中获取,用户可选择
49 | 
50 | 点击计算,可一次性计算多条计划,按照时间优先级分配库存
51 | 
52 | 资产负债公式变量同意从数据库中获取
53 | 
54 | 可以一输出计算多个变量的公式
55 | 
56 | 可手动修改库存信息(点击计算也会根据消耗自动更新库存)
57 | 
58 |
59 | ## 数据库结构
60 |
61 | `erpdata.db` 为 sqlite3 数据文件,`sql` 文件夹下包含数据库备份sql代码。
62 |
63 | 数据库结构
64 | 
65 |
66 | `supply`物料信息表
67 | 
68 | 
69 |
70 | `inventory`物料构成清单
71 | 
72 | 
73 |
74 | `store`库存表
75 | 
76 | 
77 |
78 | `bom`资产负债信息表
79 | 
80 | 
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 |
113 |
公式列表
114 |
计算
115 |
清空
116 |
117 |
118 |
119 | | 公式 |
120 |
121 |
122 |
123 | {% for item in func_output %}
124 |
125 | | {{ item }} |
126 |
127 | {% endfor %}
128 |
129 |
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 |
118 |
MPS 计划队列
119 |
计算
120 |
清空
121 |
122 |
123 |
124 | | 产品 |
125 | 数量 |
126 | 完成日期 |
127 | 任务序号 |
128 |
129 |
130 |
131 | {% for item in que %}
132 |
133 | | {{ item[0] }} |
134 | {{ item[1] }} |
135 | {{ item[2] }} |
136 | {{ item[3] }} |
137 | {{ item[4] }} |
138 | {{ item[5] }} |
139 |
140 | {% endfor %}
141 |
142 |
143 |
生产和采购计划
144 |
145 |
146 |
147 | | 产品 |
148 | 数量 |
149 | 调配方式 |
150 | 下达日期 |
151 | 完成日期 |
152 |
153 |
154 |
155 | {% for item in ans %}
156 |
157 | | {{ item[0] }} |
158 | {{ item[1] }} |
159 | {{ item[2] }} |
160 | {{ item[3] }} |
161 | {{ item[4] }} |
162 | {{ item[5] }} |
163 |
164 | {% endfor %}
165 |
166 |
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 |
--------------------------------------------------------------------------------