diff --git a/apps/mtm/serializers.py b/apps/mtm/serializers.py index 0bd47eba..ffbab9eb 100644 --- a/apps/mtm/serializers.py +++ b/apps/mtm/serializers.py @@ -3,11 +3,12 @@ from apps.mtm.models import (Shift, Material, Mgroup, Team, Goal, Process, Route, TeamMember, RoutePack, Srule, RouteMat) from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE, EXCLUDE_FIELDS_DEPT from rest_framework import serializers -from rest_framework.exceptions import ValidationError, ParseError +from rest_framework.exceptions import ValidationError, ParseError, PermissionDenied from apps.system.models import Dept, UserPost from django.db import transaction from apps.wf.serializers import TicketSimpleSerializer from apps.system.serializers import FileSerializer +from apps.utils.permission import has_perm class ShiftSerializer(CustomModelSerializer): @@ -177,6 +178,23 @@ class RoutePackCopySerializer(serializers.Serializer): material_in = serializers.CharField(label='原料ID') material_out = serializers.CharField(label='产品ID') + +class RouteMaterialOutCreateSerializer(serializers.Serializer): + """工艺路线内快捷新建半成品时允许录入的物料字段。""" + name = serializers.CharField(label='名称', max_length=50) + number = serializers.CharField(label='编号', max_length=100, required=False, allow_blank=True, allow_null=True) + cate = serializers.CharField(label='大类', max_length=20, required=False, allow_blank=True) + model = serializers.CharField(label='型号', max_length=100, required=False, allow_blank=True, allow_null=True) + specification = serializers.CharField(label='规格', max_length=100, required=False, allow_blank=True, allow_null=True) + unit = serializers.CharField(label='基准计量单位', max_length=10, required=False, default='个') + test_mode_in = serializers.ChoiceField( + label='入库检验方式', + choices=(Material.MA_TEST_EXEMPT, Material.MA_TEST_REQUIRED), + required=False, + default=Material.MA_TEST_REQUIRED, + ) + + class RouteSerializer(CustomModelSerializer): name = serializers.CharField(source='__str__', read_only=True) material_ = MaterialSerializer(source='material', read_only=True) @@ -194,6 +212,7 @@ class RouteSerializer(CustomModelSerializer): material_out_is_hidden = serializers.BooleanField( source='material_out.is_hidden', read_only=True) material_out_tracking = serializers.IntegerField(write_only=True, required=False, allow_null=True) + material_out_create = RouteMaterialOutCreateSerializer(write_only=True, required=False) class Meta: model = Route @@ -201,6 +220,8 @@ class RouteSerializer(CustomModelSerializer): read_only_fields = EXCLUDE_FIELDS def validate(self, attrs): + if attrs.get('material_out') and attrs.get('material_out_create'): + raise ParseError('选择已有输出物料和新建半成品不可同时提交') if attrs.get('routepack', None): attrs['material'] = attrs['routepack'].material if 'mgroup' in attrs and attrs['mgroup']: @@ -261,13 +282,31 @@ class RouteSerializer(CustomModelSerializer): 'update_by': user, }) return material_out + + def create_material_out(self, material_data, process, tracking): + if self.request and not has_perm(self.request.user, ['material.create']): + raise PermissionDenied('无新建物料权限') + material_data.update({ + 'type': Material.MA_TYPE_HALFGOOD, + 'process': process.id, + 'tracking': tracking, + 'is_hidden': False, + }) + serializer = MaterialSerializer(data=material_data, context=self.context) + serializer.is_valid(raise_exception=True) + return serializer.save() + @transaction.atomic def create(self, validated_data): process = validated_data['process'] routepack = validated_data.get('routepack', None) + material_out_create = validated_data.pop('material_out_create', None) material_out_tracking = validated_data.pop("material_out_tracking", Material.MA_TRACKING_BATCH) if material_out_tracking is None: material_out_tracking = Material.MA_TRACKING_BATCH + if material_out_create: + validated_data['material_out'] = self.create_material_out( + material_out_create, process, material_out_tracking) if routepack: pass # if Route.objects.filter(routepack=routepack, process=process).exists(): @@ -308,12 +347,17 @@ class RouteSerializer(CustomModelSerializer): # raise ParseError(f"该工艺步骤已存在-{msg}") return instance + @transaction.atomic def update(self, instance, validated_data): validated_data.pop('material', None) - process = validated_data.pop('process', None) + process = validated_data.pop('process', None) or instance.process + material_out_create = validated_data.pop('material_out_create', None) material_out_tracking = validated_data.pop("material_out_tracking", Material.MA_TRACKING_BATCH) if material_out_tracking is None: material_out_tracking = Material.MA_TRACKING_BATCH + if material_out_create: + validated_data['material_out'] = self.create_material_out( + material_out_create, process, material_out_tracking) if not validated_data.get('material_out', instance.material_out) and not instance.material: raise ParseError('未指定输出物料,且工艺包未设置产品无法自动生成;请指定输出物料,或先在工艺包基本信息中选择产品') @@ -384,4 +428,4 @@ class RouteMatSerializer(CustomModelSerializer): class MaterialExportSerializer(CustomModelSerializer): class Meta: model = Material - fields = ["id", "number", "name", "specfication", "unit", "bin_number_main", "cate", "count_safe", "unit_price"] \ No newline at end of file + fields = ["id", "number", "name", "specfication", "unit", "bin_number_main", "cate", "count_safe", "unit_price"] diff --git a/apps/mtm/tests.py b/apps/mtm/tests.py index 7ce503c2..df0e8681 100644 --- a/apps/mtm/tests.py +++ b/apps/mtm/tests.py @@ -1,3 +1,69 @@ from django.test import TestCase -# Create your tests here. +from apps.mtm.models import Material, Process, Route, RoutePack +from apps.mtm.serializers import RouteSerializer + + +class RouteMaterialOutCreateSerializerTests(TestCase): + def setUp(self): + self.process = Process.objects.create(name='切割') + self.product = Material.objects.create( + name='产品A', + number='CP-A', + type=Material.MA_TYPE_GOOD, + process=self.process, + ) + self.material_in = Material.objects.create( + name='原料A', + number='YL-A', + type=Material.MA_TYPE_MAINSO, + ) + self.routepack = RoutePack.objects.create(name='产品A工艺', material=self.product) + + def test_create_route_and_visible_halfgood_in_one_serializer_save(self): + serializer = RouteSerializer(data={ + 'routepack': self.routepack.id, + 'process': self.process.id, + 'material_in': self.material_in.id, + 'material_out_tracking': Material.MA_TRACKING_SINGLE, + 'material_out_create': { + 'name': '分支半成品A', + 'number': 'BCP-A', + 'specification': 'S1', + 'model': 'M1', + 'unit': '件', + }, + }) + + serializer.is_valid(raise_exception=True) + route = serializer.save() + + material_out = route.material_out + self.assertEqual(material_out.type, Material.MA_TYPE_HALFGOOD) + self.assertEqual(material_out.process, self.process) + self.assertEqual(material_out.tracking, Material.MA_TRACKING_SINGLE) + self.assertFalse(material_out.is_hidden) + self.assertEqual(material_out.name, '分支半成品A') + self.assertEqual(material_out.unit, '件') + self.assertTrue(Route.objects.filter(pk=route.pk, material_out=material_out).exists()) + + def test_existing_and_new_material_out_are_mutually_exclusive(self): + existing = Material.objects.create( + name='已有半成品', + number='BCP-OLD', + type=Material.MA_TYPE_HALFGOOD, + process=self.process, + ) + serializer = RouteSerializer(data={ + 'routepack': self.routepack.id, + 'process': self.process.id, + 'material_in': self.material_in.id, + 'material_out': existing.id, + 'material_out_create': { + 'name': '新半成品', + 'number': 'BCP-NEW', + }, + }) + + self.assertFalse(serializer.is_valid()) + self.assertIn('non_field_errors', serializer.errors)