1
0
forked from erp-dev/erp

feat: printing module

This commit is contained in:
2025-11-13 18:22:42 +08:00
parent 8097d6941c
commit f071e5fff9
35 changed files with 5537 additions and 440 deletions

View File

@@ -1,9 +1,199 @@
from django.contrib import admin
from django.contrib.admin import action
from django.forms import BaseInlineFormSet
from django.utils.html import format_html
from . import models
@admin.register(models.PlateOrder)
class PlateOrderAdmin(admin.ModelAdmin):
list_display = (
'id',
'design_code',
'customer_name',
'style_name',
'plate_type',
'development_status_display',
'urgency_display',
'progress_display',
'salesperson_name',
'required_completion_date',
'created_at',
)
list_filter = (
'urgency_level',
'plate_type',
'is_ordered',
'is_mark_frame',
'created_at',
'required_completion_date',
)
search_fields = (
'design_code',
'style_name',
'customer__name',
'salesperson__name',
'merchandiser__name',
)
readonly_fields = ('progress_display', 'status_display')
actions = ['advance_to_next_state_action', 'step_back_one_state_action', 'reset_progress_action']
autocomplete_fields = ['customer', 'salesperson', 'merchandiser']
fieldsets = [
('基本信息', {
'fields': ['design_code', 'plate_type', 'plate_date', 'urgency_level']
}),
('客户信息', {
'fields': ['customer', 'area', 'default_address', 'salesperson', 'merchandiser']
}),
('产品信息', {
'fields': ['style_name', 'fabric', 'width', 'production_method']
}),
('开版信息', {
'fields': [
'plate_method', 'plate_image', 'plate_notes', 'reprint_reason',
'is_mark_frame', 'sample_meter', 'required_sample_meters'
]
}),
('质量评级', {
'fields': [
'drawing_rating', 'color_matching_rating',
'sample_rating', 'difficulty_rating'
]
}),
('时间管理', {
'fields': ['required_completion_date', 'completion_date']
}),
('审批与反馈', {
'fields': [
'approval_result', 'customer_feedback',
'is_ordered'
]
}),
('流程管理', {
'fields': ['business_object', 'status_display', 'progress_display'],
'classes': ['collapse']
}),
]
@admin.display(description='客户')
def customer_name(self, obj):
return obj.customer.name if obj.customer else '-'
@admin.display(description='销售员')
def salesperson_name(self, obj):
return obj.salesperson.name if obj.salesperson else '-'
@admin.display(description='开发进程')
def development_status_display(self, obj):
status = obj.status
color_map = {
'未打印': 'gray',
'待画图': 'orange',
'画图完成': 'blue',
'调色样': 'purple',
'调色完成': 'green',
'套纸样': 'cyan',
'取消版': 'red',
'客户审批': 'yellow',
'开版完成': 'darkgreen',
'已下单': 'black',
}
color = color_map.get(status, 'black')
return format_html(
'<span style="color: {}; font-weight: bold;">{}</span>',
color, status
)
@admin.display(description='紧急程度')
def urgency_display(self, obj):
if obj.urgency_level == '加急':
return format_html(
'<span style="color: red; font-weight: bold;">⚠️ 加急</span>'
)
return obj.urgency_level
@admin.display(description='进度')
def progress_display(self, obj):
progress = obj.progress_percentage
if progress >= 100:
color = 'green'
elif progress >= 50:
color = 'orange'
else:
color = 'gray'
return format_html(
'<span style="color: {}; font-weight: bold;">{:.1f}%</span>',
color, progress
)
@admin.display(description='当前状态')
def status_display(self, obj):
return obj.status
@action(description='推进到下一个状态')
def advance_to_next_state_action(self, request, queryset):
from stateflow import services
success_count = 0
messages = []
for obj in queryset:
if not obj.business_object:
messages.append(f"{obj.plate_code}: 没有关联的流程实例,无法推进")
continue
success, message = services.advance_to_next_state(obj.business_object, request.user)
if success:
success_count += 1
messages.append(f"{obj.plate_code}: {message}")
self.message_user(request, f'成功推进 {success_count}/{queryset.count()} 个开版订单。详情: {"; ".join(messages[:5])}')
@action(description='回退一步')
def step_back_one_state_action(self, request, queryset):
from stateflow import services
success_count = 0
messages = []
for obj in queryset:
if not obj.business_object:
messages.append(f"{obj.plate_code}: 没有关联的流程实例,无法回退")
continue
success, message = services.step_back_one_state(obj.business_object, request.user)
if success:
success_count += 1
messages.append(f"{obj.plate_code}: {message}")
self.message_user(request, f'成功回退 {success_count}/{queryset.count()} 个开版订单。详情: {"; ".join(messages[:5])}')
@action(description='重置进度')
def reset_progress_action(self, request, queryset):
from stateflow import services
reset_count = 0
for obj in queryset:
if not obj.business_object:
continue
services.reset_business_object_progress(obj.business_object)
reset_count += 1
self.message_user(request, f'已重置 {reset_count} 个开版订单的进度。')
class PrintingJobInlineFormSet(BaseInlineFormSet):
"""自定义 formset 处理 PrintingJob 的创建"""
def save_new(self, form, commit=True):
"""保存新的 PrintingJob暂时不创建 BusinessObject等 order 保存完成后再创建)"""
obj = super().save_new(form, commit=commit)
return obj
class PrintingJobInline(admin.StackedInline):
model = models.PrintingJob
formset = PrintingJobInlineFormSet
extra = 1
@@ -12,11 +202,93 @@ class PrintingOrderAdmin(admin.ModelAdmin):
list_display = (
'oid',
'customer_name',
'progress_percent',
'created_at',
'outgoing_date',
)
inlines = [PrintingJobInline]
def save_formset(self, request, form, formset, change):
"""保存 formset 后,为新创建的 PrintingJob 创建 BusinessObject"""
# 先调用默认的保存
formset.save()
# 此时 PrintingOrder 已经保存完成(包括 process 字段)
# 为所有新创建且没有 business_object 的 job 创建 BusinessObject
if isinstance(formset.model, type) and issubclass(formset.model, models.PrintingJob):
order = form.instance
for job in order.printing_jobs.all():
if not job.business_object and order.process:
from stateflow.models import BusinessObject
business_object = BusinessObject.objects.create(
name=f"PrintingJob-{job.id}",
process=order.process,
description=f"印染任务 {job.id} 的流程实例",
)
job.business_object = business_object
job.save(update_fields=['business_object'])
def save_model(self, request, obj, form, change):
"""保存 PrintingOrder 时使用 service 层"""
from api_v1.views.printing.services import PrintingOrderService
if not change: # 新创建
# 使用 service 创建(会自动设置默认 process
data = {
'customer': obj.customer,
'fabric': obj.fabric,
'width': obj.width,
'is_urgent': obj.is_urgent,
'area': obj.area,
'address': obj.address,
'fabric_source': obj.fabric_source,
'is_fabric_received': obj.is_fabric_received,
'craft': obj.craft,
'description': obj.description,
'outgoing_date': obj.outgoing_date,
'curve': obj.curve,
'new_curve': obj.new_curve,
'position': obj.position,
'printing_warn': obj.printing_warn,
'rolling_warn': obj.rolling_warn,
'production_warn': obj.production_warn,
'is_invalid': obj.is_invalid,
'process': obj.process,
}
created_obj = PrintingOrderService.create_printing_order(data, request.user)
# 将创建的对象的所有字段复制到当前对象
for field in created_obj._meta.fields:
setattr(obj, field.name, getattr(created_obj, field.name))
else: # 更新
# 使用 service 更新
data = {
'customer': obj.customer,
'fabric': obj.fabric,
'width': obj.width,
'is_urgent': obj.is_urgent,
'area': obj.area,
'address': obj.address,
'fabric_source': obj.fabric_source,
'is_fabric_received': obj.is_fabric_received,
'craft': obj.craft,
'description': obj.description,
'outgoing_date': obj.outgoing_date,
'curve': obj.curve,
'new_curve': obj.new_curve,
'position': obj.position,
'printing_warn': obj.printing_warn,
'rolling_warn': obj.rolling_warn,
'production_warn': obj.production_warn,
'is_invalid': obj.is_invalid,
'process': obj.process,
}
success, message, updated_obj = PrintingOrderService.update_printing_order(
obj, data, request.user
)
if not success:
from django.core.exceptions import ValidationError
raise ValidationError(message)
@admin.display(description='客户名称')
def customer_name(self, obj: models.PrintingOrder):
return obj.customer.name
@@ -24,3 +296,140 @@ class PrintingOrderAdmin(admin.ModelAdmin):
@admin.display(description='订单编号(虚拟)')
def oid(self, obj: models.PrintingOrder):
return obj.human_id
@admin.display(description='进度')
def progress_percent(self, obj: models.PrintingOrder):
return f"{obj.progress}%"
@admin.register(models.PrintingJob)
class PrintingJobAdmin(admin.ModelAdmin):
list_display = (
'id',
'printing_order',
'customer_name',
'product',
'quantity',
'unit',
'size',
'pieces',
'business_object',
'current_progress',
'execution_status',
'created_at',
)
search_fields = ('printing_order__human_id', 'product__name')
list_filter = ('created_at', 'printing_order__customer__name')
actions = ['advance_to_next_state_action', 'step_back_one_state_action', 'reset_progress_action']
def save_model(self, request, obj, form, change):
"""保存 PrintingJob 时使用 service 层"""
from api_v1.views.printing.services import PrintingJobService
if not change: # 新创建
# 使用 service 创建(这样会自动创建 BusinessObject
data = {
'printing_order': obj.printing_order,
'product': obj.product,
'quantity': obj.quantity,
'unit': obj.unit,
'size': obj.size,
'pieces': obj.pieces,
'description': obj.description,
}
created_obj = PrintingJobService.create_printing_job(data, request.user)
# 将创建的对象的 ID 赋值给当前对象(这样 admin 可以正确跳转)
obj.pk = created_obj.pk
obj.id = created_obj.id
else: # 更新
# 使用 service 更新
data = {
'printing_order': obj.printing_order,
'product': obj.product,
'quantity': obj.quantity,
'unit': obj.unit,
'size': obj.size,
'pieces': obj.pieces,
'description': obj.description,
}
success, message, updated_obj = PrintingJobService.update_printing_job(
obj, data, request.user
)
if not success:
from django.core.exceptions import ValidationError
raise ValidationError(message)
@admin.display(description='客户名称')
def customer_name(self, obj: models.PrintingJob):
return obj.printing_order.customer.name
@admin.display(description='当前进度')
def current_progress(self, obj: models.PrintingJob):
"""显示该 job 自己的进度(已完成节点数 / 总节点数)"""
if not obj.business_object or not obj.printing_order.process:
return "0%"
total_nodes = obj.business_object.process.process_nodes.count()
if total_nodes == 0:
return "0%"
completed_nodes = obj.business_object.state_logs.filter(is_cancelled=False).count()
progress = int((completed_nodes / total_nodes) * 100)
return f"{progress}%"
@admin.display(description='执行状态')
def execution_status(self, obj: models.PrintingJob):
return obj.status
@action(description='前进到下一个状态')
def advance_to_next_state_action(self, request, queryset):
from stateflow import services
success_count = 0
messages = []
for obj in queryset:
# 检查是否有 business_object
if not obj.business_object:
messages.append(f"PrintingJob #{obj.id}: 没有关联的业务对象,无法推进")
continue
success, message = services.advance_to_next_state(obj.business_object, request.user)
if success:
success_count += 1
messages.append(f"PrintingJob #{obj.id}: {message}")
self.message_user(request, f'成功推进 {success_count}/{queryset.count()} 个印染任务。详情: {"; ".join(messages[:5])}')
@action(description='回退一步')
def step_back_one_state_action(self, request, queryset):
from stateflow import services
success_count = 0
messages = []
for obj in queryset:
# 检查是否有 business_object
if not obj.business_object:
messages.append(f"PrintingJob #{obj.id}: 没有关联的业务对象,无法回退")
continue
success, message = services.step_back_one_state(obj.business_object, request.user)
if success:
success_count += 1
messages.append(f"PrintingJob #{obj.id}: {message}")
self.message_user(request, f'成功回退 {success_count}/{queryset.count()} 个印染任务。详情: {"; ".join(messages[:5])}')
@action(description='重置进度')
def reset_progress_action(self, request, queryset):
from stateflow import services
reset_count = 0
for obj in queryset:
# 检查是否有 business_object
if not obj.business_object:
continue
services.reset_business_object_progress(obj.business_object)
reset_count += 1
self.message_user(request, f'已重置 {reset_count} 个印染任务的进度。')