from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE
from apps.utils.serializers import CustomModelSerializer
from rest_framework import serializers
from rest_framework.exceptions import ParseError
from datetime import datetime

from .models import (SfLog, StLog, SfLogExp, WMaterial, Mlog, 
                    Handover, Handoverb, Mlogb, AttLog,
                     OtherLog, Fmlog, BatchSt, Mlogbw, Handoverbw, MlogbDefect, MlogUser, BatchLog)
from apps.system.models import Dept, User
from apps.system.serializers import UserSimpleSerializer
from apps.pm.models import Mtask, Mtaskb
from apps.wpm.tasks import cal_enstat_when_pcoal_heat_change, cal_enstat_when_team_change, cal_exp_duration_sec
from apps.wpm.services import get_sflog, find_material_can_change, generate_new_batch
from apps.mtm.models import Mgroup, TeamMember, Shift, Material, Route, Process, WmScope
from apps.mtm.serializers import MaterialSimpleSerializer
from django.db import transaction
from django.utils import timezone
from django.core.cache import cache
from django.utils.timezone import localdate
from apps.qm.models import NotOkOption, Qct
from apps.wf.serializers import TicketSimpleSerializer
from apps.wpmw.models import Wpr
from apps.qm.serializers import FtestProcessSerializer, FtestProcessListSerializer
import logging
from apps.qm.models import Defect
from apps.utils.snowflake import idWorker
from decimal import Decimal
from apps.em.models import Equipment
from django.db.models import Q
mylogger = logging.getLogger("log")

class OtherLogSerializer(CustomModelSerializer):
    class Meta:
        model = OtherLog
        fields = '__all__'
        read_only_fields = EXCLUDE_FIELDS


class StLogSerializer(CustomModelSerializer):
    mgroup_name = serializers.CharField(source='mgroup.name', read_only=True)
    current_sflog = serializers.CharField(label='当前处理值班', write_only=True)
    current_note = serializers.CharField(
        label='值班处理备注', write_only=True, allow_blank=True)

    class Meta:
        model = StLog
        fields = '__all__'
        read_only_fields = EXCLUDE_FIELDS + \
            ['is_shutdown', 'sflog', 'duration']

    def create(self, validated_data):
        current_sflog_id = validated_data.pop('current_sflog')
        current_note = validated_data.pop('current_note', '')
        sflog = get_sflog(
            validated_data['mgroup'], validated_data['start_time'])
        if current_sflog_id != sflog.id:
            raise ParseError('值班时间与发生时间不一致')
        instance = super().create(validated_data)
        instance.sflog = sflog
        instance.save()
        SfLogExp.objects.create(
            sflog=sflog, stlog=instance, create_by=self.context['request'].user, note=current_note)
        return instance

    def update(self, instance, validated_data):
        if instance.is_shutdown:  # 停机不可编辑end_time
            validated_data.pop('end_time', None)
        validated_data.pop('mgroup', None)
        validated_data.pop('start_time', None)
        end_time = validated_data.pop('end_time', None)
        current_sflog = validated_data.pop('current_sflog')
        current_note = validated_data.pop('current_note', '')
        instance = super().update(instance, validated_data)
        if end_time:  # 需要把涉及到的sflog都拉入
            cal_exp_duration_sec(instance.id)
        try:
            sflogexp = SfLogExp.objects.get(
                stlog=instance, sflog=current_sflog)
            sflogexp.note = current_note
            sflogexp.save()
        except SfLogExp.DoesNotExist:
            raise ParseError('该异常无需本班填写')
        return instance

    def validate(self, attrs):
        now = timezone.now()
        start_time: datetime = attrs['start_time']
        end_time: datetime = attrs.get('end_time', None)
        if start_time > now:
            raise ParseError('开始时间应为历史时间')
        if end_time:
            if end_time > now:
                raise ParseError('开始时间应为历史时间')
            if end_time > start_time:
                attrs['duration_sec'] = (
                    end_time - start_time).total_seconds()
            else:
                raise ParseError('结束时间应大于开始时间')
        return super().validate(attrs)


class SfLogSerializer(CustomModelSerializer):
    mgroup_name = serializers.CharField(source='mgroup.name', read_only=True)
    team_name = serializers.CharField(source='team.name', read_only=True)
    shift_name = serializers.CharField(source='shift.name', read_only=True)
    leader_name = serializers.CharField(source='leader.name', read_only=True)

    class Meta:
        model = SfLog
        fields = '__all__'
        read_only_fields = EXCLUDE_FIELDS + \
            ['mgroup', 'start_time', 'end_time', 'belong_dept']
        extra_kwargs = {
            'team': {'required': True},
            'leader': {'required': True}
        }

    def update(self, instance, validated_data):
        old_pcoal_heat = instance.pcoal_heat
        old_team = instance.team
        instance: SfLog = super().update(instance, validated_data)
        new_pcoal_heat = instance.pcoal_heat
        new_team = instance.team
        mgroup: Mgroup = instance.mgroup

        # 更新煤粉热值触发计算
        if instance.mgroup.name == '回转窑' and instance.shift.name in ['早班', '白班']:
            mylogger.info(f'回转窑白班/早班{instance.get_ymd}更新{new_pcoal_heat}-{old_pcoal_heat}')
            year_s, month_s, day_s = instance.get_ymd
            if new_pcoal_heat:  # 更新一下缓存
                cache.set(f'pcoal_heat_{year_s}_{month_s}_{day_s}', new_pcoal_heat, timeout=60 * 60 * 48)
            if new_pcoal_heat != old_pcoal_heat:
                cal_enstat_when_pcoal_heat_change.delay(year_s, month_s, day_s)

        # 更新班组触发计算
        if new_team != old_team:
            default_state = 'pending'
            if timezone.now() > instance.end_time:
                default_state = 'normal'
            # 分配班组时创建人员到岗情况
            for item in TeamMember.objects.filter(team=new_team, mgroup=instance.mgroup):
                AttLog.objects.get_or_create(sflog=instance, user=item.user, defaults={
                                                'sflog': instance, 'user': item.user, 'post': item.post, 'state': default_state, 'create_by': self.context['request'].user})
            if mgroup.need_enm:
                from apps.qm.tasks import cal_quastat_sflog
                cal_enstat_when_team_change.delay(instance.id)
                cal_quastat_sflog.delay(instance.id)
        return instance
        
    def to_representation(self, instance):
        ret = super().to_representation(instance)
        if "shut_sec" in ret:
            ret["shut_hour"] = "{:.2f}".format(round(ret['shut_sec']/3600, 2))
        return ret
    # def to_internal_value(self, data):
    #     if hasattr(self.Meta, 'update_fields') and self.context['request'].method in ['PUT', 'PATCH']:
    #         u_fields = self.Meta.update_fields
    #         new_data = {key: data[key] for key in u_fields if key in data}
    #         return super().to_internal_value(new_data)
    #     return super().to_internal_value(data)


class SflogExpSerializer(CustomModelSerializer):
    # mgroup = serializers.CharField(source='sflog.mgroup.id', read_only=True)
    # mgroup_name = serializers.CharField(
    #     source='sflog.mgroup.name', read_only=True)
    # stlog_ = StLogSerializer(source='stlog', read_only=True)
    # happen_time = serializers.DateTimeField(required=True, label='发生时间')
    # cate = serializers.CharField(required=True, label='原因类别')
    sflog_ = SfLogSerializer(source='sflog', read_only=True)
    stlog_ = StLogSerializer(source='stlog', read_only=True)

    class Meta:
        model = SfLogExp
        fields = '__all__'
        read_only_fields = EXCLUDE_FIELDS + ['sflog', 'stlog', 'duration']


class WMaterialSerializer(CustomModelSerializer):
    material_ = MaterialSimpleSerializer(source='material', read_only=True)
    supplier_name = serializers.CharField(source='supplier.name', read_only=True)
    material_name = serializers.StringRelatedField(
        source='material', read_only=True)
    mgroup_name = serializers.StringRelatedField(source='mgroup.name', read_only=True)
    belong_dept_name = serializers.CharField(
        source='belong_dept.name', read_only=True)
    material_ofrom_name = serializers.StringRelatedField(source='material_ofrom', read_only=True)
    material_origin_name = serializers.StringRelatedField(source='material_origin', read_only=True)
    notok_sign_name = serializers.SerializerMethodField()
    defect_name = serializers.CharField(source="defect.name", read_only=True)
    count_working = serializers.SerializerMethodField(label='在制数量')
    count_handovering = serializers.SerializerMethodField(label='正在交送的数量')

    def get_notok_sign_name(self, obj):
        return getattr(NotOkOption, obj.notok_sign, NotOkOption.qt).label if obj.notok_sign else None

    def get_count_working(self, obj):
        # 列表接口 queryset 已注解(单次聚合); 嵌套等无注解场景回退模型属性
        # 保留 decimal 精度(原 IntegerField 会截断在途量, 导致可用/可交接量偏大)
        return obj.count_working_anno if hasattr(obj, 'count_working_anno') else obj.count_working

    def get_count_handovering(self, obj):
        return obj.count_handovering_anno if hasattr(obj, 'count_handovering_anno') else obj.count_handovering

    class Meta:
        model = WMaterial
        fields = '__all__'

    def to_representation(self, instance):
        ret = super().to_representation(instance)
        if 'count' in ret:
            # 优先用已输出字段, 否则用注解值; 都没有(调用方用 query 关闭了该组字段)则不算, 避免误触发 N+1
            if 'count_working' in ret:
                working = ret['count_working']
            elif hasattr(instance, 'count_working_anno'):
                working = instance.count_working_anno
            else:
                working = None
            if working is not None:
                ret['count_cando'] = str(Decimal(ret['count']) - Decimal(working))
            if 'count_handovering' in ret:
                handovering = ret['count_handovering']
            elif hasattr(instance, 'count_handovering_anno'):
                handovering = instance.count_handovering_anno
            else:
                handovering = None
            if handovering is not None:
                ret['count_canhandover'] = str(Decimal(ret['count']) - Decimal(handovering))
        return ret

