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 ValidationError, ParseError
from datetime import datetime

from .models import (SfLog, StLog, SfLogExp, WMaterial, Mlog, 
                    Handover, Handoverb, Mlogb, AttLog,
                     OtherLog, Fmlog, BatchSt, Mlogbw, Handoverbw)
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
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, Ftest
from apps.wf.serializers import TicketSimpleSerializer
from apps.wpmw.models import Wpr
from apps.qm.serializers import FtestProcessSerializer
import logging
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', '')
        with transaction.atomic():
            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)
        # if instance.end_time:
        #     raise ParseError('该异常已结束无需编辑')
        with transaction.atomic():
            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_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.IntegerField(read_only=True, label='在制数量')

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

    class Meta:
        model = WMaterial
        fields = '__all__'
    
    def to_representation(self, instance):
        ret = super().to_representation(instance)
        ret['count_cando'] = ret['count'] - ret['count_working']
        return ret


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']
        extra_kwargs = {
            'material_out': {'required': True, 'allow_null': False}}
        
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)

    class Meta:
        model = Mlogb
        fields = '__all__'

class MlogSerializer(CustomModelSerializer):
    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)

    class Meta:
        model = Mlog
        fields = '__all__'
        read_only_fields = EXCLUDE_FIELDS + \
            ['submit_time', 'submit_user', 'material_outs']
        extra_kwargs = {
           "batch": {"required": True},
           "shift": {"required": True},
           "material_out": {"required": True}
        }

    def create(self, validated_data):
        material_out = validated_data['material_out']
        mtask:Mtask = validated_data.get('mtask', None)
        if mtask and mtask.state != Mtask.MTASK_ASSGINED:
            raise ParseError('该任务非下达中不可选择')
        with transaction.atomic():
            mlogb = validated_data.pop('mlogb', [])
            instance: Mlog = super().create(validated_data)
            # 自动生成mlogb
            batch_in = instance.batch
            wm_in = instance.wm_in
            if wm_in:
                batch_in = wm_in.batch
            if instance.material_in: # 如果有消耗
                add_dict = {
                    'mlog': instance, 'batch': batch_in, 'wm_in': wm_in,
                    'mtask': instance.mtask, 'material_in': instance.material_in,
                    '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
                Mlogb.objects.create(**add_dict)

            # 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, 
                                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 ValidationError('缺少产出物信息')
            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,
                    'count_ok': instance.count_ok, 'count_notok': instance.count_notok, 
                    'count_break_t': instance.count_break_t
                }
                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}
                Mlogb.objects.get_or_create(**add_dict_2, defaults=ddict)
        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)
        if instance.mtask:
            validated_data.pop('handle_date', None)
        # validated_data.pop('handle_user', None)
        with transaction.atomic():
            mlogb = validated_data.pop('mlogb', [])
            instance: Mlog = super().update(instance, validated_data)
            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.save()
                    Mlogb.objects.filter(mlog=instance, material_in__isnull=False).exclude(id=minx.id).delete()
            
            # 修改产出
            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_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
                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()
        return instance

    def validate(self, attrs):
        attrs['fill_way'] = Mlog.MLOG_2    # 只填第二级
        attrs['mtype'] = Mlog.MTYPE_SELF   # 默认为自生产
        fmlog = attrs.get('fmlog', None)
        mtaskb = attrs.get('mtaskb', None)
        if fmlog:
            attrs['fill_way'] = Mlog.MLOG_12
            wm_in: WMaterial = attrs.get('wm_in', None)
            if wm_in:
                pass
            else:
                raise ParseError('未提供消耗的车间物料')
            attrs['mgroup'] = fmlog.mgroup
            attrs['mtask'] = fmlog.mtask
            if attrs['mtask'].route:
                attrs['route'] = attrs['mtask'].route
            if attrs['mtask'].mtaskb and mtaskb is None:
                raise ParseError('子任务不能为空')
            if mtaskb and mtaskb.mtask != fmlog.mtask:
                raise ParseError('子任务不一致')
            if wm_in.state in [WMaterial.WM_OK, WMaterial.WM_REPAIR]:
                pass
            else:
                raise ValidationError('非合格/返修品不可使用')
            if wm_in.material != attrs['mtask'].material_in:
                raise ParseError('消耗物料与任务不一致')
        mtask = attrs.get('mtask', None)
        count_notok = 0
        for i in attrs:
            if 'count_n_' in i:
                count_notok = count_notok + attrs[i]
        attrs['count_notok'] = count_notok
        if attrs['count_real'] >= attrs['count_ok'] + attrs['count_notok']:
            pass
        else:
            raise ValidationError('生产数量不能小于合格数量')
        if mtask:
            if mtask.start_date == mtask.end_date:
                attrs['handle_date'] = mtask.start_date
            else:
                if attrs['work_end_time']:
                    attrs['handle_date'] = localdate(attrs['work_end_time'])
                elif attrs['work_start_time']:
                    attrs['handle_date'] = localdate(attrs['work_start_time'])
        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
            attrs['material_out'] = material_out
            if mtask.start_date == mtask.end_date:
                attrs['handle_date'] = mtask.end_date
        else:
            mgroup = attrs['mgroup']
            material_out = attrs['material_out']
            if not (mgroup and material_out):
                raise ValidationError('缺少工段或产物!')
        handle_user = attrs.get('handle_user', None)
        if handle_user is None and hasattr(self, "request"):
            handle_user = self.request.user
        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', 'mtype', 'supplier', 'test_file', 'test_user', 'test_time', 'oinfo_json']
        extra_kwargs = {
            'work_start_time': {'required': True},
            'route':{'required': True},
            'mgroup': {'required': True},
            'mtype': {'required': True}
        }
    
    def validate(self, attrs):
        mtype = attrs['mtype']
        route: Route = attrs['route']
        mgroup: Mgroup = attrs['mgroup']
        if route is None:
            raise ParseError('缺少工艺路线')
        if 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 mtype == Mlog.MTYPE_OUT:
            supplier = attrs.get('supplier', None)
            if not supplier:
                raise ParseError('外协必须选择外协单位')
        if attrs.get('work_end_time', None):
            attrs['handle_date'] = localdate(attrs['work_end_time'])
        return attrs
    
