from rest_framework import serializers from rest_framework.exceptions import ValidationError, ParseError from apps.mtm.serializers import MaterialSerializer from apps.pum.models import PuOrder from apps.sam.models import Order from apps.system.models import Dept, User from apps.utils.constants import EXCLUDE_FIELDS_BASE, EXCLUDE_FIELDS_DEPT, EXCLUDE_FIELDS from apps.utils.serializers import CustomModelSerializer from apps.mtm.models import Material from .models import MIO, MaterialBatch, MIOItem, WareHouse, MIOItemA, MaterialBatchA, MIOItemw, Pack from django.db import transaction from django.db.models import F, Sum, DecimalField from server.settings import get_sysconfig from apps.wpmw.models import Wpr from decimal import Decimal class WareHourseSerializer(CustomModelSerializer): class Meta: model = WareHouse fields = '__all__' read_only_fields = EXCLUDE_FIELDS_DEPT class MaterialBatchAListSerializer(CustomModelSerializer): material_ = MaterialSerializer(source='material', read_only=True) class Meta: model = MaterialBatchA fields = ['material', 'batch', 'rate', 'mb', 'id', 'material_'] class MaterialBatchAListSerializer2(CustomModelSerializer): material_name = serializers.StringRelatedField( source='material', read_only=True) class Meta: model = MaterialBatchA fields = ['material', 'batch', 'rate', 'mb', 'id', 'material_name'] class MaterialBatchSerializer(CustomModelSerializer): warehouse_name = serializers.CharField( source='warehouse.name', read_only=True) material_name = serializers.StringRelatedField( source='material', read_only=True) supplier_name = serializers.StringRelatedField( source='supplier', read_only=True) material_ = MaterialSerializer(source='material', read_only=True) defect_name = serializers.CharField(source="defect.name", read_only=True) count_mioing = serializers.IntegerField(read_only=True, label='正在出入库数量') class Meta: model = MaterialBatch fields = '__all__' read_only_fields = EXCLUDE_FIELDS_BASE def to_representation(self, instance): ret = super().to_representation(instance) if 'count' in ret and 'count_mioing' in ret: ret['count_canmio'] = str(Decimal(ret['count']) - Decimal(ret['count_mioing'])) return ret class MaterialBatchDetailSerializer(CustomModelSerializer): warehouse_name = serializers.CharField( source='warehouse.name', read_only=True) material_name = serializers.CharField( source='material.name', read_only=True) material_ = MaterialSerializer(source='material', read_only=True) assemb = MaterialBatchAListSerializer( source='a_mb', read_only=True, many=True) supplier_name = serializers.StringRelatedField( source='supplier', read_only=True) class Meta: model = MaterialBatch fields = '__all__' class MIOListSerializer(CustomModelSerializer): create_by_name = serializers.CharField( source='create_by.name', read_only=True) submit_user_name = serializers.CharField( source='submit_user.name', read_only=True) do_user_name = serializers.CharField( source='do_user.name', read_only=True) mio_user_name = serializers.CharField( source='mio_user.name', read_only=True) belong_dept_name = serializers.CharField( source='belong_dept.name', read_only=True) mgroup_name = serializers.CharField(source='mgroup.name', read_only=True) supplier_name = serializers.CharField( source='supplier.name', read_only=True) customer_name = serializers.CharField( source='customer.name', read_only=True) order_number = serializers.CharField(source='order.number', read_only=True) pu_order_number = serializers.CharField( source='pu_order.number', read_only=True) total_price = serializers.SerializerMethodField(label='总价格') class Meta: model = MIO fields = '__all__' read_only_fields = EXCLUDE_FIELDS + \ ['state', 'submit_time', 'submit_user', 'number'] def get_total_price(self, obj): res = MIOItem.objects.filter(mio=obj, unit_price__isnull=False).aggregate( total=Sum(F('count') * F('unit_price'), output_field=DecimalField(max_digits=18, decimal_places=4))) return res['total'] class MIOItemACreateSerializer(CustomModelSerializer): class Meta: model = MIOItemA fields = ['material', 'batch', 'zhuidu', 'b_zuidawaijing', 'g_zuidaneijing'] class MIOItemwSerializer(CustomModelSerializer): class Meta: model = MIOItemw fields = "__all__" read_only_fields = EXCLUDE_FIELDS_BASE + ["mioitem"] class MIOItemCreateSerializer(CustomModelSerializer): assemb = MIOItemACreateSerializer( label='组合件信息', many=True, write_only=True, required=False) mioitemw = MIOItemwSerializer(many=True, required=False) class Meta: model = MIOItem fields = ['mio', 'warehouse', 'material', 'batch', 'count', 'assemb', 'is_testok', 'mioitemw', 'mb', 'wm', 'unit_price', 'note', "pack_index", "count_send"] extra_kwargs = { 'mio': {'required': True}, 'warehouse': {'required': False}, 'material': {'required': False}, 'batch': {'required': False, "allow_null": True, "allow_blank": True}} def create(self, validated_data): mio:MIO = validated_data['mio'] mio_type = mio.type mb = validated_data.get('mb', None) wm = validated_data.get('wm', None) assemb = validated_data.pop('assemb', []) if mio.type == MIO.MIO_TYPE_DO_IN and not assemb: if not wm: raise ParseError('生产入库必须指定车间库存') elif mio.type == MIO.MIO_TYPE_DO_OUT: if not mb: raise ParseError('生产领料必须指定仓库库存') if mb: validated_data["material"] = mb.material validated_data["batch"] = mb.batch validated_data["warehouse"] = mb.warehouse elif wm: validated_data["material"] = wm.material validated_data["batch"] = wm.batch material: Material = validated_data['material'] batch = validated_data.get("batch", None) if not batch: batch = "无" if batch != '无' and len(batch) < 5: raise ParseError('批次号格式错误') if material.is_hidden: raise ParseError('隐式物料不可出入库') if mio.type in [MIO.MIO_TYPE_RETURN_IN, MIO.MIO_TYPE_BORROW_OUT]: if not material.into_wm: raise ParseError('该物料不可领用或归还') if mio.state != MIO.MIO_CREATE: raise ParseError('出入库记录非创建中不可新增') # 生产领料要校验是否进行检验 # 某些客户此处无需校验 check_test_when_do_out = get_sysconfig('mes.check_test_when_do_out', True) if check_test_when_do_out and mio.type == MIO.MIO_TYPE_DO_OUT: mis = MIOItem.objects.filter(batch=batch, material=material, mio__type__in=[MIO.MIO_TYPE_PUR_IN, MIO.MIO_TYPE_DO_IN, MIO.MIO_TYPE_OTHER_IN]) if mis.exists() and (not mis.exclude(test_date=None).exists()): raise ParseError('该批次的物料未经检验') count = validated_data["count"] batch = validated_data["batch"] mioitemw = validated_data.pop('mioitemw', []) instance:MIOItem = super().create(validated_data) assemb_dict = {} for i in assemb: assemb_dict[i['material'].id] = i if material.is_assemb and '_in' in mio.type: # 仅入库且是组合件的时候需要填写下一级 components = material.components for k, v in components.items(): if k in assemb_dict: mia = assemb_dict[k] MIOItemA.objects.create( mioitem=instance, rate=v, **mia) else: raise ParseError('缺少组合件') if material.tracking == Material.MA_TRACKING_SINGLE: if len(mioitemw) == 0: if mb: wpr_qs = Wpr.get_qs_by_mb(mb) if wpr_qs.count() == validated_data["count"]: for item in wpr_qs: MIOItemw.objects.create(mioitem=instance, number=item.number, wpr=item) else: raise ParseError('请提供产品明细编号') elif wm: wpr_qs = Wpr.get_qs_by_wm(wm) if wpr_qs.count() == validated_data["count"]: for item in wpr_qs: MIOItemw.objects.create(mioitem=instance, number=item.number, wpr=item) else: raise ParseError('请提供产品明细编号') elif mio.type in [MIO.MIO_TYPE_PUR_IN, MIO.MIO_TYPE_OTHER_IN] and count==1: MIOItemw.objects.create(mioitem=instance, number=batch) else: raise ParseError('不支持自动生成请提供产品明细') elif len(mioitemw) >= 1: mio_type = mio.type if mio_type != "pur_in" and mio_type != "other_in": wprIds = [i["wpr"].id for i in mioitemw] if instance.mb: mb_ids = list(Wpr.objects.filter(id__in=wprIds).values_list("mb__id", flat=True).distinct()) if len(mb_ids) == 1 and mb_ids[0] == instance.mb.id: pass else: raise ParseError(f'{batch}物料明细中存在{len(mb_ids)}个不同物料批次') elif instance.wm: wm_ids = list(Wpr.objects.filter(id__in=wprIds).values_list("wm__id", flat=True).distinct()) if len(wm_ids) == 1 and wm_ids[0] == instance.wm.id: pass else: raise ParseError(f'{batch}物料明细中存在{len(wm_ids)}个不同物料批次') for item in mioitemw: if item.get("wpr", None) is None and mio_type != "pur_in" and mio_type != "other_in": raise ParseError(f'{item["number"]}_请提供产品明细ID') elif item.get("number_out", None) is not None and mio_type != MIO.MIO_TYPE_SALE_OUT: raise ParseError(f'{item["number"]}_非销售出库不可赋予产品对外编号') else: MIOItemw.objects.create(mioitem=instance, **item) return instance class MIOItemAListSerializer(CustomModelSerializer): material_ = MaterialSerializer(source='material', read_only=True) material_name = serializers.StringRelatedField( source='material', read_only=True) class Meta: model = MIOItemA fields = "__all__" read_only_fields = EXCLUDE_FIELDS_BASE class MIOItemListSimpleSerializer(CustomModelSerializer): warehouse_name = serializers.CharField(source='warehouse.name', read_only=True) material_name = serializers.StringRelatedField( source='material', read_only=True) class Meta: model = MIOItem fields = ["id", "mio", "material", "warehouse", "material_name", "warehouse_name", "batch", "count", "test_date", "count_notok"] class MIOItemSerializer(CustomModelSerializer): warehouse_name = serializers.CharField(source='warehouse.name', read_only=True) material_ = MaterialSerializer(source='material', read_only=True) assemb = serializers.SerializerMethodField(label="组合件信息") material_name = serializers.StringRelatedField( source='material', read_only=True) inout_date = serializers.DateField(source='mio.inout_date', read_only=True) test_user_name = serializers.CharField(source='test_user.name', read_only=True) mioitemw = MIOItemwSerializer(many=True, required=False) class Meta: model = MIOItem fields = '__all__' def to_representation(self, instance): ret = super().to_representation(instance) ret["price"] = None if ret["unit_price"] is not None: ret["price"] = Decimal(ret["count"]) * Decimal(ret["unit_price"]) return ret def get_assemb(self, obj): qs = MIOItemA.objects.filter(mioitem=obj) if qs.exists(): return MIOItemAListSerializer(qs, many=True).data elif obj.mb and obj.mb.material.is_assemb: return MaterialBatchAListSerializer2(MaterialBatchA.objects.filter(mb=obj.mb), many=True).data return None class MioItemDetailSerializer(MIOItemSerializer): mio_ = MIOListSerializer(source='mio', read_only=True) class MIODoSerializer(CustomModelSerializer): belong_dept = serializers.PrimaryKeyRelatedField( label="车间", queryset=Dept.objects.all(), required=False) do_user = serializers.PrimaryKeyRelatedField( label="执行人", queryset=User.objects.all(), required=True) note = serializers.CharField( required=False, allow_blank=True) class Meta: model = MIO fields = ['id', 'number', 'note', 'do_user', 'belong_dept', 'type', 'inout_date', 'mgroup', 'mio_user', 'type'] extra_kwargs = {'inout_date': {'required': True}, 'do_user': {'required': True}, 'number': {"required": False, "allow_blank": True}, 'type': {'required': True}} def validate(self, attrs): if 'mgroup' in attrs and attrs['mgroup']: attrs['belong_dept'] = attrs['mgroup'].belong_dept if not attrs.get("belong_dept", None): raise ParseError('请选择车间或工段') return attrs def create(self, validated_data): type = validated_data['type'] if type in [MIO.MIO_TYPE_DO_IN, MIO.MIO_TYPE_DO_OUT, MIO.MIO_TYPE_BORROW_OUT, MIO.MIO_TYPE_RETURN_IN]: pass else: raise ValidationError('出入库类型错误') if not validated_data.get("number", None): validated_data["number"] = MIO.get_a_number(validated_data["type"]) return super().create(validated_data) def update(self, instance, validated_data): validated_data.pop('type', None) return super().update(instance, validated_data) class MIOSaleSerializer(CustomModelSerializer): # order = serializers.PrimaryKeyRelatedField( # label="订单", queryset=Order.objects.all(), required=True) note = serializers.CharField( required=False, allow_blank=True) class Meta: model = MIO fields = ['id', 'number', 'note', 'order', 'inout_date', 'customer', 'mio_user'] extra_kwargs = {'inout_date': {'required': True}, 'number': {"required": False, "allow_blank": True}} def create(self, validated_data): validated_data['type'] = MIO.MIO_TYPE_SALE_OUT order: Order = validated_data.get('order', None) if not validated_data.get("number", None): validated_data["number"] = MIO.get_a_number(validated_data["type"]) if order: if order.state in [Order.ORDER_CREATE, Order.ORDER_DELIVERED]: raise ValidationError('销售订单状态错误') validated_data['customer'] = order.customer if order.belong_dept: validated_data['belong_dept'] = order.belong_dept return super().create(validated_data) def update(self, instance, validated_data): validated_data.pop('type', None) return super().update(instance, validated_data) class MIOPurSerializer(CustomModelSerializer): # pu_order = serializers.PrimaryKeyRelatedField( # label="采购订单", queryset=PuOrder.objects.all(), required=True) note = serializers.CharField( required=False, allow_blank=True) class Meta: model = MIO fields = ['id', 'number', 'note', 'pu_order', 'inout_date', 'supplier', 'mio_user', 'type'] extra_kwargs = {'inout_date': {'required': True}, 'number': {"required": False, "allow_blank": True}, 'type': {'required': True}} def create(self, validated_data): type = validated_data["type"] if type in [MIO.MIO_TYPE_PUR_IN, MIO.MIO_TYPE_PUR_OUT]: pass else: raise ValidationError('出入库类型错误') if not validated_data.get("number", None): validated_data["number"] = MIO.get_a_number(validated_data["type"]) pu_order: PuOrder = validated_data.get('pu_order', None) if pu_order: if pu_order.state in [PuOrder.PUORDER_CREATE, PuOrder.PUORDER_DONE]: raise ValidationError('采购订单状态错误') validated_data['supplier'] = pu_order.supplier if pu_order.belong_dept: validated_data['belong_dept'] = pu_order.belong_dept return super().create(validated_data) def update(self, instance, validated_data): validated_data.pop('type', None) return super().update(instance, validated_data) class MIOOtherSerializer(CustomModelSerializer): note = serializers.CharField( required=False, allow_blank=True) class Meta: model = MIO fields = ['id', 'number', 'note', 'supplier', 'customer', 'type', 'inout_date', 'mio_user'] extra_kwargs = {'inout_date': {'required': True}, 'number': {"required": False, "allow_blank": True}} def create(self, validated_data): if not validated_data.get("number", None): validated_data["number"] = MIO.get_a_number(validated_data["type"]) if validated_data['type'] not in [MIO.MIO_TYPE_OTHER_OUT, MIO.MIO_TYPE_OTHER_IN]: raise ValidationError('出入库类型错误') return super().create(validated_data) def update(self, instance, validated_data): validated_data.pop('type', None) return super().update(instance, validated_data) class MIODetailSerializer(MIOListSerializer): items = MIOItemSerializer(source='item_mio', many=True, read_only=True) class MIOItemTestSerializer(CustomModelSerializer): class Meta: model = MIOItem fields = ['id', 'test_date', 'test_user', 'count_notok', 'count_n_zw', 'count_n_tw', 'count_n_qp', 'count_n_wq', 'count_n_dl', 'count_n_pb', 'count_n_dxt', 'count_n_js', 'count_n_qx', 'count_n_zz', 'count_n_ysq', 'count_n_qt', 'count_n_hs', 'count_n_b', 'test_note'] extra_kwargs = { 'test_date': {'required': True}, 'test_user': {'required': True} } def validate(self, attrs): count_notok = 0 for i in attrs: if 'count_n_' in i: count_notok = count_notok + attrs[i] attrs['count_notok'] = count_notok return attrs class MioItemAnaSerializer(serializers.Serializer): start_date = serializers.DateField(label='开始日期', required=True) end_date = serializers.DateField(label='结束日期', required=True) material_cate = serializers.CharField(label='物料系列', required=True) class MIOItemPurInTestSerializer(CustomModelSerializer): class Meta: model = MIOItem fields = ['id', 'test_date', 'test_user', 'count_bag', 'count_sampling', 'weight_kgs', 'is_testok', 'test_note'] extra_kwargs = { 'test_date': {'required': True}, 'test_user': {'required': True} } def validate(self, attrs): if 'count_sampling' in attrs and attrs['count_sampling']: pass else: weight_kgs = attrs['weight_kgs'] attrs['weight_kgs'] = [float(i) for i in weight_kgs] attrs['count_sampling'] = len(attrs['weight_kgs']) return super().validate(attrs) class PackSerializer(CustomModelSerializer): class Meta: model = Pack fields = "__all__" read_only_fields = EXCLUDE_FIELDS_BASE def create(self, validated_data): index = validated_data["index"] mio = validated_data["mio"] if Pack.objects.filter(mio=mio, index=index).exists(): raise ParseError('包装箱已存在') return super().create(validated_data) class PackMioSerializer(serializers.Serializer): mioitems = serializers.ListField(child=serializers.CharField(), label="明细ID") pack_index = serializers.IntegerField(label="包装箱序号") # pack = serializers.CharField(label="包装箱ID")