class WMaterialCreateSerializer(CustomModelSerializer):
    class Meta:
        model = WMaterial
        fields = ['material', 'count', 'batch', 'mgroup', 'belong_dept']
        extra_kwargs = {
            'material': {'required': True},
            'count': {'required': True},
            'batch': {'required': True},
            'mgroup': {'required': False, 'allow_null': True},
            'belong_dept': {'required': False, 'allow_null': True},
        }

    def validate(self, attrs):
        mgroup = attrs.get('mgroup')
        if mgroup:
            attrs['belong_dept'] = mgroup.belong_dept
        return attrs

    @transaction.atomic
    def create(self, validated_data):
        lookup = {
            field: validated_data.pop(field)
            for field in WMaterial.INVENTORY_KEY_FIELDS
            if field in validated_data
        }
        instance, created = WMaterial.locked_get_or_create_inventory(
            **lookup,
            defaults=validated_data,
        )
        if not created:
            raise serializers.ValidationError('相同业务键的车间库存已存在')
        return instance


class MlogbDefectSerializer(CustomModelSerializer):
    defect_name = serializers.CharField(source="defect.name", read_only=True)
    defect_okcate = serializers.CharField(source="defect.okcate", read_only=True)
    class Meta:
        model = MlogbDefect
        fields = ["id", "defect_name", "count", "mlogb", "defect", "defect_okcate", "count_has", "is_inherited"]
        read_only_fields = EXCLUDE_FIELDS_BASE + ["mlogb"]
        extra_kwargs = {
            'count_has': {'required': False},
            'is_inherited': {'required': False},
        }
    
    def validate(self, attrs):
        if attrs["count"] < 0:
            raise serializers.ValidationError("存在负数!")
        if "count_has" not in attrs or attrs["count_has"] < attrs["count"]:
            attrs["count_has"] = attrs["count"]
        return attrs

class MlogbSerializer(CustomModelSerializer):
    material_out_ = MaterialSimpleSerializer(
        source='material_out', read_only=True)
    material_out_name = serializers.StringRelatedField(
        source='material_out', read_only=True)

    class Meta:
        model = Mlogb
        fields = ['id', 'material_out', 'count_ok',
                  'material_out_', 'material_out_name', "mlog"]
        extra_kwargs = {
            'material_out': {'required': True, 'allow_null': False}}
        read_only_fields = EXCLUDE_FIELDS_BASE + ["mlog"]
        

class MlogListSerializer(CustomModelSerializer):
    team_name = serializers.CharField(source="team.name", read_only=True)
    mstate_json = serializers.JSONField(source='mgroup.process.mstate_json', read_only=True)
    supplier_name = serializers.CharField(source='supplier.name', read_only=True)
    # routepack_name = serializers.CharField(source='route.routepack.name', read_only=True)
    belong_dept = serializers.CharField(
        source='mgroup.belong_dept.id', read_only=True)
    belong_dept_name = serializers.CharField(
        source='mgroup.belong_dept.name', read_only=True)
    mgroup_name = serializers.CharField(
        source='mgroup.name', read_only=True)
    mtask_count = serializers.IntegerField(
        source='mtask.count', read_only=True)
    create_by_name = serializers.CharField(
        source='create_by.name', read_only=True)
    update_by_name = serializers.CharField(
        source='update_by.name', read_only=True)
    # + 0.3s
    material_in_ = MaterialSimpleSerializer(
        source='material_in', read_only=True)
    material_out_ = MaterialSimpleSerializer(
        source='material_out', read_only=True)
    material_out_name = serializers.StringRelatedField(
        source='material_out', read_only=True)
    material_in_name = serializers.StringRelatedField(
        source='material_in', read_only=True)
    #
    handle_user_name = serializers.CharField(
        source='handle_user.name', read_only=True)
    handle_user_2_name = serializers.CharField(
        source='handle_user_2.name', read_only=True)
    handle_leader_name = serializers.CharField(
        source='handle_leader.name', read_only=True)
    equipment_name = serializers.StringRelatedField(
        source='equipment', read_only=True)
    equipment_2_name = serializers.StringRelatedField(
        source='equipment_2', read_only=True)
    shift_name = serializers.CharField(source='shift.name', read_only=True)
    test_user_name = serializers.CharField(source='test_user.name', read_only=True)
    ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
    submit_user_name = serializers.CharField(source='submit_user.name', read_only=True)

    handle_users_ = UserSimpleSerializer(
        source='handle_users', many=True, read_only=True)
    wm_in_defect = serializers.PrimaryKeyRelatedField(source='wm_in.defect', read_only=True)
    wm_in_defect_name = serializers.CharField(source='wm_in.defect.name', read_only=True)
    wm_in_state = serializers.IntegerField(source='wm_in.state', read_only=True)

    class Meta:
        model = Mlog
        fields = '__all__'
        # exclude = ["equipments","handle_users", "material_outs"]


class MlogbDetailSerializer(CustomModelSerializer):
    material_out_name = serializers.StringRelatedField(
        source='material_out', read_only=True)
    material_in_name = serializers.StringRelatedField(source='material_in', read_only=True)
    material_out_tracking = serializers.IntegerField(source="material_out.tracking", read_only=True)
    mlogbdefect = MlogbDefectSerializer(many=True, read_only=True)
    test_user_name = serializers.CharField(source='test_user.name', read_only=True)
    wm_in_defect = serializers.PrimaryKeyRelatedField(source='wm_in.defect', read_only=True)
    wm_in_defect_name = serializers.CharField(source='wm_in.defect.name', read_only=True)
    wm_in_state = serializers.IntegerField(source='wm_in.state', read_only=True)

    class Meta:
        model = Mlogb
        fields = '__all__'