class MlogChangeSerializer(CustomModelSerializer):
    class Meta:
        model = Mlog
        fields = ['id', 'work_end_time', 'handle_user', 'note', 'oinfo_json', 'test_file', 'test_user', 'test_time']
    
    def validate(self, attrs):
        if attrs.get('work_end_time', None):
            attrs['handle_date'] = localdate(attrs['work_end_time'])
        return attrs


class MlogbInSerializer(CustomModelSerializer):
    class Meta:
        model = Mlogb
        fields = ['id', 'mlog', 'mtask', 'wm_in', 'count_use', 'count_pn_jgqbl',
                  'count_break', 'note', "parent"]
        extra_kwargs = {'count_use': {'required': True}, 'mtask': {'required': True},
                        'wm_in': {'required': True, "allow_empty": False}}
    
    def validate(self, attrs):
        mlog: Mlog = attrs['mlog']
        mtask: Mtask = attrs['mtask']
        if mtask.state != Mtask.MTASK_ASSGINED:
            raise ValidationError('该任务非下达中不可选择')
        wm_in: WMaterial = attrs['wm_in']
        if wm_in.state in [WMaterial.WM_OK, WMaterial.WM_REPAIR]:
            pass
        else:
            raise ValidationError('非合格/返修品不可使用')
        if mlog.route != mtask.route:
            raise ValidationError('工序不匹配')
        route = mlog.route
        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
        if route.batch_bind:
            if not WMaterial.mat_in_qs(mtask).filter(id=wm_in.id).exists():
                raise ValidationError('该车间库存非本任务使用')
        return attrs

    def create(self, validated_data):
        mlog: Mlog = validated_data['mlog']
        if Mlogb.objects.filter(mlog=mlog, mtask=validated_data['mtask'], wm_in=validated_data['wm_in'], parent=None).exists():
            raise ValidationError('该记录已存在')
        if mlog.submit_time is not None:
            raise ParseError('生产日志已提交不可编辑')
        return super().create(validated_data)
    
    def update(self, instance, validated_data):
        mlog: Mlog =  instance.mlog
        if Mlogb.objects.filter(mlog=mlog, mtask=validated_data['mtask'], wm_in=validated_data['wm_in'], parent=None).exclude(id=instance.id).exists():
            raise ValidationError('该记录已存在')
        if mlog.submit_time is not None:
            raise ParseError('生产日志已提交不可编辑')
        return super().update(instance, validated_data)

class MlogbInUpdateSerializer(CustomModelSerializer):
    class Meta:
        model = Mlogb
        fields = ['id', 'count_use', 'count_break', 'count_pn_jgqbl', 'note']

class MlogbwCreateUpdateSerializer(CustomModelSerializer):
    ftest = FtestProcessSerializer(required=False)
    class Meta:
        model = Mlogbw
        fields = ["id", "number", "wpr", "note", "mlogb", "ftest"]

    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' and not wpr:
            raise ParseError('请选择相应产品')
        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

    @transaction.atomic
    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('该产品已选入')
        ftest_data = validated_data.pop("ftest", None)
        mlogbw = super().create(validated_data)
        if ftest_data:
            mlogbw = self.save_ftest(mlogbw, ftest_data)
        return mlogbw

    @transaction.atomic
    def update(self, instance, validated_data):
        validated_data.pop("mlogb")
        ftest_data = validated_data.pop("ftest", None)
        mlogbw = super().update(instance, validated_data)
        if ftest_data:
            mlogbw = self.save_ftest(mlogbw, ftest_data)
        return mlogbw

