from django.contrib import admin from django.contrib.admin import action from django.contrib.contenttypes.models import ContentType 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_display', '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', 'designer__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', 'designer'] fieldsets = [ ('基本信息', { 'fields': ['design_code', 'plate_type', 'plate_date', 'urgency_level'] }), ('客户信息', { 'fields': ['customer', 'area', 'default_address', 'salesperson', 'merchandiser', 'designer'] }), ('产品信息', { 'fields': ['style_name', 'fabric', 'fabric_source', 'width', 'production_method'] }), ('开版信息', { 'fields': [ 'plate_method', 'plate_image', 'image_name', '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 design_code_display(self, obj): return obj.design_code or (str(obj.id) if obj.id else '-') @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): return obj.status @admin.display(description='紧急程度') def urgency_display(self, obj): if obj.urgency_level == '加急': return format_html( '⚠️ 加急' ) return obj.urgency_level @admin.display(description='进度') def progress_display(self, obj): return obj.progress_percentage @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.id}: 没有关联的流程实例,无法推进") continue # 修复历史/异常数据:确保该订单的流程实例正确绑定到订单(避免 BusinessObject.object_id/content_type 为空) services.ensure_business_object_bound_to_instance( obj.business_object, obj, default_name=f"PlateOrder-{obj.id}", ) success, message, state_log = services.advance_to_next_state(obj.business_object, request.user) if success: success_count += 1 messages.append(f"{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: if not obj.business_object: messages.append(f"{obj.id}: 没有关联的流程实例,无法回退") continue success, message = services.step_back_one_state(obj.business_object, request.user) if success: success_count += 1 messages.append(f"{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: 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 @admin.register(models.PrintingOrder) 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 ct = ContentType.objects.get_for_model(models.PrintingJob) 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} 的流程实例", content_type=ct, object_id=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 @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 # 修复历史/异常数据:确保该 job 的流程实例正确绑定到 job(避免 BusinessObject.object_id/content_type 为空) services.ensure_business_object_bound_to_instance( obj.business_object, obj, default_name=f"PrintingJob-{obj.id}", ) success, message, state_log = 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} 个印染任务的进度。')