Support manual route output materials

This commit is contained in:
caoqianming 2026-07-29 08:57:20 +08:00
parent d5d940c5e4
commit bcbb27374f
2 changed files with 114 additions and 4 deletions

View File

@ -3,11 +3,12 @@ from apps.mtm.models import (Shift, Material, Mgroup, Team, Goal,
Process, Route, TeamMember, RoutePack, Srule, RouteMat) Process, Route, TeamMember, RoutePack, Srule, RouteMat)
from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE, EXCLUDE_FIELDS_DEPT from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE, EXCLUDE_FIELDS_DEPT
from rest_framework import serializers 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 apps.system.models import Dept, UserPost
from django.db import transaction from django.db import transaction
from apps.wf.serializers import TicketSimpleSerializer from apps.wf.serializers import TicketSimpleSerializer
from apps.system.serializers import FileSerializer from apps.system.serializers import FileSerializer
from apps.utils.permission import has_perm
class ShiftSerializer(CustomModelSerializer): class ShiftSerializer(CustomModelSerializer):
@ -177,6 +178,23 @@ class RoutePackCopySerializer(serializers.Serializer):
material_in = serializers.CharField(label='原料ID') material_in = serializers.CharField(label='原料ID')
material_out = 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): class RouteSerializer(CustomModelSerializer):
name = serializers.CharField(source='__str__', read_only=True) name = serializers.CharField(source='__str__', read_only=True)
material_ = MaterialSerializer(source='material', read_only=True) material_ = MaterialSerializer(source='material', read_only=True)
@ -194,6 +212,7 @@ class RouteSerializer(CustomModelSerializer):
material_out_is_hidden = serializers.BooleanField( material_out_is_hidden = serializers.BooleanField(
source='material_out.is_hidden', read_only=True) source='material_out.is_hidden', read_only=True)
material_out_tracking = serializers.IntegerField(write_only=True, required=False, allow_null=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: class Meta:
model = Route model = Route
@ -201,6 +220,8 @@ class RouteSerializer(CustomModelSerializer):
read_only_fields = EXCLUDE_FIELDS read_only_fields = EXCLUDE_FIELDS
def validate(self, attrs): def validate(self, attrs):
if attrs.get('material_out') and attrs.get('material_out_create'):
raise ParseError('选择已有输出物料和新建半成品不可同时提交')
if attrs.get('routepack', None): if attrs.get('routepack', None):
attrs['material'] = attrs['routepack'].material attrs['material'] = attrs['routepack'].material
if 'mgroup' in attrs and attrs['mgroup']: if 'mgroup' in attrs and attrs['mgroup']:
@ -262,12 +283,30 @@ class RouteSerializer(CustomModelSerializer):
}) })
return material_out 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): def create(self, validated_data):
process = validated_data['process'] process = validated_data['process']
routepack = validated_data.get('routepack', None) 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) material_out_tracking = validated_data.pop("material_out_tracking", Material.MA_TRACKING_BATCH)
if material_out_tracking is None: if material_out_tracking is None:
material_out_tracking = Material.MA_TRACKING_BATCH 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: if routepack:
pass pass
# if Route.objects.filter(routepack=routepack, process=process).exists(): # if Route.objects.filter(routepack=routepack, process=process).exists():
@ -308,12 +347,17 @@ class RouteSerializer(CustomModelSerializer):
# raise ParseError(f"该工艺步骤已存在-{msg}") # raise ParseError(f"该工艺步骤已存在-{msg}")
return instance return instance
@transaction.atomic
def update(self, instance, validated_data): def update(self, instance, validated_data):
validated_data.pop('material', None) 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) material_out_tracking = validated_data.pop("material_out_tracking", Material.MA_TRACKING_BATCH)
if material_out_tracking is None: if material_out_tracking is None:
material_out_tracking = Material.MA_TRACKING_BATCH 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: if not validated_data.get('material_out', instance.material_out) and not instance.material:
raise ParseError('未指定输出物料,且工艺包未设置产品无法自动生成;请指定输出物料,或先在工艺包基本信息中选择产品') raise ParseError('未指定输出物料,且工艺包未设置产品无法自动生成;请指定输出物料,或先在工艺包基本信息中选择产品')

View File

@ -1,3 +1,69 @@
from django.test import TestCase 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)