class MlogbOutUpdateSerializer(CustomModelSerializer):
    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']
    
    def validate(self, attrs):
        count_notok_json = attrs.get('count_notok_json', [])
        # count_notok_json字段处理
        if count_notok_json:
            # 先置0字段
            for i in attrs:
                if 'count_n_' in i:
                    i = 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 ValidationError(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 i in attrs:
            if 'count_n_' in i:
                if not hasattr(Mlogb, i):
                    raise ValidationError(f'{i}不存在')
                count_notok = count_notok + attrs[i]
        attrs['count_notok'] = count_notok
        if attrs['count_real'] >= attrs['count_ok'] + attrs['count_notok']:
            pass
        else:
            raise ValidationError('生产数量不能小于合格数量')
        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):
    batch = serializers.CharField(source='wm.batch', read_only=True)
    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 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)
    wm_notok_sign = serializers.CharField(source='wm.notok_sign', read_only=True)
    handoverb = HandoverbSerializer(many=True, required=False)
    
    def validate(self, attrs):
        if 'type' not in attrs:
            attrs['type'] = Handover.H_NORMAL
        if attrs["type"] == Handover.H_CHANGE:
            if "material_changed" not in attrs:
                raise ParseError("必须指定变更后的物料")
        wm:WMaterial = attrs.get('wm', None)
        handoverb = attrs.get('handoverb', [])
        if wm:
            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
        attrs['send_dept'] = wm.belong_dept
        if wm.mgroup:
            attrs['send_mgroup'] = wm.mgroup
        if attrs['material'].process and attrs['material'].process.into_wm_mgroup and 'recive_mgroup' not in attrs:
            raise ValidationError('必须指定交接工段')
        if 'recive_mgroup' in attrs and attrs['recive_mgroup']:
            attrs['recive_dept'] = attrs['recive_mgroup'].belong_dept
        if 'recive_dept' not in attrs and 'recive_mgroup' not in attrs:
            raise ValidationError('收料车间和收料工段必须有一个')
        if 'send_dept' not in attrs and 'send_mgroup' not in attrs:
            raise ValidationError('送料车间和送料工段必须有一个')
        t_count = 0
        for ind, item in enumerate(attrs['handoverb']):
            wm = item["wm"]
            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.notok_sign or wm.defect)  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('返工交接需指定工段')
            attrs['material_changed'] = find_material_can_change(attrs['material'], recive_mgroup)
        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', [])
        with transaction.atomic():
            ins = super().create(validated_data)
            for item in handoverb:
                wm = item["wm"]
                count = item["count"]
                handoverbw = item.pop("handoverbw", [])
                handoverb, _ = Handoverb.objects.get_or_create(handover=ins, wm=wm, count=count)
                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'请提供交接物料明细编号')
        return ins
    
    def update(self, instance, validated_data):
        handoverb = validated_data.pop('handoverb', [])
        with transaction.atomic():
            super().update(instance, validated_data)
            wmIds = []
            for item in handoverb:
                wm = item["wm"]
                count = item["count"]
                handoverbw = item.pop("handoverbw", [])
                wmIds.append(item["wm"].id)
                hb, _ = Handoverb.objects.get_or_create(handover=instance, wm=wm)
                hb.count = count
                hb.save()
                if wm.material.tracking == Material.MA_TRACKING_SINGLE:
                    if handoverbw:
                        hb.count = len(handoverbw)
                        hb.save()
                        wprIds = [item["wpr"].id for item in handoverbw]
                        Handoverbw.objects.filter(handoverb=hb).exclude(wpr__in=wprIds).delete()
                        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)
                        Handoverbw.objects.filter(handoverb=hb).exclude(wpr__in=wpr_qs).delete()
                        for item in wpr_qs:
                            Handoverbw.objects.get_or_create(wpr=item, handoverb=handoverb,
                                                             defaults={"number": item.number})
                    else:
                        raise ParseError(f'请提供交接物料明细编号')
            Handoverb.objects.filter(handover=instance).exclude(wm__in=wmIds).delete()
        return instance

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



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 ValidationError('交送数量必须大于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)
    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):
        mtask: Mtask = attrs['mtask']
        if mtask.state != Mtask.MTASK_ASSGINED:
            raise ParseError('该任务非下达中不可选择')
        return attrs

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


class MlogTCreateSerializer(CustomModelSerializer):
    pass


class BatchStSerializer(CustomModelSerializer):
    class Meta:
        model = BatchSt
        fields = "__all__"


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