class MlogSerializer(CustomModelSerializer):
    team_name = serializers.CharField(source="team.name", read_only=True)
    mstate_json = serializers.JSONField(source='mgroup.process.mstate_json', read_only=True)
    supplier_name = serializers.CharField(source='supplier.name', read_only=True)
    # routepack_name = serializers.CharField(source='route.routepack.name', read_only=True)
    belong_dept = serializers.CharField(
        source='mgroup.belong_dept.id', read_only=True)
    belong_dept_name = serializers.CharField(
        source='mgroup.belong_dept.name', read_only=True)
    mgroup_name = serializers.CharField(
        source='mgroup.name', read_only=True)
    mtask_count = serializers.IntegerField(
        source='mtask.count', read_only=True)
    create_by_name = serializers.CharField(
        source='create_by.name', read_only=True)
    update_by_name = serializers.CharField(
        source='update_by.name', read_only=True)
    material_in_ = MaterialSimpleSerializer(
        source='material_in', read_only=True)
    material_out_ = MaterialSimpleSerializer(
        source='material_out', read_only=True)
    material_out_name = serializers.StringRelatedField(
        source='material_out', read_only=True)
    material_in_name = serializers.StringRelatedField(
        source='material_in', read_only=True)
    handle_user_name = serializers.CharField(
        source='handle_user.name', read_only=True)
    handle_user_2_name = serializers.CharField(
        source='handle_user_2.name', read_only=True)
    handle_leader_name = serializers.CharField(
        source='handle_leader.name', read_only=True)
    equipment_name = serializers.StringRelatedField(
        source='equipment', read_only=True)
    equipment_2_name = serializers.StringRelatedField(
        source='equipment_2', read_only=True)
    shift_name = serializers.CharField(source='shift.name', read_only=True)
    mlogb = MlogbSerializer(
        label='多产出件信息', many=True, required=False)
    mlogb_full = MlogbDetailSerializer(
        label='物料信息', many=True, read_only=True)
    handle_users_ = UserSimpleSerializer(
        source='handle_users', many=True, read_only=True)
    ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
    test_user_name = serializers.CharField(source='test_user.name', read_only=True)
    submit_user_name = serializers.CharField(source='submit_user.name', read_only=True)

    mlogdefect = MlogbDefectSerializer(many=True, required=False)
    mlogindefect = MlogbDefectSerializer(many=True, label="前道不良", required=False)
    wm_in_defect = serializers.PrimaryKeyRelatedField(source='wm_in.defect', read_only=True)
    wm_in_defect_name = serializers.CharField(source='wm_in.defect.name', read_only=True)
    wm_in_state = serializers.IntegerField(source='wm_in.state', read_only=True)
    class Meta:
        model = Mlog
        fields = '__all__'
        read_only_fields = EXCLUDE_FIELDS + \
            ['submit_time', 'submit_user', 'material_outs']
        extra_kwargs = {
           "batch": {"required": True},
           "shift": {"required": False},
           "material_out": {"required": True},
           "work_start_time": {"required": False}
        }

    def create(self, validated_data):
        material_out = validated_data['material_out']
        mtask:Mtask = validated_data.get('mtask', None)
        mlogdefect = validated_data.pop('mlogdefect', None)
        mlogbindefect = validated_data.pop('mlogindefect', None)
        if mtask and mtask.state != Mtask.MTASK_ASSGINED:
            raise ParseError('该任务非下达中不可选择')
        
        mlogb = validated_data.pop('mlogb', [])
        instance: Mlog = super().create(validated_data)
        ## 返工没有加工前不良
        if instance.is_fix and instance.count_pn_jgqbl >0:
            raise ParseError("返工不支持加工前不良")
        # 自动生成mlogb
        batch_in = instance.batch
        wm_in = instance.wm_in
        if wm_in:
            batch_in = wm_in.batch
            instance.batch = batch_in
            instance.save(update_fields=['batch'])
        mlogbin = None
        if instance.material_in: # 如果有消耗
            add_dict = {
                'mlog': instance, 'batch': batch_in, 'wm_in': wm_in,
                'mtask': instance.mtask, 'material_in': instance.material_in,
                'route': instance.route,
                'count_use': instance.count_use, 'count_break': instance.count_break, 
                'count_pn_jgqbl': instance.count_pn_jgqbl
            }
            if wm_in:
                add_dict['batch_ofrom'] = wm_in.batch_ofrom
                add_dict['material_ofrom'] = wm_in.material_ofrom
            mlogbin = Mlogb.objects.create(**add_dict)
            if mlogbindefect is not None:
                mlogbin_defect_objects = [
                    MlogbDefect(**{**item, "mlogb": mlogbin, "id": idWorker.get_id()})
                    for item in mlogbindefect if item["count"] > 0
                ]
                if mlogbin_defect_objects:
                    MlogbDefect.objects.bulk_create(mlogbin_defect_objects)
                mlogbin.cal_count_pn_jgqbl(cal_mlog=False)

        # mlogb只用于组合件输出物填写
        brotherId_should_list = material_out.brothers
        if brotherId_should_list:
            if mlogb:
                for item in mlogb:
                    if item['material_out'].id in brotherId_should_list:
                        mlogbx = Mlogb.objects.create(
                            mlog=instance, 
                            batch=instance.batch,
                            route=instance.route, 
                            mtask=instance.mtask, 
                            material_out=item['material_out'], 
                            count_ok=item['count_ok'])
                        if wm_in:
                            mlogbx.batch_ofrom = wm_in.batch_ofrom
                            mlogbx.material_ofrom = wm_in.material_ofrom
                            mlogbx.save(update_fields=["batch_ofrom", "material_ofrom"])
            else:
                raise ParseError('缺少产出物信息')
        else:
            # 生成产出物
            batch_out = validated_data.get('batch', None)
            if batch_out:
                pass
            else:
                batch_out = generate_new_batch(batch_in, instance)
            
            add_dict_2 = {
                'mlog': instance, 'batch': batch_out,
                'mtask': instance.mtask, 'material_out': instance.material_out,
                'count_real': instance.count_real, 'route': instance.route,
                'count_ok': instance.count_ok, 'count_notok': instance.count_notok, 
                'count_break_t': instance.count_break_t, 
                'qct': instance.qct
            }
            need_mdfect = False
            if mlogdefect is not None:
                need_mdfect = True
            else:
                for f in Mlogb._meta.fields:
                        if 'count_n_' in f.name:
                            add_dict_2[f.name] = getattr(instance, f.name)
            ddict = {}
            if wm_in:
                ddict = {"batch_ofrom": wm_in.batch_ofrom, "material_ofrom": wm_in.material_ofrom}
            if mlogbin:
                ddict["mlogb_from"] = mlogbin
            mlogb, _ = Mlogb.objects.get_or_create(**add_dict_2, defaults=ddict)
            if need_mdfect:
                mlogb_defect_objects = [
                    MlogbDefect(**{**item, "mlogb": mlogb, "id": idWorker.get_id()})
                    for item in mlogdefect if item["count"] > 0
                ]
                if mlogb_defect_objects:
                    MlogbDefect.objects.bulk_create(mlogb_defect_objects)
                mlogb.cal_count_notok(cal_mlog=False)
            mlogb.sync_inherited_defect(cal_count=True)
        instance.cal_mlog_count_from_mlogb()
        return instance

    def update(self, instance, validated_data):
        if instance.fill_way == Mlog.MLOG_23:
            raise ParseError('不支持的填写类型')
        validated_data.pop('mtask', None)
        validated_data.pop('mgroup', None)
        mlogdefect = validated_data.pop('mlogdefect', None)
        mlogbindefect = validated_data.pop('mlogindefect', None)
        if instance.mtask:
            validated_data.pop('handle_date', None)
        # validated_data.pop('handle_user', None)

        mlogb = validated_data.pop('mlogb', [])
        instance: Mlog = super().update(instance, validated_data)
        ## 返工没有加工前不良
        if instance.is_fix and instance.count_pn_jgqbl >0:
            raise ParseError("返工不支持加工前不良")
        wm_in = instance.wm_in
        batch_in = instance.batch
        if wm_in:
            batch_in = wm_in.batch
        # 修改消耗
        if instance.fill_way in [Mlog.MLOG_12, Mlog.MLOG_2]:
            # 自动生成mlogb
            if instance.material_in:   # 有消耗的情况
                minx, _ = Mlogb.objects.get_or_create(
                    mlog=instance, 
                    batch=batch_in, 
                    wm_in=instance.wm_in,
                    mtask=instance.mtask, 
                    material_in=instance.material_in
                )
                if wm_in:
                    minx.batch_ofrom = wm_in.batch_ofrom
                    minx.material_ofrom = wm_in.material_ofrom
                minx.count_use = instance.count_use
                minx.count_break = instance.count_break
                minx.count_pn_jgqbl = instance.count_pn_jgqbl
                minx.qct = instance.qct
                minx.save()
                Mlogb.objects.filter(mlog=instance, material_in__isnull=False).exclude(id=minx.id).delete()
                if mlogbindefect is not None:
                    mlogbin_defect_objects = [
                        MlogbDefect(**{**item, "mlogb": minx, "id": idWorker.get_id()})
                        for item in mlogbindefect if item["count"] > 0
                    ]
                    if mlogbin_defect_objects:
                        MlogbDefect.objects.bulk_create(mlogbin_defect_objects)
                    minx.cal_count_pn_jgqbl(cal_mlog=False)
        
        # 修改产出
        if instance.fill_way == Mlog.MLOG_2 and instance.material_out.brothers:
            # 针对兄弟件的情况
            Mlogb.objects.filter(mlog=instance, material_out__isnull=False).update(
                batch=instance.batch,  # 注意mlog的batch有可能会进行修改
                count_ok=0)
            for item in mlogb:
                Mlogb.objects.filter(mlog=instance, material_out=item['material_out']).update(
                    batch=instance.batch,
                    count_ok=item['count_ok'])
                
        elif instance.fill_way in [Mlog.MLOG_12, Mlog.MLOG_2]:
            # 生成产出物
            batch_out = instance.batch
            if batch_out:
                pass
            else:
                batch_out = generate_new_batch(batch_in, instance)
            mox, _ = Mlogb.objects.get_or_create(mlog=instance, batch=batch_out,
                mtask=instance.mtask, material_out=instance.material_out)
            # 需要同步更新数量
            mox.count_real = instance.count_real
            mox.count_ok = instance.count_ok
            mox.count_notok = instance.count_notok
            mox.count_break_t = instance.count_break_t
            if wm_in:
                mox.batch_ofrom = wm_in.batch
                mox.material_ofrom = wm_in.material_ofrom
            need_mdefect=False
            if mlogdefect is not None:
                need_mdefect = True
            else:
                for f in Mlogb._meta.fields:
                    if 'count_n_' in f.name:
                        setattr(mox, f.name, getattr(instance, f.name))
            mox.save()
            Mlogb.objects.filter(mlog=instance, material_out__isnull=False).exclude(id=mox.id).delete()
            if need_mdefect:
                MlogbDefect.objects.filter(mlogb__mlog=instance, is_inherited=False).delete()
                mlogb_defect_objects = [
                    MlogbDefect(**{**item, "mlogb": mox, "id": idWorker.get_id()})
                    for item in mlogdefect if item["count"] > 0
                ]
                if mlogb_defect_objects:
                    MlogbDefect.objects.bulk_create(mlogb_defect_objects)
                mox.cal_count_notok(cal_mlog=False)
            mox.sync_inherited_defect(cal_count=True)
        instance.cal_mlog_count_from_mlogb()
        return instance

    def validate(self, attrs):
        attrs['fill_way'] = Mlog.MLOG_2    # 只填第二级
        attrs['mtype'] = Mlog.MTYPE_SELF   # 默认为自生产
        fmlog:Fmlog = attrs.get('fmlog', None)
        mtaskb = attrs.get('mtaskb', None)
        wm_in: WMaterial = attrs.get('wm_in', None)
        if fmlog:
            attrs['fill_way'] = Mlog.MLOG_12
            if not wm_in:
                raise ParseError('未提供消耗的车间物料')
            attrs['mgroup'] = fmlog.mgroup
            attrs['is_fix'] = fmlog.is_fix
            attrs['mtask'] = fmlog.mtask
            attrs['route'] = fmlog.route
            attrs['mtype'] = fmlog.mgroup.mtype
            if attrs['mtask'] and attrs['mtask'].route:
                attrs['route'] = attrs['mtask'].route
            if mtaskb and mtaskb.mtask != fmlog.mtask:
                raise ParseError('子任务不一致')
        if wm_in:
            attrs["material_in"] = wm_in.material
            attrs["batch"] = wm_in.batch
            if attrs["is_fix"]:
                attrs["material_out"] = attrs["material_in"]
                if wm_in.state in [WMaterial.WM_REPAIR, WMaterial.WM_NOTOK]:
                    pass
                else:
                    raise ParseError('返修或复检需使用返修品/不合格品')
            elif wm_in.state != WMaterial.WM_OK:
                raise ParseError('非合格品不可使用')
            if attrs['route'] and wm_in.material != attrs['route'].material_in:
                raise ParseError('消耗物料与工艺步骤不一致')
        if attrs['mtype']  == Mlog.MTYPE_OUT:
            supplier = attrs.get('supplier', None)
            if not supplier:
                raise ParseError('外协必须选择外协单位')
            
        mtask = attrs.get('mtask', None)
        mlogdefect = attrs.get('mlogdefect', None)
        if mlogdefect is None:
            count_notok = 0
            for i in attrs:
                if 'count_n_' in i:
                    if attrs[i] < 0:
                        raise ParseError(f'{attrs[i]}不能小于0')
                    count_notok = count_notok + attrs[i]
            attrs['count_notok'] = count_notok
        if attrs['count_ok'] < 0:
            raise ParseError('合格数量不能小于0')
        if attrs['count_real'] != attrs['count_ok'] + attrs['count_notok']:
            raise ParseError('生产数量需等于合格数量+不合格数量')
        
        mtaskb: Mtaskb = attrs.get('mtaskb', None)
        if mtaskb:
            mtask = mtaskb.mtask
            attrs['mtask'] = mtask
            attrs['handle_user'] = mtaskb.handle_user
        if mtask:
            attrs['mgroup'] = mtask.mgroup
            attrs['material_in'] = mtask.material_in
            material_out = mtask.material_out
            if wm_in and wm_in.material != mtask.material_in:
                raise ParseError('消耗物料与任务不一致')
            attrs['material_out'] = material_out
        else:
            mgroup = attrs['mgroup']
            material_out = attrs['material_out']
            if not (mgroup and material_out):
                raise ParseError('缺少工段或产物!')
            
        # 时间
        mgroup:Mgroup = attrs['mgroup']
        work_start_time:datetime = attrs.get('work_start_time', None)
        if work_start_time:
            attrs['handle_date'], attrs["shift"] = mgroup.get_shift(work_start_time)
        else:
            if "handle_date" in attrs and attrs["handle_date"]:
                pass
            else:
                raise ParseError('缺少生产日期')
        if mtask and mtask.start_date == mtask.end_date:
            if attrs['handle_date'] != mtask.end_date:
                if work_start_time:
                    raise ParseError('任务日期与生产日期不一致')
                else:
                    attrs['handle_date'] = mtask.end_date

        handle_user = attrs.get('handle_user', None)
        if handle_user is None and hasattr(self, "request"):
            handle_user = self.request.user
        if self.instance is None and mgroup.process:
            attrs['clear_defect'] = mgroup.process.clear_defect
        return attrs


