1
0
forked from erp-dev/erp
Files
erpnew/api_v2/views/mes.py
2026-05-11 21:37:41 +08:00

468 lines
18 KiB
Python

import json
import re
from django.contrib.contenttypes.models import ContentType
from django.db.models import ProtectedError
from django.shortcuts import get_object_or_404
from rest_framework import permissions, serializers, status
from rest_framework.response import Response
from rest_framework.views import APIView
from mes.models import CapacityUnitEnum, ProductionAssignment, ProductionAssignmentStatusEnum
from mes import services as mes_services
from mes.models import Device, DeviceCategory
EXTRA_JSON_PATH_SEGMENT_RE = re.compile(r'^[A-Za-z0-9_-]+$')
def _get_employee(request):
employee = getattr(request.user, "employee", None)
if employee is None:
raise serializers.ValidationError("当前用户未关联员工")
return employee
def _employee_payload(employee):
if employee is None:
return None
return {
"id": employee.id,
"name": employee.name,
"merchant_id": employee.merchant_id,
}
def _user_payload(user):
if user is None:
return None
return {
"id": user.id,
"username": user.username,
}
def _parse_extra_filter(extra_path: str | None, extra_value: str | None):
if not extra_path and extra_value is None:
return None, mes_services.UNSET
if not extra_path or extra_value is None:
raise serializers.ValidationError({'detail': 'extra_path 和 extra_value 必须同时传入'})
normalized_segments = [segment.strip() for segment in extra_path.split('.') if segment.strip()]
if not normalized_segments:
raise serializers.ValidationError({'detail': 'extra_path 不合法'})
for segment in normalized_segments:
if not EXTRA_JSON_PATH_SEGMENT_RE.match(segment):
raise serializers.ValidationError({'detail': 'extra_path 不合法'})
try:
parsed_value = json.loads(extra_value)
except json.JSONDecodeError:
parsed_value = extra_value
return normalized_segments, parsed_value
class DeviceCategoryWriteSerializer(serializers.Serializer):
name = serializers.CharField(allow_blank=False, max_length=100)
class DeviceWriteSerializer(serializers.Serializer):
name = serializers.CharField(required=False, allow_blank=False, max_length=100)
category = serializers.IntegerField(required=False, min_value=1)
peak_capacity = serializers.IntegerField(required=False, min_value=1)
capacity_unit = serializers.ChoiceField(required=False, choices=CapacityUnitEnum.choices)
extra = serializers.JSONField(required=False, allow_null=True)
def __init__(self, *args, **kwargs):
self.is_create = kwargs.pop("is_create", False)
super().__init__(*args, **kwargs)
if self.is_create:
self.fields["name"].required = True
self.fields["category"].required = True
self.fields["peak_capacity"].required = True
def validate_category(self, value):
employee = self.context["employee"]
try:
return DeviceCategory.objects.get(id=value, merchant=employee.merchant)
except DeviceCategory.DoesNotExist as exc:
raise serializers.ValidationError("设备分类不存在") from exc
class DeviceCategorySerializer(serializers.ModelSerializer):
created_by = serializers.SerializerMethodField()
operator = serializers.SerializerMethodField()
class Meta:
model = DeviceCategory
fields = [
"id",
"merchant",
"name",
"created_by",
"operator",
"created_at",
"updated_at",
]
read_only_fields = fields
def get_created_by(self, obj):
return _user_payload(obj.created_by)
def get_operator(self, obj):
return _employee_payload(obj.operator)
class DeviceSerializer(serializers.ModelSerializer):
created_by = serializers.SerializerMethodField()
operator = serializers.SerializerMethodField()
category_name = serializers.CharField(source="category.name", read_only=True)
category = serializers.IntegerField(source="category_id", read_only=True)
capacity_unit_label = serializers.CharField(source='get_capacity_unit_display', read_only=True)
class Meta:
model = Device
fields = [
"id",
"merchant",
"category",
"category_name",
"name",
"peak_capacity",
"capacity_unit",
"capacity_unit_label",
"extra",
"created_by",
"operator",
"created_at",
"updated_at",
]
read_only_fields = fields
def get_created_by(self, obj):
return _user_payload(obj.created_by)
def get_operator(self, obj):
return _employee_payload(obj.operator)
class ProductionAssignmentWriteSerializer(serializers.Serializer):
device = serializers.IntegerField(required=False, min_value=1)
content_type = serializers.IntegerField(required=False, min_value=1)
object_id = serializers.IntegerField(required=False, min_value=1)
assigner = serializers.IntegerField(required=False, min_value=1)
assignee = serializers.IntegerField(required=False, min_value=1, allow_null=True)
production_quantity = serializers.IntegerField(required=False, min_value=1)
status = serializers.ChoiceField(required=False, choices=ProductionAssignmentStatusEnum.choices)
extra = serializers.JSONField(required=False, allow_null=True)
def __init__(self, *args, **kwargs):
self.is_create = kwargs.pop("is_create", False)
super().__init__(*args, **kwargs)
if self.is_create:
self.fields["device"].required = True
self.fields["content_type"].required = True
self.fields["object_id"].required = True
self.fields["assigner"].required = True
self.fields["production_quantity"].required = True
def validate_device(self, value):
employee = self.context["employee"]
try:
return Device.objects.get(id=value, merchant=employee.merchant)
except Device.DoesNotExist as exc:
raise serializers.ValidationError("设备不存在") from exc
def validate_content_type(self, value):
try:
return ContentType.objects.get(id=value)
except ContentType.DoesNotExist as exc:
raise serializers.ValidationError("关联对象类型不存在") from exc
def validate_assigner(self, value):
employee = self.context["employee"]
try:
return employee.merchant.employees.get(id=value)
except Exception as exc:
raise serializers.ValidationError("指派者不存在") from exc
def validate_assignee(self, value):
if value is None:
return None
employee = self.context["employee"]
try:
return employee.merchant.employees.get(id=value)
except Exception as exc:
raise serializers.ValidationError("被指派人不存在") from exc
class ProductionAssignmentSerializer(serializers.ModelSerializer):
device_name = serializers.CharField(source='device.name', read_only=True)
assigner = serializers.SerializerMethodField()
assignee = serializers.SerializerMethodField()
created_by = serializers.SerializerMethodField()
operator = serializers.SerializerMethodField()
status_label = serializers.CharField(source='get_status_display', read_only=True)
class Meta:
model = ProductionAssignment
fields = [
'id',
'merchant',
'device',
'device_name',
'content_type',
'object_id',
'assigner',
'assignee',
'production_quantity',
'status',
'status_label',
'extra',
'created_by',
'operator',
'created_at',
'updated_at',
]
read_only_fields = fields
def get_assigner(self, obj):
return _employee_payload(obj.assigner)
def get_assignee(self, obj):
return _employee_payload(obj.assignee)
def get_created_by(self, obj):
return _user_payload(obj.created_by)
def get_operator(self, obj):
return _employee_payload(obj.operator)
class DeviceCategoryListCreateView(APIView):
permission_classes = [permissions.IsAuthenticated]
def get(self, request):
employee = _get_employee(request)
queryset = mes_services.list_device_categories_for_merchant(merchant=employee.merchant)
name = request.query_params.get("name")
if name:
queryset = queryset.filter(name__icontains=name.strip())
return Response(DeviceCategorySerializer(queryset, many=True).data)
def post(self, request):
employee = _get_employee(request)
serializer = DeviceCategoryWriteSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
try:
category = mes_services.create_device_category(
merchant=employee.merchant,
name=serializer.validated_data["name"],
created_by=request.user,
operator=employee,
)
except ValueError as exc:
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
return Response(DeviceCategorySerializer(category).data, status=status.HTTP_201_CREATED)
class DeviceCategoryDetailView(APIView):
permission_classes = [permissions.IsAuthenticated]
def get_object(self, request, category_id):
employee = _get_employee(request)
return get_object_or_404(
mes_services.list_device_categories_for_merchant(merchant=employee.merchant),
id=category_id,
)
def get(self, request, category_id):
category = self.get_object(request, category_id)
return Response(DeviceCategorySerializer(category).data)
def patch(self, request, category_id):
employee = _get_employee(request)
category = self.get_object(request, category_id)
serializer = DeviceCategoryWriteSerializer(data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
try:
category = mes_services.update_device_category(
category=category,
operator=employee,
name=serializer.validated_data.get("name", mes_services.UNSET),
)
except ValueError as exc:
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
return Response(DeviceCategorySerializer(category).data)
def delete(self, request, category_id):
employee = _get_employee(request)
category = self.get_object(request, category_id)
try:
mes_services.delete_device_category(category=category, operator=employee)
except ProtectedError:
return Response({"detail": "设备分类已被设备引用,不能删除"}, status=status.HTTP_400_BAD_REQUEST)
return Response(status=status.HTTP_204_NO_CONTENT)
class DeviceListCreateView(APIView):
permission_classes = [permissions.IsAuthenticated]
def get(self, request):
employee = _get_employee(request)
category_id = request.query_params.get("category")
extra_json_path, extra_json_value = _parse_extra_filter(
request.query_params.get("extra_path"),
request.query_params.get("extra_value"),
)
queryset = mes_services.list_devices_for_merchant(
merchant=employee.merchant,
category_id=int(category_id) if category_id else None,
extra_json_path=extra_json_path,
extra_json_value=extra_json_value,
)
name = request.query_params.get("name")
if name:
queryset = queryset.filter(name__icontains=name.strip())
return Response(DeviceSerializer(queryset, many=True).data)
def post(self, request):
employee = _get_employee(request)
serializer = DeviceWriteSerializer(data=request.data, is_create=True, context={"employee": employee})
serializer.is_valid(raise_exception=True)
try:
device = mes_services.create_device(
merchant=employee.merchant,
category=serializer.validated_data["category"],
name=serializer.validated_data["name"],
created_by=request.user,
operator=employee,
peak_capacity=serializer.validated_data['peak_capacity'],
capacity_unit=serializer.validated_data.get('capacity_unit', CapacityUnitEnum.METER),
extra=serializer.validated_data.get("extra"),
)
except ValueError as exc:
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
return Response(DeviceSerializer(device).data, status=status.HTTP_201_CREATED)
class DeviceDetailView(APIView):
permission_classes = [permissions.IsAuthenticated]
def get_object(self, request, device_id):
employee = _get_employee(request)
return get_object_or_404(
mes_services.list_devices_for_merchant(merchant=employee.merchant),
id=device_id,
)
def get(self, request, device_id):
device = self.get_object(request, device_id)
return Response(DeviceSerializer(device).data)
def patch(self, request, device_id):
employee = _get_employee(request)
device = self.get_object(request, device_id)
serializer = DeviceWriteSerializer(data=request.data, partial=True, context={"employee": employee})
serializer.is_valid(raise_exception=True)
try:
device = mes_services.update_device(
device=device,
operator=employee,
name=serializer.validated_data.get("name", mes_services.UNSET),
category=serializer.validated_data.get("category", mes_services.UNSET),
peak_capacity=serializer.validated_data.get('peak_capacity', mes_services.UNSET),
capacity_unit=serializer.validated_data.get('capacity_unit', mes_services.UNSET),
extra=serializer.validated_data.get("extra", mes_services.UNSET),
)
except ValueError as exc:
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
return Response(DeviceSerializer(device).data)
def delete(self, request, device_id):
employee = _get_employee(request)
device = self.get_object(request, device_id)
mes_services.delete_device(device=device, operator=employee)
return Response(status=status.HTTP_204_NO_CONTENT)
class ProductionAssignmentListCreateView(APIView):
permission_classes = [permissions.IsAuthenticated]
def get(self, request):
employee = _get_employee(request)
device_id = request.query_params.get('device')
content_type_id = request.query_params.get('content_type')
object_id = request.query_params.get('object_id')
status_value = request.query_params.get('status')
queryset = mes_services.list_production_assignments_for_merchant(
merchant=employee.merchant,
device_id=int(device_id) if device_id else None,
content_type_id=int(content_type_id) if content_type_id else None,
object_id=int(object_id) if object_id else None,
status=int(status_value) if status_value else None,
)
return Response(ProductionAssignmentSerializer(queryset, many=True).data)
def post(self, request):
employee = _get_employee(request)
serializer = ProductionAssignmentWriteSerializer(data=request.data, is_create=True, context={'employee': employee})
serializer.is_valid(raise_exception=True)
try:
assignment = mes_services.create_production_assignment(
merchant=employee.merchant,
device=serializer.validated_data['device'],
content_type=serializer.validated_data['content_type'],
object_id=serializer.validated_data['object_id'],
assigner=serializer.validated_data['assigner'],
assignee=serializer.validated_data.get('assignee'),
production_quantity=serializer.validated_data['production_quantity'],
status=serializer.validated_data.get('status', ProductionAssignmentStatusEnum.DRAFT),
created_by=request.user,
operator=employee,
extra=serializer.validated_data.get('extra'),
)
except ValueError as exc:
return Response({'detail': str(exc)}, status=status.HTTP_400_BAD_REQUEST)
return Response(ProductionAssignmentSerializer(assignment).data, status=status.HTTP_201_CREATED)
class ProductionAssignmentDetailView(APIView):
permission_classes = [permissions.IsAuthenticated]
def get_object(self, request, assignment_id):
employee = _get_employee(request)
return get_object_or_404(
mes_services.list_production_assignments_for_merchant(merchant=employee.merchant),
id=assignment_id,
)
def get(self, request, assignment_id):
assignment = self.get_object(request, assignment_id)
return Response(ProductionAssignmentSerializer(assignment).data)
def patch(self, request, assignment_id):
employee = _get_employee(request)
assignment = self.get_object(request, assignment_id)
serializer = ProductionAssignmentWriteSerializer(data=request.data, partial=True, context={'employee': employee})
serializer.is_valid(raise_exception=True)
try:
assignment = mes_services.update_production_assignment(
assignment=assignment,
operator=employee,
device=serializer.validated_data.get('device', mes_services.UNSET),
assignee=serializer.validated_data.get('assignee', mes_services.UNSET),
production_quantity=serializer.validated_data.get('production_quantity', mes_services.UNSET),
status=serializer.validated_data.get('status', mes_services.UNSET),
extra=serializer.validated_data.get('extra', mes_services.UNSET),
)
except ValueError as exc:
return Response({'detail': str(exc)}, status=status.HTTP_400_BAD_REQUEST)
return Response(ProductionAssignmentSerializer(assignment).data)
def delete(self, request, assignment_id):
employee = _get_employee(request)
assignment = self.get_object(request, assignment_id)
mes_services.delete_production_assignment(assignment=assignment, operator=employee)
return Response(status=status.HTTP_204_NO_CONTENT)