ERP 微型系統建置
簡介
ERP代表企業資源規劃,是一個全面集成和管理企業所有業務流程和資源的軟件系統。 ERP系統通常由多個模塊組成,包括財務、購買、銷售、庫存、生產、人力資源和客戶關係管理等。
ERP系統可以幫助企業自動化許多業務流程,從而提高效率、降低成本並改善生產力。 ERP系統還可以讓企業更好地管理供應鏈、生產進度和銷售情況,從而更好地預測市場需求和優化企業運營。
不同的ERP系統有不同的特點和功能,企業可以根據自身的需求和預算選擇適合的ERP系統。 然而,實施ERP系統是一個複雜的過程,需要企業精心計劃和管理,以確保系統能夠順利運作並產生預期的效益。
Open Source ERP System
https://github.com/himool/Himool-ERP
本專案僅供學術交流使用
初步規劃
環境規劃
IDE
- PyCharm Professional
- Navicat Premium 16
- Docker
前端
- Vue 2.6
- 元件: Ant Design Vue 1.x
後端
- Python 3.9
- Django 3.2
- Django REST framework 3.12.4
資料庫
- MySQL 8.0
安裝環境
基本安裝
pip install -r requirements.txt
前端安裝
yarn
cd frontend
yarn config set ignore-engines true
yarn add @vue/cli-service
package.json
1 | "devDependencies": { |
yarn add @vue/cli-plugin-babel
yarn install
npm
cd frontend
npm i @vue/cli-service
npm i @vue/cli-plugin-babel
MySQL
資料庫安裝
Docker 下載 MySQL image
1
docker pull mysql
Docker 啟動容器
1
docker run --name sql2 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=Dev127336 -d mysql:8
run : docker 建立 container 並且執行的指令
–name : 指定容器為 sql2
-p 3306:3306 : 將容器的 3306 端口映射到主機的 3306 端口。
-e MYSQL_ROOT_PASSWORD=Dev127336 : 初始化 root 用戶的密碼為 Dev127336。
-d mysql:8 : 背景執行 MySQL 映像登入mysql
1
mysql -u root -p
修改密碼
1
ALTER USER 'root'@'localhost' IDENTIFIED BY 'Dev127336';
添加遠端登入
1
2CREATE USER 'DevAuth'@'%' IDENTIFIED WITH mysql_native_password BY 'Dev127336';
GRANT ALL PRIVILEGES ON *.* TO 'DevAuth'@'%';
https://ithelp.ithome.com.tw/articles/10272193
- Error
Python MySQL OperationalError: 1045, “Access denied for user root@’localhost’
https://stackoverflow.com/questions/17425523/python-mysql-operationalerror-1045-access-denied-for-user-rootlocalhost
資料庫設定
資料庫連結
1
2
3
4
5
6
7
8
9
10
11DATABASES = {
# 方法一
'default': {
'ENGINE': 'django.db.backends.mysql', # 資料庫引擎
'NAME': 'erp_db', # 資料庫名稱
'USER': 'admin', # 資料庫登錄用戶名
'PASSWORD': 'admin', # 密碼
'HOST': '127.0.0.1', # 資料庫主機IP,如保持預設,則為127.0.0.1
'PORT': 3306, # 資料庫埠號,如保持默認,則為3306
}
}資料庫字串設定為urf8mb4
tools/create_configs1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
BASE_DIR = Path(__file__).resolve().parent.parent
DATABASES = {{
'default': {{
'ENGINE': 'django.db.backends.mysql',
'HOST': '{host}',
'PORT': '3306',
'USER': '{user}',
'PASSWORD': '{passowrd}',
'NAME': '{database_name}',
'OPTIONS': {{'charset': 'utf8mb4'}},
}}
}}1
2
3
4set global character_set_server=utf8mb4;
set global character_set_database=utf8mb4;
set global character_set_client=utf8mb4;
set global character_set_connection=utf8mb4;建立資料庫
1
CREATE DATABASE erp_db;
轉移資料庫
- 重新製作資料遷移
- python manage.py makemigrations
- 應用資料遷移
- python manage.py migrate
- 建立使用者
- python manage.py runscript create_user
本地端執行
yarn
yarn serve
npm
npm run serve
伺服器端執行
功能擴充
前端頁面新增
menus.js
1
2
3
4
5{
key: '10', name: '工程管理', icon: 'team', submenus: [
{key: '/engineering/engineer', name: '工程師'}
]
}frontend/src/views
- views -> engineering/engineer/index.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48<template>
<div>
<a-card title="生產計畫詳情">
<a-button slot="extra" type="primary" style="margin-right: 8px;" ghost v-print="'#printContent'">
<a-icon type="printer" />列印</a-button
>
<a-button
slot="extra"
type="primary"
ghost
@click="
() => {
this.$router.go(-1);
}
"
>
<a-icon type="left" />返回</a-button
>
<section id="printContent">
<a-spin :spinning="loading">
<img id="barcode" style="float: right" />
<a-descriptions bordered>
<a-descriptions-item label="生產計畫單號">
{{ item.number }}
</a-descriptions-item>
<a-descriptions-item label="銷售單號">
{{ item.sales_order_number }}
</a-descriptions-item>
<a-descriptions-item label="狀態">
{{ item.status_display }}
</a-descriptions-item>
</a-descriptions>
</a-spin>
</section>
</a-card>
</div>
</template>
<script>
export default {
data() {
return {
loading: false,
item: {}
};
}
};
</script>
- frontend/src/components
- components -> frontend/components/Engineering/Engineer.vue
- frontend/src/api
- api -> frontend/api/engineering.js
- frontend/src/router
router -> frontend/router/index.js
1
import engineering from './engineering'
1
const routes = [index, user, account, manage, system, report, basicData, goods, purchasing, sale, warehouse, finance, production,engineering];
router -> frontend/router/engineering.js
1
2
3
4
5
6
7
8
9
10
11
12
13export default {
path: '/engineering',
name: 'engineering',
component: () => import('@/layouts/BaseLayout'),
redirect: '/engineering/engineer',
children: [
{
path: 'engineer',
meta: { title: '工程管理', permission: 'engineering' },
component: () => import('@/views/engineering/engineer/index')
}
],
}
- frontend/src
- frontend/src/permissions.js
1
2
3
4export let permissions = {
...
'engineer':'工程管理'
}
- scripts/init_permission
1
2
3
4
5
6{
'name': '工程管理',
'permissions': [
{'name': '工程師', 'code': 'engineer'}
]
}
後端建立API
- 根目錄建立app 將musics資料夾移動到apps資料夾
1
python manage.py startapp musics
後端建立的檔案
1
2
3
4
5
6
7
8
9- musics
- migrations
- __init__.py
- __init__.py
- admin.py
- apps.py
- models.py
- tests.py
- views.py專案的檔案
1
2
3
4
5
6
7
8
9
10
11
12
13
14- message
- migrations
- __init__.py
- __init__.py
- admin.py
- apps.py
- filters.py
- models.py
- permissions.py
- schemas.py
- serializers.py
- tests.py
- urls.py
- views.py
- 加入設定檔
project/setting.py
添加
- apps.musics
- rest_framework
1 | INSTALLED_APPS = [ |
- Model-Template-View
- Model: 透過model引入資料庫,建立後台
- models.py
- View: 透過view的定義,傳送頁面與get/post
- views.py
1
2
3
4
5
6
7
8
9
10
11from extensions.common.schema import *
from extensions.common.base import *
from extensions.permissions import *
from extensions.exceptions import *
from extensions.viewsets import *
from apps.flow.serializers import *
from apps.flow.permissions import *
from apps.flow.filters import *
from apps.flow.schemas import *
from apps.flow.models import *
- views.py
- Template: 頁面顯示資料
- 顯示頁面
- frontend/src/views
- frontend/src/components
- 路由控制
- frontend/src/router
- Django REST Framework (DRF)
- frontend/src/api
- 顯示頁面
- Model 介紹
- class Meta
在 Django 中,每個模型(Model)都可以包含一個名為 Meta 的內部類別(Inner Class),該類定義了一些元數據(Meta Data),這些元數據描述了模型本身的一些特性。
Meta 內部類通常被用來定義以下內容:
資料表的名稱:使用 db_table 屬性可以自定義資料表的名稱,否則 Django 會根據模型名稱自動生成一個名稱。
模型的排序方式:使用 ordering 屬性可以定義模型實例的預設排序方式。該屬性可以是一個單一的欄位名稱,也可以是多個欄位名稱的列表,以逗號分隔。
模型的限制條件:使用 unique_together 屬性可以指定模型的多個欄位必須共同滿足唯一性限制。使用 constraints 屬性可以指定模型的其他限制條件,例如 CHECK 約束。
模型的資料庫連接:使用 using 屬性可以指定模型使用的資料庫連接,如果有多個資料庫,可以為不同的模型指定不同的連接。
模型的顯示名稱:使用 verbose_name 屬性可以為模型定義一個人類可讀的名稱,例如 “文章” 或 “使用者”。
模型的複數顯示名稱:使用 verbose_name_plural 屬性可以為模型定義一個複數形式的顯示名稱,例如 “文章” 的複數形式為 “文章”。
總之,Meta 內部類提供了一種簡單而方便的方式來定義模型的元數據,這些元數據能夠影響模型的行為和外觀。
- unique_together
它允許您在定義模型時指定多個欄位的組合必須唯一,這樣就可以實現複合唯一性約束(Compound Unique Constraint)。
例如,假設您有一個模型名為 Person,該模型有兩個欄位 first_name 和 last_name,您希望這兩個欄位的組合必須唯一,這樣就可以使用 unique_together 選項來實現:
1 | class Person(models.Model): |
當您使用 unique_together 定義了一個唯一性約束後,Django 會自動創建一個複合索引(Compound Index)來加速查詢。這個索引可以確保該欄位組合的唯一性,如果嘗試插入一個重複的組合,則會引發一個 IntegrityError 錯誤。
需要注意的是,unique_together 選項接受一個元組(Tuple)作為參數,其中每個元素是一個欄位名稱。您可以定義任意數量的欄位,但最少需要定義兩個欄位。此外,使用 unique_together 定義的唯一性約束只對 Django 級別有效,不會在資料庫中建立真正的唯一性約束。如果需要在資料庫層面實現唯一性約束,請考慮使用 unique=True 屬性或 unique constraint。
- 多出的檔案
filters.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14from django_filters.rest_framework import FilterSet
from django_filters.filters import *
from apps.stock_transfer.models import *
class StockTransferOrderFilter(FilterSet):
start_date = DateFilter(field_name='create_time', lookup_expr='gte', label='開始日期')
end_date = DateFilter(field_name='create_time', lookup_expr='lt', label='結束日期')
class Meta:
model = StockTransferOrder
fields = ['number', 'out_warehouse', 'in_warehouse', 'handler', 'is_void', 'creator','start_date', 'end_date']
__all__ = [
'StockTransferOrderFilter',
]permissions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23from extensions.permissions import ModelPermission
class InventoryWarningPermission(ModelPermission):
code = 'inventory_warning'
class ShelfLifeWarningPermission(ModelPermission):
code = 'shelf_life_warning'
class StockInReminderPermission(ModelPermission):
code = 'stock_in_reminder'
class StockOutReminderPermission(ModelPermission):
code = 'stock_out_reminder'
__all__ = [
'InventoryWarningPermission', 'ShelfLifeWarningPermission',
'StockInReminderPermission', 'StockOutReminderPermission',
]schemas.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14from extensions.serializers import *
class CSRFTokenResponse(Serializer):
token = CharField(label='權杖')
class LoginRequest(Serializer):
username = CharField(label='用戶名')
password = CharField(label='密碼')
__all__ = [
'CSRFTokenResponse', 'LoginRequest',
]serializers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34from extensions.common.base import *
from extensions.serializers import *
from extensions.exceptions import *
from apps.stock_in.models import *
from apps.stock_out.models import *
class StockInOrderReminderSerializer(BaseSerializer):
"""入庫任務提醒"""
warehouse_number = CharField(source='warehouse.number', read_only=True, label='倉庫編號')
warehouse_name = CharField(source='warehouse.name', read_only=True, label='倉庫名稱')
class Meta:
model = StockInOrder
fields = ['id', 'number', 'warehouse', 'warehouse_number', 'warehouse_name',
'total_quantity', 'remain_quantity']
class StockOutOrderReminderSerializer(BaseSerializer):
"""出庫任務提醒"""
warehouse_number = CharField(source='warehouse.number', read_only=True, label='倉庫編號')
warehouse_name = CharField(source='warehouse.name', read_only=True, label='倉庫名稱')
class Meta:
model = StockOutOrder
fields = ['id', 'number', 'warehouse', 'warehouse_number', 'warehouse_name',
'total_quantity', 'remain_quantity']
__all__ = [
'StockInOrderReminderSerializer', 'StockOutOrderReminderSerializer',
]urls.py
1
2
3
4
5
6
7
8
9from extensions.routers import *
from apps.message.views import *
router = BaseRouter()
router.register('inventory_warnings', InventoryWarningViewSet, 'inventory_warning')
router.register('stock_in_order_reminders', StockInOrderReminderViewSet, 'stock_in_order_reminder')
router.register('stock_out_order_reminders', StockOutOrderReminderViewSet, 'stock_out_order_reminder')
urlpatterns = router.urls
後續改善
- 套件升級
- 修改業務流程
前端
- Vue 3.x
- Component: Ant Design Vue 3.x
- https://antdv.com/components/overview
- React 18
- Component: Ant Design React 5.4.4
- https://ant.design/docs/react/introduce
後端
- Django
- Python 3.11
- Django 4.2.0
- Python Flask
資料庫
- MySQL 8.0