class MlogInitSerializer(CustomModelSerializer):
    class Meta:
        model = Mlog
        fields = ['id', 
        'work_start_time', 'work_end_time', 'mgroup', 'reminder_interval_list', 
        'route', 'equipment', 'handle_user', 'note', 'supplier', 'test_file', 'test_user', 'test_time', 'oinfo_json', 'is_fix', "handle_users", "team", "handle_date"]
        read_only_fields = ["mtype"]
        extra_kwargs = {
            'work_start_time': {'required': True},
            'route':{'required': False},
            'mgroup': {'required': True},
            'mtype': {'required': True}
        }
    
    def validate(self, attrs):
        mgroup: Mgroup = attrs['mgroup']
        is_fix:bool = attrs.get('is_fix', False)
        attrs['mtype'] = mgroup.mtype
        if is_fix:
            attrs["route"] = None
            attrs['hour_work'] = None
            attrs['material_in'] = None
            attrs['material_out'] = None
        else:
            route: Route = attrs.get('route', None)
            # if not route:
            #     raise ParseError('缺少工艺路线')
            if route:
                if route and route.process != mgroup.process:
                    raise ParseError('工序不匹配')
                attrs['hour_work'] = route.hour_work
                attrs['material_in'] = route.material_in
                attrs['material_out'] = route.material_out
        attrs['fill_way'] = Mlog.MLOG_23
        if attrs['mtype']  == Mlog.MTYPE_OUT:
            supplier = attrs.get('supplier', None)
            if not supplier:
                raise ParseError('外协必须选择外协单位')
        # 如果已经确定产出，则自动获取qct
        if attrs.get("material_out", None):
            attrs["qct"] = Qct.get(attrs["material_out"], "process", "out")

        attrs["handle_date"], attrs["shift"] = mgroup.get_shift(attrs['work_start_time'])
        if mgroup.process:
            attrs['clear_defect'] = mgroup.process.clear_defect
        return attrs
    
class MlogChangeSerializer(CustomModelSerializer):
    class Meta:
        model = Mlog
        fields = ['id', 'work_start_time', 'work_end_time', 'handle_user', 'note', 'oinfo_json', 'test_file', 'test_user', 'test_time', 'equipment', "team", "clear_defect"]
    
    def update(self, instance, validated_data):
        work_start_time = validated_data.get('work_start_time', None)
        if work_start_time:
            mgroup:Mgroup = instance.mgroup
            validated_data["handle_date"], validated_data["shift"] = mgroup.get_shift(work_start_time)
        return super().update(instance, validated_data)


class CountJsonSerializer(serializers.Serializer):
    count_use = serializers.IntegerField(min_value=0, required=True)
    floor = serializers.IntegerField(min_value=1, required=True)
    count_test = serializers.IntegerField(min_value=0, required=True)
    count_notok = serializers.IntegerField(min_value=0, required=True)

class CountJsonFromSerializer(serializers.Serializer):
    start = serializers.IntegerField(min_value=0, required=True)
    end = serializers.IntegerField(min_value=0, required=True)
    count_t = serializers.IntegerField(min_value=0, required=True, label="总数")

class MlogbInSerializer(CustomModelSerializer):
    mlogbdefect = MlogbDefectSerializer(many=True, required=False)
    count_json_from = CountJsonFromSerializer(required=False, many=True)
    wprs_in = serializers.ListField(child=serializers.CharField(), label="单个ID的列表", required=False)

    class Meta:
        model = Mlogb
        fields = ['id', 'mlog', 'mtask', 'route', 'wm_in', 'count_use', 'count_pn_jgqbl',
                  'count_break', 'note', "parent", "mlogbdefect", "count_json_from", "wprs_in"]
        extra_kwargs = {'count_use': {'required': True}, 'mtask': {'required': False},
                        'wm_in': {'required': True, "allow_empty": False}}
    
    def validate(self, attrs):
        if attrs["count_use"] < 0 or attrs.get("count_pn_jgqbl", 0) < 0 or attrs.get("count_break", 0) < 0:
            raise ParseError('存在负数!')
        mlog:Mlog = attrs['mlog']
        is_fix = mlog.is_fix
        mtask: Mtask = attrs.get("mtask", None)
        if mtask and mtask.state != Mtask.MTASK_ASSGINED:
            raise ParseError('该任务非下达中不可选择')
        wm_in: WMaterial = attrs['wm_in']
        if wm_in is None:
            raise ParseError("请选择相应车间库存!")
        if is_fix: # 返修或复检
            if wm_in.state in [WMaterial.WM_REPAIR, WMaterial.WM_NOTOK]:
                pass
            else:
                raise ParseError('返修或复检需使用返修品/不合格品')
        elif wm_in.state != WMaterial.WM_OK:
            raise ParseError('非合格品不可使用')
        
        route = None
        mlog_route = mlog.route
        if mlog_route is None:
            if is_fix is False:
                route = attrs.get("route", None)
                if not route:
                    raise ParseError("缺少工艺步骤")
            else:
                attrs["route"] = route
        else:
            route = mlog_route
            attrs["route"] = route
        
        if mtask and route != mtask.route:
            raise ParseError('工艺步骤与任务不匹配')
        attrs['material_in'] = wm_in.material
        attrs['batch'] = wm_in.batch
        attrs["batch_ofrom"] = wm_in.batch_ofrom
        attrs["material_ofrom"] = wm_in.material_ofrom
        attrs["number_from"] = wm_in.number_from
        if route and route.batch_bind and mtask is not None:
            if not WMaterial.mat_in_qs(mtask).filter(id=wm_in.id).exists():
                raise ParseError('该车间库存非本任务使用')
        return attrs

    def create(self, validated_data):
        mlog: Mlog = validated_data['mlog']
        mtask: Mtask = validated_data.get("mtask", None)
        mlogbdefect = validated_data.pop("mlogbdefect", None)
        wprs_in = validated_data.pop("wprs_in", [])
        if Mlogb.objects.filter(mlog=mlog, mtask=mtask, wm_in=validated_data['wm_in'], parent=None).exists():
            raise ParseError('该记录已存在')
        if mlog.submit_time is not None:
            raise ParseError('生产日志已提交不可编辑')

        ins:Mlogb = super().create(validated_data)
        validated_data["wprs_in"] = wprs_in
        if mlogbdefect is not None and ins.material_in.tracking == Material.MA_TRACKING_BATCH:
            mlogb_defect_objects = [
                    MlogbDefect(**{**item, "mlogb": ins, "id": idWorker.get_id()})
                    for item in mlogbdefect if item["count"] > 0
                ]
            if mlogb_defect_objects:
                MlogbDefect.objects.bulk_create(mlogb_defect_objects)
            ins.cal_count_pn_jgqbl(cal_mlog=False)
        return ins

