1
0
forked from erp-dev/erp

feat: celery_beat

This commit is contained in:
2025-12-08 18:37:41 +08:00
parent e95e61f3ae
commit fc18f71615
16 changed files with 120 additions and 4856 deletions

2
.env
View File

@@ -1,6 +1,6 @@
SECRET_KEY=testing_$weak_secret_is_allowd SECRET_KEY=testing_$weak_secret_is_allowd
DEBUG=True DEBUG=True
ALLOWED_HOSTS=*, ALLOWED_HOSTS=yuwenerp.yuwen.cloud,localhost,
DB_HOST=localhost DB_HOST=localhost
DB_PORT=5432 DB_PORT=5432
DB_NAME=flower DB_NAME=flower

View File

@@ -119,5 +119,3 @@ class StatementRecordView(StatementViewBase):
'records': matched_records, 'records': matched_records,
} }
return self._build_response(single_payload) return self._build_response(single_payload)

View File

@@ -307,6 +307,7 @@ class PlateOrderListSerializer(PlateOrderDesignCodeMixin, serializers.ModelSeria
progress_percentage = serializers.IntegerField(read_only=True) progress_percentage = serializers.IntegerField(read_only=True)
process_name = serializers.SerializerMethodField() process_name = serializers.SerializerMethodField()
plate_image_url = serializers.SerializerMethodField() plate_image_url = serializers.SerializerMethodField()
last_completed_state = serializers.CharField(read_only=True)
class Meta: class Meta:
model = models.PlateOrder model = models.PlateOrder
@@ -326,12 +327,12 @@ class PlateOrderListSerializer(PlateOrderDesignCodeMixin, serializers.ModelSeria
'required_completion_date', 'completion_date', 'required_completion_date', 'completion_date',
'approval_result', 'is_ordered', 'customer_feedback', 'print_count', 'approval_result', 'is_ordered', 'customer_feedback', 'print_count',
'process', 'process_name', 'process', 'process_name',
'status', 'status_id', 'is_completed', 'has_started', 'status', 'status_id', 'is_completed', 'has_started', 'last_completed_state',
'progress_percentage', 'business_object_id', 'progress_percentage', 'business_object_id',
'created_at', 'updated_at' 'created_at', 'updated_at'
] ]
read_only_fields = [ read_only_fields = [
'id', 'status', 'progress_percentage', 'id', 'status', 'progress_percentage', 'last_completed_state',
'created_at', 'updated_at', 'print_count' 'created_at', 'updated_at', 'print_count'
] ]
@@ -367,6 +368,7 @@ class PlateOrderDetailSerializer(PlateOrderDesignCodeMixin, serializers.ModelSer
business_object_id = serializers.SerializerMethodField() business_object_id = serializers.SerializerMethodField()
plate_image_url = serializers.SerializerMethodField() plate_image_url = serializers.SerializerMethodField()
process_name = serializers.SerializerMethodField() process_name = serializers.SerializerMethodField()
last_completed_state = serializers.CharField(read_only=True)
class Meta: class Meta:
model = models.PlateOrder model = models.PlateOrder
@@ -386,12 +388,12 @@ class PlateOrderDetailSerializer(PlateOrderDesignCodeMixin, serializers.ModelSer
'approval_result', 'is_ordered', 'customer_feedback', 'print_count', 'approval_result', 'is_ordered', 'customer_feedback', 'print_count',
'process', 'process_name', 'process', 'process_name',
'status', 'status_id', 'is_completed', 'has_started', 'status', 'status_id', 'is_completed', 'has_started',
'progress_percentage', 'business_object_id', 'progress_percentage', 'business_object_id', 'last_completed_state',
'created_at', 'updated_at' 'created_at', 'updated_at'
] ]
read_only_fields = [ read_only_fields = [
'id', 'status', 'status_id', 'is_completed', 'has_started', 'id', 'status', 'status_id', 'is_completed', 'has_started',
'progress_percentage', 'business_object_id', 'progress_percentage', 'business_object_id', 'last_completed_state',
'created_at', 'updated_at', 'print_count' 'created_at', 'updated_at', 'print_count'
] ]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.8 on 2025-12-08 10:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('basic_info', '0016_merchantsetting_type'),
]
operations = [
migrations.AlterField(
model_name='customer',
name='mobile',
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='手机'),
),
]

