forked from erp-dev/erp
feat: printing module
This commit is contained in:
@@ -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} 个印染任务的进度。')
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-13 02:09
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('printing', '0005_alter_printingorder_options'),
|
||||
('stateflow', '0014_alter_process_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='printingjob',
|
||||
name='business_object',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='printing_job', to='stateflow.businessobject', verbose_name='流程实例'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='printingorder',
|
||||
name='process',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='printing_orders', to='stateflow.process', verbose_name='印染流程'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-13 02:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('printing', '0006_printingjob_business_object_printingorder_process'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='printingjob',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, null=True, verbose_name='备注'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='printingjob',
|
||||
name='pieces',
|
||||
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='件数'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='printingjob',
|
||||
name='size',
|
||||
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='一段尺寸'),
|
||||
),
|
||||
]
|
||||
61
printing/migrations/0008_plateorder.py
Normal file
61
printing/migrations/0008_plateorder.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-13 08:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('basic_info', '0009_product_minimum_quantity'),
|
||||
('printing', '0007_alter_printingjob_description_and_more'),
|
||||
('stateflow', '0015_stateparameter_attachment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PlateOrder',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('plate_code', models.CharField(max_length=50, unique=True, verbose_name='版单编号')),
|
||||
('design_code', models.CharField(blank=True, max_length=50, null=True, verbose_name='设计编号')),
|
||||
('plate_type', models.CharField(blank=True, max_length=20, null=True, verbose_name='起版情况')),
|
||||
('plate_date', models.DateTimeField(blank=True, null=True, verbose_name='下版时间')),
|
||||
('plate_method', models.CharField(blank=True, max_length=50, null=True, verbose_name='开版方式')),
|
||||
('plate_image', models.FileField(blank=True, null=True, upload_to='plate_images/', verbose_name='开版图')),
|
||||
('plate_notes', models.TextField(blank=True, null=True, verbose_name='打版注意事项')),
|
||||
('reprint_reason', models.TextField(blank=True, null=True, verbose_name='复版原因')),
|
||||
('development_status', models.CharField(blank=True, max_length=20, null=True, verbose_name='开发进程')),
|
||||
('urgency_level', models.CharField(default='正常', max_length=20, verbose_name='紧急程度')),
|
||||
('area', models.CharField(blank=True, max_length=255, null=True, verbose_name='区域')),
|
||||
('default_address', models.CharField(blank=True, max_length=500, null=True, verbose_name='默认地址')),
|
||||
('is_mark_frame', models.BooleanField(default=False, verbose_name='是否套唛架')),
|
||||
('drawing_rating', models.CharField(blank=True, max_length=20, null=True, verbose_name='画图评级')),
|
||||
('color_matching_rating', models.CharField(blank=True, max_length=20, null=True, verbose_name='调色评级')),
|
||||
('sample_rating', models.CharField(blank=True, max_length=20, null=True, verbose_name='套样评级')),
|
||||
('difficulty_rating', models.CharField(blank=True, max_length=20, null=True, verbose_name='难度评级')),
|
||||
('required_completion_date', models.DateField(blank=True, null=True, verbose_name='要求完成时间')),
|
||||
('completion_date', models.DateTimeField(blank=True, null=True, verbose_name='完成时间')),
|
||||
('fabric', models.CharField(blank=True, max_length=100, null=True, verbose_name='布料')),
|
||||
('width', models.CharField(blank=True, max_length=50, null=True, verbose_name='幅宽')),
|
||||
('style_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='款号名称')),
|
||||
('production_method', models.CharField(blank=True, max_length=50, null=True, verbose_name='做货方式')),
|
||||
('sample_meter', models.CharField(blank=True, max_length=100, null=True, verbose_name='米样')),
|
||||
('required_sample_meters', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='客户要求米样米数')),
|
||||
('approval_result', models.CharField(blank=True, max_length=50, null=True, verbose_name='审批结果')),
|
||||
('is_ordered', models.BooleanField(default=False, verbose_name='是否已下单')),
|
||||
('customer_feedback', models.TextField(blank=True, null=True, verbose_name='客户修改意见')),
|
||||
('business_object', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='plate_order', to='stateflow.businessobject', verbose_name='流程实例')),
|
||||
('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='plate_orders', to='basic_info.customer', verbose_name='客户')),
|
||||
('merchandiser', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='plate_orders_as_merchandiser', to='basic_info.employee', verbose_name='跟单员')),
|
||||
('salesperson', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='plate_orders_as_salesperson', to='basic_info.employee', verbose_name='销售员')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '开版订单',
|
||||
'verbose_name_plural': '开版订单',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-13 09:18
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('printing', '0008_plateorder'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='plateorder',
|
||||
name='development_status',
|
||||
),
|
||||
]
|
||||
17
printing/migrations/0010_remove_plateorder_plate_code.py
Normal file
17
printing/migrations/0010_remove_plateorder_plate_code.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-13 09:44
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('printing', '0009_remove_plateorder_development_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='plateorder',
|
||||
name='plate_code',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-13 09:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('printing', '0010_remove_plateorder_plate_code'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='plateorder',
|
||||
options={'ordering': ['-created_at'], 'permissions': [('can_invalidate_plateorder', '可以作废开版订单'), ('can_activate_plateorder', '可以恢复开版订单')], 'verbose_name': '开版订单', 'verbose_name_plural': '开版订单'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plateorder',
|
||||
name='is_invalid',
|
||||
field=models.BooleanField(default=False, verbose_name='是否作废'),
|
||||
),
|
||||
]
|
||||
@@ -1,6 +1,171 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from flower.common import ModelBase
|
||||
from basic_info import models as basic_models
|
||||
from stateflow import models as stateflow_models
|
||||
|
||||
|
||||
class PlateOrder(ModelBase):
|
||||
"""开版管理订单"""
|
||||
|
||||
# 自动编号相关
|
||||
design_code = models.CharField(max_length=50, blank=True, null=True, verbose_name='设计编号')
|
||||
|
||||
# 版相关信息
|
||||
plate_type = models.CharField(max_length=20, blank=True, null=True, verbose_name='起版情况') # 首版/复版等
|
||||
plate_date = models.DateTimeField(null=True, blank=True, verbose_name='下版时间')
|
||||
plate_method = models.CharField(max_length=50, blank=True, null=True, verbose_name='开版方式')
|
||||
plate_image = models.FileField(upload_to='plate_images/', null=True, blank=True, verbose_name='开版图')
|
||||
plate_notes = models.TextField(blank=True, null=True, verbose_name='打版注意事项')
|
||||
reprint_reason = models.TextField(blank=True, null=True, verbose_name='复版原因')
|
||||
|
||||
# 状态和进程
|
||||
# development_status = models.CharField(max_length=20, blank=True, null=True, verbose_name='开发进程') # 未打印/待画图等
|
||||
urgency_level = models.CharField(max_length=20, default='正常', verbose_name='紧急程度') # 正常/加急
|
||||
|
||||
# 人员关联
|
||||
salesperson = models.ForeignKey(
|
||||
basic_models.Employee,
|
||||
on_delete=models.PROTECT,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='plate_orders_as_salesperson',
|
||||
verbose_name='销售员'
|
||||
)
|
||||
merchandiser = models.ForeignKey(
|
||||
basic_models.Employee,
|
||||
on_delete=models.PROTECT,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='plate_orders_as_merchandiser',
|
||||
verbose_name='跟单员'
|
||||
)
|
||||
|
||||
# 客户和地址信息
|
||||
customer = models.ForeignKey(
|
||||
basic_models.Customer,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='plate_orders',
|
||||
verbose_name='客户'
|
||||
)
|
||||
area = models.CharField(max_length=255, blank=True, null=True, verbose_name='区域')
|
||||
default_address = models.CharField(max_length=500, blank=True, null=True, verbose_name='默认地址')
|
||||
|
||||
# 质量评级
|
||||
is_mark_frame = models.BooleanField(default=False, verbose_name='是否套唛架')
|
||||
drawing_rating = models.CharField(max_length=20, blank=True, null=True, verbose_name='画图评级')
|
||||
color_matching_rating = models.CharField(max_length=20, blank=True, null=True, verbose_name='调色评级')
|
||||
sample_rating = models.CharField(max_length=20, blank=True, null=True, verbose_name='套样评级')
|
||||
difficulty_rating = models.CharField(max_length=20, blank=True, null=True, verbose_name='难度评级')
|
||||
|
||||
# 时间相关
|
||||
required_completion_date = models.DateField(null=True, blank=True, verbose_name='要求完成时间')
|
||||
completion_date = models.DateTimeField(null=True, blank=True, verbose_name='完成时间')
|
||||
|
||||
# 产品信息
|
||||
fabric = models.CharField(max_length=100, blank=True, null=True, verbose_name='布料')
|
||||
width = models.CharField(max_length=50, blank=True, null=True, verbose_name='幅宽')
|
||||
style_name = models.CharField(max_length=100, blank=True, null=True, verbose_name='款号名称')
|
||||
|
||||
# 生产方式
|
||||
production_method = models.CharField(max_length=50, blank=True, null=True, verbose_name='做货方式')
|
||||
|
||||
# 样品相关
|
||||
sample_meter = models.CharField(max_length=100, blank=True, null=True, verbose_name='米样')
|
||||
required_sample_meters = models.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name='客户要求米样米数'
|
||||
)
|
||||
|
||||
# 审批和订单状态
|
||||
approval_result = models.CharField(max_length=50, blank=True, null=True, verbose_name='审批结果')
|
||||
is_ordered = models.BooleanField(default=False, verbose_name='是否已下单')
|
||||
is_invalid = models.BooleanField(default=False, verbose_name='是否作废')
|
||||
customer_feedback = models.TextField(blank=True, null=True, verbose_name='客户修改意见')
|
||||
|
||||
# 流程管理
|
||||
business_object = models.OneToOneField(
|
||||
stateflow_models.BusinessObject,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='plate_order',
|
||||
verbose_name='流程实例',
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.human_id or f'PlateOrder-{self.id}'
|
||||
|
||||
class Meta:
|
||||
verbose_name = '开版订单'
|
||||
verbose_name_plural = '开版订单'
|
||||
ordering = ['-created_at']
|
||||
permissions = [
|
||||
('can_invalidate_plateorder', '可以作废开版订单'),
|
||||
('can_activate_plateorder', '可以恢复开版订单'),
|
||||
]
|
||||
|
||||
@property
|
||||
def status(self) -> str:
|
||||
"""返回当前状态名称"""
|
||||
if not self.business_object:
|
||||
return '未开始'
|
||||
|
||||
current_state = self.business_object.get_current_state()
|
||||
|
||||
# current_state 为 None 有两种情况:未开始或已完成
|
||||
if current_state is None:
|
||||
# 检查是否有完成记录来区分
|
||||
has_completed_records = self.business_object.state_logs.filter(is_cancelled=False).exists()
|
||||
if has_completed_records:
|
||||
return '已完成'
|
||||
else:
|
||||
return '未开始'
|
||||
|
||||
return current_state.name
|
||||
|
||||
@property
|
||||
def status_id(self):
|
||||
"""返回当前状态ID"""
|
||||
if not self.business_object:
|
||||
return None
|
||||
current_state = self.business_object.get_current_state()
|
||||
return current_state.id if current_state else None
|
||||
|
||||
@property
|
||||
def is_completed(self) -> bool:
|
||||
"""判断是否完成"""
|
||||
if not self.business_object:
|
||||
return False
|
||||
|
||||
# 检查流程是否完成
|
||||
from stateflow.services import get_overall_status
|
||||
return get_overall_status(self.business_object) == 'completed'
|
||||
|
||||
@property
|
||||
def has_started(self) -> bool:
|
||||
"""判断是否已开始"""
|
||||
if not self.business_object:
|
||||
return False
|
||||
|
||||
# 检查是否有状态流转记录
|
||||
from stateflow.models import StateFlowRecord
|
||||
has_records = StateFlowRecord.objects.filter(
|
||||
business_object=self.business_object
|
||||
).exists()
|
||||
|
||||
return has_records
|
||||
|
||||
@property
|
||||
def progress_percentage(self) -> float:
|
||||
"""计算进度百分比"""
|
||||
if not self.business_object:
|
||||
return 0.0
|
||||
|
||||
return self.business_object.get_progress_percentage()
|
||||
|
||||
|
||||
class PrintingOrder(ModelBase):
|
||||
@@ -28,6 +193,14 @@ class PrintingOrder(ModelBase):
|
||||
rolling_warn = models.TextField(blank=True, null=True, verbose_name='滚筒注意事项')
|
||||
production_warn = models.TextField(blank=True, null=True, verbose_name='生产注意事项')
|
||||
is_invalid = models.BooleanField(default=False, verbose_name='是否作废')
|
||||
process = models.ForeignKey(
|
||||
stateflow_models.Process,
|
||||
on_delete=models.PROTECT,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='printing_orders',
|
||||
verbose_name='印染流程',
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.human_id
|
||||
@@ -42,8 +215,24 @@ class PrintingOrder(ModelBase):
|
||||
|
||||
@property
|
||||
def human_id(self) -> str:
|
||||
if self.id is None or self.created_at is None:
|
||||
return ""
|
||||
return f"{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}"
|
||||
|
||||
def can_change_process(self) -> bool:
|
||||
"""检查是否可以修改流程(所有job都未开始)"""
|
||||
return not any(job.has_started for job in self.printing_jobs.all())
|
||||
|
||||
@property
|
||||
def progress(self) -> int:
|
||||
"""计算订单完成进度百分比(已完成的任务数 / 总任务数)"""
|
||||
jobs = self.printing_jobs.all()
|
||||
if not jobs.exists():
|
||||
return 0
|
||||
|
||||
completed_count = sum(1 for job in jobs if job.is_completed)
|
||||
return int((completed_count / jobs.count()) * 100)
|
||||
|
||||
|
||||
class PrintingJob(ModelBase):
|
||||
"""PrintingJob model representing a printing job associated with an order."""
|
||||
@@ -61,13 +250,81 @@ class PrintingJob(ModelBase):
|
||||
)
|
||||
quantity = models.PositiveIntegerField(verbose_name='数量')
|
||||
unit = models.CharField(max_length=50, verbose_name='单位')
|
||||
size = models.CharField(max_length=100, verbose_name='一段尺寸')
|
||||
pieces = models.PositiveIntegerField(verbose_name='件数')
|
||||
description = models.TextField(blank=True, verbose_name='备注')
|
||||
size = models.CharField(max_length=100, null=True, blank=True, verbose_name='一段尺寸')
|
||||
pieces = models.PositiveIntegerField(null=True, blank=True, verbose_name='件数')
|
||||
description = models.TextField(blank=True, null=True, verbose_name='备注')
|
||||
business_object = models.OneToOneField(
|
||||
stateflow_models.BusinessObject,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='printing_job',
|
||||
verbose_name='流程实例',
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.printing_order.human_id
|
||||
|
||||
@property
|
||||
def status(self) -> str:
|
||||
"""返回当前状态名称"""
|
||||
if not self.business_object:
|
||||
return '未开始'
|
||||
|
||||
current_state = self.business_object.get_current_state()
|
||||
|
||||
# current_state 为 None 有两种情况:未开始或已完成
|
||||
if current_state is None:
|
||||
# 检查是否有完成记录来区分
|
||||
has_completed_records = self.business_object.state_logs.filter(is_cancelled=False).exists()
|
||||
if has_completed_records:
|
||||
return '已完成'
|
||||
else:
|
||||
return '未开始'
|
||||
|
||||
return current_state.name
|
||||
|
||||
@property
|
||||
def status_id(self):
|
||||
"""返回当前状态ID"""
|
||||
if not self.business_object:
|
||||
return None
|
||||
current_state = self.business_object.get_current_state()
|
||||
return current_state.id if current_state else None
|
||||
|
||||
@property
|
||||
def is_completed(self) -> bool:
|
||||
"""判断是否完成(所有流程节点都已完成)"""
|
||||
if not self.business_object or not self.printing_order.process:
|
||||
return False
|
||||
|
||||
# 获取流程的所有节点
|
||||
process_nodes = self.printing_order.process.process_nodes.all()
|
||||
total_nodes = process_nodes.count()
|
||||
|
||||
if total_nodes == 0:
|
||||
return False
|
||||
|
||||
# 检查已完成的节点数
|
||||
completed_count = self.business_object.state_logs.filter(is_cancelled=False).count()
|
||||
|
||||
# 所有节点都完成了才算完成
|
||||
return completed_count >= total_nodes
|
||||
|
||||
@property
|
||||
def has_started(self) -> bool:
|
||||
"""判断是否已开始(有BusinessObject且有状态记录,或不在初始状态)"""
|
||||
if not self.business_object:
|
||||
return False
|
||||
|
||||
# 检查是否有状态流转记录
|
||||
from stateflow.models import StateFlowRecord
|
||||
has_records = StateFlowRecord.objects.filter(
|
||||
business_object=self.business_object
|
||||
).exists()
|
||||
|
||||
return has_records
|
||||
|
||||
class Meta:
|
||||
verbose_name = '印染款式明细'
|
||||
verbose_name_plural = '印染款式明细'
|
||||
|
||||
710
printing/printing_api.yml
Normal file
710
printing/printing_api.yml
Normal file
@@ -0,0 +1,710 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Printing API
|
||||
description: |
|
||||
印染模块接口文档,包含 PrintingOrder(印染订单)和 PrintingJob(印染款式明细)的 CRUD 和自定义动作。
|
||||
所有接口仅允许印染工厂用户访问(IsPrintingFactory)。
|
||||
version: '1.0.0'
|
||||
servers:
|
||||
- url: /api/v1
|
||||
description: 本地开发 API 前缀
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
schemas:
|
||||
PrintingOrder:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
description: 订单 ID
|
||||
human_id:
|
||||
type: string
|
||||
description: 由系统生成的人类可读编号(格式: YYYYMMDD000001)
|
||||
example: "20251112000001"
|
||||
customer:
|
||||
type: integer
|
||||
description: 客户 ID
|
||||
customer_name:
|
||||
type: string
|
||||
description: 客户名称
|
||||
customer_phone:
|
||||
type: string
|
||||
description: 客户电话
|
||||
customer_address:
|
||||
type: string
|
||||
description: 客户地址
|
||||
fabric:
|
||||
type: string
|
||||
description: 面料
|
||||
example: "纯棉布料"
|
||||
width:
|
||||
type: string
|
||||
description: 幅宽
|
||||
example: "150cm"
|
||||
is_urgent:
|
||||
type: boolean
|
||||
description: 是否紧急
|
||||
default: false
|
||||
area:
|
||||
type: string
|
||||
description: 地区
|
||||
example: "广州"
|
||||
address:
|
||||
type: string
|
||||
description: 地址
|
||||
example: "白云区xxx"
|
||||
fabric_source:
|
||||
type: string
|
||||
description: 布料来源
|
||||
example: "客户提供"
|
||||
is_fabric_received:
|
||||
type: boolean
|
||||
description: 布料是否已收
|
||||
default: false
|
||||
craft:
|
||||
type: string
|
||||
description: 工艺
|
||||
example: "活性印花"
|
||||
description:
|
||||
type: string
|
||||
description: 订单描述
|
||||
outgoing_date:
|
||||
type: string
|
||||
format: date
|
||||
description: 出货日期
|
||||
example: "2025-11-20"
|
||||
curve:
|
||||
type: string
|
||||
description: 曲线
|
||||
new_curve:
|
||||
type: string
|
||||
description: 新加曲线
|
||||
position:
|
||||
type: string
|
||||
description: 位置
|
||||
printing_warn:
|
||||
type: string
|
||||
description: 打印注意事项
|
||||
rolling_warn:
|
||||
type: string
|
||||
description: 滚筒注意事项
|
||||
production_warn:
|
||||
type: string
|
||||
description: 生产注意事项
|
||||
is_invalid:
|
||||
type: boolean
|
||||
description: 是否作废
|
||||
default: false
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 创建时间
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 更新时间
|
||||
required: [customer, fabric, width]
|
||||
PrintingOrderCreate:
|
||||
type: object
|
||||
properties:
|
||||
customer:
|
||||
type: integer
|
||||
description: 客户 ID
|
||||
fabric:
|
||||
type: string
|
||||
description: 面料
|
||||
example: "纯棉布料"
|
||||
width:
|
||||
type: string
|
||||
description: 幅宽
|
||||
example: "150cm"
|
||||
is_urgent:
|
||||
type: boolean
|
||||
description: 是否紧急
|
||||
default: false
|
||||
area:
|
||||
type: string
|
||||
description: 地区
|
||||
example: "广州"
|
||||
address:
|
||||
type: string
|
||||
description: 地址
|
||||
example: "白云区xxx"
|
||||
fabric_source:
|
||||
type: string
|
||||
description: 布料来源
|
||||
example: "客户提供"
|
||||
is_fabric_received:
|
||||
type: boolean
|
||||
description: 布料是否已收
|
||||
default: false
|
||||
craft:
|
||||
type: string
|
||||
description: 工艺
|
||||
example: "活性印花"
|
||||
description:
|
||||
type: string
|
||||
description: 订单描述
|
||||
outgoing_date:
|
||||
type: string
|
||||
format: date
|
||||
description: 出货日期
|
||||
example: "2025-11-20"
|
||||
curve:
|
||||
type: string
|
||||
description: 曲线
|
||||
new_curve:
|
||||
type: string
|
||||
description: 新加曲线
|
||||
position:
|
||||
type: string
|
||||
description: 位置
|
||||
printing_warn:
|
||||
type: string
|
||||
description: 打印注意事项
|
||||
rolling_warn:
|
||||
type: string
|
||||
description: 滚筒注意事项
|
||||
production_warn:
|
||||
type: string
|
||||
description: 生产注意事项
|
||||
is_invalid:
|
||||
type: boolean
|
||||
description: 是否作废
|
||||
default: false
|
||||
required: [customer, fabric, width]
|
||||
PrintingJob:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
description: 款式明细 ID
|
||||
printing_order:
|
||||
type: integer
|
||||
description: 印染订单 ID
|
||||
printing_order_id:
|
||||
type: string
|
||||
description: 印染订单人类可读编号
|
||||
example: "20251112000001"
|
||||
product:
|
||||
type: integer
|
||||
description: 产品 ID
|
||||
product_name:
|
||||
type: string
|
||||
description: 产品名称
|
||||
example: "测试产品"
|
||||
product_code:
|
||||
type: string
|
||||
description: 产品编号
|
||||
example: "TEST001"
|
||||
quantity:
|
||||
type: integer
|
||||
description: 数量(必须 > 0)
|
||||
example: 100
|
||||
minimum: 1
|
||||
unit:
|
||||
type: string
|
||||
description: 单位
|
||||
example: "米"
|
||||
size:
|
||||
type: string
|
||||
description: 一段尺寸
|
||||
example: "50*60"
|
||||
pieces:
|
||||
type: integer
|
||||
description: 件数(必须 > 0)
|
||||
example: 10
|
||||
minimum: 1
|
||||
description:
|
||||
type: string
|
||||
description: 备注
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 创建时间
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 更新时间
|
||||
required: [printing_order, product, quantity, unit, size, pieces]
|
||||
PrintingJobCreate:
|
||||
type: object
|
||||
properties:
|
||||
printing_order:
|
||||
type: integer
|
||||
description: 印染订单 ID
|
||||
product:
|
||||
type: integer
|
||||
description: 产品 ID
|
||||
quantity:
|
||||
type: integer
|
||||
description: 数量(必须 > 0)
|
||||
example: 100
|
||||
minimum: 1
|
||||
unit:
|
||||
type: string
|
||||
description: 单位
|
||||
example: "米"
|
||||
size:
|
||||
type: string
|
||||
description: 一段尺寸
|
||||
example: "50*60"
|
||||
pieces:
|
||||
type: integer
|
||||
description: 件数(必须 > 0)
|
||||
example: 10
|
||||
minimum: 1
|
||||
description:
|
||||
type: string
|
||||
description: 备注
|
||||
required: [printing_order, product, quantity, unit, size, pieces]
|
||||
parameters:
|
||||
limit:
|
||||
name: limit
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
description: 返回条目上限(分页)
|
||||
offset:
|
||||
name: offset
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
description: 分页偏移
|
||||
search:
|
||||
name: search
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: 全文搜索关键字
|
||||
ordering:
|
||||
name: ordering
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: 排序字段,例如 id 或 -id
|
||||
paths:
|
||||
/printing-orders/:
|
||||
get:
|
||||
summary: 获取印染订单列表
|
||||
description: 支持过滤(customer, is_urgent, is_fabric_received, is_invalid, area, outgoing_date_from/to, created_date_from/to)、search、ordering 和分页。
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/limit'
|
||||
- $ref: '#/components/parameters/offset'
|
||||
- $ref: '#/components/parameters/search'
|
||||
- $ref: '#/components/parameters/ordering'
|
||||
- name: customer
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
description: 客户 ID
|
||||
- name: customer_name
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: 客户名称(模糊)
|
||||
- name: customer_phone
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: 客户电话(模糊)
|
||||
- name: fabric
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: 面料(模糊)
|
||||
- name: is_urgent
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: is_fabric_received
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: is_invalid
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: area
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: outgoing_date_from
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date
|
||||
- name: outgoing_date_to
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date
|
||||
responses:
|
||||
'200':
|
||||
description: 列表
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
oneOf:
|
||||
- type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PrintingOrder'
|
||||
- type: object
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PrintingOrder'
|
||||
'401':
|
||||
description: 未认证
|
||||
'403':
|
||||
description: 没有权限
|
||||
security:
|
||||
- BearerAuth: []
|
||||
post:
|
||||
summary: 创建印染订单
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PrintingOrderCreate'
|
||||
responses:
|
||||
'201':
|
||||
description: 创建成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PrintingOrder'
|
||||
'400':
|
||||
description: 校验错误
|
||||
'403':
|
||||
description: 没有权限
|
||||
security:
|
||||
- BearerAuth: []
|
||||
/printing-orders/{id}/:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
get:
|
||||
summary: 获取印染订单详情
|
||||
responses:
|
||||
'200':
|
||||
description: 详情
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PrintingOrder'
|
||||
'404':
|
||||
description: 未找到
|
||||
security:
|
||||
- BearerAuth: []
|
||||
put:
|
||||
summary: 更新印染订单(全量)
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PrintingOrderCreate'
|
||||
responses:
|
||||
'200':
|
||||
description: 更新成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PrintingOrder'
|
||||
'400':
|
||||
description: 校验错误
|
||||
'403':
|
||||
description: 没有权限
|
||||
security:
|
||||
- BearerAuth: []
|
||||
patch:
|
||||
summary: 更新印染订单(部分)
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PrintingOrderCreate'
|
||||
responses:
|
||||
'200':
|
||||
description: 更新成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PrintingOrder'
|
||||
'400':
|
||||
description: 校验错误
|
||||
security:
|
||||
- BearerAuth: []
|
||||
delete:
|
||||
summary: 删除(不支持)
|
||||
description: 删除操作已禁用,请使用 作废 操作。
|
||||
responses:
|
||||
'405':
|
||||
description: 不支持删除
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
detail:
|
||||
type: string
|
||||
security:
|
||||
- BearerAuth: []
|
||||
/printing-orders/{id}/invalidate/:
|
||||
post:
|
||||
summary: 作废订单
|
||||
description: 将订单标记为已作废(需要 printing.can_invalidate_printingorder 权限)。
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
description: 作废成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
detail:
|
||||
type: string
|
||||
data:
|
||||
$ref: '#/components/schemas/PrintingOrder'
|
||||
'400':
|
||||
description: 已作废或请求错误
|
||||
'403':
|
||||
description: 没有权限
|
||||
security:
|
||||
- BearerAuth: []
|
||||
/printing-orders/{id}/activate/:
|
||||
post:
|
||||
summary: 恢复订单
|
||||
description: 将订单从作废状态恢复(需要 printing.can_activate_printingorder 权限)。
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
description: 恢复成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
detail:
|
||||
type: string
|
||||
data:
|
||||
$ref: '#/components/schemas/PrintingOrder'
|
||||
'400':
|
||||
description: 未作废或请求错误
|
||||
'403':
|
||||
description: 没有权限
|
||||
security:
|
||||
- BearerAuth: []
|
||||
/printing-orders/{id}/mark_fabric_received/:
|
||||
post:
|
||||
summary: 标记布料已收
|
||||
description: 将订单标记为布料已收到。
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
description: 标记成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
detail:
|
||||
type: string
|
||||
data:
|
||||
$ref: '#/components/schemas/PrintingOrder'
|
||||
'400':
|
||||
description: 已标记或请求错误
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
/printing-jobs/:
|
||||
get:
|
||||
summary: 获取印染款式明细列表
|
||||
description: 支持过滤(printing_order, product, product_name, unit, quantity_min/max, pieces_min/max)、search、ordering 和分页。
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/limit'
|
||||
- $ref: '#/components/parameters/offset'
|
||||
- $ref: '#/components/parameters/search'
|
||||
- $ref: '#/components/parameters/ordering'
|
||||
- name: printing_order
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
- name: product
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
- name: product_name
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: unit
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: quantity_min
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
- name: quantity_max
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
- name: pieces_min
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
- name: pieces_max
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
description: 列表
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
oneOf:
|
||||
- type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PrintingJob'
|
||||
- type: object
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PrintingJob'
|
||||
'401':
|
||||
description: 未认证
|
||||
'403':
|
||||
description: 没有权限
|
||||
security:
|
||||
- BearerAuth: []
|
||||
post:
|
||||
summary: 创建印染款式明细
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PrintingJobCreate'
|
||||
responses:
|
||||
'201':
|
||||
description: 创建成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PrintingJob'
|
||||
'400':
|
||||
description: 校验错误
|
||||
'403':
|
||||
description: 没有权限
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
/printing-jobs/{id}/:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
get:
|
||||
summary: 获取款式明细详情
|
||||
responses:
|
||||
'200':
|
||||
description: 详情
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PrintingJob'
|
||||
'404':
|
||||
description: 未找到
|
||||
security:
|
||||
- BearerAuth: []
|
||||
put:
|
||||
summary: 更新款式明细(全量)
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PrintingJobCreate'
|
||||
responses:
|
||||
'200':
|
||||
description: 更新成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PrintingJob'
|
||||
'400':
|
||||
description: 校验错误
|
||||
security:
|
||||
- BearerAuth: []
|
||||
patch:
|
||||
summary: 更新款式明细(部分)
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PrintingJobCreate'
|
||||
responses:
|
||||
'200':
|
||||
description: 更新成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PrintingJob'
|
||||
'400':
|
||||
description: 校验错误
|
||||
security:
|
||||
- BearerAuth: []
|
||||
delete:
|
||||
summary: 删除(不支持)
|
||||
description: 删除操作已禁用,请使用所需的业务操作。
|
||||
responses:
|
||||
'405':
|
||||
description: 不支持删除
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
detail:
|
||||
type: string
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
components_end: {}
|
||||
@@ -1,124 +0,0 @@
|
||||
"""
|
||||
Printing 序列化器
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from printing import models
|
||||
|
||||
|
||||
class PrintingOrderListSerializer(serializers.ModelSerializer):
|
||||
"""印染订单列表序列化器"""
|
||||
customer_name = serializers.CharField(source='customer.name', read_only=True)
|
||||
customer_phone = serializers.CharField(source='customer.phone', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.PrintingOrder
|
||||
fields = [
|
||||
'id', 'human_id', 'customer', 'customer_name', 'customer_phone',
|
||||
'fabric', 'width', 'is_urgent', 'area', 'address',
|
||||
'is_fabric_received', 'outgoing_date', 'is_invalid',
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
read_only_fields = ['id', 'human_id', 'created_at', 'updated_at']
|
||||
|
||||
|
||||
class PrintingOrderDetailSerializer(serializers.ModelSerializer):
|
||||
"""印染订单详情序列化器"""
|
||||
customer_name = serializers.CharField(source='customer.name', read_only=True)
|
||||
customer_phone = serializers.CharField(source='customer.phone', read_only=True)
|
||||
customer_address = serializers.CharField(source='customer.address', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.PrintingOrder
|
||||
fields = [
|
||||
'id', 'human_id', 'customer', 'customer_name', 'customer_phone', 'customer_address',
|
||||
'fabric', 'width', 'is_urgent', 'area', 'address', 'fabric_source',
|
||||
'is_fabric_received', 'craft', 'description', 'outgoing_date',
|
||||
'curve', 'new_curve', 'position',
|
||||
'printing_warn', 'rolling_warn', 'production_warn',
|
||||
'is_invalid', 'created_at', 'updated_at'
|
||||
]
|
||||
read_only_fields = ['id', 'human_id', 'created_at', 'updated_at']
|
||||
|
||||
|
||||
class PrintingOrderCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
"""印染订单创建/更新序列化器"""
|
||||
|
||||
class Meta:
|
||||
model = models.PrintingOrder
|
||||
fields = [
|
||||
'customer', 'fabric', 'width', 'is_urgent', 'area', 'address',
|
||||
'fabric_source', 'is_fabric_received', 'craft', 'description',
|
||||
'outgoing_date', 'curve', 'new_curve', 'position',
|
||||
'printing_warn', 'rolling_warn', 'production_warn', 'is_invalid'
|
||||
]
|
||||
|
||||
def validate_customer(self, value):
|
||||
"""验证客户是否存在"""
|
||||
if not value:
|
||||
raise serializers.ValidationError("客户不能为空")
|
||||
return value
|
||||
|
||||
|
||||
class PrintingJobListSerializer(serializers.ModelSerializer):
|
||||
"""印染款式明细列表序列化器"""
|
||||
printing_order_id = serializers.CharField(source='printing_order.human_id', read_only=True)
|
||||
product_name = serializers.CharField(source='product.name', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.PrintingJob
|
||||
fields = [
|
||||
'id', 'printing_order', 'printing_order_id', 'product', 'product_name',
|
||||
'quantity', 'unit', 'size', 'pieces', 'description',
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
read_only_fields = ['id', 'created_at', 'updated_at']
|
||||
|
||||
|
||||
class PrintingJobDetailSerializer(serializers.ModelSerializer):
|
||||
"""印染款式明细详情序列化器"""
|
||||
printing_order_id = serializers.CharField(source='printing_order.human_id', read_only=True)
|
||||
product_name = serializers.CharField(source='product.name', read_only=True)
|
||||
product_code = serializers.CharField(source='product.human_id', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.PrintingJob
|
||||
fields = [
|
||||
'id', 'printing_order', 'printing_order_id', 'product', 'product_name', 'product_code',
|
||||
'quantity', 'unit', 'size', 'pieces', 'description',
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
read_only_fields = ['id', 'created_at', 'updated_at']
|
||||
|
||||
|
||||
class PrintingJobCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
"""印染款式明细创建/更新序列化器"""
|
||||
|
||||
class Meta:
|
||||
model = models.PrintingJob
|
||||
fields = [
|
||||
'printing_order', 'product', 'quantity', 'unit', 'size', 'pieces', 'description'
|
||||
]
|
||||
|
||||
def validate_printing_order(self, value):
|
||||
"""验证印染订单是否存在"""
|
||||
if not value:
|
||||
raise serializers.ValidationError("印染订单不能为空")
|
||||
return value
|
||||
|
||||
def validate_product(self, value):
|
||||
"""验证产品是否存在"""
|
||||
if not value:
|
||||
raise serializers.ValidationError("产品不能为空")
|
||||
return value
|
||||
|
||||
def validate_quantity(self, value):
|
||||
"""验证数量"""
|
||||
if value <= 0:
|
||||
raise serializers.ValidationError("数量必须大于0")
|
||||
return value
|
||||
|
||||
def validate_pieces(self, value):
|
||||
"""验证件数"""
|
||||
if value <= 0:
|
||||
raise serializers.ValidationError("件数必须大于0")
|
||||
return value
|
||||
233
printing/test_plate_order.py
Normal file
233
printing/test_plate_order.py
Normal file
@@ -0,0 +1,233 @@
|
||||
"""
|
||||
PlateOrder 模型测试
|
||||
"""
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils import timezone
|
||||
from decimal import Decimal
|
||||
from printing import models as printing_models
|
||||
from basic_info import models as basic_models
|
||||
from stateflow import models as stateflow_models
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class PlateOrderModelTestCase(TestCase):
|
||||
"""测试 PlateOrder 模型"""
|
||||
|
||||
def setUp(self):
|
||||
"""设置测试数据"""
|
||||
self.user = User.objects.create_user(username='testuser', password='testpass')
|
||||
|
||||
# 创建商户
|
||||
self.merchant = basic_models.Merchant.objects.create(
|
||||
name='测试印花厂',
|
||||
type=basic_models.MerchantTypeEnum.FACTORY
|
||||
)
|
||||
|
||||
# 创建客户
|
||||
self.customer = basic_models.Customer.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='测试客户',
|
||||
mobile='13900139000',
|
||||
area='测试地区'
|
||||
)
|
||||
|
||||
# 创建员工
|
||||
self.salesperson = basic_models.Employee.objects.create(
|
||||
sys_user=self.user,
|
||||
merchant=self.merchant,
|
||||
name='销售员',
|
||||
mobile='13800138000',
|
||||
job_type=basic_models.EmployeeTypeEnum.PRINTER,
|
||||
status=basic_models.EmployeeStatusEnum.ACTIVE
|
||||
)
|
||||
|
||||
self.merchandiser = basic_models.Employee.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='跟单员',
|
||||
mobile='13800138001',
|
||||
job_type=basic_models.EmployeeTypeEnum.ROLLING,
|
||||
status=basic_models.EmployeeStatusEnum.ACTIVE
|
||||
)
|
||||
|
||||
def test_create_plate_order_basic(self):
|
||||
"""测试创建基本的开版订单"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
plate_code='20251113-1',
|
||||
design_code='DES001',
|
||||
customer=self.customer,
|
||||
plate_type='首版',
|
||||
urgency_level='正常',
|
||||
style_name='测试款式',
|
||||
fabric='纯棉',
|
||||
width='150cm',
|
||||
)
|
||||
|
||||
self.assertIsNotNone(plate_order.id)
|
||||
self.assertEqual(plate_order.plate_code, '20251113-1')
|
||||
self.assertEqual(plate_order.customer, self.customer)
|
||||
self.assertEqual(plate_order.style_name, '测试款式')
|
||||
|
||||
def test_plate_order_with_employees(self):
|
||||
"""测试带销售员和跟单员的开版订单"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
plate_code='20251113-2',
|
||||
customer=self.customer,
|
||||
salesperson=self.salesperson,
|
||||
merchandiser=self.merchandiser,
|
||||
urgency_level='加急',
|
||||
)
|
||||
|
||||
self.assertEqual(plate_order.salesperson, self.salesperson)
|
||||
self.assertEqual(plate_order.merchandiser, self.merchandiser)
|
||||
self.assertEqual(plate_order.urgency_level, '加急')
|
||||
|
||||
def test_plate_order_with_ratings(self):
|
||||
"""测试带质量评级的开版订单"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
plate_code='20251113-3',
|
||||
customer=self.customer,
|
||||
drawing_rating='A',
|
||||
color_matching_rating='B',
|
||||
sample_rating='A',
|
||||
difficulty_rating='高',
|
||||
is_mark_frame=True,
|
||||
)
|
||||
|
||||
self.assertEqual(plate_order.drawing_rating, 'A')
|
||||
self.assertEqual(plate_order.color_matching_rating, 'B')
|
||||
self.assertEqual(plate_order.sample_rating, 'A')
|
||||
self.assertEqual(plate_order.difficulty_rating, '高')
|
||||
self.assertTrue(plate_order.is_mark_frame)
|
||||
|
||||
def test_plate_order_with_dates(self):
|
||||
"""测试带时间信息的开版订单"""
|
||||
now = timezone.now()
|
||||
completion_date = now + timezone.timedelta(days=7)
|
||||
required_date = (now + timezone.timedelta(days=10)).date()
|
||||
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
plate_code='20251113-4',
|
||||
customer=self.customer,
|
||||
plate_date=now,
|
||||
completion_date=completion_date,
|
||||
required_completion_date=required_date,
|
||||
)
|
||||
|
||||
self.assertIsNotNone(plate_order.plate_date)
|
||||
self.assertIsNotNone(plate_order.completion_date)
|
||||
self.assertIsNotNone(plate_order.required_completion_date)
|
||||
|
||||
def test_plate_order_with_sample_info(self):
|
||||
"""测试带样品信息的开版订单"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
plate_code='20251113-5',
|
||||
customer=self.customer,
|
||||
sample_meter='米样1',
|
||||
required_sample_meters=Decimal('100.50'),
|
||||
)
|
||||
|
||||
self.assertEqual(plate_order.sample_meter, '米样1')
|
||||
self.assertEqual(plate_order.required_sample_meters, Decimal('100.50'))
|
||||
|
||||
def test_plate_order_status_without_business_object(self):
|
||||
"""测试没有 business_object 时的状态"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
plate_code='20251113-6',
|
||||
customer=self.customer,
|
||||
)
|
||||
|
||||
self.assertEqual(plate_order.status, '未开始')
|
||||
self.assertFalse(plate_order.is_completed)
|
||||
self.assertFalse(plate_order.has_started) # 因为没有 business_object
|
||||
self.assertEqual(plate_order.progress_percentage, 0.0)
|
||||
|
||||
def test_plate_order_with_business_object(self):
|
||||
"""测试带 business_object 的开版订单"""
|
||||
# 创建流程
|
||||
state1 = stateflow_models.State.objects.create(name='画图')
|
||||
state2 = stateflow_models.State.objects.create(name='调色')
|
||||
state3 = stateflow_models.State.objects.create(name='套样')
|
||||
|
||||
process = stateflow_models.Process.objects.create(name='开版流程')
|
||||
process.replace_nodes([state1, state2, state3])
|
||||
|
||||
# 创建 BusinessObject
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name='PlateOrder-Test',
|
||||
process=process,
|
||||
)
|
||||
|
||||
# 创建开版订单
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
plate_code='20251113-7',
|
||||
customer=self.customer,
|
||||
business_object=business_object,
|
||||
)
|
||||
|
||||
self.assertIsNotNone(plate_order.business_object)
|
||||
self.assertEqual(plate_order.progress_percentage, 0.0)
|
||||
|
||||
# 推进一个状态
|
||||
from stateflow.services import advance_to_next_state
|
||||
advance_to_next_state(business_object, self.user)
|
||||
|
||||
# 刷新并检查状态
|
||||
plate_order.refresh_from_db()
|
||||
self.assertEqual(plate_order.status, state2.name) # 应该在第二个状态
|
||||
self.assertFalse(plate_order.is_completed)
|
||||
self.assertTrue(plate_order.has_started)
|
||||
self.assertGreater(plate_order.progress_percentage, 0)
|
||||
|
||||
def test_plate_order_str(self):
|
||||
"""测试字符串表示"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
plate_code='20251113-8',
|
||||
customer=self.customer,
|
||||
)
|
||||
|
||||
self.assertEqual(str(plate_order), '20251113-8')
|
||||
|
||||
def test_plate_order_with_approval_and_order_status(self):
|
||||
"""测试带审批和订单状态的开版订单"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
plate_code='20251113-9',
|
||||
customer=self.customer,
|
||||
approval_result='通过',
|
||||
is_ordered=True,
|
||||
customer_feedback='颜色需要调整',
|
||||
)
|
||||
|
||||
self.assertEqual(plate_order.approval_result, '通过')
|
||||
self.assertTrue(plate_order.is_ordered)
|
||||
self.assertEqual(plate_order.customer_feedback, '颜色需要调整')
|
||||
|
||||
def test_plate_order_unique_plate_code(self):
|
||||
"""测试版单编号的唯一性"""
|
||||
printing_models.PlateOrder.objects.create(
|
||||
plate_code='20251113-10',
|
||||
customer=self.customer,
|
||||
)
|
||||
|
||||
# 尝试创建相同编号的订单应该失败
|
||||
with self.assertRaises(Exception):
|
||||
printing_models.PlateOrder.objects.create(
|
||||
plate_code='20251113-10',
|
||||
customer=self.customer,
|
||||
)
|
||||
|
||||
def test_plate_order_nullable_fields(self):
|
||||
"""测试可空字段"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
plate_code='20251113-11',
|
||||
customer=self.customer,
|
||||
)
|
||||
|
||||
# 验证所有可空字段都可以为空
|
||||
self.assertIsNone(plate_order.design_code)
|
||||
self.assertIsNone(plate_order.plate_type)
|
||||
self.assertIsNone(plate_order.plate_date)
|
||||
self.assertIsNone(plate_order.salesperson)
|
||||
self.assertIsNone(plate_order.merchandiser)
|
||||
self.assertIsNone(plate_order.business_object)
|
||||
@@ -1,303 +0,0 @@
|
||||
"""
|
||||
Printing API ViewSet
|
||||
"""
|
||||
from rest_framework import viewsets, filters, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import BasePermission
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
from rest_framework.permissions import DjangoModelPermissions
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django_filters import rest_framework as django_filters
|
||||
from printing import models
|
||||
from basic_info.models import MerchantTypeEnum
|
||||
|
||||
from printing.serializers import (
|
||||
PrintingOrderListSerializer,
|
||||
PrintingOrderDetailSerializer,
|
||||
PrintingOrderCreateUpdateSerializer,
|
||||
PrintingJobListSerializer,
|
||||
PrintingJobDetailSerializer,
|
||||
PrintingJobCreateUpdateSerializer,
|
||||
)
|
||||
|
||||
|
||||
class IsPrintingFactory(BasePermission):
|
||||
"""自定义权限类,允许印染工厂用户访问"""
|
||||
|
||||
message = '您没有访问印染订单的权限'
|
||||
|
||||
def has_permission(self, request, view):
|
||||
if not request.user.is_authenticated:
|
||||
return False
|
||||
if hasattr(request.user, 'employee'):
|
||||
return request.user.employee.merchant.type == MerchantTypeEnum.FACTORY
|
||||
return False
|
||||
|
||||
|
||||
class HasInvalidatePrintingOrderPermission(BasePermission):
|
||||
"""自定义权限类,检查用户是否有作废印染订单的权限"""
|
||||
|
||||
message = '您没有权限作废订单'
|
||||
|
||||
def has_permission(self, request, view):
|
||||
if not request.user.is_authenticated:
|
||||
return False
|
||||
return request.user.has_perm('printing.can_invalidate_printingorder')
|
||||
|
||||
|
||||
class HasActivatePrintingOrderPermission(BasePermission):
|
||||
"""自定义权限类,检查用户是否有恢复印染订单的权限"""
|
||||
|
||||
message = '您没有权限恢复订单'
|
||||
|
||||
def has_permission(self, request, view):
|
||||
if not request.user.is_authenticated:
|
||||
return False
|
||||
return request.user.has_perm('printing.can_activate_printingorder')
|
||||
|
||||
|
||||
class PrintingOrderFilterSet(django_filters.FilterSet):
|
||||
"""印染订单过滤器"""
|
||||
customer_name = django_filters.CharFilter(field_name='customer__name', lookup_expr='icontains')
|
||||
customer_phone = django_filters.CharFilter(field_name='customer__phone', lookup_expr='icontains')
|
||||
fabric = django_filters.CharFilter(lookup_expr='icontains')
|
||||
is_urgent = django_filters.BooleanFilter()
|
||||
is_fabric_received = django_filters.BooleanFilter()
|
||||
is_invalid = django_filters.BooleanFilter()
|
||||
area = django_filters.CharFilter(lookup_expr='icontains')
|
||||
outgoing_date_from = django_filters.DateFilter(field_name='outgoing_date', lookup_expr='gte')
|
||||
outgoing_date_to = django_filters.DateFilter(field_name='outgoing_date', lookup_expr='lte')
|
||||
created_date_from = django_filters.DateFilter(field_name='created_at', lookup_expr='gte')
|
||||
created_date_to = django_filters.DateFilter(field_name='created_at', lookup_expr='lte')
|
||||
|
||||
class Meta:
|
||||
model = models.PrintingOrder
|
||||
fields = ['customer', 'is_urgent', 'is_fabric_received', 'is_invalid']
|
||||
|
||||
|
||||
class PrintingOrderViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
印染订单 ViewSet
|
||||
|
||||
提供印染订单的增改查功能(不支持删除)
|
||||
|
||||
list: 获取印染订单列表
|
||||
retrieve: 获取印染订单详情
|
||||
create: 创建印染订单
|
||||
update: 更新印染订单
|
||||
partial_update: 部分更新印染订单
|
||||
|
||||
查询参数:
|
||||
- customer: 客户ID
|
||||
- customer_name: 客户名称(模糊查询)
|
||||
- customer_phone: 客户电话(模糊查询)
|
||||
- fabric: 面料(模糊查询)
|
||||
- is_urgent: 是否紧急(true/false)
|
||||
- is_fabric_received: 布料是否已收(true/false)
|
||||
- is_invalid: 是否作废(true/false)
|
||||
- area: 地区(模糊查询)
|
||||
- outgoing_date_from: 出货日期起始
|
||||
- outgoing_date_to: 出货日期结束
|
||||
- created_date_from: 创建日期起始
|
||||
- created_date_to: 创建日期结束
|
||||
- search: 全文搜索(客户名称、面料、地区、工艺)
|
||||
- ordering: 排序字段
|
||||
"""
|
||||
queryset = models.PrintingOrder.objects.all()
|
||||
permission_classes = [DjangoModelPermissions]
|
||||
pagination_class = LimitOffsetPagination
|
||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
||||
filterset_class = PrintingOrderFilterSet
|
||||
search_fields = ['customer__name', 'fabric', 'area', 'craft', 'description']
|
||||
ordering_fields = [
|
||||
'id', 'created_at', 'updated_at', 'outgoing_date',
|
||||
'is_urgent', 'is_fabric_received', 'is_invalid'
|
||||
]
|
||||
ordering = ['-created_at']
|
||||
|
||||
def get_permissions(self):
|
||||
permissions = super().get_permissions() + [IsPrintingFactory()]
|
||||
if self.action == 'invalidate':
|
||||
permissions.append(HasInvalidatePrintingOrderPermission())
|
||||
elif self.action == 'activate':
|
||||
permissions.append(HasActivatePrintingOrderPermission())
|
||||
return permissions
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""根据动作选择序列化器"""
|
||||
if self.action == 'list':
|
||||
return PrintingOrderListSerializer
|
||||
elif self.action in ['create', 'update', 'partial_update']:
|
||||
return PrintingOrderCreateUpdateSerializer
|
||||
else: # retrieve
|
||||
return PrintingOrderDetailSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
"""优化查询"""
|
||||
queryset = super().get_queryset()
|
||||
if self.action in ['list', 'retrieve']:
|
||||
queryset = queryset.select_related('customer')
|
||||
return queryset
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
"""禁用删除操作"""
|
||||
return Response(
|
||||
{'detail': '印染订单不支持删除操作,请使用作废功能'},
|
||||
status=status.HTTP_405_METHOD_NOT_ALLOWED
|
||||
)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def invalidate(self, request, pk=None):
|
||||
"""
|
||||
作废订单
|
||||
|
||||
需要权限: printing.can_invalidate_printingorder
|
||||
"""
|
||||
# 检查权限
|
||||
if not request.user.has_perm('printing.can_invalidate_printingorder'):
|
||||
return Response(
|
||||
{'detail': '您没有权限作废订单'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
printing_order = self.get_object()
|
||||
|
||||
if printing_order.is_invalid:
|
||||
return Response(
|
||||
{'detail': '该订单已经作废'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
printing_order.is_invalid = True
|
||||
printing_order.save()
|
||||
|
||||
serializer = PrintingOrderDetailSerializer(printing_order)
|
||||
return Response({
|
||||
'detail': '订单已作废',
|
||||
'data': serializer.data
|
||||
})
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def activate(self, request, pk=None):
|
||||
"""
|
||||
恢复订单
|
||||
|
||||
需要权限: printing.can_activate_printingorder
|
||||
"""
|
||||
# 检查权限
|
||||
if not request.user.has_perm('printing.can_activate_printingorder'):
|
||||
return Response(
|
||||
{'detail': '您没有权限恢复订单'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
printing_order = self.get_object()
|
||||
|
||||
if not printing_order.is_invalid:
|
||||
return Response(
|
||||
{'detail': '该订单未作废,无需恢复'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
printing_order.is_invalid = False
|
||||
printing_order.save()
|
||||
|
||||
serializer = PrintingOrderDetailSerializer(printing_order)
|
||||
return Response({
|
||||
'detail': '订单已恢复',
|
||||
'data': serializer.data
|
||||
})
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def mark_fabric_received(self, request, pk=None):
|
||||
"""标记布料已收"""
|
||||
printing_order = self.get_object()
|
||||
|
||||
if printing_order.is_fabric_received:
|
||||
return Response(
|
||||
{'detail': '布料已经标记为已收'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
printing_order.is_fabric_received = True
|
||||
printing_order.save()
|
||||
|
||||
serializer = PrintingOrderDetailSerializer(printing_order)
|
||||
return Response({
|
||||
'detail': '已标记布料已收',
|
||||
'data': serializer.data
|
||||
})
|
||||
|
||||
|
||||
class PrintingJobFilterSet(django_filters.FilterSet):
|
||||
"""印染款式明细过滤器"""
|
||||
printing_order = django_filters.NumberFilter()
|
||||
product = django_filters.NumberFilter()
|
||||
product_name = django_filters.CharFilter(field_name='product__name', lookup_expr='icontains')
|
||||
unit = django_filters.CharFilter(lookup_expr='icontains')
|
||||
quantity_min = django_filters.NumberFilter(field_name='quantity', lookup_expr='gte')
|
||||
quantity_max = django_filters.NumberFilter(field_name='quantity', lookup_expr='lte')
|
||||
pieces_min = django_filters.NumberFilter(field_name='pieces', lookup_expr='gte')
|
||||
pieces_max = django_filters.NumberFilter(field_name='pieces', lookup_expr='lte')
|
||||
|
||||
class Meta:
|
||||
model = models.PrintingJob
|
||||
fields = ['printing_order', 'product']
|
||||
|
||||
|
||||
class PrintingJobViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
印染款式明细 ViewSet
|
||||
|
||||
提供印染款式明细的增改查功能(不支持删除)
|
||||
|
||||
list: 获取款式明细列表
|
||||
retrieve: 获取款式明细详情
|
||||
create: 创建款式明细
|
||||
update: 更新款式明细
|
||||
partial_update: 部分更新款式明细
|
||||
|
||||
查询参数:
|
||||
- printing_order: 印染订单ID
|
||||
- product: 产品ID
|
||||
- product_name: 产品名称(模糊查询)
|
||||
- unit: 单位(模糊查询)
|
||||
- quantity_min: 最小数量
|
||||
- quantity_max: 最大数量
|
||||
- pieces_min: 最小件数
|
||||
- pieces_max: 最大件数
|
||||
- search: 全文搜索(产品名称、单位、尺寸、备注)
|
||||
- ordering: 排序字段
|
||||
"""
|
||||
queryset = models.PrintingJob.objects.all()
|
||||
permission_classes = [DjangoModelPermissions, IsPrintingFactory]
|
||||
pagination_class = LimitOffsetPagination
|
||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
||||
filterset_class = PrintingJobFilterSet
|
||||
search_fields = ['product__name', 'unit', 'size', 'description']
|
||||
ordering_fields = ['id', 'created_at', 'updated_at', 'quantity', 'pieces']
|
||||
ordering = ['-created_at']
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""根据动作选择序列化器"""
|
||||
if self.action == 'list':
|
||||
return PrintingJobListSerializer
|
||||
elif self.action in ['create', 'update', 'partial_update']:
|
||||
return PrintingJobCreateUpdateSerializer
|
||||
else: # retrieve
|
||||
return PrintingJobDetailSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
"""优化查询"""
|
||||
queryset = super().get_queryset()
|
||||
if self.action in ['list', 'retrieve']:
|
||||
queryset = queryset.select_related('printing_order', 'product')
|
||||
return queryset
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
"""禁用删除操作"""
|
||||
return Response(
|
||||
{'detail': '印染款式明细不支持删除操作'},
|
||||
status=status.HTTP_405_METHOD_NOT_ALLOWED
|
||||
)
|
||||
Reference in New Issue
Block a user