class MlogbInUpdateSerializer(CustomModelSerializer):
    mlogbdefect = MlogbDefectSerializer(many=True, required=False)
    class Meta:
        model = Mlogb
        fields = ['id', 'count_use', 'count_pn_jgqbl', 'note', 'mlogbdefect', "need_inout"]

    def validate(self, attrs):
        if attrs["count_use"] < 0 or attrs.get("count_pn_jgqbl", 0) < 0:
            raise ParseError('存在负数!')
        return attrs
    def update(self, instance, validated_data):
        mlog: Mlog =  instance.mlog
        mlogbdefect = validated_data.pop("mlogbdefect", None)
        if mlog.submit_time is not None:
            raise ParseError('生产日志已提交不可编辑')
        ins:Mlogb = super().update(instance, validated_data)
        if mlogbdefect is not None and ins.material_in.tracking == Material.MA_TRACKING_BATCH:
            MlogbDefect.objects.filter(mlogb=ins).delete()
            mlogb_defect_objects = [
                    MlogbDefect(**{**item, "mlogb": ins, "id": idWorker.get_id()})
                    for item in mlogbdefect if item["count"] > 0
                ]
            if mlogb_defect_objects:
                MlogbDefect.objects.bulk_create(mlogb_defect_objects)
            ins.cal_count_pn_jgqbl(cal_mlog=False)
            # 只有普通工序的才可联动
            route:Route = ins.route if ins.route else ins.mlog.route
            if route and route.process and route.process.mtype == Process.PRO_NORMAL:
                if route.material_out.tracking == Material.MA_TRACKING_BATCH:
                    mlogbout_qs = Mlogb.objects.filter(mlog=ins.mlog, mlogb_from=ins)
                    if mlogbout_qs.count() == 1:
                        mlogbout = mlogbout_qs.first()
                        mlogbout.count_real = ins.count_use - ins.count_pn_jgqbl - ins.count_break
                        mlogbout.count_ok = mlogbout.count_real - mlogbout.count_notok
                        if mlogbout.count_real < 0 or mlogbout.count_ok < 0:
                            raise ParseError("对应的产出数异常")
                        mlogbout.save(update_fields=["count_real", "count_ok"])
                        mlogbout.cal_count_notok(cal_mlog=False)
        return ins
    
class MlogbwCreateUpdateSerializer(CustomModelSerializer):
    ftest = FtestProcessSerializer(required=False, allow_null=True)
    equip_name = serializers.CharField(source='equip.name', read_only=True)
    equip_number = serializers.CharField(source='equip.number', read_only=True)
    tooling_name = serializers.CharField(source='tooling.name', read_only=True)
    tooling_number = serializers.CharField(source='tooling.number', read_only=True)
    wpr_number_out = serializers.CharField(source='wpr.number_out', read_only=True)
    wpr_pre_info = serializers.JSONField(source='wpr.pre_info', read_only=True)
    mlogb__batch = serializers.CharField(source='mlogb.batch', read_only=True)
    class Meta:
        model = Mlogbw
        fields = ["id", "number", "wpr", "note",
                  "mlogb", "ftest", "equip", "tooling", "work_start_time",
                  "work_end_time", "mlogbw_from", "equip_name", "equip_number",
                  "tooling_name", "tooling_number", "wpr_number_out", "wpr_pre_info", "mlogb__batch"]
        read_only_fields = ["mlogbw_from"]

    def validate(self, attrs):
        mlogb:Mlogb = attrs["mlogb"]
        if mlogb.mlog.submit_time is not None:
            raise ParseError('生产日志已提交不可编辑')
        wpr:Wpr = attrs.get("wpr", None)
        in_or_out, tracking = mlogb.get_tracking()
        if tracking != Material.MA_TRACKING_SINGLE:
            raise ParseError('非单件追踪不可使用')
        if in_or_out == 'in':
            if not wpr:
                raise ParseError('请选择相应产品')
            if mlogb.wm_in and wpr.wm != mlogb.wm_in:
                raise ParseError(f'{wpr.number}-该产品非本批次')
        return attrs

    def save_ftest(self, mlogbw, ftest_data):
        ftest = mlogbw.ftest
        if not ftest:
            ftest_sr = FtestProcessSerializer(data=ftest_data)
            ftest = ftest_sr.create(ftest_data)
            mlogbw.ftest = ftest
            mlogbw.save()
        else:
            ftest_sr = FtestProcessSerializer()
            ftest_sr.update(instance=ftest, validated_data=ftest_data)
        return mlogbw

    def create(self, validated_data):
        wpr: Wpr = validated_data.get("wpr", None)
        if wpr:
            mlogb: Mlogb = validated_data["mlogb"]
            if Mlogbw.objects.filter(mlogb=mlogb, wpr=wpr).exists():
                raise ParseError(f'{wpr.number}-该产品已选入')
            if Mlogbw.objects.filter(mlogb__mlog__submit_time__isnull=True, wpr=wpr).exists():
                raise ParseError(f'{wpr.number}-该产品已在其他日志中选入')
        ftest_data = validated_data.pop("ftest", None)
        mlogbw = super().create(validated_data)
        if ftest_data:
            mlogbw = self.save_ftest(mlogbw, ftest_data)
        return mlogbw

    def update(self, instance, validated_data):
        validated_data.pop("mlogb")
        ftest_data = validated_data.pop("ftest", None)
        mlogbw:Mlogbw = super().update(instance, validated_data)
        if ftest_data:
            mlogbw = self.save_ftest(mlogbw, ftest_data)
        elif ftest_data is None:
            ftest = mlogbw.ftest
            if ftest:
                mlogbw.ftest = None
                mlogbw.save()
                ftest.delete()
        return mlogbw


class MlogbwListSerializer(MlogbwCreateUpdateSerializer):
    ftest = FtestProcessListSerializer(read_only=True)