View File

@@ -313,7 +313,7 @@ class Customer(ModelBase):
null=True, null=True,
verbose_name='创建者', verbose_name='创建者',
) )
mobile = models.CharField(max_length=20, verbose_name='手机') mobile = models.CharField(max_length=20, null=True, blank=True, verbose_name='手机')
area = models.CharField(max_length=100, blank=True, null=True, verbose_name='地区') area = models.CharField(max_length=100, blank=True, null=True, verbose_name='地区')
email = models.EmailField(blank=True, null=True, verbose_name='电子邮箱') email = models.EmailField(blank=True, null=True, verbose_name='电子邮箱')
contact = models.CharField(blank=True, null=True, verbose_name='联系人') contact = models.CharField(blank=True, null=True, verbose_name='联系人')

BIN
celerybeat-schedule Normal file

Binary file not shown.

BIN
celerybeat-schedule-shm Normal file

Binary file not shown.

BIN
celerybeat-schedule-wal Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -51,6 +51,8 @@ services:
- rabbitmq_data:/var/lib/rabbitmq - rabbitmq_data:/var/lib/rabbitmq
restart: unless-stopped restart: unless-stopped
web: web:
build: build:
context: . context: .
@@ -65,7 +67,7 @@ services:
DJANGO_SETTINGS_MODULE: flower.settings DJANGO_SETTINGS_MODULE: flower.settings
PYTHONPATH: /app PYTHONPATH: /app
DEBUG: "1" DEBUG: "1"
ALLOWED_HOSTS: "localhost,127.0.0.1,0.0.0.0,8.148.215.233" ALLOWED_HOSTS: "localhost,127.0.0.1,0.0.0.0,8.148.215.233,yuwenerp.yuwen.cloud"
DB_HOST: postgres DB_HOST: postgres
DB_PORT: "5432" DB_PORT: "5432"
DB_NAME: flower DB_NAME: flower
@@ -108,6 +110,36 @@ services:
DB_PASSWORD: postgres DB_PASSWORD: postgres
restart: unless-stopped restart: unless-stopped
celery_beat:
build:
context: .
target: base
container_name: celery_beat
command:
- celery
- -A
- flower
- beat
- -l
- info
volumes:
- .:/app
depends_on:
- postgres
- redis
- rabbitmq
environment:
DJANGO_SETTINGS_MODULE: flower.settings
PYTHONPATH: /app
DB_HOST: postgres
DB_PORT: "5432"
DB_NAME: flower
DB_USER: postgres
DB_PASSWORD: postgres
CELERY_BROKER_URL: amqp://guest:guest@rabbitmq:5672//
CELERY_RESULT_BACKEND: redis://redis:6379/0
restart: unless-stopped
volumes: volumes:
db_data: db_data:
redis_data: redis_data:

View File

