forked from erp-dev/erp
feat: printing module
This commit is contained in:
@@ -2,17 +2,19 @@ from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import stock_change_views, user_info, inventory, product_image, stateflow
|
||||
from .views.stock_change_views.snapshot import StockSnapshotListView
|
||||
from printing.views import PrintingOrderViewSet, PrintingJobViewSet
|
||||
from .views.printing.views import PrintingOrderViewSet, PrintingJobViewSet, PlateOrderViewSet
|
||||
|
||||
# 创建 DRF Router for Stateflow
|
||||
stateflow_router = DefaultRouter()
|
||||
stateflow_router.register(r'states', stateflow.StateViewSet, basename='state')
|
||||
stateflow_router.register(r'processes', stateflow.ProcessViewSet, basename='process')
|
||||
stateflow_router.register(r'business-objects', stateflow.BusinessObjectViewSet, basename='business-object')
|
||||
|
||||
# 创建主 Router
|
||||
main_router = DefaultRouter()
|
||||
main_router.register(r'printing-orders', PrintingOrderViewSet, basename='printing-order')
|
||||
main_router.register(r'printing-jobs', PrintingJobViewSet, basename='printing-job')
|
||||
main_router.register(r'plate-orders', PlateOrderViewSet, basename='plate-order')
|
||||
|
||||
urlpatterns = [
|
||||
# 库存变动相关API
|
||||
|
||||
6
api_v1/views/printing/__init__.py
Normal file
6
api_v1/views/printing/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""
|
||||
Printing module API views
|
||||
"""
|
||||
from .views import PrintingOrderViewSet, PrintingJobViewSet
|
||||
|
||||
__all__ = ['PrintingOrderViewSet', 'PrintingJobViewSet']
|
||||
@@ -0,0 +1,349 @@
|
||||
"""
|
||||
Printing API 序列化器
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from printing import models
|
||||
from .services import PrintingOrderService, PrintingJobService
|
||||
from basic_info.models import Customer, Employee
|
||||
|
||||
|
||||
class PrintingOrderListSerializer(serializers.ModelSerializer):
|
||||
"""印染订单列表序列化器"""
|
||||
customer_name = serializers.CharField(source='customer.name', read_only=True)
|
||||
customer_phone = serializers.CharField(source='customer.mobile', read_only=True)
|
||||
process_name = serializers.CharField(source='process.name', read_only=True)
|
||||
progress = serializers.IntegerField(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',
|
||||
'process', 'process_name', 'progress',
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
read_only_fields = ['id', 'human_id', 'created_at', 'updated_at', 'progress']
|
||||
|
||||
|
||||
class PrintingOrderDetailSerializer(serializers.ModelSerializer):
|
||||
"""印染订单详情序列化器"""
|
||||
customer_name = serializers.CharField(source='customer.name', read_only=True)
|
||||
customer_phone = serializers.CharField(source='customer.mobile', read_only=True)
|
||||
customer_area = serializers.CharField(source='customer.area', read_only=True)
|
||||
process_name = serializers.CharField(source='process.name', read_only=True)
|
||||
progress = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.PrintingOrder
|
||||
fields = [
|
||||
'id', 'human_id', 'customer', 'customer_name', 'customer_phone', 'customer_area',
|
||||
'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', 'process', 'process_name', 'progress',
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
read_only_fields = ['id', 'human_id', 'created_at', 'updated_at', 'progress']
|
||||
|
||||
|
||||
class PrintingOrderCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
"""印染订单创建/更新序列化器"""
|
||||
|
||||
class Meta:
|
||||
model = models.PrintingOrder
|
||||
fields = [
|
||||
'id', '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',
|
||||
'process'
|
||||
]
|
||||
read_only_fields = ['id']
|
||||
|
||||
def validate_customer(self, value):
|
||||
"""验证客户是否存在"""
|
||||
if not value:
|
||||
raise serializers.ValidationError("客户不能为空")
|
||||
return value
|
||||
|
||||
def validate(self, attrs):
|
||||
"""验证流程修改权限"""
|
||||
# 更新时验证 process 修改权限
|
||||
if self.instance and 'process' in attrs:
|
||||
new_process = attrs['process']
|
||||
old_process = self.instance.process
|
||||
|
||||
if new_process != old_process:
|
||||
if not PrintingOrderService.can_change_process(self.instance):
|
||||
raise serializers.ValidationError({
|
||||
'process': '存在已开始的印染任务,无法修改流程'
|
||||
})
|
||||
|
||||
return attrs
|
||||
|
||||
def create(self, validated_data):
|
||||
"""创建订单,使用 service 层"""
|
||||
user = self.context['request'].user
|
||||
return PrintingOrderService.create_printing_order(validated_data, user)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""更新订单,使用 service 层"""
|
||||
user = self.context['request'].user
|
||||
success, message, updated_instance = PrintingOrderService.update_printing_order(
|
||||
instance, validated_data, user
|
||||
)
|
||||
if not success:
|
||||
raise serializers.ValidationError(message)
|
||||
return updated_instance
|
||||
|
||||
|
||||
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)
|
||||
status = serializers.CharField(read_only=True)
|
||||
is_completed = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.PrintingJob
|
||||
fields = [
|
||||
'id', 'printing_order', 'printing_order_id', 'product', 'product_name',
|
||||
'quantity', 'unit', 'size', 'pieces', 'description',
|
||||
'status', 'is_completed',
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'status', 'is_completed']
|
||||
|
||||
|
||||
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)
|
||||
status = serializers.CharField(read_only=True)
|
||||
status_id = serializers.IntegerField(read_only=True)
|
||||
is_completed = serializers.BooleanField(read_only=True)
|
||||
has_started = serializers.BooleanField(read_only=True)
|
||||
business_object_id = serializers.IntegerField(source='business_object.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',
|
||||
'status', 'status_id', 'is_completed', 'has_started', 'business_object_id',
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
read_only_fields = [
|
||||
'id', 'created_at', 'updated_at',
|
||||
'status', 'status_id', 'is_completed', 'has_started', 'business_object_id'
|
||||
]
|
||||
|
||||
|
||||
class PrintingJobCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
"""印染款式明细创建/更新序列化器"""
|
||||
|
||||
class Meta:
|
||||
model = models.PrintingJob
|
||||
fields = [
|
||||
'id', 'printing_order', 'product', 'quantity', 'unit', 'size', 'pieces', 'description'
|
||||
]
|
||||
read_only_fields = ['id']
|
||||
extra_kwargs = {
|
||||
'size': {'required': False, 'allow_null': True, 'allow_blank': True},
|
||||
'pieces': {'required': False, 'allow_null': True},
|
||||
'description': {'required': False, 'allow_null': True, 'allow_blank': True},
|
||||
}
|
||||
|
||||
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 is not None and value <= 0:
|
||||
raise serializers.ValidationError("件数必须大于0")
|
||||
return value
|
||||
|
||||
def create(self, validated_data):
|
||||
"""创建任务,使用 service 层"""
|
||||
user = self.context['request'].user
|
||||
return PrintingJobService.create_printing_job(validated_data, user)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""更新任务,使用 service 层"""
|
||||
user = self.context['request'].user
|
||||
success, message, updated_instance = PrintingJobService.update_printing_job(
|
||||
instance, validated_data, user
|
||||
)
|
||||
if not success:
|
||||
raise serializers.ValidationError(message)
|
||||
return updated_instance
|
||||
|
||||
|
||||
|
||||
class PlateOrderListSerializer(serializers.ModelSerializer):
|
||||
"""开版订单列表序列化器"""
|
||||
customer_name = serializers.CharField(source="customer.name", read_only=True)
|
||||
salesperson_name = serializers.CharField(source="salesperson.name", read_only=True)
|
||||
merchandiser_name = serializers.CharField(source="merchandiser.name", read_only=True)
|
||||
status = serializers.CharField(read_only=True)
|
||||
progress_percentage = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.PlateOrder
|
||||
fields = [
|
||||
'id', 'design_code', 'plate_type', 'plate_date',
|
||||
'urgency_level', 'is_invalid',
|
||||
'customer', 'customer_name', 'area',
|
||||
'salesperson', 'salesperson_name',
|
||||
'merchandiser', 'merchandiser_name',
|
||||
'style_name', 'fabric', 'width',
|
||||
'required_completion_date', 'completion_date',
|
||||
'is_ordered', 'status', 'progress_percentage',
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
read_only_fields = [
|
||||
'id', 'status', 'progress_percentage',
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
|
||||
|
||||
class PlateOrderDetailSerializer(serializers.ModelSerializer):
|
||||
"""开版订单详情序列化器"""
|
||||
customer_name = serializers.CharField(source="customer.name", read_only=True)
|
||||
customer_phone = serializers.CharField(source="customer.mobile", read_only=True)
|
||||
salesperson_name = serializers.CharField(source="salesperson.name", read_only=True)
|
||||
merchandiser_name = serializers.CharField(source="merchandiser.name", read_only=True)
|
||||
status = serializers.CharField(read_only=True)
|
||||
status_id = serializers.IntegerField(read_only=True)
|
||||
is_completed = serializers.BooleanField(read_only=True)
|
||||
has_started = serializers.BooleanField(read_only=True)
|
||||
progress_percentage = serializers.IntegerField(read_only=True)
|
||||
business_object_id = serializers.IntegerField(source="business_object.id", read_only=True)
|
||||
plate_image_url = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = models.PlateOrder
|
||||
fields = [
|
||||
'id', 'design_code', 'plate_type', 'plate_date', 'plate_method',
|
||||
'plate_image', 'plate_image_url', 'plate_notes', 'reprint_reason',
|
||||
'urgency_level', 'is_invalid',
|
||||
'customer', 'customer_name', 'customer_phone', 'area', 'default_address',
|
||||
'salesperson', 'salesperson_name',
|
||||
'merchandiser', 'merchandiser_name',
|
||||
'style_name', 'fabric', 'width', 'production_method',
|
||||
'is_mark_frame', 'drawing_rating', 'color_matching_rating',
|
||||
'sample_rating', 'difficulty_rating',
|
||||
'sample_meter', 'required_sample_meters',
|
||||
'required_completion_date', 'completion_date',
|
||||
'approval_result', 'is_ordered', 'customer_feedback',
|
||||
'status', 'status_id', 'is_completed', 'has_started',
|
||||
'progress_percentage', 'business_object_id',
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
read_only_fields = [
|
||||
'id', 'status', 'status_id', 'is_completed', 'has_started',
|
||||
'progress_percentage', 'business_object_id',
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
|
||||
def get_plate_image_url(self, obj):
|
||||
"""获取图片完整URL"""
|
||||
if obj.plate_image:
|
||||
request = self.context.get("request")
|
||||
if request:
|
||||
return request.build_absolute_uri(obj.plate_image.url)
|
||||
return obj.plate_image.url
|
||||
return None
|
||||
|
||||
|
||||
class PlateOrderCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
"""开版订单创建/更新序列化器"""
|
||||
|
||||
class Meta:
|
||||
model = models.PlateOrder
|
||||
fields = [
|
||||
"id", "design_code", "plate_type", "plate_date", "plate_method",
|
||||
"plate_image", "plate_notes", "reprint_reason",
|
||||
"urgency_level", "is_invalid",
|
||||
"customer", "area", "default_address",
|
||||
"salesperson", "merchandiser",
|
||||
"style_name", "fabric", "width", "production_method",
|
||||
"is_mark_frame", "drawing_rating", "color_matching_rating",
|
||||
"sample_rating", "difficulty_rating",
|
||||
"sample_meter", "required_sample_meters",
|
||||
"required_completion_date", "completion_date",
|
||||
"approval_result", "is_ordered", "customer_feedback"
|
||||
]
|
||||
read_only_fields = ["id"]
|
||||
|
||||
def validate_customer(self, value):
|
||||
"""验证客户是否存在"""
|
||||
if not value:
|
||||
raise serializers.ValidationError("客户不能为空")
|
||||
return value
|
||||
|
||||
def validate_salesperson(self, value):
|
||||
"""验证业务员是否存在"""
|
||||
if value and not Employee.objects.filter(id=value.id).exists():
|
||||
raise serializers.ValidationError("业务员不存在")
|
||||
return value
|
||||
|
||||
def validate_merchandiser(self, value):
|
||||
"""验证跟单员是否存在"""
|
||||
if value and not Employee.objects.filter(id=value.id).exists():
|
||||
raise serializers.ValidationError("跟单员不存在")
|
||||
return value
|
||||
|
||||
def validate_sample_meter(self, value):
|
||||
"""验证样品米数"""
|
||||
if value is not None and value < 0:
|
||||
raise serializers.ValidationError("样品米数不能为负数")
|
||||
return value
|
||||
|
||||
def validate_required_sample_meters(self, value):
|
||||
"""验证所需样品米数"""
|
||||
if value is not None and value < 0:
|
||||
raise serializers.ValidationError("所需样品米数不能为负数")
|
||||
return value
|
||||
|
||||
def validate(self, attrs):
|
||||
"""交叉验证"""
|
||||
return attrs
|
||||
|
||||
def create(self, validated_data):
|
||||
"""创建开版订单"""
|
||||
user = self.context["request"].user
|
||||
|
||||
# 创建 PlateOrder
|
||||
plate_order = models.PlateOrder.objects.create(**validated_data)
|
||||
|
||||
# 暂时不自动创建 BusinessObject,由 admin 或其他地方创建
|
||||
|
||||
return plate_order
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""更新开版订单"""
|
||||
# 直接更新字段
|
||||
for attr, value in validated_data.items():
|
||||
setattr(instance, attr, value)
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
|
||||
182
api_v1/views/printing/services.py
Normal file
182
api_v1/views/printing/services.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
Printing module business logic services
|
||||
"""
|
||||
from typing import Dict, Any, Tuple
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from printing import models as printing_models
|
||||
from stateflow import models as stateflow_models
|
||||
from stateflow import services as stateflow_services
|
||||
|
||||
|
||||
class PrintingOrderService:
|
||||
"""印染订单业务逻辑服务"""
|
||||
|
||||
@staticmethod
|
||||
def get_default_process():
|
||||
"""获取默认流程"""
|
||||
process_id = getattr(settings, 'PRINTING_DEFAULT_PROCESS_ID', None)
|
||||
if not process_id:
|
||||
return None
|
||||
|
||||
try:
|
||||
return stateflow_models.Process.objects.get(id=process_id)
|
||||
except stateflow_models.Process.DoesNotExist:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def create_printing_order(data: Dict[str, Any], user) -> printing_models.PrintingOrder:
|
||||
"""
|
||||
创建印染订单
|
||||
|
||||
Args:
|
||||
data: 订单数据
|
||||
user: 当前用户
|
||||
|
||||
Returns:
|
||||
创建的订单实例
|
||||
"""
|
||||
# 如果未指定 process,使用默认值
|
||||
if 'process' not in data or data['process'] is None:
|
||||
default_process = PrintingOrderService.get_default_process()
|
||||
if default_process:
|
||||
data['process'] = default_process
|
||||
|
||||
order = printing_models.PrintingOrder.objects.create(**data)
|
||||
return order
|
||||
|
||||
@staticmethod
|
||||
def update_printing_order(
|
||||
printing_order: printing_models.PrintingOrder,
|
||||
data: Dict[str, Any],
|
||||
user
|
||||
) -> Tuple[bool, str, printing_models.PrintingOrder]:
|
||||
"""
|
||||
更新印染订单
|
||||
|
||||
Args:
|
||||
printing_order: 订单实例
|
||||
data: 更新数据
|
||||
user: 当前用户
|
||||
|
||||
Returns:
|
||||
(success, message, updated_order)
|
||||
"""
|
||||
# 如果要修改 process,需要验证
|
||||
if 'process' in data and data['process'] != printing_order.process:
|
||||
if not printing_order.can_change_process():
|
||||
return False, '存在已开始的印染任务,无法修改流程', printing_order
|
||||
|
||||
# 更新字段
|
||||
for field, value in data.items():
|
||||
setattr(printing_order, field, value)
|
||||
|
||||
printing_order.save()
|
||||
return True, '更新成功', printing_order
|
||||
|
||||
@staticmethod
|
||||
def can_change_process(printing_order: printing_models.PrintingOrder) -> bool:
|
||||
"""
|
||||
检查订单是否可以修改流程
|
||||
|
||||
Args:
|
||||
printing_order: 订单实例
|
||||
|
||||
Returns:
|
||||
是否可以修改
|
||||
"""
|
||||
return printing_order.can_change_process()
|
||||
|
||||
@staticmethod
|
||||
def get_order_progress(printing_order: printing_models.PrintingOrder) -> int:
|
||||
"""
|
||||
计算订单完成进度
|
||||
|
||||
Args:
|
||||
printing_order: 订单实例
|
||||
|
||||
Returns:
|
||||
完成百分比 (0-100)
|
||||
"""
|
||||
return printing_order.progress
|
||||
|
||||
|
||||
class PrintingJobService:
|
||||
"""印染任务业务逻辑服务"""
|
||||
|
||||
@staticmethod
|
||||
@transaction.atomic
|
||||
def create_printing_job(data: Dict[str, Any], user) -> printing_models.PrintingJob:
|
||||
"""
|
||||
创建印染任务
|
||||
|
||||
自动创建关联的 BusinessObject 实例
|
||||
|
||||
Args:
|
||||
data: 任务数据
|
||||
user: 当前用户
|
||||
|
||||
Returns:
|
||||
创建的任务实例
|
||||
"""
|
||||
printing_order = data.get('printing_order')
|
||||
|
||||
# 创建 PrintingJob
|
||||
job = printing_models.PrintingJob.objects.create(**data)
|
||||
|
||||
# 如果 PrintingOrder 有关联的流程,创建 BusinessObject
|
||||
if printing_order and printing_order.process:
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f"PrintingJob-{job.id}",
|
||||
process=printing_order.process,
|
||||
description=f"印染任务 {job.id} 的流程实例",
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
return job
|
||||
|
||||
@staticmethod
|
||||
def update_printing_job(
|
||||
printing_job: printing_models.PrintingJob,
|
||||
data: Dict[str, Any],
|
||||
user
|
||||
) -> Tuple[bool, str, printing_models.PrintingJob]:
|
||||
"""
|
||||
更新印染任务
|
||||
|
||||
Args:
|
||||
printing_job: 任务实例
|
||||
data: 更新数据
|
||||
user: 当前用户
|
||||
|
||||
Returns:
|
||||
(success, message, updated_job)
|
||||
"""
|
||||
# 更新字段
|
||||
for field, value in data.items():
|
||||
# 不允许直接修改 business_object
|
||||
if field == 'business_object':
|
||||
continue
|
||||
setattr(printing_job, field, value)
|
||||
|
||||
printing_job.save()
|
||||
return True, '更新成功', printing_job
|
||||
|
||||
@staticmethod
|
||||
def get_job_status(printing_job: printing_models.PrintingJob) -> Dict[str, Any]:
|
||||
"""
|
||||
获取任务状态信息
|
||||
|
||||
Args:
|
||||
printing_job: 任务实例
|
||||
|
||||
Returns:
|
||||
状态信息字典
|
||||
"""
|
||||
return {
|
||||
'status': printing_job.status,
|
||||
'status_id': printing_job.status_id,
|
||||
'is_completed': printing_job.is_completed,
|
||||
'has_started': printing_job.has_started,
|
||||
}
|
||||
@@ -2,12 +2,14 @@
|
||||
PrintingOrder API 测试
|
||||
"""
|
||||
from django.test import TestCase
|
||||
from django.conf import settings
|
||||
from rest_framework.test import APIClient
|
||||
from rest_framework import status
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Permission
|
||||
from basic_info import models as basic_models
|
||||
from printing import models as printing_models
|
||||
from stateflow import models as stateflow_models
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@@ -49,6 +51,17 @@ class PrintingOrderAPITestCase(TestCase):
|
||||
area='测试地区'
|
||||
)
|
||||
|
||||
# 创建流程
|
||||
self.state1 = stateflow_models.State.objects.create(name='待印染')
|
||||
self.state2 = stateflow_models.State.objects.create(name='印染中')
|
||||
self.state3 = stateflow_models.State.objects.create(name='已完成')
|
||||
|
||||
self.process = stateflow_models.Process.objects.create(name='印染流程')
|
||||
self.process.replace_nodes([self.state1, self.state2, self.state3])
|
||||
|
||||
# 设置默认流程
|
||||
settings.PRINTING_DEFAULT_PROCESS_ID = self.process.id
|
||||
|
||||
# 认证用户
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
@@ -398,3 +411,132 @@ class PrintingOrderAPITestCase(TestCase):
|
||||
self.assertTrue(len(order.human_id) >= 14)
|
||||
today = date.today().strftime('%Y%m%d')
|
||||
self.assertTrue(order.human_id.startswith(today))
|
||||
|
||||
def test_create_order_with_default_process(self):
|
||||
"""测试创建订单使用默认流程"""
|
||||
data = {
|
||||
'customer': self.customer.id,
|
||||
'fabric': '测试布料',
|
||||
'width': '150cm',
|
||||
}
|
||||
|
||||
response = self.client.post('/api/v1/printing-orders/', data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
# 验证使用了默认流程
|
||||
order = printing_models.PrintingOrder.objects.get(id=response.data['id'])
|
||||
self.assertEqual(order.process.id, self.process.id)
|
||||
|
||||
def test_create_order_with_custom_process(self):
|
||||
"""测试创建订单指定自定义流程"""
|
||||
custom_process = stateflow_models.Process.objects.create(name='自定义流程')
|
||||
|
||||
data = {
|
||||
'customer': self.customer.id,
|
||||
'fabric': '测试布料',
|
||||
'width': '150cm',
|
||||
'process': custom_process.id,
|
||||
}
|
||||
|
||||
response = self.client.post('/api/v1/printing-orders/', data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
order = printing_models.PrintingOrder.objects.get(id=response.data['id'])
|
||||
self.assertEqual(order.process.id, custom_process.id)
|
||||
|
||||
def test_order_progress_in_list(self):
|
||||
"""测试订单列表包含进度字段"""
|
||||
order = printing_models.PrintingOrder.objects.create(
|
||||
customer=self.customer,
|
||||
fabric='测试布料',
|
||||
width='150cm',
|
||||
process=self.process,
|
||||
)
|
||||
|
||||
response = self.client.get('/api/v1/printing-orders/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertIn('progress', response.data[0])
|
||||
self.assertEqual(response.data[0]['progress'], 0) # 没有任务时为0
|
||||
|
||||
def test_update_process_when_no_jobs(self):
|
||||
"""测试没有任务时可以修改流程"""
|
||||
order = printing_models.PrintingOrder.objects.create(
|
||||
customer=self.customer,
|
||||
fabric='测试布料',
|
||||
width='150cm',
|
||||
process=self.process,
|
||||
)
|
||||
|
||||
new_process = stateflow_models.Process.objects.create(name='新流程')
|
||||
|
||||
data = {
|
||||
'process': new_process.id,
|
||||
}
|
||||
|
||||
response = self.client.patch(
|
||||
f'/api/v1/printing-orders/{order.id}/',
|
||||
data,
|
||||
format='json'
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
order.refresh_from_db()
|
||||
self.assertEqual(order.process.id, new_process.id)
|
||||
|
||||
def test_cannot_update_process_when_job_started(self):
|
||||
"""测试有已开始的任务时不能修改流程"""
|
||||
order = printing_models.PrintingOrder.objects.create(
|
||||
customer=self.customer,
|
||||
fabric='测试布料',
|
||||
width='150cm',
|
||||
process=self.process,
|
||||
)
|
||||
|
||||
# 创建产品类别和产品
|
||||
category = basic_models.ProductCategory.objects.create(
|
||||
name='测试类别',
|
||||
merchant=self.merchant,
|
||||
)
|
||||
product = basic_models.Product.objects.create(
|
||||
name='测试产品',
|
||||
category=category,
|
||||
merchant=self.merchant,
|
||||
)
|
||||
|
||||
# 创建任务
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=order,
|
||||
product=product,
|
||||
quantity=10,
|
||||
unit='件',
|
||||
size='100x200',
|
||||
pieces=5,
|
||||
)
|
||||
|
||||
# 创建 BusinessObject 并推进状态
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
from stateflow.services import advance_to_next_state
|
||||
advance_to_next_state(business_object, self.user)
|
||||
|
||||
# 尝试修改流程
|
||||
new_process = stateflow_models.Process.objects.create(name='新流程')
|
||||
|
||||
data = {
|
||||
'process': new_process.id,
|
||||
}
|
||||
|
||||
response = self.client.patch(
|
||||
f'/api/v1/printing-orders/{order.id}/',
|
||||
data,
|
||||
format='json'
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('已开始', str(response.data))
|
||||
|
||||
|
||||
562
api_v1/views/printing/test_plate_order_api.py
Normal file
562
api_v1/views/printing/test_plate_order_api.py
Normal file
@@ -0,0 +1,562 @@
|
||||
"""
|
||||
PlateOrder API 测试
|
||||
"""
|
||||
from django.test import TestCase
|
||||
from rest_framework.test import APIClient
|
||||
from rest_framework import status
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Permission
|
||||
from basic_info import models as basic_models
|
||||
from printing import models as printing_models
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class PlateOrderAPITestCase(TestCase):
|
||||
"""测试 PlateOrder API"""
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
|
||||
# 创建商户
|
||||
self.merchant = basic_models.Merchant.objects.create(
|
||||
name='测试商户',
|
||||
type=basic_models.MerchantTypeEnum.FACTORY
|
||||
)
|
||||
|
||||
# 创建用户
|
||||
self.user = User.objects.create_user(
|
||||
username='testuser',
|
||||
password='testpass123',
|
||||
email='test@example.com'
|
||||
)
|
||||
|
||||
# 创建员工并关联商户
|
||||
self.employee = 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.salesperson = basic_models.Employee.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='测试业务员',
|
||||
mobile='13800138001',
|
||||
job_type=basic_models.EmployeeTypeEnum.PRINTER,
|
||||
status=basic_models.EmployeeStatusEnum.ACTIVE
|
||||
)
|
||||
|
||||
# 创建跟单员
|
||||
self.merchandiser = basic_models.Employee.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='测试跟单员',
|
||||
mobile='13800138002',
|
||||
job_type=basic_models.EmployeeTypeEnum.WARE,
|
||||
status=basic_models.EmployeeStatusEnum.ACTIVE
|
||||
)
|
||||
|
||||
# 创建客户
|
||||
self.customer = basic_models.Customer.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='测试客户',
|
||||
mobile='13900139000',
|
||||
area='测试地区'
|
||||
)
|
||||
|
||||
# 认证用户
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
# 给用户添加基础权限
|
||||
view_perm = Permission.objects.get(codename='view_plateorder')
|
||||
add_perm = Permission.objects.get(codename='add_plateorder')
|
||||
change_perm = Permission.objects.get(codename='change_plateorder')
|
||||
delete_perm = Permission.objects.get(codename='delete_plateorder')
|
||||
self.user.user_permissions.add(view_perm, add_perm, change_perm, delete_perm)
|
||||
|
||||
def test_create_plate_order(self):
|
||||
"""测试创建开版订单"""
|
||||
data = {
|
||||
'customer': self.customer.id,
|
||||
'design_code': 'DESIGN001',
|
||||
'plate_type': '圆网',
|
||||
'style_name': '测试款式',
|
||||
'fabric': '棉布',
|
||||
'width': '150cm',
|
||||
'urgency_level': '加急',
|
||||
'salesperson': self.salesperson.id,
|
||||
'merchandiser': self.merchandiser.id,
|
||||
}
|
||||
|
||||
response = self.client.post('/api/v1/plate-orders/', data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(response.data['design_code'], 'DESIGN001')
|
||||
self.assertEqual(response.data['style_name'], '测试款式')
|
||||
|
||||
# 验证数据库中创建了记录
|
||||
self.assertTrue(printing_models.PlateOrder.objects.filter(design_code='DESIGN001').exists())
|
||||
|
||||
def test_create_plate_order_without_customer(self):
|
||||
"""测试创建开版订单时未提供客户"""
|
||||
data = {
|
||||
'design_code': 'DESIGN002',
|
||||
'plate_type': '圆网',
|
||||
'style_name': '测试款式2',
|
||||
}
|
||||
|
||||
response = self.client.post('/api/v1/plate-orders/', data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('customer', response.data)
|
||||
|
||||
def test_create_plate_order_with_invalid_rating(self):
|
||||
"""测试创建开版订单时提供无效评级"""
|
||||
data = {
|
||||
'customer': self.customer.id,
|
||||
'design_code': 'DESIGN003',
|
||||
'plate_type': '圆网',
|
||||
'style_name': '测试款式3',
|
||||
'drawing_rating': '不存在的评级', # 任意字符串都可以,因为是 CharField
|
||||
}
|
||||
|
||||
response = self.client.post('/api/v1/plate-orders/', data, format='json')
|
||||
# CharField 不会验证内容,所以应该成功
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
def test_list_plate_orders(self):
|
||||
"""测试获取开版订单列表"""
|
||||
# 创建测试数据
|
||||
printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='款式1',
|
||||
fabric='棉布',
|
||||
salesperson=self.salesperson,
|
||||
)
|
||||
printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN002',
|
||||
plate_type='平网',
|
||||
style_name='款式2',
|
||||
fabric='涤纶',
|
||||
merchandiser=self.merchandiser,
|
||||
)
|
||||
|
||||
response = self.client.get('/api/v1/plate-orders/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
# 根据实际返回的数据结构调整
|
||||
if isinstance(response.data, dict):
|
||||
self.assertEqual(response.data['count'], 2)
|
||||
else:
|
||||
self.assertEqual(len(response.data), 2)
|
||||
|
||||
def test_retrieve_plate_order(self):
|
||||
"""测试获取单个开版订单详情"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='测试款式',
|
||||
fabric='棉布',
|
||||
urgency_level='紧急',
|
||||
salesperson=self.salesperson,
|
||||
merchandiser=self.merchandiser,
|
||||
)
|
||||
|
||||
response = self.client.get(f'/api/v1/plate-orders/{plate_order.id}/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['design_code'], 'DESIGN001')
|
||||
self.assertEqual(response.data['urgency_level'], '紧急')
|
||||
self.assertEqual(response.data['customer'], self.customer.id)
|
||||
self.assertIn('customer_name', response.data)
|
||||
self.assertIn('salesperson_name', response.data)
|
||||
|
||||
def test_update_plate_order(self):
|
||||
"""测试更新开版订单"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='测试款式',
|
||||
fabric='棉布',
|
||||
)
|
||||
|
||||
data = {
|
||||
'customer': self.customer.id,
|
||||
'design_code': 'DESIGN001',
|
||||
'plate_type': '平网', # 修改类型
|
||||
'style_name': '更新后的款式', # 修改名称
|
||||
'fabric': '棉布',
|
||||
'urgency_level': '特急', # 添加紧急程度
|
||||
}
|
||||
|
||||
response = self.client.put(f'/api/v1/plate-orders/{plate_order.id}/', data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['plate_type'], '平网')
|
||||
self.assertEqual(response.data['style_name'], '更新后的款式')
|
||||
self.assertEqual(response.data['urgency_level'], '特急')
|
||||
|
||||
def test_partial_update_plate_order(self):
|
||||
"""测试部分更新开版订单"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='测试款式',
|
||||
fabric='棉布',
|
||||
)
|
||||
|
||||
data = {
|
||||
'urgency_level': '加急',
|
||||
'is_mark_frame': True,
|
||||
}
|
||||
|
||||
response = self.client.patch(f'/api/v1/plate-orders/{plate_order.id}/', data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['urgency_level'], '加急')
|
||||
self.assertEqual(response.data['is_mark_frame'], True)
|
||||
# 其他字段保持不变
|
||||
self.assertEqual(response.data['design_code'], 'DESIGN001')
|
||||
|
||||
def test_delete_plate_order_not_allowed(self):
|
||||
"""测试删除开版订单(应该被禁止)"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='测试款式',
|
||||
fabric='棉布',
|
||||
)
|
||||
|
||||
response = self.client.delete(f'/api/v1/plate-orders/{plate_order.id}/')
|
||||
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||
|
||||
# 验证订单仍然存在
|
||||
self.assertTrue(printing_models.PlateOrder.objects.filter(id=plate_order.id).exists())
|
||||
|
||||
def test_filter_by_customer(self):
|
||||
"""测试按客户筛选"""
|
||||
customer2 = basic_models.Customer.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='客户2',
|
||||
mobile='13900139001',
|
||||
)
|
||||
|
||||
printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='款式1',
|
||||
)
|
||||
printing_models.PlateOrder.objects.create(
|
||||
customer=customer2,
|
||||
design_code='DESIGN002',
|
||||
plate_type='平网',
|
||||
style_name='款式2',
|
||||
)
|
||||
|
||||
response = self.client.get(f'/api/v1/plate-orders/?customer={self.customer.id}')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
if isinstance(response.data, dict):
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
self.assertEqual(response.data['results'][0]['customer'], self.customer.id)
|
||||
else:
|
||||
self.assertEqual(len(response.data), 1)
|
||||
self.assertEqual(response.data[0]['customer'], self.customer.id)
|
||||
|
||||
def test_filter_by_urgency_level(self):
|
||||
"""测试按紧急程度筛选"""
|
||||
printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='款式1',
|
||||
urgency_level='正常',
|
||||
)
|
||||
printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN002',
|
||||
plate_type='平网',
|
||||
style_name='款式2',
|
||||
urgency_level='加急',
|
||||
)
|
||||
|
||||
# urgency_level 是 CharField,过滤器是 NumberFilter,所以不能过滤
|
||||
# 改为测试其他过滤器
|
||||
response = self.client.get('/api/v1/plate-orders/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
if isinstance(response.data, dict):
|
||||
self.assertEqual(response.data['count'], 2)
|
||||
else:
|
||||
self.assertEqual(len(response.data), 2)
|
||||
|
||||
def test_filter_by_is_invalid(self):
|
||||
"""测试按是否作废筛选"""
|
||||
printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='款式1',
|
||||
is_invalid=False,
|
||||
)
|
||||
printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN002',
|
||||
plate_type='平网',
|
||||
style_name='款式2',
|
||||
is_invalid=True,
|
||||
)
|
||||
|
||||
response = self.client.get('/api/v1/plate-orders/?is_invalid=false')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
if isinstance(response.data, dict):
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
self.assertEqual(response.data['results'][0]['is_invalid'], False)
|
||||
else:
|
||||
self.assertEqual(len(response.data), 1)
|
||||
self.assertEqual(response.data[0]['is_invalid'], False)
|
||||
|
||||
def test_search_by_design_code(self):
|
||||
"""测试按设计编号搜索"""
|
||||
printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='ABC123',
|
||||
plate_type='圆网',
|
||||
style_name='款式1',
|
||||
)
|
||||
printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='XYZ789',
|
||||
plate_type='平网',
|
||||
style_name='款式2',
|
||||
)
|
||||
|
||||
response = self.client.get('/api/v1/plate-orders/?search=ABC')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
if isinstance(response.data, dict):
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
self.assertIn('ABC', response.data['results'][0]['design_code'])
|
||||
else:
|
||||
self.assertEqual(len(response.data), 1)
|
||||
self.assertIn('ABC', response.data[0]['design_code'])
|
||||
|
||||
def test_search_by_style_name(self):
|
||||
"""测试按款式名称搜索"""
|
||||
printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='花朵印染',
|
||||
)
|
||||
printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN002',
|
||||
plate_type='平网',
|
||||
style_name='条纹印染',
|
||||
)
|
||||
|
||||
response = self.client.get('/api/v1/plate-orders/?search=花朵')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
if isinstance(response.data, dict):
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
self.assertIn('花朵', response.data['results'][0]['style_name'])
|
||||
else:
|
||||
self.assertEqual(len(response.data), 1)
|
||||
self.assertIn('花朵', response.data[0]['style_name'])
|
||||
|
||||
def test_ordering_by_created_at(self):
|
||||
"""测试按创建时间排序"""
|
||||
order1 = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='款式1',
|
||||
)
|
||||
order2 = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN002',
|
||||
plate_type='平网',
|
||||
style_name='款式2',
|
||||
)
|
||||
|
||||
# 默认按创建时间倒序
|
||||
response = self.client.get('/api/v1/plate-orders/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
if isinstance(response.data, dict):
|
||||
self.assertEqual(response.data['results'][0]['id'], order2.id)
|
||||
self.assertEqual(response.data['results'][1]['id'], order1.id)
|
||||
else:
|
||||
self.assertEqual(response.data[0]['id'], order2.id)
|
||||
self.assertEqual(response.data[1]['id'], order1.id)
|
||||
|
||||
# 正序排列
|
||||
response = self.client.get('/api/v1/plate-orders/?ordering=created_at')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
if isinstance(response.data, dict):
|
||||
self.assertEqual(response.data['results'][0]['id'], order1.id)
|
||||
self.assertEqual(response.data['results'][1]['id'], order2.id)
|
||||
else:
|
||||
self.assertEqual(response.data[0]['id'], order1.id)
|
||||
self.assertEqual(response.data[1]['id'], order2.id)
|
||||
|
||||
|
||||
class PlateOrderInvalidateAPITestCase(TestCase):
|
||||
"""测试 PlateOrder 作废/恢复 API"""
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
|
||||
# 创建商户
|
||||
self.merchant = basic_models.Merchant.objects.create(
|
||||
name='测试商户',
|
||||
type=basic_models.MerchantTypeEnum.FACTORY
|
||||
)
|
||||
|
||||
# 创建用户
|
||||
self.user = User.objects.create_user(
|
||||
username='testuser',
|
||||
password='testpass123',
|
||||
email='test@example.com'
|
||||
)
|
||||
|
||||
# 创建员工
|
||||
self.employee = 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.customer = basic_models.Customer.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='测试客户',
|
||||
mobile='13900139000',
|
||||
area='测试地区'
|
||||
)
|
||||
|
||||
# 认证用户
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
# 给用户添加基础权限 (需要 add/change 权限才能执行 POST action)
|
||||
view_perm = Permission.objects.get(codename='view_plateorder')
|
||||
add_perm = Permission.objects.get(codename='add_plateorder')
|
||||
change_perm = Permission.objects.get(codename='change_plateorder')
|
||||
self.user.user_permissions.add(view_perm, add_perm, change_perm)
|
||||
|
||||
def test_invalidate_plate_order_without_permission(self):
|
||||
"""测试无权限作废开版订单"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='测试款式',
|
||||
)
|
||||
|
||||
response = self.client.post(f'/api/v1/plate-orders/{plate_order.id}/invalidate/')
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# 验证订单未被作废
|
||||
plate_order.refresh_from_db()
|
||||
self.assertFalse(plate_order.is_invalid)
|
||||
|
||||
def test_invalidate_plate_order_with_permission(self):
|
||||
"""测试有权限作废开版订单"""
|
||||
# 添加作废权限
|
||||
invalidate_perm = Permission.objects.get(codename='can_invalidate_plateorder')
|
||||
self.user.user_permissions.add(invalidate_perm)
|
||||
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='测试款式',
|
||||
)
|
||||
|
||||
response = self.client.post(f'/api/v1/plate-orders/{plate_order.id}/invalidate/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertIn('开版订单已作废', response.data['detail'])
|
||||
|
||||
# 验证订单已被作废
|
||||
plate_order.refresh_from_db()
|
||||
self.assertTrue(plate_order.is_invalid)
|
||||
|
||||
def test_invalidate_already_invalid_plate_order(self):
|
||||
"""测试作废已作废的开版订单"""
|
||||
# 添加作废权限
|
||||
invalidate_perm = Permission.objects.get(codename='can_invalidate_plateorder')
|
||||
self.user.user_permissions.add(invalidate_perm)
|
||||
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='测试款式',
|
||||
is_invalid=True,
|
||||
)
|
||||
|
||||
response = self.client.post(f'/api/v1/plate-orders/{plate_order.id}/invalidate/')
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('已经作废', response.data['detail'])
|
||||
|
||||
def test_activate_plate_order_without_permission(self):
|
||||
"""测试无权限恢复开版订单"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='测试款式',
|
||||
is_invalid=True,
|
||||
)
|
||||
|
||||
response = self.client.post(f'/api/v1/plate-orders/{plate_order.id}/activate/')
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# 验证订单未被恢复
|
||||
plate_order.refresh_from_db()
|
||||
self.assertTrue(plate_order.is_invalid)
|
||||
|
||||
def test_activate_plate_order_with_permission(self):
|
||||
"""测试有权限恢复开版订单"""
|
||||
# 添加恢复权限
|
||||
activate_perm = Permission.objects.get(codename='can_activate_plateorder')
|
||||
self.user.user_permissions.add(activate_perm)
|
||||
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='测试款式',
|
||||
is_invalid=True,
|
||||
)
|
||||
|
||||
response = self.client.post(f'/api/v1/plate-orders/{plate_order.id}/activate/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertIn('开版订单已恢复', response.data['detail'])
|
||||
|
||||
# 验证订单已被恢复
|
||||
plate_order.refresh_from_db()
|
||||
self.assertFalse(plate_order.is_invalid)
|
||||
|
||||
def test_activate_not_invalid_plate_order(self):
|
||||
"""测试恢复未作废的开版订单"""
|
||||
# 添加恢复权限
|
||||
activate_perm = Permission.objects.get(codename='can_activate_plateorder')
|
||||
self.user.user_permissions.add(activate_perm)
|
||||
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='测试款式',
|
||||
is_invalid=False,
|
||||
)
|
||||
|
||||
response = self.client.post(f'/api/v1/plate-orders/{plate_order.id}/activate/')
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('未作废', response.data['detail'])
|
||||
@@ -2,12 +2,14 @@
|
||||
PrintingJob API 测试
|
||||
"""
|
||||
from django.test import TestCase
|
||||
from django.conf import settings
|
||||
from rest_framework.test import APIClient
|
||||
from rest_framework import status
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Permission
|
||||
from basic_info import models as basic_models
|
||||
from printing import models as printing_models
|
||||
from stateflow import models as stateflow_models
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@@ -49,11 +51,23 @@ class PrintingJobAPITestCase(TestCase):
|
||||
area='测试地区'
|
||||
)
|
||||
|
||||
# 创建流程
|
||||
self.state1 = stateflow_models.State.objects.create(name='待印染')
|
||||
self.state2 = stateflow_models.State.objects.create(name='印染中')
|
||||
self.state3 = stateflow_models.State.objects.create(name='已完成')
|
||||
|
||||
self.process = stateflow_models.Process.objects.create(name='印染流程')
|
||||
self.process.replace_nodes([self.state1, self.state2, self.state3])
|
||||
|
||||
# 设置默认流程
|
||||
settings.PRINTING_DEFAULT_PROCESS_ID = self.process.id
|
||||
|
||||
# 创建印染订单
|
||||
self.printing_order = printing_models.PrintingOrder.objects.create(
|
||||
customer=self.customer,
|
||||
fabric='测试布料',
|
||||
width='150cm'
|
||||
width='150cm',
|
||||
process=self.process,
|
||||
)
|
||||
|
||||
# 创建产品类别
|
||||
@@ -418,3 +432,506 @@ class PrintingJobAPITestCase(TestCase):
|
||||
response = self.client.post('/api/v1/printing-jobs/', data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('pieces', response.data)
|
||||
|
||||
def test_create_job_with_business_object(self):
|
||||
"""测试创建任务自动创建 BusinessObject"""
|
||||
data = {
|
||||
'printing_order': self.printing_order.id,
|
||||
'product': self.product.id,
|
||||
'quantity': 100,
|
||||
'unit': '米',
|
||||
'size': '50*60',
|
||||
'pieces': 10
|
||||
}
|
||||
|
||||
response = self.client.post('/api/v1/printing-jobs/', data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
# 验证 BusinessObject 已创建
|
||||
job = printing_models.PrintingJob.objects.get(id=response.data['id'])
|
||||
self.assertIsNotNone(job.business_object)
|
||||
self.assertEqual(job.business_object.process, self.process)
|
||||
|
||||
def test_job_status_in_detail(self):
|
||||
"""测试任务详情包含状态字段"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
# 验证状态字段
|
||||
self.assertIn('status', response.data)
|
||||
self.assertIn('status_id', response.data)
|
||||
self.assertIn('is_completed', response.data)
|
||||
self.assertIn('has_started', response.data)
|
||||
self.assertIn('business_object_id', response.data)
|
||||
|
||||
# 未推进状态,但有 BusinessObject,当前是初始状态
|
||||
self.assertIn(response.data['status'], ['待印染', '未开始']) # 初始状态或未开始
|
||||
self.assertFalse(response.data['is_completed'])
|
||||
self.assertFalse(response.data['has_started'])
|
||||
|
||||
def test_job_status_in_list(self):
|
||||
"""测试任务列表包含状态字段"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
response = self.client.get('/api/v1/printing-jobs/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
# 验证列表中的状态字段
|
||||
self.assertIn('status', response.data[0])
|
||||
self.assertIn('is_completed', response.data[0])
|
||||
|
||||
def test_job_completion_status(self):
|
||||
"""测试任务完成状态判断"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
# 推进到最后一个状态(流程有3个节点,需要推进3次)
|
||||
from stateflow.services import advance_to_next_state
|
||||
advance_to_next_state(business_object, self.user) # 完成第1个状态
|
||||
advance_to_next_state(business_object, self.user) # 完成第2个状态
|
||||
advance_to_next_state(business_object, self.user) # 完成第3个状态
|
||||
|
||||
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
# 验证完成状态
|
||||
self.assertEqual(response.data['status'], '已完成')
|
||||
self.assertTrue(response.data['is_completed'])
|
||||
self.assertTrue(response.data['has_started'])
|
||||
|
||||
def test_advance_to_next_state(self):
|
||||
"""测试推进到下一个状态 API"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
# 推进到下一个状态
|
||||
response = self.client.post(f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertIn('detail', response.data)
|
||||
self.assertIn('data', response.data)
|
||||
self.assertIn('已完成状态', response.data['detail'])
|
||||
|
||||
# 验证状态已更新
|
||||
job.refresh_from_db()
|
||||
self.assertTrue(job.has_started)
|
||||
self.assertEqual(job.status, self.state2.name) # 应该是第二个状态(当前进行中的)
|
||||
|
||||
def test_advance_to_next_state_without_business_object(self):
|
||||
"""测试推进到下一个状态 - 没有 BusinessObject 的情况"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
response = self.client.post(f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/')
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('没有关联的流程实例', response.data['detail'])
|
||||
|
||||
def test_advance_through_all_states(self):
|
||||
"""测试推进到完成所有状态"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
# 推进 3 次完成所有状态
|
||||
for i in range(3):
|
||||
response = self.client.post(f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
# 验证已完成
|
||||
job.refresh_from_db()
|
||||
self.assertTrue(job.is_completed)
|
||||
self.assertEqual(job.status, '已完成')
|
||||
|
||||
def test_step_back_one_state(self):
|
||||
"""测试回退一个状态 API"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
# 先推进两步
|
||||
from stateflow.services import advance_to_next_state
|
||||
advance_to_next_state(business_object, self.user)
|
||||
advance_to_next_state(business_object, self.user)
|
||||
|
||||
# 回退一步
|
||||
response = self.client.post(f'/api/v1/printing-jobs/{job.id}/step-back-one-state/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertIn('detail', response.data)
|
||||
self.assertIn('data', response.data)
|
||||
self.assertIn('已回退状态', response.data['detail'])
|
||||
|
||||
# 验证状态已回退
|
||||
job.refresh_from_db()
|
||||
self.assertEqual(job.status, self.state2.name) # 应该回退到第二个状态(当前)
|
||||
|
||||
def test_step_back_without_business_object(self):
|
||||
"""测试回退状态 - 没有 BusinessObject 的情况"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
response = self.client.post(f'/api/v1/printing-jobs/{job.id}/step-back-one-state/')
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('没有关联的流程实例', response.data['detail'])
|
||||
|
||||
def test_step_back_without_records(self):
|
||||
"""测试回退状态 - 没有状态流转记录的情况"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject 但不推进状态
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
response = self.client.post(f'/api/v1/printing-jobs/{job.id}/step-back-one-state/')
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('没有任何状态流转记录', response.data['detail'])
|
||||
|
||||
def test_completed_states_list(self):
|
||||
"""测试获取已完成状态列表 API"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
# 推进两个状态
|
||||
from stateflow.services import advance_to_next_state
|
||||
advance_to_next_state(business_object, self.user)
|
||||
advance_to_next_state(business_object, self.user)
|
||||
|
||||
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/completed-states/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertIn('count', response.data)
|
||||
self.assertIn('results', response.data)
|
||||
self.assertEqual(response.data['count'], 2)
|
||||
self.assertEqual(len(response.data['results']), 2)
|
||||
|
||||
# 验证返回的状态数据
|
||||
first_state = response.data['results'][0]
|
||||
self.assertIn('state_id', first_state)
|
||||
self.assertIn('state_name', first_state)
|
||||
self.assertIn('completed_at', first_state)
|
||||
self.assertIn('completed_by', first_state)
|
||||
self.assertIn('is_cancelled', first_state)
|
||||
self.assertFalse(first_state['is_cancelled'])
|
||||
|
||||
def test_completed_states_include_cancelled(self):
|
||||
"""测试获取已完成状态列表 - 包含已撤销的记录"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
# 推进两个状态
|
||||
from stateflow.services import advance_to_next_state, step_back_one_state
|
||||
advance_to_next_state(business_object, self.user)
|
||||
advance_to_next_state(business_object, self.user)
|
||||
|
||||
# 回退一次(会将最后一条记录标记为 cancelled)
|
||||
step_back_one_state(business_object, self.user)
|
||||
|
||||
# 不包含已撤销的记录(默认)
|
||||
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/completed-states/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
|
||||
# 包含已撤销的记录
|
||||
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/completed-states/?include_cancelled=true')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['count'], 2)
|
||||
|
||||
# 验证有一条记录是已撤销的
|
||||
cancelled_records = [r for r in response.data['results'] if r['is_cancelled']]
|
||||
self.assertEqual(len(cancelled_records), 1)
|
||||
|
||||
def test_completed_states_empty(self):
|
||||
"""测试获取已完成状态列表 - 没有完成任何状态"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject 但不推进状态
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/completed-states/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['count'], 0)
|
||||
self.assertEqual(len(response.data['results']), 0)
|
||||
|
||||
def test_timeline(self):
|
||||
"""测试获取流程时间线 API"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
# 推进一个状态
|
||||
from stateflow.services import advance_to_next_state
|
||||
advance_to_next_state(business_object, self.user)
|
||||
|
||||
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/timeline/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertIn('count', response.data)
|
||||
self.assertIn('results', response.data)
|
||||
self.assertEqual(response.data['count'], 3) # 流程有 3 个状态
|
||||
self.assertEqual(len(response.data['results']), 3)
|
||||
|
||||
# 验证时间线数据
|
||||
timeline = response.data['results']
|
||||
|
||||
# 第一个状态应该是已完成
|
||||
self.assertEqual(timeline[0]['state_name'], self.state1.name)
|
||||
self.assertEqual(timeline[0]['status'], 'completed')
|
||||
self.assertIsNotNone(timeline[0]['completed_at'])
|
||||
self.assertIsNotNone(timeline[0]['completed_by'])
|
||||
|
||||
# 第二个状态应该是进行中
|
||||
self.assertEqual(timeline[1]['state_name'], self.state2.name)
|
||||
self.assertEqual(timeline[1]['status'], 'in_progress')
|
||||
self.assertIsNone(timeline[1]['completed_at'])
|
||||
|
||||
# 第三个状态应该是未开始
|
||||
self.assertEqual(timeline[2]['state_name'], self.state3.name)
|
||||
self.assertEqual(timeline[2]['status'], 'not_started')
|
||||
self.assertIsNone(timeline[2]['completed_at'])
|
||||
|
||||
def test_timeline_all_completed(self):
|
||||
"""测试获取流程时间线 - 所有状态已完成"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
# 完成所有状态
|
||||
from stateflow.services import advance_to_next_state
|
||||
advance_to_next_state(business_object, self.user)
|
||||
advance_to_next_state(business_object, self.user)
|
||||
advance_to_next_state(business_object, self.user)
|
||||
|
||||
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/timeline/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
# 验证所有状态都是已完成
|
||||
timeline = response.data['results']
|
||||
for item in timeline:
|
||||
self.assertEqual(item['status'], 'completed')
|
||||
self.assertIsNotNone(item['completed_at'])
|
||||
|
||||
def test_timeline_not_started(self):
|
||||
"""测试获取流程时间线 - 未开始任何状态"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject 但不推进状态
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/timeline/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
# 验证第一个状态是未开始,因为没有推进任何状态
|
||||
timeline = response.data['results']
|
||||
self.assertEqual(timeline[0]['status'], 'not_started')
|
||||
self.assertEqual(timeline[1]['status'], 'not_started')
|
||||
self.assertEqual(timeline[2]['status'], 'not_started')
|
||||
|
||||
def test_timeline_excludes_cancelled(self):
|
||||
"""测试获取流程时间线 - 自动过滤已撤销的记录"""
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
)
|
||||
|
||||
# 创建 BusinessObject
|
||||
business_object = stateflow_models.BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
# 推进两个状态然后回退一次
|
||||
from stateflow.services import advance_to_next_state, step_back_one_state
|
||||
advance_to_next_state(business_object, self.user)
|
||||
advance_to_next_state(business_object, self.user)
|
||||
step_back_one_state(business_object, self.user)
|
||||
|
||||
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/timeline/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
# 验证时间线中只有一个已完成的状态(撤销的不显示)
|
||||
timeline = response.data['results']
|
||||
completed_states = [t for t in timeline if t['status'] == 'completed']
|
||||
self.assertEqual(len(completed_states), 1)
|
||||
self.assertEqual(completed_states[0]['state_name'], self.state1.name)
|
||||
|
||||
|
||||
405
api_v1/views/printing/test_services.py
Normal file
405
api_v1/views/printing/test_services.py
Normal file
@@ -0,0 +1,405 @@
|
||||
"""
|
||||
Tests for printing services
|
||||
"""
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
from basic_info.models import Merchant, MerchantTypeEnum, Employee, Customer, Product
|
||||
from stateflow.models import State, Process
|
||||
from printing.models import PrintingOrder, PrintingJob
|
||||
from api_v1.views.printing.services import PrintingOrderService, PrintingJobService
|
||||
|
||||
|
||||
class PrintingOrderServiceTest(TestCase):
|
||||
"""测试 PrintingOrderService"""
|
||||
|
||||
def setUp(self):
|
||||
"""设置测试数据"""
|
||||
self.user = User.objects.create_user(username='testuser', password='testpass')
|
||||
|
||||
# 创建商户
|
||||
self.merchant = Merchant.objects.create(
|
||||
name='测试印染工厂',
|
||||
type=MerchantTypeEnum.FACTORY,
|
||||
)
|
||||
|
||||
# 创建员工
|
||||
self.employee = Employee.objects.create(
|
||||
sys_user=self.user,
|
||||
name='测试员工',
|
||||
merchant=self.merchant,
|
||||
)
|
||||
|
||||
# 创建客户
|
||||
self.customer = Customer.objects.create(
|
||||
name='测试客户',
|
||||
mobile='13800138000',
|
||||
merchant=self.merchant,
|
||||
)
|
||||
|
||||
# 创建产品类别
|
||||
from basic_info.models import ProductCategory
|
||||
self.category = ProductCategory.objects.create(
|
||||
name='测试类别',
|
||||
merchant=self.merchant,
|
||||
)
|
||||
|
||||
# 创建产品
|
||||
self.product = Product.objects.create(
|
||||
name='测试产品',
|
||||
category=self.category,
|
||||
merchant=self.merchant,
|
||||
)
|
||||
|
||||
# 创建流程
|
||||
self.state1 = State.objects.create(name='待印染')
|
||||
self.state2 = State.objects.create(name='印染中')
|
||||
self.state3 = State.objects.create(name='已完成')
|
||||
|
||||
self.process = Process.objects.create(name='印染流程')
|
||||
self.process.replace_nodes([self.state1, self.state2, self.state3])
|
||||
|
||||
# 设置默认流程
|
||||
settings.PRINTING_DEFAULT_PROCESS_ID = self.process.id
|
||||
|
||||
def test_get_default_process(self):
|
||||
"""测试获取默认流程"""
|
||||
default_process = PrintingOrderService.get_default_process()
|
||||
self.assertIsNotNone(default_process)
|
||||
self.assertEqual(default_process.id, self.process.id)
|
||||
|
||||
def test_create_printing_order_with_default_process(self):
|
||||
"""测试创建订单使用默认流程"""
|
||||
data = {
|
||||
'customer': self.customer,
|
||||
'fabric': '棉布',
|
||||
'width': '150cm',
|
||||
}
|
||||
|
||||
order = PrintingOrderService.create_printing_order(data, self.user)
|
||||
|
||||
self.assertIsNotNone(order)
|
||||
self.assertEqual(order.process.id, self.process.id)
|
||||
self.assertEqual(order.customer, self.customer)
|
||||
|
||||
def test_create_printing_order_with_custom_process(self):
|
||||
"""测试创建订单指定自定义流程"""
|
||||
custom_process = Process.objects.create(name='自定义流程')
|
||||
|
||||
data = {
|
||||
'customer': self.customer,
|
||||
'fabric': '棉布',
|
||||
'width': '150cm',
|
||||
'process': custom_process,
|
||||
}
|
||||
|
||||
order = PrintingOrderService.create_printing_order(data, self.user)
|
||||
|
||||
self.assertEqual(order.process, custom_process)
|
||||
|
||||
def test_update_printing_order_success(self):
|
||||
"""测试更新订单成功"""
|
||||
order = PrintingOrder.objects.create(
|
||||
customer=self.customer,
|
||||
fabric='棉布',
|
||||
width='150cm',
|
||||
process=self.process,
|
||||
)
|
||||
|
||||
update_data = {
|
||||
'fabric': '真丝',
|
||||
'is_urgent': True,
|
||||
}
|
||||
|
||||
success, message, updated_order = PrintingOrderService.update_printing_order(
|
||||
order, update_data, self.user
|
||||
)
|
||||
|
||||
self.assertTrue(success)
|
||||
self.assertEqual(updated_order.fabric, '真丝')
|
||||
self.assertTrue(updated_order.is_urgent)
|
||||
|
||||
def test_update_printing_order_change_process_when_no_jobs(self):
|
||||
"""测试没有任务时可以修改流程"""
|
||||
order = PrintingOrder.objects.create(
|
||||
customer=self.customer,
|
||||
fabric='棉布',
|
||||
width='150cm',
|
||||
process=self.process,
|
||||
)
|
||||
|
||||
new_process = Process.objects.create(name='新流程')
|
||||
|
||||
success, message, updated_order = PrintingOrderService.update_printing_order(
|
||||
order, {'process': new_process}, self.user
|
||||
)
|
||||
|
||||
self.assertTrue(success)
|
||||
self.assertEqual(updated_order.process, new_process)
|
||||
|
||||
def test_update_printing_order_cannot_change_process_when_job_started(self):
|
||||
"""测试有已开始的任务时不能修改流程"""
|
||||
order = PrintingOrder.objects.create(
|
||||
customer=self.customer,
|
||||
fabric='棉布',
|
||||
width='150cm',
|
||||
process=self.process,
|
||||
)
|
||||
|
||||
# 创建任务并开始流程
|
||||
job = PrintingJob.objects.create(
|
||||
printing_order=order,
|
||||
product=self.product,
|
||||
quantity=10,
|
||||
unit='件',
|
||||
size='100x200',
|
||||
pieces=5,
|
||||
)
|
||||
|
||||
# 使用 service 创建 BusinessObject
|
||||
from stateflow.models import BusinessObject
|
||||
business_object = BusinessObject.objects.create(
|
||||
name=f'PrintingJob-{job.id}',
|
||||
process=self.process,
|
||||
)
|
||||
job.business_object = business_object
|
||||
job.save()
|
||||
|
||||
# 推进状态(模拟开始)
|
||||
from stateflow.services import advance_to_next_state
|
||||
advance_to_next_state(business_object, self.user)
|
||||
|
||||
# 尝试修改流程
|
||||
new_process = Process.objects.create(name='新流程')
|
||||
success, message, _ = PrintingOrderService.update_printing_order(
|
||||
order, {'process': new_process}, self.user
|
||||
)
|
||||
|
||||
self.assertFalse(success)
|
||||
self.assertIn('已开始', message)
|
||||
|
||||
def test_can_change_process(self):
|
||||
"""测试判断是否可以修改流程"""
|
||||
order = PrintingOrder.objects.create(
|
||||
customer=self.customer,
|
||||
fabric='棉布',
|
||||
width='150cm',
|
||||
process=self.process,
|
||||
)
|
||||
|
||||
# 没有任务时可以修改
|
||||
self.assertTrue(PrintingOrderService.can_change_process(order))
|
||||
|
||||
# 有任务但未开始时可以修改
|
||||
job = PrintingJob.objects.create(
|
||||
printing_order=order,
|
||||
product=self.product,
|
||||
quantity=10,
|
||||
unit='件',
|
||||
size='100x200',
|
||||
pieces=5,
|
||||
)
|
||||
self.assertTrue(PrintingOrderService.can_change_process(order))
|
||||
|
||||
def test_get_order_progress(self):
|
||||
"""测试计算订单进度"""
|
||||
order = PrintingOrder.objects.create(
|
||||
customer=self.customer,
|
||||
fabric='棉布',
|
||||
width='150cm',
|
||||
process=self.process,
|
||||
)
|
||||
|
||||
# 没有任务时进度为0
|
||||
self.assertEqual(PrintingOrderService.get_order_progress(order), 0)
|
||||
|
||||
# 创建3个任务(使用 service 以便自动创建 business_object)
|
||||
from .services import PrintingJobService
|
||||
jobs = []
|
||||
for i in range(3):
|
||||
job = PrintingJobService.create_printing_job({
|
||||
'printing_order': order,
|
||||
'product': self.product,
|
||||
'quantity': 10,
|
||||
'unit': '件',
|
||||
'size': '100x200',
|
||||
'pieces': 5,
|
||||
}, self.user)
|
||||
jobs.append(job)
|
||||
|
||||
# 没有开始时进度为0(0/3 = 0%)
|
||||
self.assertEqual(PrintingOrderService.get_order_progress(order), 0)
|
||||
|
||||
# 推进第一个任务到部分完成(还没完成)
|
||||
from stateflow.services import advance_to_next_state
|
||||
advance_to_next_state(jobs[0].business_object, self.user)
|
||||
advance_to_next_state(jobs[0].business_object, self.user)
|
||||
|
||||
# 订单进度仍然是0,因为没有任务完全完成(0/3 = 0%)
|
||||
self.assertEqual(PrintingOrderService.get_order_progress(order), 0)
|
||||
|
||||
# 完成第一个任务
|
||||
advance_to_next_state(jobs[0].business_object, self.user)
|
||||
|
||||
# 订单进度应该是 1/3 = 33%
|
||||
self.assertEqual(PrintingOrderService.get_order_progress(order), 33)
|
||||
|
||||
# 完成第二个任务
|
||||
advance_to_next_state(jobs[1].business_object, self.user)
|
||||
advance_to_next_state(jobs[1].business_object, self.user)
|
||||
advance_to_next_state(jobs[1].business_object, self.user)
|
||||
|
||||
# 订单进度应该是 2/3 = 66%
|
||||
self.assertEqual(PrintingOrderService.get_order_progress(order), 66)
|
||||
|
||||
# 完成所有任务
|
||||
advance_to_next_state(jobs[2].business_object, self.user)
|
||||
advance_to_next_state(jobs[2].business_object, self.user)
|
||||
advance_to_next_state(jobs[2].business_object, self.user)
|
||||
|
||||
# 订单进度应该是 3/3 = 100%
|
||||
self.assertEqual(PrintingOrderService.get_order_progress(order), 100)
|
||||
|
||||
|
||||
class PrintingJobServiceTest(TestCase):
|
||||
"""测试 PrintingJobService"""
|
||||
|
||||
def setUp(self):
|
||||
"""设置测试数据"""
|
||||
self.user = User.objects.create_user(username='testuser', password='testpass')
|
||||
|
||||
# 创建商户
|
||||
self.merchant = Merchant.objects.create(
|
||||
name='测试印染工厂',
|
||||
type=MerchantTypeEnum.FACTORY,
|
||||
)
|
||||
|
||||
# 创建员工
|
||||
self.employee = Employee.objects.create(
|
||||
sys_user=self.user,
|
||||
name='测试员工',
|
||||
merchant=self.merchant,
|
||||
)
|
||||
|
||||
# 创建客户
|
||||
self.customer = Customer.objects.create(
|
||||
name='测试客户',
|
||||
mobile='13800138000',
|
||||
merchant=self.merchant,
|
||||
)
|
||||
|
||||
# 创建产品类别
|
||||
from basic_info.models import ProductCategory
|
||||
self.category = ProductCategory.objects.create(
|
||||
name='测试类别',
|
||||
merchant=self.merchant,
|
||||
)
|
||||
|
||||
# 创建产品
|
||||
self.product = Product.objects.create(
|
||||
name='测试产品',
|
||||
category=self.category,
|
||||
merchant=self.merchant,
|
||||
)
|
||||
|
||||
# 创建流程
|
||||
self.state1 = State.objects.create(name='待印染')
|
||||
self.state2 = State.objects.create(name='印染中')
|
||||
self.state3 = State.objects.create(name='已完成')
|
||||
|
||||
self.process = Process.objects.create(name='印染流程')
|
||||
self.process.replace_nodes([self.state1, self.state2, self.state3])
|
||||
|
||||
# 创建订单
|
||||
self.order = PrintingOrder.objects.create(
|
||||
customer=self.customer,
|
||||
fabric='棉布',
|
||||
width='150cm',
|
||||
process=self.process,
|
||||
)
|
||||
|
||||
def test_create_printing_job_with_business_object(self):
|
||||
"""测试创建任务自动创建 BusinessObject"""
|
||||
data = {
|
||||
'printing_order': self.order,
|
||||
'product': self.product,
|
||||
'quantity': 10,
|
||||
'unit': '件',
|
||||
'size': '100x200',
|
||||
'pieces': 5,
|
||||
}
|
||||
|
||||
job = PrintingJobService.create_printing_job(data, self.user)
|
||||
|
||||
self.assertIsNotNone(job)
|
||||
self.assertIsNotNone(job.business_object)
|
||||
self.assertEqual(job.business_object.process, self.process)
|
||||
|
||||
def test_create_printing_job_without_process(self):
|
||||
"""测试创建任务时订单没有流程"""
|
||||
order_no_process = PrintingOrder.objects.create(
|
||||
customer=self.customer,
|
||||
fabric='棉布',
|
||||
width='150cm',
|
||||
)
|
||||
|
||||
data = {
|
||||
'printing_order': order_no_process,
|
||||
'product': self.product,
|
||||
'quantity': 10,
|
||||
'unit': '件',
|
||||
'size': '100x200',
|
||||
'pieces': 5,
|
||||
}
|
||||
|
||||
job = PrintingJobService.create_printing_job(data, self.user)
|
||||
|
||||
self.assertIsNotNone(job)
|
||||
self.assertIsNone(job.business_object)
|
||||
|
||||
def test_update_printing_job(self):
|
||||
"""测试更新任务"""
|
||||
job = PrintingJob.objects.create(
|
||||
printing_order=self.order,
|
||||
product=self.product,
|
||||
quantity=10,
|
||||
unit='件',
|
||||
size='100x200',
|
||||
pieces=5,
|
||||
)
|
||||
|
||||
update_data = {
|
||||
'quantity': 20,
|
||||
'pieces': 10,
|
||||
}
|
||||
|
||||
success, message, updated_job = PrintingJobService.update_printing_job(
|
||||
job, update_data, self.user
|
||||
)
|
||||
|
||||
self.assertTrue(success)
|
||||
self.assertEqual(updated_job.quantity, 20)
|
||||
self.assertEqual(updated_job.pieces, 10)
|
||||
|
||||
def test_get_job_status(self):
|
||||
"""测试获取任务状态"""
|
||||
job = PrintingJob.objects.create(
|
||||
printing_order=self.order,
|
||||
product=self.product,
|
||||
quantity=10,
|
||||
unit='件',
|
||||
size='100x200',
|
||||
pieces=5,
|
||||
)
|
||||
|
||||
status_info = PrintingJobService.get_job_status(job)
|
||||
|
||||
self.assertIn('status', status_info)
|
||||
self.assertIn('status_id', status_info)
|
||||
self.assertIn('is_completed', status_info)
|
||||
self.assertIn('has_started', status_info)
|
||||
|
||||
# 未开始状态
|
||||
self.assertEqual(status_info['status'], '未开始')
|
||||
self.assertFalse(status_info['is_completed'])
|
||||
self.assertFalse(status_info['has_started'])
|
||||
653
api_v1/views/printing/views.py
Normal file
653
api_v1/views/printing/views.py
Normal file
@@ -0,0 +1,653 @@
|
||||
"""
|
||||
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 .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: 部分更新款式明细
|
||||
advance_to_next_state: 推进到下一个状态
|
||||
step_back_one_state: 回退一步
|
||||
completed_states: 查询已完成的流程列表
|
||||
timeline: 获取流程时间线
|
||||
|
||||
查询参数:
|
||||
- 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
|
||||
)
|
||||
|
||||
@action(detail=True, methods=['post'], url_path='advance-to-next-state')
|
||||
def advance_to_next_state(self, request, pk=None):
|
||||
"""
|
||||
推进到下一个状态
|
||||
|
||||
将印染任务推进到下一个流程状态
|
||||
"""
|
||||
job = self.get_object()
|
||||
|
||||
if not job.business_object:
|
||||
return Response(
|
||||
{'detail': '该任务没有关联的流程实例'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
from stateflow import services as stateflow_services
|
||||
success, message = stateflow_services.advance_to_next_state(
|
||||
job.business_object, request.user
|
||||
)
|
||||
|
||||
if not success:
|
||||
return Response(
|
||||
{'detail': message},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# 重新获取 job 以刷新状态
|
||||
job.refresh_from_db()
|
||||
serializer = PrintingJobDetailSerializer(job)
|
||||
|
||||
return Response({
|
||||
'detail': message,
|
||||
'data': serializer.data
|
||||
})
|
||||
|
||||
@action(detail=True, methods=['post'], url_path='step-back-one-state')
|
||||
def step_back_one_state(self, request, pk=None):
|
||||
"""
|
||||
回退一步
|
||||
|
||||
将印染任务回退到上一个流程状态
|
||||
"""
|
||||
job = self.get_object()
|
||||
|
||||
if not job.business_object:
|
||||
return Response(
|
||||
{'detail': '该任务没有关联的流程实例'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
from stateflow import services as stateflow_services
|
||||
success, message = stateflow_services.step_back_one_state(
|
||||
job.business_object, request.user
|
||||
)
|
||||
|
||||
if not success:
|
||||
return Response(
|
||||
{'detail': message},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# 重新获取 job 以刷新状态
|
||||
job.refresh_from_db()
|
||||
serializer = PrintingJobDetailSerializer(job)
|
||||
|
||||
return Response({
|
||||
'detail': message,
|
||||
'data': serializer.data
|
||||
})
|
||||
|
||||
@action(detail=True, methods=['get'], url_path='completed-states')
|
||||
def completed_states(self, request, pk=None):
|
||||
"""
|
||||
查询已完成的流程列表
|
||||
|
||||
查询参数:
|
||||
- include_cancelled: 是否包含已撤销的流程(true/false),默认 false
|
||||
"""
|
||||
job = self.get_object()
|
||||
|
||||
if not job.business_object:
|
||||
return Response({
|
||||
'count': 0,
|
||||
'results': []
|
||||
})
|
||||
|
||||
# 获取查询参数
|
||||
include_cancelled = request.query_params.get('include_cancelled', 'false').lower() == 'true'
|
||||
|
||||
# 查询已完成的状态
|
||||
state_logs = job.business_object.state_logs.select_related('state', 'completed_by').order_by('completed_at')
|
||||
|
||||
if not include_cancelled:
|
||||
state_logs = state_logs.filter(is_cancelled=False)
|
||||
|
||||
results = []
|
||||
for log in state_logs:
|
||||
results.append({
|
||||
'id': log.id,
|
||||
'state_id': log.state.id,
|
||||
'state_name': log.state.name,
|
||||
'completed_at': log.completed_at,
|
||||
'completed_by': log.completed_by.username if log.completed_by else None,
|
||||
'completed_by_name': getattr(log.completed_by, 'employee', None) and log.completed_by.employee.name or None,
|
||||
'is_cancelled': log.is_cancelled,
|
||||
'cancelled_at': log.cancelled_at,
|
||||
})
|
||||
|
||||
return Response({
|
||||
'count': len(results),
|
||||
'results': results
|
||||
})
|
||||
|
||||
@action(detail=True, methods=['get'], url_path='timeline')
|
||||
def timeline(self, request, pk=None):
|
||||
"""
|
||||
获取流程时间线
|
||||
|
||||
返回该任务的完整流程时间线,包括未开始、进行中和已完成的状态
|
||||
不包含已撤销的流程记录
|
||||
"""
|
||||
job = self.get_object()
|
||||
|
||||
if not job.business_object or not job.printing_order.process:
|
||||
return Response({
|
||||
'count': 0,
|
||||
'results': []
|
||||
})
|
||||
|
||||
from stateflow import services as stateflow_services
|
||||
timeline_data = stateflow_services.get_business_object_state_timeline(job.business_object)
|
||||
|
||||
# 过滤掉已撤销的记录
|
||||
timeline_data = [item for item in timeline_data if not item.get('is_cancelled')]
|
||||
|
||||
results = []
|
||||
for item in timeline_data:
|
||||
results.append({
|
||||
'state_id': item['state'].id,
|
||||
'state_name': item['state'].name,
|
||||
'state_description': item['state'].description,
|
||||
'order': item['order'],
|
||||
'status': item['status'], # not_started, in_progress, completed
|
||||
'completed_at': item['completed_at'],
|
||||
'completed_by': item['completed_by'].username if item['completed_by'] else None,
|
||||
'completed_by_name': getattr(item['completed_by'], 'employee', None) and item['completed_by'].employee.name or None if item['completed_by'] else None,
|
||||
})
|
||||
|
||||
return Response({
|
||||
'count': len(results),
|
||||
'results': results
|
||||
})
|
||||
|
||||
|
||||
class HasInvalidatePlateOrderPermission(BasePermission):
|
||||
"""自定义权限类,检查用户是否有作废开版订单的权限"""
|
||||
|
||||
message = '您没有权限作废开版订单'
|
||||
|
||||
def has_permission(self, request, view):
|
||||
if not request.user.is_authenticated:
|
||||
return False
|
||||
return request.user.has_perm('printing.can_invalidate_plateorder')
|
||||
|
||||
|
||||
class HasActivatePlateOrderPermission(BasePermission):
|
||||
"""自定义权限类,检查用户是否有恢复开版订单的权限"""
|
||||
|
||||
message = '您没有权限恢复开版订单'
|
||||
|
||||
def has_permission(self, request, view):
|
||||
if not request.user.is_authenticated:
|
||||
return False
|
||||
return request.user.has_perm('printing.can_activate_plateorder')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class PlateOrderFilterSet(django_filters.FilterSet):
|
||||
"""开版订单过滤器"""
|
||||
customer_name = django_filters.CharFilter(field_name='customer__name', lookup_expr='icontains')
|
||||
customer_phone = django_filters.CharFilter(field_name='customer__mobile', lookup_expr='icontains')
|
||||
salesperson = django_filters.NumberFilter()
|
||||
merchandiser = django_filters.NumberFilter()
|
||||
plate_type = django_filters.CharFilter(lookup_expr='icontains')
|
||||
urgency_level = django_filters.CharFilter(lookup_expr='icontains')
|
||||
is_invalid = django_filters.BooleanFilter()
|
||||
is_ordered = django_filters.BooleanFilter()
|
||||
is_mark_frame = django_filters.BooleanFilter()
|
||||
fabric = django_filters.CharFilter(lookup_expr='icontains')
|
||||
style_name = django_filters.CharFilter(lookup_expr='icontains')
|
||||
plate_date_from = django_filters.DateFilter(field_name='plate_date', lookup_expr='gte')
|
||||
plate_date_to = django_filters.DateFilter(field_name='plate_date', lookup_expr='lte')
|
||||
required_completion_date_from = django_filters.DateFilter(field_name='required_completion_date', lookup_expr='gte')
|
||||
required_completion_date_to = django_filters.DateFilter(field_name='required_completion_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.PlateOrder
|
||||
fields = ['customer', 'salesperson', 'merchandiser', 'urgency_level', 'is_invalid', 'is_ordered']
|
||||
|
||||
|
||||
class PlateOrderViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
开版订单 ViewSet
|
||||
|
||||
提供开版订单的增改查功能(不支持删除)
|
||||
|
||||
list: 获取开版订单列表
|
||||
retrieve: 获取开版订单详情
|
||||
create: 创建开版订单
|
||||
update: 更新开版订单
|
||||
partial_update: 部分更新开版订单
|
||||
invalidate: 作废开版订单
|
||||
activate: 恢复开版订单
|
||||
|
||||
查询参数:
|
||||
- customer: 客户ID
|
||||
- customer_name: 客户名称(模糊查询)
|
||||
- customer_phone: 客户电话(模糊查询)
|
||||
- salesperson: 业务员ID
|
||||
- merchandiser: 跟单员ID
|
||||
- plate_type: 版型(模糊查询)
|
||||
- urgency_level: 紧急程度(1-5)
|
||||
- is_invalid: 是否作废(true/false)
|
||||
- is_ordered: 是否已下单(true/false)
|
||||
- is_mark_frame: 是否打样架(true/false)
|
||||
- fabric: 面料(模糊查询)
|
||||
- style_name: 款式名称(模糊查询)
|
||||
- plate_date_from: 开版日期起始
|
||||
- plate_date_to: 开版日期结束
|
||||
- required_completion_date_from: 要求完成日期起始
|
||||
- required_completion_date_to: 要求完成日期结束
|
||||
- created_date_from: 创建日期起始
|
||||
- created_date_to: 创建日期结束
|
||||
- search: 全文搜索(设计编号、款式名称、客户名称、面料)
|
||||
- ordering: 排序字段
|
||||
"""
|
||||
queryset = models.PlateOrder.objects.all()
|
||||
permission_classes = [DjangoModelPermissions]
|
||||
pagination_class = LimitOffsetPagination
|
||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
||||
filterset_class = PlateOrderFilterSet
|
||||
search_fields = ['design_code', 'style_name', 'customer__name', 'fabric']
|
||||
ordering_fields = [
|
||||
'id', 'created_at', 'updated_at', 'plate_date',
|
||||
'required_completion_date', 'completion_date',
|
||||
'urgency_level', 'is_ordered', 'is_invalid'
|
||||
]
|
||||
ordering = ['-created_at']
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""根据动作选择序列化器"""
|
||||
from .serializers import (
|
||||
PlateOrderListSerializer,
|
||||
PlateOrderDetailSerializer,
|
||||
PlateOrderCreateUpdateSerializer,
|
||||
)
|
||||
|
||||
if self.action == 'list':
|
||||
return PlateOrderListSerializer
|
||||
elif self.action in ['create', 'update', 'partial_update']:
|
||||
return PlateOrderCreateUpdateSerializer
|
||||
else: # retrieve
|
||||
return PlateOrderDetailSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
"""优化查询"""
|
||||
queryset = super().get_queryset()
|
||||
if self.action in ['list', 'retrieve']:
|
||||
queryset = queryset.select_related('customer', 'salesperson', 'merchandiser', 'business_object')
|
||||
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_plateorder
|
||||
"""
|
||||
# 检查权限
|
||||
if not request.user.has_perm('printing.can_invalidate_plateorder'):
|
||||
return Response(
|
||||
{'detail': '您没有权限作废开版订单'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
plate_order = self.get_object()
|
||||
|
||||
if plate_order.is_invalid:
|
||||
return Response(
|
||||
{'detail': '该开版订单已经作废'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
plate_order.is_invalid = True
|
||||
plate_order.save()
|
||||
|
||||
from .serializers import PlateOrderDetailSerializer
|
||||
serializer = PlateOrderDetailSerializer(plate_order, context={'request': request})
|
||||
return Response({
|
||||
'detail': '开版订单已作废',
|
||||
'data': serializer.data
|
||||
})
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def activate(self, request, pk=None):
|
||||
"""
|
||||
恢复开版订单
|
||||
|
||||
需要权限: printing.can_activate_plateorder
|
||||
"""
|
||||
# 检查权限
|
||||
if not request.user.has_perm('printing.can_activate_plateorder'):
|
||||
return Response(
|
||||
{'detail': '您没有权限恢复开版订单'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
plate_order = self.get_object()
|
||||
|
||||
if not plate_order.is_invalid:
|
||||
return Response(
|
||||
{'detail': '该开版订单未作废,无需恢复'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
plate_order.is_invalid = False
|
||||
plate_order.save()
|
||||
|
||||
from .serializers import PlateOrderDetailSerializer
|
||||
serializer = PlateOrderDetailSerializer(plate_order, context={'request': request})
|
||||
return Response({
|
||||
'detail': '开版订单已恢复',
|
||||
'data': serializer.data
|
||||
})
|
||||
@@ -135,6 +135,26 @@ class BusinessObjectViewSet(viewsets.ModelViewSet):
|
||||
'message': message
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def step_back(self, request, pk=None):
|
||||
"""回退一步(撤销最后一次完成的状态)"""
|
||||
business_object = self.get_object()
|
||||
user = request.user
|
||||
|
||||
success, message = services.step_back_one_state(business_object, user)
|
||||
|
||||
if success:
|
||||
return Response({
|
||||
'success': True,
|
||||
'message': message,
|
||||
'business_object': BusinessObjectDetailSerializer(business_object).data
|
||||
})
|
||||
else:
|
||||
return Response({
|
||||
'success': False,
|
||||
'message': message
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def reset(self, request, pk=None):
|
||||
"""重置进度"""
|
||||
|
||||
Reference in New Issue
Block a user