class MlogbwStartTestSerializer(serializers.Serializer):
    mlogbw_ids = serializers.ListField(child=serializers.CharField(), label="mlogbwId列表")
    test_equip = serializers.CharField(label="测试设备", allow_null=True, required=False, allow_blank=True)
    test_user = serializers.CharField(label="测试人员")
    defects = serializers.ListField(child=serializers.CharField(), required=False, allow_null=True, label="缺陷项列表")
    testitems = serializers.ListField(child=serializers.CharField(), required=False, allow_null=True, label="测试项列表")
    test_date = serializers.DateField(label="测试日期")
    qct = serializers.CharField(label="检测表id")

    def save(self, **kwargs):
        from apps.qm.models import Ftest, FtestDefect, FtestItem
        
        validated_data = self.validated_data
        mlogbw_ids = validated_data["mlogbw_ids"]
        test_equip_id = validated_data.get("test_equip")
        test_user_id = validated_data["test_user"]
        defect_ids = validated_data.get("defects", [])
        testitem_ids = validated_data.get("testitems", [])
        test_date = validated_data["test_date"]
        qct_id = validated_data["qct"]

        # 预加载相关对象
        test_equip = Equipment.objects.get(id=test_equip_id) if test_equip_id else None
        test_user = User.objects.get(id=test_user_id)
        qct = Qct.objects.get(id=qct_id)
        
        # 批量获取所有mlogbw对象
        mlogbws = Mlogbw.objects.filter(id__in=mlogbw_ids).select_related('ftest')
        
        # 预加载缺陷和测试项
        defects = Defect.objects.filter(id__in=defect_ids) if defect_ids else []
        testitems = FtestItem.objects.filter(id__in=testitem_ids) if testitem_ids else []
        
        existing_ftests = {}
        new_ftests = []
        mlogbws_to_update = []
        
        # 分离已存在和需要新建的ftest
        for mlogbw in mlogbws:
            if mlogbw.ftest:
                existing_ftests[mlogbw.ftest_id] = mlogbw.ftest
            else:
                ftest = Ftest(test_date=test_date, qct=qct, test_user=test_user, type="process", id=idWorker.get_id(), is_ok=True)
                new_ftests.append(ftest)
                mlogbws_to_update.append(mlogbw)

        # 批量创建新的ftest
        if new_ftests:
            Ftest.objects.bulk_create(new_ftests)
            
            # 更新mlogbw的ftest关系
            for mlogbw, ftest in zip(mlogbws_to_update, new_ftests):
                mlogbw.ftest = ftest
            Mlogbw.objects.bulk_update(mlogbws_to_update, ['ftest'])
            
            # 将新创建的ftest添加到existing_ftests中
            for ftest in new_ftests:
                existing_ftests[ftest.id] = ftest

        # 批量处理缺陷项（需要更新test_user）
        if defects and existing_ftests:
            # 获取所有现有的FtestDefect记录
            existing_defect_ids = FtestDefect.objects.filter(
                ftest_id__in=existing_ftests.keys(),
                defect_id__in=[d.id for d in defects]
            ).values_list('defect_id', 'ftest_id')
            
            existing_defect_map = {(defect_id, ftest_id) for defect_id, ftest_id in existing_defect_ids}
            
            defects_to_create = []
            defects_to_update = []
            
            for ftest in existing_ftests.values():
                for defect in defects:
                    if (defect.id, ftest.id) in existing_defect_map:
                        # 已有记录，需要更新
                        defects_to_update.append((ftest.id, defect.id))
                    else:
                        # 新记录，需要创建
                        defects_to_create.append(
                            FtestDefect(ftest=ftest, defect=defect, test_user=test_user, id=idWorker.get_id())
                        )
            
            # 批量创建新记录
            if defects_to_create:
                FtestDefect.objects.bulk_create(defects_to_create)
            
            # 批量更新已有记录的test_user
            if defects_to_update:
                FtestDefect.objects.filter(
                    ftest_id__in=[item[0] for item in defects_to_update],
                    defect_id__in=[item[1] for item in defects_to_update]
                ).update(test_user=test_user)

        # 批量处理测试项（需要更新test_user和test_equip）
        if testitems and existing_ftests:
            # 获取所有现有的FtestItem记录
            existing_testitem_ids = FtestItem.objects.filter(
                ftest_id__in=existing_ftests.keys(),
                testitem_id__in=[t.id for t in testitems]
            ).values_list('testitem_id', 'ftest_id')
            
            existing_testitem_map = {(testitem_id, ftest_id) for testitem_id, ftest_id in existing_testitem_ids}
            
            testitems_to_create = []
            testitems_to_update_condition = Q()
            
            for ftest in existing_ftests.values():
                for testitem in testitems:
                    if (testitem.id, ftest.id) in existing_testitem_map:
                        # 已有记录，添加到更新条件
                        testitems_to_update_condition |= Q(ftest=ftest, testitem=testitem)
                    else:
                        # 新记录，需要创建
                        testitems_to_create.append(
                            FtestItem(
                                ftest=ftest, 
                                testitem=testitem, 
                                test_user=test_user, 
                                test_equip=test_equip,
                                id=idWorker.get_id()
                            )
                        )
            
            # 批量创建新记录
            if testitems_to_create:
                FtestItem.objects.bulk_create(testitems_to_create)
            
            # 批量更新已有记录的test_user和test_equip
            if testitems_to_update_condition:
                FtestItem.objects.filter(testitems_to_update_condition).update(
                    test_user=test_user,
                    test_equip=test_equip
                )

class MlogbOutPatchUpdateSerializer(CustomModelSerializer):
    class Meta:
        model = Mlogb
        fields = ["batch"]

class MlogbOutUpdateSerializer(CustomModelSerializer):
    mlogbdefect = MlogbDefectSerializer(many=True, required=False)
    count_json = CountJsonSerializer(required=False, many=True)

    class Meta:
        model = Mlogb
        fields = "__all__"
        read_only_fields = EXCLUDE_FIELDS_BASE + ['mlog', 'mtask', 'wm_in', 'material_in', 'material_out',
                                                  'count_use', 'count_break', 'count_pn_jgqbl', 'mlogbdefect', "qct", "count_json"]

    def update(self, instance, validated_data):
        mlog:Mlog = instance.mlog
        if mlog.submit_time is not None:
            raise ParseError('生产日志已提交不可编辑')
        mlogbdefect = validated_data.pop("mlogbdefect", None)

        ins:Mlogb = super().update(instance, validated_data)
        if ins.need_inout is False:
            if ins.mlogb_from:
                if Mlogb.objects.filter(mlog=ins.mlog, material_out__isnull=False, mlogb_from=ins.mlogb_from).count() == 1:
                    ins_from = ins.mlogb_from
                    ins_from.need_inout = False
                    ins_from.save(update_fields=["need_inout"])
                else:
                    raise ParseError("对应消耗的产出有多个, 需手动指定消耗是否出库")
            else:
                raise ParseError("该产出需入库!")
        else:
            if ins.mlogb_from:
                ins_from = ins.mlogb_from
                ins_from.need_inout = True
                ins_from.save(update_fields=["need_inout"])
        if mlogbdefect is not None and ins.material_out.tracking == Material.MA_TRACKING_BATCH:
            MlogbDefect.objects.filter(mlogb=ins).delete()
            mlogb_defect_objects = [
                    MlogbDefect(**{**item, "mlogb": ins, "id": idWorker.get_id()})
                    for item in mlogbdefect if item["count"] > 0
                ]
            if mlogb_defect_objects:
                MlogbDefect.objects.bulk_create(mlogb_defect_objects)
            ins.cal_count_notok(cal_mlog=False)
        elif ins.material_out.tracking == Material.MA_TRACKING_BATCH:
            ins.sync_inherited_defect(cal_count=True)
        return ins
    
    def validate(self, attrs):
        mlogbdefect = attrs.get("mlogbdefect", [])
        if mlogbdefect:
            attrs.pop("count_notok_json", None)
            for k in list(attrs.keys()):
                if 'count_n_' in k:
                    attrs.pop(k, None)
        else:
            count_notok_json = attrs.get('count_notok_json', [])
            # count_notok_json字段处理
            if count_notok_json:
                # 先置0字段
                for k in list(attrs.keys()):
                    if 'count_n_' in k:
                        attrs[k] = 0
                count_notok_dict = {}
                for item in count_notok_json:
                    notok = item['notok']
                    count = item['count']
                    full_notok = f'count_n_{notok}'
                    if not hasattr(Mlogb, full_notok):
                        raise ParseError(f'{notok}-该不合格项不存在')
                    if full_notok in count_notok_dict:
                        count_notok_dict[full_notok] = count_notok_dict[full_notok] + count
                    else:
                        count_notok_dict[full_notok] = count
                for k, v in count_notok_dict.items():
                    attrs[k] = v

            count_notok = 0
            for k in list(attrs.keys()):
                if 'count_n_' in k:
                    if not hasattr(Mlogb, k):
                        raise ParseError(f'{k}不存在')
                    count_notok = count_notok + attrs[k]
            attrs['count_notok'] = count_notok
            if attrs['count_real'] >= attrs['count_ok'] + attrs['count_notok']:
                pass
            else:
                raise ParseError('生产数量不能小于合格数量')
        return attrs

class MlogRevertSerializer(serializers.Serializer):
    change_reason = serializers.CharField(label='撤回原因')


class MlogRelatedSerializer(serializers.Serializer):
    mtask = serializers.PrimaryKeyRelatedField(
        label='小任务ID', queryset=Mtask.objects.all())


class DeptBatchSerializer(serializers.Serializer):
    belong_dept_name = serializers.CharField(label='车间名称')

class Handoverbwserializer(CustomModelSerializer):
    class Meta:
        model = Handoverbw
        fields = '__all__'
        read_only_fields = EXCLUDE_FIELDS_BASE + ["handoverb", "number"]
        extra_kwargs = {'wpr': {'required': True}}

class HandoverbSerializer(CustomModelSerializer):
    notok_sign = serializers.CharField(source='wm.notok_sign', read_only=True)
    defect_name = serializers.CharField(source="wm.defect.name", read_only=True)
    handoverbw = Handoverbwserializer(many=True, required=False)
    class Meta:
        model = Handoverb
        fields = "__all__"
        read_only_fields = EXCLUDE_FIELDS_BASE + ['handover']
        extra_kwargs = {'wm': {'required': True}}

class HandoverbListSerializer(CustomModelSerializer):
    defect_name = serializers.CharField(source="wm.defect.name", read_only=True)
    class Meta:
        model = Handoverb
        fields = "__all__"

