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)
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"]
fields = ["id", "number", "name", "specfication", "unit", "bin_number_main", "cate", "count_safe", "unit_price"]

View File

@ -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)