forked from erp-dev/erp
470 lines
17 KiB
Python
470 lines
17 KiB
Python
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', 'original_id']
|
||
}),
|
||
('客户信息', {
|
||
'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(
|
||
'<span style="color: red; font-weight: bold;">⚠️ 加急</span>'
|
||
)
|
||
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',
|
||
)
|
||
search_fields = (
|
||
'=id',
|
||
'customer__name',
|
||
'fabric',
|
||
'craft',
|
||
'description',
|
||
)
|
||
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} 个印染任务的进度。')
|
||
|
||
|
||
@admin.register(models.PrintingJobBatchAdvanceRecord)
|
||
class PrintingJobBatchAdvanceRecordAdmin(admin.ModelAdmin):
|
||
list_display = (
|
||
'id',
|
||
'printing_order',
|
||
'state',
|
||
'created_by',
|
||
'jobs_count',
|
||
'created_at',
|
||
)
|
||
list_filter = (
|
||
'state',
|
||
'created_by',
|
||
'created_at',
|
||
)
|
||
search_fields = (
|
||
'printing_order__customer__name',
|
||
'state__name',
|
||
'created_by__username',
|
||
)
|
||
readonly_fields = (
|
||
'created_at',
|
||
'updated_at',
|
||
)
|
||
date_hierarchy = 'created_at'
|
||
ordering = ('-created_at',)
|
||
autocomplete_fields = ('printing_order', 'state', 'created_by', 'printing_jobs')
|
||
|
||
@admin.display(description='涉及任务数')
|
||
def jobs_count(self, obj: models.PrintingJobBatchAdvanceRecord) -> int:
|
||
return obj.printing_jobs.count()
|