class HandoverSerializer(CustomModelSerializer):
    # wm = serializers.PrimaryKeyRelatedField(
    #     label='车间库存ID', queryset=WMaterial.objects.all())
    # material = serializers.PrimaryKeyRelatedField(
    #     required=True, label='物料ID', queryset=Material.objects.all())
    send_user_name = serializers.CharField(
        source='send_user.name', read_only=True)
    recive_user_name = serializers.CharField(
        source='recive_user.name', read_only=True)
    recive_dept_name = serializers.CharField(
        source='recive_dept', read_only=True)
    send_mgroup_name = serializers.CharField(source='send_mgroup.name', read_only=True)
    recive_mgroup_name = serializers.CharField(source='recive_mgroup.name', read_only=True)
    material_ = MaterialSimpleSerializer(source='material', read_only=True)
    material_name = serializers.StringRelatedField(
        source='material', read_only=True)
    material_changed_fname = serializers.CharField(source='material_changed.fname', read_only=True)
    wm_notok_sign = serializers.CharField(source='wm.notok_sign', read_only=True)
    handoverb = HandoverbSerializer(many=True, required=False)
    ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
    
    def validate(self, attrs):
        if "mtype" not in attrs:
            attrs['mtype'] = Handover.H_NORMAL
        if 'type' not in attrs:
            attrs['type'] = Handover.H_NORMAL
        
        mtype = attrs["mtype"]

        new_wm:WMaterial = attrs.get("new_wm", None)
        if new_wm:
            attrs['new_batch'] = new_wm.batch
            if attrs["type"] == Handover.H_CHANGE:
                attrs["material_changed"] = new_wm.material
                attrs["state_changed"] = new_wm.state

        if mtype == Handover.H_MERGE:
            if not attrs.get("new_batch", None):
                raise ParseError("必须指定合并后的批次")
            if 'undefined' in attrs['new_batch'] or 'null' in attrs['new_batch'] or '#' in attrs['new_batch']:
                raise ParseError("新批次号含有不允许信息!")

        if attrs["type"] == Handover.H_CHANGE:
            if "material_changed" in attrs and attrs["material_changed"]:
                pass
            else:
                raise ParseError("必须指定改版后的物料")

        wm:WMaterial = attrs.get('wm', None)
        if wm:
            attrs["batch"] = wm.batch
        handoverb = attrs.get('handoverb', [])
        if wm and not handoverb:
            attrs['handoverb'] = [{"wm": wm, "count": attrs["count"] }]
            handoverb = attrs['handoverb']
            attrs['batch'] = wm.batch
        elif handoverb:
            wm: WMaterial = handoverb[0]["wm"]
        if wm:
            pass
        else:
            raise ParseError('必须指定车间库存')
        attrs['material'] = wm.material
        is_global_source = wm.mgroup_id is None and wm.belong_dept_id is None
        send_mgroup = attrs.get('send_mgroup')
        if send_mgroup:
            attrs['send_dept'] = send_mgroup.belong_dept
        else:
            attrs['send_dept'] = wm.belong_dept
        if not send_mgroup and wm.mgroup:
            attrs['send_mgroup'] = wm.mgroup

        if (
            is_global_source
            and attrs['type'] == Handover.H_NORMAL
            and mtype == Handover.H_NORMAL
        ):
            raise ParseError('全局库存无需正常交接')

        is_global_rebatch = (
            is_global_source
            and attrs['type'] == Handover.H_NORMAL
            and mtype in [Handover.H_DIV, Handover.H_MERGE]
        )
        if is_global_rebatch:
            # 全局库存的拆批、合批不改变库存归属。
            attrs['recive_mgroup'] = None
            attrs['recive_dept'] = None
            if new_wm and (
                new_wm.mgroup_id is not None
                or new_wm.belong_dept_id is not None
            ):
                raise ParseError('全局库存合批目标必须是全局库存')
        elif (
            is_global_source
            and attrs['type'] in [
                Handover.H_REPAIR,
                Handover.H_SCRAP,
                Handover.H_CHANGE,
            ]
            and not attrs.get('recive_mgroup')
        ):
            raise ParseError('全局库存返修、报废或改版必须指定接收工段')

        material_process = attrs['material'].process
        if (
            not is_global_rebatch
            and material_process
            and WmScope.requires_mgroup(material_process.into_wm_scope)
            and not attrs.get('recive_mgroup')
        ):
            raise ParseError('必须指定交接工段')
        if 'recive_mgroup' in attrs and attrs['recive_mgroup']:
            attrs['recive_dept'] = attrs['recive_mgroup'].belong_dept
        if (
            not is_global_rebatch
            and not attrs.get('recive_dept', None)
            and not attrs.get('recive_mgroup', None)
        ):
            raise ParseError('必须指定收料车间或收料工段')
        if (
            not is_global_source
            and not attrs.get('send_dept', None)
            and not attrs.get('send_mgroup', None)
        ):
            raise ParseError('必须指定送料车间或送料工段')
        
        # if attrs["mtype"] == Handover.H_NORMAL and attrs.get("recive_mgroup", None) == attrs.get("send_mgroup", None):
        #     raise ParseError('正常交接收料工段与送料工段不能相同')
        t_count = 0
        tracking = attrs["material"].tracking
        
        next_mat = None
        next_state = None
        next_defect = None
        if new_wm and attrs["type"] != Handover.H_CHANGE:
            next_mat = new_wm.material
            next_state = new_wm.state
            next_defect = new_wm.defect
        deptOrmgroupId = None
        for ind, item in enumerate(attrs['handoverb']):
            if item["count"] > 0:
                pass
            else:
                raise ParseError(f'第{ind+1}行-交接数量必须大于0')
            wm: WMaterial = item["wm"]
            current_mdept_id = wm.belong_dept_or_mgroup_id
            if deptOrmgroupId is None:
                deptOrmgroupId = current_mdept_id
            elif deptOrmgroupId != current_mdept_id:
                raise ParseError(f'第{ind+1}行-交接物料所属工段/车间不一致')
            if mtype == Handover.H_MERGE:
                clear_defect = attrs.get('clear_defect', False)
                if clear_defect and wm.state != WMaterial.WM_OK:
                    raise ParseError(f'第{ind+1}行-清除缺陷合批仅支持合格品')
                if clear_defect and new_wm is not None and new_wm.defect is not None:
                    raise ParseError('清除缺陷合批的目标批次不能带缺陷')
                if next_mat is None:
                    next_mat = wm.material
                    next_state = wm.state
                    next_defect = None if clear_defect else wm.defect
                else:
                    if next_mat != wm.material:
                        raise ParseError(f'第{ind+1}行-合并的物料不一致')
                    if next_state != wm.state:
                        raise ParseError(f'第{ind+1}行-合并的物料状态不一致')
                    if not clear_defect and next_defect != wm.defect:
                        raise ParseError(f'第{ind+1}行-合并的物料缺陷不一致')
            if tracking == Material.MA_TRACKING_SINGLE:
                handoverbw = item.get("handoverbw", [])
                if handoverbw:
                    item["count"] = len(handoverbw)
                    t_count += len(handoverbw)
                    wprIds = [i["wpr"].id for i in handoverbw]
                    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] == wm.id:
                        pass
                    else:
                        raise ParseError(f'{wm.batch}物料明细中存在{len(wm_ids)}个不同物料批次')
                    
                elif wm.count == item["count"]:
                    t_count += item["count"]
                else:
                    raise ParseError(f'第{ind+1}行-请提供交接物料明细编号')
            else:
                t_count += item["count"]
            # if wm.mgroup and wm.mgroup != attrs['send_mgroup']:
            #     raise ParseError(f'第{ind+1}物料与交接工段不一致')
            # if wm.belong_dept and wm.belong_dept != attrs['send_dept']:
            #     raise ParseError(f'第{ind+1}物料与交接部门不一致')
            if attrs["material"] != wm.material:
                raise ParseError(f'第{ind+1}物料与交接物料不一致')
            # if wm.state != WMaterial.WM_OK  and attrs['type'] in [Handover.H_NORMAL, Handover.H_TEST]:
            #     raise ParseError(f'第{ind+1}物料不合格，不能进行正常/检验交接')
            if wm.count_xtest is not None:
                raise ParseError(f'第{ind+1}物料检验中，不能进行交接')
        attrs["count"] = t_count
        if attrs['type'] == Handover.H_REPAIR:
            # 返修时还是该物料
            recive_mgroup = attrs.get("recive_mgroup", None)
            if recive_mgroup is None:
                raise ParseError('返工交接需指定工段')
        return attrs

    class Meta:
        model = Handover
        fields = '__all__'
        read_only_fields = EXCLUDE_FIELDS + ["mlog"]
        extra_kwargs = {
            "type": {"required": False},
            "wm": {"required": False},
            "send_dept": {"required": False},
            "recive_mgroup": {"required": False},
            "recive_dept": {"required": False},
            "material": {"required": False},
            "material_changed": {"required": False},
            "batch": {"required": False},
            "count": {"required": False},
            "count_eweight": {"required": False}
        }
    
    def create(self, validated_data):
        handoverb = validated_data.pop('handoverb', [])
        ins = super().create(validated_data)
        mtype = validated_data["mtype"]
        for ind, item in enumerate(handoverb):
            wm = item["wm"]
            count = item["count"]
            handoverbw = item.pop("handoverbw", [])
            if mtype == Handover.H_DIV:
                if not item["batch"]:
                    raise ParseError(f'第{ind+1}行-请提供拆批批次号')
                if ins.wm is None:
                    raise ParseError("请提供拆批库存")
                handoverb = Handoverb.objects.create(handover=ins, batch=item["batch"], count=count, wm=ins.wm)
            else:
                handoverb = Handoverb.objects.create(handover=ins, wm=wm, count=count, batch=wm.batch)
            if wm.material.tracking == Material.MA_TRACKING_SINGLE:
                if handoverbw:
                    handoverb.count = len(handoverbw)
                    handoverb.save()
                    for item in handoverbw:
                        wpr = item["wpr"]
                        Handoverbw.objects.get_or_create(wpr=wpr, handoverb=handoverb, defaults={"number":wpr.number, "note": item.get("note", None)})
                elif count == wm.count:
                    for item in Wpr.get_qs_by_wm(wm):
                        Handoverbw.objects.get_or_create(wpr=item, handoverb=handoverb, defaults={"number":item.number})
                else:
                    raise ParseError(f'第{ind+1}行-请提供交接物料明细编号')
        return ins
    
    def update(self, instance, validated_data):
        handoverb = validated_data.pop('handoverb', [])
        
        insx = super().update(instance, validated_data)
        Handoverb.objects.filter(handover=instance).delete()
        for ind, item in enumerate(handoverb):
            wm = item["wm"]
            count = item["count"]
            handoverbw = item.pop("handoverbw", [])
            if validated_data["mtype"] == Handover.H_DIV:
                if not item["batch"]:
                    raise ParseError(f'第{ind+1}行-请提供拆批批次号')
                if insx.wm is None:
                    raise ParseError("请提供拆批库存")
                hb, _ = Handoverb.objects.get_or_create(handover=instance, batch=item["batch"], wm=insx.wm,
                                                        defaults={"count": count})
            else:
                hb, _ = Handoverb.objects.get_or_create(handover=instance, wm=wm, defaults={"count": count,
                "batch": wm.batch})
            if wm.material.tracking == Material.MA_TRACKING_SINGLE:
                Handoverbw.objects.filter(handoverb=hb).delete()
                if handoverbw:
                    hb.count = len(handoverbw)
                    hb.save()
                    for item in handoverbw:
                        wpr = item["wpr"]
                        Handoverbw.objects.get_or_create(wpr=wpr, handoverb=hb, defaults={"number": wpr.number, "note": item.get("note", None)})
                elif count == wm.count:
                    wpr_qs = Wpr.get_qs_by_wm(wm)
                    for item in wpr_qs:
                        Handoverbw.objects.get_or_create(wpr=item, handoverb=handoverb,
                                                            defaults={"number": item.number})
                else:
                    raise ParseError(f'第{ind+1}行-请提供交接物料明细')
        return instance