@@ -46,18 +46,22 @@ SECRET_KEY = env('SECRET_KEY', default='weak_secret_key_for_dev_only')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env('DEBUG', default=False) DEBUG = env('DEBUG', default=False)
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[]) ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[
'yuwenerp.yuwen.cloud',
])
CSRF_TRUSTED_ORIGINS = env.list('CSRF_TRUSTED_ORIGINS', default=[
'https://yuwenerp.yuwen.cloud',
])
# CORS 配置 # CORS 配置
CORS_ALLOW_ALL_ORIGINS = DEBUG # 开发环境允许所有源,生产环境需要配置白名单 CORS_ALLOW_ALL_ORIGINS = DEBUG # 开发环境允许所有源,生产环境需要配置白名单
CORS_ALLOWED_ORIGINS = env.list('CORS_ALLOWED_ORIGINS', default=[ CORS_ALLOWED_ORIGINS = env.list('CORS_ALLOWED_ORIGINS', default=[
'http://localhost:3000', 'http://localhost:8000',
'http://localhost:5173',
'http://localhost:5174',
'http://127.0.0.1:3000',
'http://127.0.0.1:5173', 'http://127.0.0.1:5173',
'http://127.0.0.1:5174', 'http://127.0.0.1:5174',
'http://127.0.0.1:5179',
'https://yuwenerp.yuwen.cloud',
]) ])
CORS_ALLOW_CREDENTIALS = True # 允许携带凭证(如 Cookie、认证头 CORS_ALLOW_CREDENTIALS = True # 允许携带凭证(如 Cookie、认证头
@@ -220,7 +224,7 @@ QINIU_ACCESS_KEY = 'IySZff0zEN7jHeDuxj5PrQNMv0qjKoVqBRCuFMzi'
QINIU_SECRET_KEY = 'Mph5XkjUQO3VHFPT1JulYHY442uMOya2bXjbSZp8' QINIU_SECRET_KEY = 'Mph5XkjUQO3VHFPT1JulYHY442uMOya2bXjbSZp8'
QINIU_BUCKET_NAME = 'yunwenerp' QINIU_BUCKET_NAME = 'yunwenerp'
QINIU_BUCKET_DOMAIN = 'image.yuwen.cloud' QINIU_BUCKET_DOMAIN = 'image.yuwen.cloud'
QINIU_SECURE_URL = False QINIU_SECURE_URL = True
STORAGES = { STORAGES = {
'default': { 'default': {
@@ -287,3 +291,16 @@ CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json' CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = TIME_ZONE CELERY_TIMEZONE = TIME_ZONE
from celery.schedules import crontab
CELERY_BEAT_SCHEDULE = {
'daily_database_backup': {
'task': 'api_v1.tasks.backup_database',
'schedule': crontab(hour=3, minute=0),
'kwargs': {
'output_dir': str(BASE_DIR / 'data-bak'),
'filename_prefix': 'db-backup',
},
},
}

View File

@@ -169,6 +169,17 @@ class PlateOrder(ModelBase):
from stateflow.services import get_display_status from stateflow.services import get_display_status
return get_display_status(self.business_object) return get_display_status(self.business_object)
@property
def last_completed_state(self) -> str:
"""
返回最后一个已完成节点的名称,用于详情展示
"""
if not self.business_object:
return ''
from stateflow.services import get_last_completed_state
last_state = get_last_completed_state(self.business_object)
return last_state.name if last_state else ''
@property @property
def status_id(self): def status_id(self):
"""返回当前状态ID下一个待执行的状态ID""" """返回当前状态ID下一个待执行的状态ID"""

View File

@@ -7,7 +7,7 @@ requires-python = ">=3.14"
dependencies = [ dependencies = [
"celery>=5.5.3", "celery>=5.5.3",
"coverage>=7.12.0", "coverage>=7.12.0",
"django>=5.2.7", "django==5.2.8",
"django-cors-headers>=4.9.0", "django-cors-headers>=4.9.0",
"django-environ>=0.12.0", "django-environ>=0.12.0",
"django-filter>=25.2", "django-filter>=25.2",

View File

@@ -231,7 +231,10 @@ class StateLogParameterRecordInline(admin.TabularInline):
@admin.register(models.StateFlowRecord) @admin.register(models.StateFlowRecord)
class StateFlowRecordAdmin(admin.ModelAdmin): class StateFlowRecordAdmin(admin.ModelAdmin):
list_display = ('id', 'business_object', 'state', 'completed_at', 'completed_by', 'is_cancelled', 'cancelled_at', 'parameter_count') list_display = (
'id', 'business_object', 'business_object__object_id',
'state', 'completed_at',
'completed_by', 'is_cancelled', 'cancelled_at', 'parameter_count')
search_fields = ('business_object__name', 'state__name') search_fields = ('business_object__name', 'state__name')
list_filter = ('state', 'is_cancelled', 'completed_at', 'completed_by') list_filter = ('state', 'is_cancelled', 'completed_at', 'completed_by')
readonly_fields = ('business_object', 'state', 'completed_at', 'completed_by', 'cancelled_at', 'parameters_summary') readonly_fields = ('business_object', 'state', 'completed_at', 'completed_by', 'cancelled_at', 'parameters_summary')

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.8 on 2025-12-08 10:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('stock', '0007_transferorder'),
]
operations = [
migrations.AlterField(
model_name='stockchangerecord',
name='source_type',
field=models.IntegerField(choices=[(1, '采购'), (2, '销退'), (3, '调入'), (4, '盘盈'), (5, '合并'), (6, '销售'), (7, '采购退货'), (8, '调出'), (9, '盘亏'), (10, '拆卷'), (11, '红冲')], verbose_name='变动来源'),
),
]

8
uv.lock generated
View File

@@ -283,16 +283,16 @@ wheels = [
[[package]] [[package]]
name = "django" name = "django"
version = "5.2.7" version = "5.2.8"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "asgiref" }, { name = "asgiref" },
{ name = "sqlparse" }, { name = "sqlparse" },
{ name = "tzdata", marker = "sys_platform == 'win32'" }, { name = "tzdata", marker = "sys_platform == 'win32'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/b1/96/bd84e2bb997994de8bcda47ae4560991084e86536541d7214393880f01a8/django-5.2.7.tar.gz", hash = "sha256:e0f6f12e2551b1716a95a63a1366ca91bbcd7be059862c1b18f989b1da356cdd", size = 10865812, upload-time = "2025-10-01T14:22:12.081Z" } sdist = { url = "https://files.pythonhosted.org/packages/05/a2/933dbbb3dd9990494960f6e64aca2af4c0745b63b7113f59a822df92329e/django-5.2.8.tar.gz", hash = "sha256:23254866a5bb9a2cfa6004e8b809ec6246eba4b58a7589bc2772f1bcc8456c7f", size = 10849032, upload-time = "2025-11-05T14:07:32.778Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/8f/ef/81f3372b5dd35d8d354321155d1a38894b2b766f576d0abffac4d8ae78d9/django-5.2.7-py3-none-any.whl", hash = "sha256:59a13a6515f787dec9d97a0438cd2efac78c8aca1c80025244b0fe507fe0754b", size = 8307145, upload-time = "2025-10-01T14:22:49.476Z" }, { url = "https://files.pythonhosted.org/packages/5e/3d/a035a4ee9b1d4d4beee2ae6e8e12fe6dee5514b21f62504e22efcbd9fb46/django-5.2.8-py3-none-any.whl", hash = "sha256:37e687f7bd73ddf043e2b6b97cfe02fcbb11f2dbb3adccc6a2b18c6daa054d7f", size = 8289692, upload-time = "2025-11-05T14:07:28.761Z" },
] ]
[[package]] [[package]]
@@ -441,7 +441,7 @@ requires-dist = [
{ name = "aiohttp", specifier = ">=3.13.2" }, { name = "aiohttp", specifier = ">=3.13.2" },
{ name = "celery", specifier = ">=5.5.3" }, { name = "celery", specifier = ">=5.5.3" },
{ name = "coverage", specifier = ">=7.12.0" }, { name = "coverage", specifier = ">=7.12.0" },
{ name = "django", specifier = ">=5.2.7" }, { name = "django", specifier = "==5.2.8" },
{ name = "django-cors-headers", specifier = ">=4.9.0" }, { name = "django-cors-headers", specifier = ">=4.9.0" },
{ name = "django-environ", specifier = ">=0.12.0" }, { name = "django-environ", specifier = ">=0.12.0" },
{ name = "django-filter", specifier = ">=25.2" }, { name = "django-filter", specifier = ">=25.2" },