class HandoverUpdateSerializer(CustomModelSerializer):
    class Meta:
        model = Handover
        fields = ['id', 'send_date', 'send_user', 'count', 'count_eweight', 'recive_user', 'note']

class HandoverListSerializer(HandoverSerializer):
    handoverb = HandoverbListSerializer(many=True, required=False)


class GenHandoverSerializer(serializers.Serializer):
    mlogs = serializers.PrimaryKeyRelatedField(
        label='mlog的ID列表', queryset=Mlog.objects.all(), many=True)
    recive_dept = serializers.PrimaryKeyRelatedField(
        label='交送车间', queryset=Dept.objects.all())
    recive_user = serializers.PrimaryKeyRelatedField(
        label='接收人', queryset=User.objects.all())
    send_user = serializers.PrimaryKeyRelatedField(
        label='交送人', queryset=User.objects.all())
    send_date = serializers.DateField(label='交送日期')


class GenHandoverWmSerializer(serializers.Serializer):
    wm = serializers.PrimaryKeyRelatedField(
        label='车间物料ID', queryset=WMaterial.objects.all())
    send_mgroup = serializers.PrimaryKeyRelatedField(
        label='送料工段ID', queryset=Mgroup.objects.all())
    recive_dept = serializers.PrimaryKeyRelatedField(
        label='交送车间', queryset=Dept.objects.all())
    recive_user = serializers.PrimaryKeyRelatedField(
        label='接收人', queryset=User.objects.all())
    send_user = serializers.PrimaryKeyRelatedField(
        label='交送人', queryset=User.objects.all())
    send_date = serializers.DateField(label='交送日期')
    count = serializers.IntegerField(label='交送数量')

    def validate(self, attrs):
        if attrs['count'] <= 1:
            raise ParseError('交送数量必须大于1')
        return attrs


class MlogAnaSerializer(serializers.Serializer):
    belong_dept_name = serializers.CharField(label='车间名', required=False)
    start_date = serializers.DateField(label='开始日期', required=True)
    end_date = serializers.DateField(label='结束日期', required=True)
    material_cate = serializers.CharField(label='物料系列', required=False)


class AttLogSerializer(CustomModelSerializer):
    mgroup_name = serializers.CharField(
        source='sflog.mgroup.name', read_only=True)
    user_name = serializers.CharField(source='user.name', read_only=True)
    post_name = serializers.CharField(source='post.name', read_only=True)
    belong_dept_name = serializers.CharField(
        source='sflog.mgroup.belong_dept.name', read_only=True)

    class Meta:
        model = AttLog
        fields = '__all__'


class FmlogSerializer(CustomModelSerializer):
    # routepack_name = serializers.CharField(
    #     source='mtask.route.routepack.name', read_only=True)
    route_name = serializers.StringRelatedField(source="route", read_only=True)
    route_material_in = serializers.CharField(source='route.material_in', read_only=True)
    mgroup_name = serializers.CharField(source='mgroup.name', read_only=True)
    mtask_number = serializers.CharField(source='mtask.number', read_only=True)
    class Meta:
        model = Fmlog
        fields = '__all__'
        read_only_fields = EXCLUDE_FIELDS
    
    def validate(self, attrs):
        is_fix = attrs.get("is_fix", False)
        if is_fix:
            attrs["mtask"] = None
            attrs['route'] = None
        elif not attrs.get("mtask", None) and not attrs.get("route", None):
            raise ParseError("请选择任务或工艺步骤")
        mtask: Mtask = attrs['mtask']
        if mtask and mtask.state != Mtask.MTASK_ASSGINED:
            raise ParseError('该任务非下达中不可选择')
        if mtask:
            attrs["route"] = mtask.route
        return attrs

class FmlogUpdateSerializer(CustomModelSerializer):
    class Meta:
        model = Fmlog
        fields = ['id', 'note', 'enabled']


class MlogTCreateSerializer(CustomModelSerializer):
    pass


class BatchStSerializer(CustomModelSerializer):
    material_start_name = serializers.CharField(source='material_start.name', read_only=True)
    material_start_model = serializers.CharField(source='material_start.model', read_only=True)
    material_start_specification = serializers.CharField(source='material_start.specification', read_only=True)
    class Meta:
        model = BatchSt
        fields = "__all__"


class HandoverMgroupSerializer(serializers.Serializer):
    material = serializers.CharField(label="物料ID")
    type = serializers.IntegerField(label="交送类型")


class MlogUserSerializer(CustomModelSerializer):
    handle_user_name = serializers.CharField(source='handle_user.name', read_only=True)
    process_name = serializers.CharField(source='process.name', read_only=True)
    shift_name = serializers.CharField(source='shift.name', read_only=True)
    equipment_name = serializers.CharField(source='equipment.name', read_only=True, default=None)
    class Meta:
        model = MlogUser
        fields = "__all__"
        read_only_fields = EXCLUDE_FIELDS_BASE + ["shift", "handle_date"]
        extra_kwargs = {
            "work_start_time": {"required": True}
        }

    def create(self, validated_data):
        mlog:Mlog = validated_data["mlog"]
        work_start_time:datetime = validated_data["work_start_time"]
        if mlog.work_start_time < mlog.work_start_time:
            raise ParseError("操作时间不能早于日志开始时间")
        validated_data["handle_date"], validated_data["shift"] = mlog.mgroup.get_shift(work_start_time)
        if mlog.submit_time is not None:
            raise ParseError("该日志已提交")
        process:Process = validated_data["process"]
        if mlog.route:
            p = mlog.route.process
            if process.parent != p:
                raise ParseError("该步骤不匹配")
        else:
            raise ParseError("该日志没有工艺步骤")
        if MlogUser.objects.filter(mlog=mlog, process=process).exists():
            raise ParseError("该工序已选择")
        return super().create(validated_data)


class BatchLogSerializer(CustomModelSerializer):
    source_batch = serializers.CharField(source='source.batch', read_only=True)
    target_batch = serializers.CharField(source='target.batch', read_only=True)
    class Meta:
        model = BatchLog
        fields = "__all__"


class BatchMgroupSerializer(serializers.Serializer):
    # mgroup = serializers.PrimaryKeyRelatedField(
    #     label='工段', queryset=Mgroup.objects.all())
    mgroup_name = serializers.CharField(label="工段名")


class MlogQuickSerializer(serializers.Serializer):
    work_start_time = serializers.DateTimeField(label="开始时间")
    # handle_date = serializers.DateField(label="操作日期")
    work_end_time = serializers.DateTimeField(label="结束时间", required=False)
    team = serializers.CharField(label="班组ID", required=False)
    equipment = serializers.CharField(label="设备ID", required=False)
    wm_in = serializers.CharField(label="输入车间库存ID")
    wprs_in = serializers.ListField(child=serializers.CharField(), label="单个ID的列表", required=False)
    count_use = serializers.IntegerField(label="领用数量")
    is_fix = serializers.BooleanField(label="是否返修")
    mgroup = serializers.CharField(label="工段ID")
    route = serializers.CharField(label="工艺步骤ID", required=False)
    mtask = serializers.CharField(label="任务ID", required=False)
    handle_user = serializers.CharField(label="操作人员ID")


class BatchChangeSerializer(serializers.Serializer):
    old_batch = serializers.CharField(label="原批号")
    new_batch = serializers.CharField(label="新批号")
