1141 lines
		
	
	
		
			51 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			1141 lines
		
	
	
		
			51 KiB
		
	
	
	
		
			Python
		
	
	
	
import datetime
 | 
						||
 | 
						||
from django.core.cache import cache
 | 
						||
from django.db.models import Sum
 | 
						||
from django.utils import timezone
 | 
						||
from typing import Union
 | 
						||
 | 
						||
from rest_framework.exceptions import ParseError
 | 
						||
 | 
						||
from apps.system.models import User
 | 
						||
from apps.pm.models import Mtask
 | 
						||
from apps.mtm.models import Mgroup, Shift, Material, Route, RoutePack, Team, Srule
 | 
						||
 | 
						||
from .models import SfLog, WMaterial, Mlog, Mlogb, Mlogbw, Handover, Handoverb, Handoverbw, MlogbDefect, BatchLog, BatchSt, MlogUser
 | 
						||
from apps.mtm.services_2 import cal_material_count
 | 
						||
from apps.wf.models import Ticket
 | 
						||
from apps.wf.services import WfService
 | 
						||
import logging
 | 
						||
from apps.wpm.services_2 import ana_batch_thread, ana_wpr_thread
 | 
						||
from datetime import timedelta
 | 
						||
from apps.wpmw.models import Wpr, WprDefect
 | 
						||
from ..qm.models import Defect, Ftest
 | 
						||
from django.db.models import Count, Q
 | 
						||
from apps.utils.tasks import ctask_run
 | 
						||
from apps.mtm.models import Process
 | 
						||
from apps.mtm.services_2 import cal_material_count
 | 
						||
from apps.utils.lock import lock_model_record_d_func
 | 
						||
 | 
						||
myLogger = logging.getLogger('log')
 | 
						||
 | 
						||
def generate_new_batch(old_batch: str, mlog: Mlog):
 | 
						||
    new_batch = old_batch
 | 
						||
    supplier = mlog.supplier
 | 
						||
    mgroup = mlog.mgroup
 | 
						||
    process = mgroup.process
 | 
						||
    if mgroup.batch_append_code:
 | 
						||
        if mgroup.code:
 | 
						||
            new_batch = f'{new_batch}>{mgroup.code}'
 | 
						||
    if mlog.mtype == Mlog.MTYPE_OUT:
 | 
						||
        supplier_number = supplier.number if supplier else ''
 | 
						||
        if supplier_number:
 | 
						||
            new_batch = f'{new_batch}>{supplier_number}'
 | 
						||
    elif process.batch_append_equip:
 | 
						||
        number = mlog.equipment.number if mlog.equipment else ''
 | 
						||
        if number:
 | 
						||
            new_batch = f'{new_batch}>{number}'
 | 
						||
        if mlog.index:
 | 
						||
            new_batch = f'{new_batch}>{mlog.index}'
 | 
						||
    return new_batch
 | 
						||
 | 
						||
def find_material_can_change(material: Material, mgroup_to: Mgroup):
 | 
						||
    """
 | 
						||
    找到可转变为的物料(返工交接用)
 | 
						||
    """
 | 
						||
    routepackIds = Route.objects.filter(material_out=material, routepack__state=RoutePack.RP_S_CONFIRM).values_list('routepack', flat=True)
 | 
						||
    matIds = list(Route.objects.filter(routepack__id__in=routepackIds, process=mgroup_to.process, material_in__type__in=[Material.MA_TYPE_HALFGOOD, Material.MA_TYPE_GOOD]).values_list('material_in', flat=True).distinct())
 | 
						||
    if matIds and len(matIds) == 1:
 | 
						||
        return Material.objects.get(id=matIds[0])
 | 
						||
    raise ParseError("无法返工到该工段")
 | 
						||
 | 
						||
def get_sflog(mgroup: Mgroup, happen_time: datetime):
 | 
						||
    sflog = SfLog.objects.filter(
 | 
						||
        start_time__lte=happen_time, end_time__gt=happen_time, mgroup=mgroup).order_by('-start_time').first()
 | 
						||
    if sflog is None:  # 需要创建值班记录
 | 
						||
        make_sflogs(mgroup=mgroup, start_date=(
 | 
						||
            happen_time-datetime.timedelta(days=2)).date(), end_date=(happen_time+datetime.timedelta(days=1)).date())
 | 
						||
        sflog = SfLog.objects.filter(
 | 
						||
            start_time__lte=happen_time, end_time__gt=happen_time, mgroup=mgroup).order_by('-start_time').first()
 | 
						||
    return sflog
 | 
						||
 | 
						||
def get_team_x(sflog: SfLog):
 | 
						||
    srule = Srule.objects.filter(belong_dept=sflog.mgroup.belong_dept).first()
 | 
						||
    if srule and srule.rule:
 | 
						||
        rule = srule.rule
 | 
						||
        rule_compare = rule + rule
 | 
						||
        teamId_list = (SfLog.objects.filter(mgroup=sflog.mgroup, start_time__lt=sflog.start_time, start_time__gte=sflog.start_time - timedelta(days=5)).order_by('-start_time').values_list('team_id', flat=True)[:3])[::-1]
 | 
						||
        if None in teamId_list:
 | 
						||
            pass
 | 
						||
        else:
 | 
						||
            teamId_list_len = len(teamId_list)
 | 
						||
            for i in range(len(rule_compare)-teamId_list_len+1):
 | 
						||
                if rule_compare[i:i+teamId_list_len] == teamId_list:
 | 
						||
                    teamId = rule_compare[i+teamId_list_len]
 | 
						||
                    team = Team.objects.filter(id=teamId).first()
 | 
						||
                    return team
 | 
						||
    return None
 | 
						||
 | 
						||
 | 
						||
def make_sflogs(mgroup: Mgroup, start_date: datetime.date, end_date: datetime.date, create_by=None):
 | 
						||
    shift_rule = mgroup.shift_rule
 | 
						||
    shifts = Shift.objects.filter(rule=shift_rule).order_by('sort')  # 根据排班规则制定排班记录
 | 
						||
    current_date = start_date
 | 
						||
    while current_date <= end_date:
 | 
						||
        for shift in shifts:
 | 
						||
            start_time_o = shift.start_time_o
 | 
						||
            end_time_o = shift.end_time_o
 | 
						||
            start_time = datetime.datetime.combine(current_date, start_time_o)
 | 
						||
            end_time = datetime.datetime.combine(current_date, end_time_o)
 | 
						||
            # 以下代码是解决跨天排班时生成当天班次缺少的bug
 | 
						||
            if start_time >= end_time:
 | 
						||
                end_time += datetime.timedelta(days=1)
 | 
						||
            total_sec = (end_time - start_time).total_seconds()
 | 
						||
 | 
						||
            # 创建SfLog记录
 | 
						||
            sflog, is_created = SfLog.objects.get_or_create(mgroup=mgroup, shift=shift, start_time=start_time, defaults={
 | 
						||
                "mgroup": mgroup,
 | 
						||
                "shift": shift,
 | 
						||
                "work_date": start_time.date(),
 | 
						||
                "start_time": start_time,
 | 
						||
                "end_time": end_time,
 | 
						||
                "total_sec_now": total_sec,
 | 
						||
                "total_sec": total_sec,
 | 
						||
                "create_by": create_by
 | 
						||
            })
 | 
						||
            if is_created:
 | 
						||
                team = get_team_x(sflog)
 | 
						||
                if team:
 | 
						||
                    sflog.team = team
 | 
						||
                    sflog.leader = team.leader
 | 
						||
                    sflog.save()
 | 
						||
        current_date = current_date + datetime.timedelta(days=1)
 | 
						||
 | 
						||
 | 
						||
def get_pcoal_heat(year_s: int, month_s: int, day_s: int):
 | 
						||
    """
 | 
						||
    获取煤粉热值
 | 
						||
    只有回转窑需要录入煤粉热值
 | 
						||
    """
 | 
						||
    key = f'pcoal_heat_{year_s}_{month_s}_{day_s}'
 | 
						||
    pcoal_heat = cache.get(key)
 | 
						||
    if pcoal_heat is not None and pcoal_heat > 0:
 | 
						||
        return pcoal_heat
 | 
						||
    else:
 | 
						||
        try:
 | 
						||
            qs = SfLog.objects.get(work_date__year=year_s, work_date__month=month_s, work_date__day=day_s,
 | 
						||
                                   mgroup__name='回转窑', shift__name__in=['白班', '早班'])  # hardcode
 | 
						||
            if qs.pcoal_heat is None or qs.pcoal_heat <=0:
 | 
						||
                s = SfLog.objects.filter(pcoal_heat__isnull=False).order_by('-start_time').first()
 | 
						||
                if s and s.pcoal_heat:
 | 
						||
                    qs.pcoal_heat = s.pcoal_heat
 | 
						||
                else:
 | 
						||
                    qs.pcoal_heat = 25000
 | 
						||
                qs.save(update_fields=['pcoal_heat'])
 | 
						||
            cache.set(key, qs.pcoal_heat, timeout=60 * 60 * 8)
 | 
						||
            return qs.pcoal_heat
 | 
						||
        except Exception as e:
 | 
						||
            myLogger.error(f'获取煤粉热值失败,{e}, {year_s}, {month_s}, {day_s}', exc_info=True)
 | 
						||
            return 25000
 | 
						||
 | 
						||
# @lock_model_record_d_func(Mlog)
 | 
						||
def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
 | 
						||
    """
 | 
						||
    生产日志提交后需要执行的操作
 | 
						||
    """
 | 
						||
    if mlog.work_start_time and mlog.work_start_time > timezone.now():
 | 
						||
        raise ParseError('操作开始时间不能晚于当前时间')
 | 
						||
    if mlog.work_start_time and mlog.work_end_time and mlog.work_end_time < mlog.work_start_time:
 | 
						||
        raise ParseError('操作结束时间不能早于操作开始时间')
 | 
						||
    if mlog.count_real == 0:
 | 
						||
        raise ParseError('产出数量不能为0')
 | 
						||
    # if mlog.submit_time is not None:
 | 
						||
    #     return
 | 
						||
    if now is None:
 | 
						||
        now = timezone.now()
 | 
						||
    if mlog.handle_date is None:
 | 
						||
        raise ParseError('请选择操作结束时间/操作日期')
 | 
						||
    # if now.date() < mlog.handle_date:
 | 
						||
    #     raise ParseError('不可提交未来的日志')
 | 
						||
    
 | 
						||
    mgroup = mlog.mgroup
 | 
						||
    process = mgroup.process
 | 
						||
    into_wm_mgroup = process.into_wm_mgroup
 | 
						||
    need_store_notok = process.store_notok
 | 
						||
    belong_dept = mgroup.belong_dept
 | 
						||
    material_out: Material = mlog.material_out
 | 
						||
    material_in: Material = mlog.material_in
 | 
						||
    supplier = mlog.supplier  # 外协
 | 
						||
    is_fix = mlog.is_fix
 | 
						||
    m_ins_list = []
 | 
						||
    m_ins_bl_list = []
 | 
						||
 | 
						||
    if is_fix:
 | 
						||
        can_matoutIds = process.get_canout_mat_ids()
 | 
						||
    # 建立关系链
 | 
						||
    m_outs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False)
 | 
						||
    exclude_batchst_ids = []
 | 
						||
    for item in m_outs:
 | 
						||
        if item.mlogb_from and item.batch != item.mlogb_from.batch:
 | 
						||
            source, _ = BatchSt.g_create(batch=item.mlogb_from.batch, exclude_batchst_ids=exclude_batchst_ids)
 | 
						||
            target, _ = BatchSt.g_create(batch=item.batch, mlog=mlog, material_start=item.material_out)
 | 
						||
            exclude_batchst_ids.append(target.id)
 | 
						||
            BatchLog.g_create(source= source, target=target, mlog=mlog)
 | 
						||
        if item.mlogbw_from and item.batch != item.mlogbw_from.mlogb.batch:
 | 
						||
            source, _ = BatchSt.g_create(batch=item.mlogbw_from.mlogb.batch, exclude_batchst_ids=exclude_batchst_ids)
 | 
						||
            target, _ = BatchSt.g_create(batch=item.batch, mlog=mlog, material_start=item.material_out)
 | 
						||
            exclude_batchst_ids.append(target.id)
 | 
						||
            BatchLog.g_create(source=source, target=target, mlog=mlog)
 | 
						||
 | 
						||
    # 消耗物料处理
 | 
						||
    m_ins_list = []
 | 
						||
    m_ins = Mlogb.objects.filter(mlog=mlog, material_in__isnull=False)
 | 
						||
    if m_ins.exists():
 | 
						||
        m_ins = m_ins.filter(need_inout=True)
 | 
						||
        m_ins_list = [(mi.material_in, mi.batch if mi.batch else mi.batch, mi.count_use, None, mi) for mi in m_ins.all()]
 | 
						||
        for item in m_ins:
 | 
						||
            mbd_qs = MlogbDefect.get_defect_qs_from_mlogb(item)
 | 
						||
            for itemx in mbd_qs:
 | 
						||
                if itemx.defect and itemx.count > 0:
 | 
						||
                    m_ins_bl_list.append((item.material_in, item.batch, itemx.count, itemx.defect, item))
 | 
						||
    else:
 | 
						||
        m_ins_list = [(material_in, mlog.batch, mlog.count_use, None, mlog)]
 | 
						||
    for mi in m_ins_list:
 | 
						||
        mi_ma, mi_batch, mi_count, defect, mlog_or_b = mi
 | 
						||
        if mi_ma is None:
 | 
						||
            continue
 | 
						||
        if mi_count <= 0:
 | 
						||
            raise ParseError('存在非正数!')
 | 
						||
        # 需要判断领用数是否合理
 | 
						||
        # 优先使用工段库存
 | 
						||
        if isinstance(mlog_or_b, Mlogb) and mlog_or_b.wm_in:
 | 
						||
            wm_qs = WMaterial.objects.filter(id=mlog_or_b.wm_in.id)
 | 
						||
        else:
 | 
						||
            wm_qs = WMaterial.objects.filter(batch=mi_batch, material=mi_ma, mgroup=mgroup, state=WMaterial.WM_OK)
 | 
						||
            if not wm_qs.exists():
 | 
						||
                wm_qs = WMaterial.objects.filter(batch=mi_batch, material=mi_ma,
 | 
						||
                                                    belong_dept=belong_dept, mgroup=None, state=WMaterial.WM_OK)
 | 
						||
        count_x = wm_qs.count()
 | 
						||
        if count_x == 1:
 | 
						||
            wm = wm_qs.first()
 | 
						||
        elif count_x == 0:
 | 
						||
            raise ParseError(
 | 
						||
                f'{str(mi_ma)}-{mi_batch}-批次库存不存在!')
 | 
						||
        else:
 | 
						||
            raise ParseError(
 | 
						||
                f'{str(mi_ma)}-{mi_batch}-存在多个相同批次!')
 | 
						||
        
 | 
						||
        if mi_count > wm.count:
 | 
						||
            raise ParseError(
 | 
						||
                f'{str(mi_ma)}-{mi_batch}-该批次车间库存不足!')
 | 
						||
        else:
 | 
						||
            wm.count = wm.count - mi_count
 | 
						||
            wm.update_by = user
 | 
						||
            wm.save()
 | 
						||
            if mi_ma.tracking == Material.MA_TRACKING_SINGLE:
 | 
						||
                mlogbws = Mlogbw.objects.filter(Q(ftest=None) | Q(ftest__is_ok=True), mlogb=mlog_or_b)
 | 
						||
                if mlogbws.count() != mi_count:
 | 
						||
                    raise ParseError("日志与实际使用明细数量不一致,操作失败")
 | 
						||
                for item in mlogbws:
 | 
						||
                    Wpr.change_or_new(wpr=item.wpr, old_wm=wm, ftest=item.ftest)
 | 
						||
 | 
						||
        # 针对加工前不良的暂时额外处理 
 | 
						||
        if need_store_notok:
 | 
						||
            for item in m_ins_bl_list:
 | 
						||
                material, batch, count, defect, mi_ = item
 | 
						||
                if count <= 0:
 | 
						||
                    raise ParseError('存在非正数!')
 | 
						||
                lookup = {'batch': batch, 'material': material, 'mgroup': mgroup, 'defect': defect, 'state': WMaterial.WM_NOTOK}
 | 
						||
                wm, is_create = WMaterial.objects.get_or_create(**lookup, defaults={"belong_dept": belong_dept})
 | 
						||
                wm.count = wm.count + count
 | 
						||
                if is_create:
 | 
						||
                    wm.create_by = user
 | 
						||
                    wm.batch_ofrom = mi_.batch_ofrom
 | 
						||
                    wm.material_ofrom = mi_.material_ofrom
 | 
						||
                wm.update_by = user
 | 
						||
                wm.save()
 | 
						||
                if material.tracking == Material.MA_TRACKING_SINGLE:
 | 
						||
                    mlogbws = Mlogbw.objects.filter(mlogb=mlog_or_b, ftest__is_ok=False, ftest__defect_main=defect)
 | 
						||
                    if mlogbws.count() != count:
 | 
						||
                        raise ParseError("日志与加工前不良明细数量不一致,操作失败")
 | 
						||
                    for item in mlogbws:
 | 
						||
                        Wpr.change_or_new(wpr=item.wpr, wm=wm, old_wm=wm, ftest=item.ftest)
 | 
						||
                
 | 
						||
 | 
						||
    mlogb_out_qs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False)
 | 
						||
    stored_mgroup = into_wm_mgroup
 | 
						||
    stored_notok = need_store_notok
 | 
						||
    if mlogb_out_qs.exists():
 | 
						||
        mlogb_out_qs = mlogb_out_qs.filter(need_inout=True)
 | 
						||
        m_outs_list = [(mo.material_out, mo.batch if mo.batch else mlog.batch, mo.count_ok_full if mo.count_ok_full is not None else mo.count_ok, mlog.count_real_eweight, None, mo) for mo in mlogb_out_qs.all()]
 | 
						||
        if need_store_notok:
 | 
						||
            for item in mlogb_out_qs:
 | 
						||
                mbd_qs = MlogbDefect.get_defect_qs_from_mlogb(item)
 | 
						||
                if item.qct is not None or mbd_qs.exists():
 | 
						||
                    # if item.material_out.tracking == Material.MA_TRACKING_SINGLE:
 | 
						||
                    #     Mlogbw.cal_count_notok(item)
 | 
						||
                    for itemx in mbd_qs:
 | 
						||
                        if itemx.count > 0:
 | 
						||
                            m_outs_list.append((item.material_out, item.batch, itemx.count, 0, itemx.defect, item))
 | 
						||
                    # # 获取所有主要的不合格项/先暂时保留
 | 
						||
                    # bw_qs = Mlogbw.objects.filter(mlogb=item)
 | 
						||
                    # defectIds= Ftest.objects.filter(mlogbw_ftest__in=bw_qs).exclude(defect_main=None).values_list("defect_main__id", flat=True).distinct()
 | 
						||
                    # defects_map = {d.id: d for d in Defect.objects.filter(id__in=defectIds)}
 | 
						||
                    # # 过滤并统计相关数据
 | 
						||
                    # filtered_bw_qs = bw_qs.filter(
 | 
						||
                    #     ftest__defect_main__id__in=defects_map.keys()
 | 
						||
                    # ).values('ftest__defect_main__id').annotate(xcount=Count('id'))
 | 
						||
                    # # 整理结果
 | 
						||
                    # for defect_data in filtered_bw_qs:
 | 
						||
                    #     defect_id = defect_data['ftest__defect_main__id']
 | 
						||
                    #     xcount = defect_data['xcount']
 | 
						||
                    #     if xcount > 0:
 | 
						||
                    #         defect = defects_map[defect_id]
 | 
						||
                    #         m_outs_list.append((item.material_out, item.batch, xcount, 0, defect, item))
 | 
						||
                else:
 | 
						||
                    for f in Mlogb._meta.fields:
 | 
						||
                        if 'count_n_' in f.name and getattr(item, f.name) > 0:
 | 
						||
                            notok_sign = f.name.replace('count_n_', '')
 | 
						||
                            m_outs_list.append( (item.material_out, item.batch if item.batch else mlog.batch, getattr(item, f.name), mlog.count_real_eweight, notok_sign, item))
 | 
						||
                            stored_notok = True
 | 
						||
        # 这里有一个漏洞,在产出物为兄弟件的情况下,不合格品的数量是记录在mlog上的,
 | 
						||
        # 而不是mlogb上,以上的额外处理就没有效果了, 不过光子不记录不合格品
 | 
						||
    else:
 | 
						||
        m_outs_list = [(material_out, mlog.batch, mlog.count_ok, mlog.count_real_eweight, None, mlog)]
 | 
						||
        # 一次填写的暂时不处理不合格品
 | 
						||
    for mo in m_outs_list:
 | 
						||
        mo_ma, mo_batch, mo_count, mo_count_eweight, notok_sign_or_defect, mlog_or_b = mo
 | 
						||
        if mo_ma is None:
 | 
						||
            continue
 | 
						||
        if mo_count < 0:
 | 
						||
            raise ParseError('存在负数!')
 | 
						||
        elif mo_count == 0:
 | 
						||
            continue
 | 
						||
        if is_fix:
 | 
						||
            wm_state = WMaterial.WM_REPAIRED
 | 
						||
            if mo_ma.id in can_matoutIds:
 | 
						||
                wm_state = WMaterial.WM_OK if notok_sign_or_defect is None or (
 | 
						||
                    isinstance(notok_sign_or_defect, Defect) and notok_sign_or_defect.okcate in [Defect.DEFECT_OK, Defect.DEFECT_OK_B]
 | 
						||
                ) else WMaterial.WM_NOTOK
 | 
						||
        else:
 | 
						||
            wm_state = WMaterial.WM_OK if notok_sign_or_defect is None or (
 | 
						||
                    isinstance(notok_sign_or_defect, Defect) and notok_sign_or_defect.okcate in [Defect.DEFECT_OK, Defect.DEFECT_OK_B]
 | 
						||
                ) else WMaterial.WM_NOTOK
 | 
						||
        lookup = {'batch': mo_batch, 'material': mo_ma, 'mgroup': None,
 | 
						||
                    'notok_sign': None, 'defect': None, 'state': wm_state}
 | 
						||
        if isinstance(notok_sign_or_defect, Defect):
 | 
						||
            lookup['defect'] = notok_sign_or_defect
 | 
						||
        elif notok_sign_or_defect is not None:
 | 
						||
            lookup['notok_sign'] = notok_sign_or_defect
 | 
						||
        if into_wm_mgroup:
 | 
						||
            lookup['mgroup'] = mgroup
 | 
						||
        else:
 | 
						||
            lookup['belong_dept'] = belong_dept
 | 
						||
 | 
						||
        wm, is_create2 = WMaterial.objects.get_or_create(**lookup, defaults={**lookup, "belong_dept": belong_dept})
 | 
						||
        wm.count = wm.count + mo_count
 | 
						||
        wm.count_eweight = mo_count_eweight
 | 
						||
        wm.update_by = user
 | 
						||
        if supplier is not None:
 | 
						||
            wm.supplier = supplier
 | 
						||
        if is_create2:
 | 
						||
            wm.create_by = user
 | 
						||
            if isinstance(mlog_or_b, Mlog) and mlog_or_b.wm_in:
 | 
						||
                wm.batch_ofrom = mlog_or_b.wm_in.batch_ofrom
 | 
						||
                wm.material_ofrom = mlog_or_b.wm_in.material_ofrom
 | 
						||
            elif isinstance(mlog_or_b, Mlogb):
 | 
						||
                wm.batch_ofrom = mlog_or_b.batch_ofrom
 | 
						||
                wm.material_ofrom = mlog_or_b.material_ofrom
 | 
						||
        if isinstance(mlog_or_b, Mlogb):
 | 
						||
            if mlog_or_b.number_from and wm.number_from is not None and wm.number_from != mlog_or_b.number_from:
 | 
						||
                raise ParseError(f'{wm.batch}-该批号现有库存来源于个号{wm.number_from}')
 | 
						||
            wm.number_from = mlog_or_b.number_from
 | 
						||
        wm.save()
 | 
						||
        if mo_ma.tracking == Material.MA_TRACKING_SINGLE:
 | 
						||
            if notok_sign_or_defect:
 | 
						||
                mlogbws = Mlogbw.objects.filter(mlogb=mlog_or_b, ftest__is_ok=False, ftest__defect_main=notok_sign_or_defect)
 | 
						||
            else:
 | 
						||
                mlogbws = Mlogbw.objects.filter(Q(ftest=None) | Q(ftest__is_ok=True), mlogb=mlog_or_b)
 | 
						||
            mlogbws_count = mlogbws.count()
 | 
						||
            if mlogbws_count != mo_count:
 | 
						||
                raise ParseError("日志与明细数量不一致,操作失败")
 | 
						||
            for item in mlogbws:
 | 
						||
                if item.wpr:
 | 
						||
                    Wpr.change_or_new(wpr=item.wpr, wm=wm, ftest=item.ftest)
 | 
						||
                else:
 | 
						||
                    wpr_from = None
 | 
						||
                    if item.mlogbw_from:
 | 
						||
                        wpr_from = item.mlogbw_from.wpr
 | 
						||
                    wpr = Wpr.change_or_new(number=item.number, 
 | 
						||
                                            wm=wm, ftest=item.ftest, 
 | 
						||
                                            wpr_from=wpr_from)
 | 
						||
                    item.wpr = wpr
 | 
						||
                    item.save()
 | 
						||
        
 | 
						||
    mlog.submit_time = now
 | 
						||
    mlog.submit_user = user
 | 
						||
    mlog.stored_notok = stored_notok
 | 
						||
    mlog.stored_mgroup = stored_mgroup
 | 
						||
    if mlog.work_end_time is None and mlog.work_start_time is not None:
 | 
						||
        mlog.work_end_time = now
 | 
						||
    mlog.save()
 | 
						||
 | 
						||
    # 更新任务进度
 | 
						||
    cal_mtask_progress_from_mlog(mlog)
 | 
						||
 | 
						||
    # 更新物料数量
 | 
						||
    cal_material_count_from_mlog(mlog)
 | 
						||
 | 
						||
    # 触发批次统计分析
 | 
						||
    xbatches = list(Mlogb.objects.filter(mlog=mlog).values_list('batch', flat=True))
 | 
						||
    ana_batch_thread(xbatchs=xbatches, mgroup=mlog.mgroup)
 | 
						||
 | 
						||
    # 触发单个统计
 | 
						||
    wprIds = list(Mlogbw.objects.filter(mlogb__mlog=mlog, ftest__isnull=False, wpr__isnull=False).values_list('wpr__id', flat=True))
 | 
						||
    if wprIds:
 | 
						||
        ana_wpr_thread(wprIds, mlog.mgroup)
 | 
						||
 | 
						||
# @lock_model_record_d_func(Mlog)
 | 
						||
def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
 | 
						||
    """日志撤回
 | 
						||
    """
 | 
						||
    # if mlog.submit_time is None:
 | 
						||
    #     return
 | 
						||
    if now is None:
 | 
						||
        now = timezone.now()
 | 
						||
 | 
						||
    mgroup = mlog.mgroup
 | 
						||
    process = mgroup.process
 | 
						||
    belong_dept = mgroup.belong_dept
 | 
						||
    material_out:Material = mlog.material_out
 | 
						||
    material_in:Material = mlog.material_in
 | 
						||
    stored_notok = mlog.stored_notok
 | 
						||
    stored_mgroup = mlog.stored_mgroup
 | 
						||
    is_fix = mlog.is_fix
 | 
						||
    
 | 
						||
    if is_fix:
 | 
						||
        can_matoutIds = process.get_canout_mat_ids()
 | 
						||
    # 先回退产物
 | 
						||
    # 有多个产物的情况
 | 
						||
    # 需要考虑不合格品退回的情况
 | 
						||
    mlogb_out_qs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False)
 | 
						||
    if mlogb_out_qs.exists():
 | 
						||
        mlogb_out_qs = mlogb_out_qs.filter(need_inout=True)
 | 
						||
        m_outs_list = [
 | 
						||
            (mo.material_out, mo.batch if mo.batch else mlog.batch, mo.count_ok_full if mo.count_ok_full is not None else mo.count_ok, mlog.count_real_eweight, None, mo)
 | 
						||
            for mo in mlogb_out_qs.all()]
 | 
						||
        if stored_notok:
 | 
						||
                for item in mlogb_out_qs:
 | 
						||
                    mbd_qs = MlogbDefect.get_defect_qs_from_mlogb(item)
 | 
						||
                    if item.qct is not None or mbd_qs.exists():
 | 
						||
                        # if item.material_out.tracking == Material.MA_TRACKING_SINGLE:
 | 
						||
                        #     Mlogbw.cal_count_notok(item)
 | 
						||
                        for itemx in mbd_qs:
 | 
						||
                            if itemx.count > 0:
 | 
						||
                                m_outs_list.append((item.material_out, item.batch, itemx.count, 0, itemx.defect, item))
 | 
						||
                    # if item.material_out.tracking == Material.MA_TRACKING_SINGLE:
 | 
						||
                    #     # 获取所有主要的不合格项
 | 
						||
                    #     bw_qs = Mlogbw.objects.filter(mlogb=item)
 | 
						||
                    #     defectIds= Ftest.objects.filter(mlogbw_ftest__in=bw_qs).exclude(defect_main=None).values_list("defect_main__id", flat=True).distinct()
 | 
						||
                    #     defects_map = {d.id: d for d in Defect.objects.filter(id__in=defectIds)}
 | 
						||
                    #     # 过滤并统计相关数据
 | 
						||
                    #     filtered_bw_qs = bw_qs.filter(
 | 
						||
                    #         ftest__defect_main__id__in=defects_map.keys()
 | 
						||
                    #     ).values('ftest__defect_main__id').annotate(xcount=Count('id'))
 | 
						||
                    #     # 整理结果
 | 
						||
                    #     for defect_data in filtered_bw_qs:
 | 
						||
                    #         defect_id = defect_data['ftest__defect_main__id']
 | 
						||
                    #         xcount = defect_data['xcount']
 | 
						||
                    #         if xcount > 0:
 | 
						||
                    #             defect = defects_map[defect_id]
 | 
						||
                    #             m_outs_list.append((item.material_out, item.batch, xcount, 0, defect, item))
 | 
						||
                    else:
 | 
						||
                        for f in Mlogb._meta.fields:
 | 
						||
                            if 'count_n_' in f.name and getattr(item, f.name) > 0:
 | 
						||
                                notok_sign = f.name.replace('count_n_', '')
 | 
						||
                                m_outs_list.append((item.material_out, item.batch if item.batch else mlog.batch,
 | 
						||
                                                    getattr(item, f.name), mlog.count_real_eweight, notok_sign, item))
 | 
						||
    else:
 | 
						||
        m_outs_list = [(material_out, mlog.batch, mlog.count_ok, mlog.count_real_eweight, None, mlog)]
 | 
						||
        # 一次填写的暂时不处理不合格品
 | 
						||
 | 
						||
    for mo in m_outs_list:
 | 
						||
        mo_ma, mo_batch, mo_count, _, notok_sign_or_defect, mlog_or_b = mo
 | 
						||
        if mo_ma is None:
 | 
						||
            continue
 | 
						||
        if mo_count < 0:
 | 
						||
            raise ParseError('存在负数!')
 | 
						||
        elif mo_count == 0:
 | 
						||
            continue
 | 
						||
        if is_fix:
 | 
						||
            wm_state = WMaterial.WM_REPAIRED
 | 
						||
            if mo_ma.id in can_matoutIds:
 | 
						||
                wm_state = WMaterial.WM_OK if notok_sign_or_defect is None or (
 | 
						||
                    isinstance(notok_sign_or_defect, Defect) and notok_sign_or_defect.okcate in [Defect.DEFECT_OK, Defect.DEFECT_OK_B]
 | 
						||
                ) else WMaterial.WM_NOTOK
 | 
						||
        else:
 | 
						||
            wm_state = WMaterial.WM_OK if notok_sign_or_defect is None or (
 | 
						||
                    isinstance(notok_sign_or_defect, Defect) and notok_sign_or_defect.okcate in [Defect.DEFECT_OK, Defect.DEFECT_OK_B]
 | 
						||
                ) else WMaterial.WM_NOTOK
 | 
						||
        lookup = {'batch': mo_batch, 'material': mo_ma, 'mgroup': None, 'notok_sign': None, 'defect': None, 'state': wm_state}
 | 
						||
        if isinstance(notok_sign_or_defect, Defect):
 | 
						||
            lookup['defect'] = notok_sign_or_defect
 | 
						||
        else:
 | 
						||
            lookup['notok_sign'] = notok_sign_or_defect
 | 
						||
        if stored_mgroup:
 | 
						||
            lookup['mgroup'] = mgroup
 | 
						||
        else:
 | 
						||
            lookup['belong_dept'] = belong_dept
 | 
						||
        wm_qs = WMaterial.objects.filter(**lookup)
 | 
						||
        count_x = wm_qs.count()
 | 
						||
        if count_x == 1:
 | 
						||
            wm = wm_qs.first()
 | 
						||
        elif count_x == 0:
 | 
						||
            raise ParseError(
 | 
						||
                f'{str(mo_ma)}-{mo_batch}-批次库存不存在!')
 | 
						||
        else:
 | 
						||
            raise ParseError(
 | 
						||
                f'{str(mo_ma)}-{mo_batch}-存在多个相同批次!')
 | 
						||
        wm.count = wm.count - mo_count
 | 
						||
        if wm.count < 0:
 | 
						||
            raise ParseError(f'{wm.batch} 车间库存不足, 产物无法回退')
 | 
						||
        elif wm.count >= 0:
 | 
						||
            if isinstance(mlog_or_b, Mlogb):
 | 
						||
                if mlog_or_b.number_from == wm.number_from:
 | 
						||
                    wm.number_from = None
 | 
						||
            wm.update_by = user
 | 
						||
            wm.save()
 | 
						||
            if mo_ma.tracking == Material.MA_TRACKING_SINGLE:
 | 
						||
                if notok_sign_or_defect:
 | 
						||
                    mlogbws = Mlogbw.objects.filter(mlogb=mlog_or_b, ftest__is_ok=False, ftest__defect_main=notok_sign_or_defect)
 | 
						||
                else:
 | 
						||
                    mlogbws = Mlogbw.objects.filter(Q(ftest=None) | Q(ftest__is_ok=True), mlogb=mlog_or_b)
 | 
						||
                if mlogbws.count() != mo_count:
 | 
						||
                    raise ParseError("日志与明细数量不一致,操作失败")
 | 
						||
                for item in mlogbws:
 | 
						||
                    Wpr.change_or_new(wpr=item.wpr, old_wm=wm)
 | 
						||
 | 
						||
    # 再生成消耗
 | 
						||
    m_ins_list = []
 | 
						||
    m_ins_bl_list = []
 | 
						||
    into_wm_mgroup = process.into_wm_mgroup
 | 
						||
    m_ins = Mlogb.objects.filter(mlog=mlog, material_in__isnull=False)
 | 
						||
    if m_ins.exists():
 | 
						||
        m_ins = m_ins.filter(need_inout=True)
 | 
						||
        for mi in m_ins.all():
 | 
						||
            m_ins_list.append((mi.material_in, mi.batch, mi.count_use, None, mi))
 | 
						||
            for item in m_ins:
 | 
						||
                mbd_qs = MlogbDefect.get_defect_qs_from_mlogb(item)
 | 
						||
                for itemx in mbd_qs:
 | 
						||
                    if itemx.defect and itemx.count > 0:
 | 
						||
                        m_ins_bl_list.append((item.material_in, item.batch, itemx.count, itemx.defect, item))
 | 
						||
    else:
 | 
						||
        m_ins_list = [(material_in, mlog.batch, mlog.count_use, mlog.wm_in, mlog)]
 | 
						||
    for mi in m_ins_list:
 | 
						||
        mi_ma, mi_batch, mi_count, defect_or, mlog_or_b = mi
 | 
						||
        if mi_ma is None:
 | 
						||
            continue
 | 
						||
        if mi_count <= 0:
 | 
						||
            raise ParseError('存在非正数!')
 | 
						||
        if isinstance(mlog_or_b, Mlogb) and mlog_or_b.wm_in:
 | 
						||
            wm = WMaterial.objects.get(id=mlog_or_b.wm_in.id)
 | 
						||
        else:
 | 
						||
            # 针对光子的情况,实际上必须需要wm_in
 | 
						||
            lookup = {'batch': mi_batch, 'material': mi_ma, 'mgroup': None, 'state': WMaterial.WM_OK}
 | 
						||
            if into_wm_mgroup:
 | 
						||
                # 退回到本工段
 | 
						||
                lookup['mgroup'] = mgroup
 | 
						||
            else:
 | 
						||
                lookup['belong_dept'] = belong_dept
 | 
						||
 | 
						||
            wm, _ = WMaterial.objects.get_or_create(**lookup, defaults={**lookup, "belong_dept": belong_dept})
 | 
						||
        wm.count = wm.count + mi_count
 | 
						||
        wm.update_by = user
 | 
						||
        wm.save()
 | 
						||
        if mi_ma.tracking == Material.MA_TRACKING_SINGLE:
 | 
						||
            mlogbws = Mlogbw.objects.filter(Q(ftest=None) | Q(ftest__is_ok=True), mlogb=mlog_or_b)
 | 
						||
            if mlogbws.count() != mi_count:
 | 
						||
                raise ParseError("日志与实际使用明细数量不一致,操作失败")
 | 
						||
            for item in mlogbws:
 | 
						||
                Wpr.change_or_new(wpr=item.wpr, wm=wm)
 | 
						||
 | 
						||
    # 针对加工前不良的暂时额外处理 
 | 
						||
    if stored_notok:
 | 
						||
        for item in m_ins_bl_list:
 | 
						||
            material, batch, count, defect, mi_ = item
 | 
						||
            if count <= 0:
 | 
						||
                raise ParseError('存在非正数!')
 | 
						||
            lookup = {'batch': batch, 'material': material, 'mgroup': None, 'defect': defect, 'state': WMaterial.WM_NOTOK}
 | 
						||
            if stored_mgroup:
 | 
						||
                lookup['mgroup'] = mgroup
 | 
						||
            else:
 | 
						||
                lookup['belong_dept'] = belong_dept
 | 
						||
            wm, is_create = WMaterial.objects.get_or_create(**lookup, defaults={**lookup, "belong_dept": belong_dept})
 | 
						||
            wm.count = wm.count - count
 | 
						||
            if wm.count < 0:
 | 
						||
                raise ParseError('加工前不良数量大于库存量')
 | 
						||
            if is_create:
 | 
						||
                wm.create_by = user
 | 
						||
            else:
 | 
						||
                wm.update_by = user
 | 
						||
            wm.save()
 | 
						||
            if material.tracking == Material.MA_TRACKING_SINGLE:
 | 
						||
                mlogbws = Mlogbw.objects.filter(mlogb=mlog_or_b, ftest__is_ok=False, ftest__defect_main=defect)
 | 
						||
                if mlogbws.count() != count:
 | 
						||
                    raise ParseError("日志与加工前不良明细数量不一致,操作失败")
 | 
						||
                for item in mlogbws:
 | 
						||
                    Wpr.change_or_new(wpr=item.wpr, wm=wm)
 | 
						||
 | 
						||
    mlog.submit_time = None
 | 
						||
    mlog.submit_user = None
 | 
						||
    mlog.save()
 | 
						||
 | 
						||
    # mtask变更状态
 | 
						||
    update_mtaskIds = []
 | 
						||
    if mlog.mtask:
 | 
						||
        update_mtaskIds.append(mlog.mtask.id)
 | 
						||
    list_m = Mlogb.objects.filter(mlog=mlog).values_list('mtask__id', flat=True).distinct()
 | 
						||
    update_mtaskIds += list(list_m)
 | 
						||
    if update_mtaskIds:
 | 
						||
        Mtask.objects.filter(id__in=update_mtaskIds, state=Mtask.MTASK_SUBMIT).update(state=Mtask.MTASK_ASSGINED)
 | 
						||
 | 
						||
    # 更新任务进度
 | 
						||
    cal_mtask_progress_from_mlog(mlog)
 | 
						||
 | 
						||
    # 更新物料数量
 | 
						||
    cal_material_count_from_mlog(mlog)
 | 
						||
 | 
						||
    # 清除关系链
 | 
						||
    BatchLog.clear(mlog=mlog)
 | 
						||
 | 
						||
    # 触发批次统计分析
 | 
						||
    xbatches = list(Mlogb.objects.filter(mlog=mlog).values_list('batch', flat=True))
 | 
						||
    ana_batch_thread(xbatches, mgroup=mlog.mgroup)
 | 
						||
 | 
						||
    # 触发单个统计
 | 
						||
    wprIds = list(Mlogbw.objects.filter(mlogb__mlog=mlog, ftest__isnull=False, wpr__isnull=False).values_list('wpr__id', flat=True))
 | 
						||
    if wprIds:
 | 
						||
        ana_wpr_thread(wprIds, mlog.mgroup)
 | 
						||
 | 
						||
def cal_mtask_progress_from_mlog(mlog:Mlog):
 | 
						||
    """
 | 
						||
    更新mlog关联的任务进度(可线程中执行)
 | 
						||
    """
 | 
						||
    if mlog.fill_way in [Mlog.MLOG_2, Mlog.MLOG_12] and mlog.mtask:
 | 
						||
        update_mtask(mlog.mtask, fill_way=mlog.fill_way)
 | 
						||
    elif mlog.fill_way == Mlog.MLOG_23:
 | 
						||
        mlog.cal_mlog_count_from_mlogb()
 | 
						||
        m_outs_qs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False)
 | 
						||
        caled_mtask = []
 | 
						||
        for item in m_outs_qs.all():
 | 
						||
            mtask = item.mtask
 | 
						||
            if mtask:
 | 
						||
                if mtask in caled_mtask:
 | 
						||
                    continue
 | 
						||
                update_mtask(mtask, fill_way=mlog.fill_way)
 | 
						||
                caled_mtask.append(mtask)
 | 
						||
 | 
						||
def cal_material_count_from_mlog(mlog: Mlog):
 | 
						||
    """
 | 
						||
    更新mlog关联的物料数量(可单独执行)
 | 
						||
    """
 | 
						||
    matid_list = []
 | 
						||
    if mlog.material_in:
 | 
						||
        matid_list.append(mlog.material_in.id)
 | 
						||
    if mlog.material_out:
 | 
						||
        matid_list.append(mlog.material_out.id)
 | 
						||
    matid_list2 = Mlogb.objects.filter(mlog=mlog).values_list('material_in__id', 'material_out__id').distinct()
 | 
						||
    for matid in matid_list2:
 | 
						||
        if matid[0]:
 | 
						||
            matid_list.append(matid[0])
 | 
						||
        if matid[1]:
 | 
						||
            matid_list.append(matid[1])
 | 
						||
    matid_list = list(set(matid_list))
 | 
						||
    cal_material_count(matid_list)
 | 
						||
    
 | 
						||
 | 
						||
def update_mtask(mtask: Mtask, fill_way: int = 10):
 | 
						||
    mtask = Mtask.objects.get(id=mtask.id)  # 防止并发修改获取最新的mtask
 | 
						||
    from apps.pm.models import Utask
 | 
						||
    if fill_way == Mlog.MLOG_2:
 | 
						||
        res = Mlog.objects.filter(mtask=mtask).exclude(submit_time=None).aggregate(sum_count_real=Sum(
 | 
						||
            'count_real'), sum_count_ok=Sum('count_ok'), sum_count_notok=Sum('count_notok'))
 | 
						||
        mtask.count_real = res['sum_count_real'] if res['sum_count_real'] else 0
 | 
						||
        mtask.count_ok = res['sum_count_ok'] if res['sum_count_ok'] else 0
 | 
						||
        mtask.count_notok = res['sum_count_notok'] if res['sum_count_notok'] else 0
 | 
						||
        mtask.save()
 | 
						||
        utask = mtask.utask
 | 
						||
        if utask and mtask.is_count_utask:
 | 
						||
            res2 = Mtask.objects.filter(utask=utask, mgroup=mtask.mgroup).aggregate(sum_count_real=Sum(
 | 
						||
                'count_real'), sum_count_ok=Sum('count_ok'), sum_count_notok=Sum('count_notok'))
 | 
						||
            utask.count_real = res2['sum_count_real'] if res2['sum_count_real'] else 0
 | 
						||
            utask.count_ok = res2['sum_count_ok'] if res2['sum_count_ok'] else 0
 | 
						||
            utask.count_notok = res2['sum_count_notok'] if res2['sum_count_notok'] else 0
 | 
						||
            if utask.count_ok > 0 and utask.state == Utask.UTASK_ASSGINED:
 | 
						||
                utask.state = Utask.UTASK_WORKING
 | 
						||
            if Mtask.objects.filter(utask=utask).exclude(state=Mtask.MTASK_SUBMIT).count() == 0:
 | 
						||
                utask.state = Utask.UTASK_SUBMIT
 | 
						||
            utask.save()
 | 
						||
    elif fill_way in [Mlog.MLOG_23, Mlog.MLOG_12]:
 | 
						||
        # 已经提交的日志
 | 
						||
        m_outs_qs_mtask = Mlogb.objects.filter(mtask=mtask, material_out__isnull=False, mlog__submit_time__isnull=False)
 | 
						||
        res = m_outs_qs_mtask.aggregate(
 | 
						||
                sum_count_real=Sum('count_real', default=0),
 | 
						||
                sum_count_ok=Sum('count_ok', default=0),
 | 
						||
                sum_count_notok=Sum('count_notok', default=0)
 | 
						||
            )
 | 
						||
        mtask.count_real = res['sum_count_real'] or 0
 | 
						||
        mtask.count_ok = res['sum_count_ok'] or 0
 | 
						||
        mtask.count_notok = res['sum_count_notok'] or 0
 | 
						||
        mtask.save()
 | 
						||
        utask = mtask.utask
 | 
						||
        is_main_mgroup = False
 | 
						||
        if utask:
 | 
						||
            if utask.state == Utask.UTASK_ASSGINED:
 | 
						||
                utask.state = Utask.UTASK_WORKING
 | 
						||
                utask.save()
 | 
						||
            if mtask.is_count_utask:
 | 
						||
                is_main_mgroup = True
 | 
						||
            elif mtask.material_out == utask.material:
 | 
						||
                is_main_mgroup = True
 | 
						||
        if is_main_mgroup:
 | 
						||
            res2 = Mtask.objects.filter(utask=utask, mgroup=mtask.mgroup).aggregate(sum_count_real=Sum(
 | 
						||
                'count_real'), sum_count_ok=Sum('count_ok'), sum_count_notok=Sum('count_notok'))
 | 
						||
            utask.count_real = res2['sum_count_real'] if res2['sum_count_real'] else 0
 | 
						||
            utask.count_ok = res2['sum_count_ok'] if res2['sum_count_ok'] else 0
 | 
						||
            utask.count_notok = res2['sum_count_notok'] if res2['sum_count_notok'] else 0
 | 
						||
            # if Mtask.objects.filter(utask=utask).exclude(state=Mtask.MTASK_SUBMIT).count() == 0:
 | 
						||
            #     utask.state = Utask.UTASK_SUBMIT
 | 
						||
            utask.save()
 | 
						||
 | 
						||
@lock_model_record_d_func(Handover)
 | 
						||
def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime, None]):
 | 
						||
    """
 | 
						||
    交接提交后需要执行的操作
 | 
						||
    """
 | 
						||
    if handover.submit_time is not None:
 | 
						||
        return
 | 
						||
    now = timezone.now()
 | 
						||
    handoverb_qs = Handoverb.objects.filter(handover=handover)
 | 
						||
    need_add = True
 | 
						||
    material:Material = handover.material
 | 
						||
    mtype = handover.mtype
 | 
						||
    if '混料' in material.name:  # hard code
 | 
						||
        need_add = False
 | 
						||
    if handoverb_qs.exists():
 | 
						||
        handoverb_list = [(item.wm.id, item.count, item) for item in handoverb_qs]
 | 
						||
    else:
 | 
						||
        handoverb_list = [(handover.wm.id, handover.count, handover)]
 | 
						||
    
 | 
						||
    recive_mgroup = handover.recive_mgroup
 | 
						||
    recive_dept = handover.recive_dept
 | 
						||
    
 | 
						||
    if handover.type == Handover.H_CHANGE:
 | 
						||
        handover.mtype = Handover.H_MERGE
 | 
						||
    new_batch = handover.new_batch
 | 
						||
    if new_batch and mtype != Handover.H_MERGE:
 | 
						||
        raise ParseError("只有合并时才能提供新批次号")
 | 
						||
 | 
						||
    batches = []
 | 
						||
    mids = []
 | 
						||
    exclude_batchst_ids = []
 | 
						||
    if mtype == Handover.H_MERGE:
 | 
						||
        if new_batch:
 | 
						||
            batches = [new_batch]
 | 
						||
        else:
 | 
						||
            raise ParseError("合并批次时请提供新批次号")
 | 
						||
        target_b, _ = BatchSt.g_create(batch=new_batch, handover=handover, material_start=material)
 | 
						||
        exclude_batchst_ids.append(target_b.id)
 | 
						||
    elif mtype == Handover.H_DIV:
 | 
						||
        if handover.wm is None:
 | 
						||
            raise ParseError('拆批请选择车间库存')
 | 
						||
        batches_to_limit = BatchLog.batches_to(batch=handover.wm.batch)["batches"]
 | 
						||
        source_b, _ = BatchSt.g_create(batch=handover.wm.batch)
 | 
						||
    for indx, item in enumerate(handoverb_list):
 | 
						||
        wmId, xcount, handover_or_b = item
 | 
						||
        if xcount <= 0:
 | 
						||
            raise ParseError("存在非正数!")
 | 
						||
        wm_from = WMaterial.objects.get(id=wmId)
 | 
						||
        mids.append(wm_from.material.id)
 | 
						||
 | 
						||
        # 合并为新批
 | 
						||
        if mtype == Handover.H_MERGE:
 | 
						||
            batch = new_batch
 | 
						||
            source, _ = BatchSt.g_create(batch=wm_from.batch, exclude_batchst_ids=exclude_batchst_ids)
 | 
						||
            BatchLog.g_create(source=source, target=target_b, handover=handover, relation_type="merge")
 | 
						||
            batches.append(wm_from.batch)
 | 
						||
        elif mtype == Handover.H_DIV:
 | 
						||
            batch = handover_or_b.batch
 | 
						||
            batches.append(batch)
 | 
						||
            if batch in batches_to_limit:
 | 
						||
                raise ParseError(f'{batch} 不在可拆批次中')
 | 
						||
            else:
 | 
						||
                batches_to_limit.append(batch)
 | 
						||
            target, _ = BatchSt.g_create(batch=batch, handover=handover, material_start=material)
 | 
						||
            exclude_batchst_ids.append(target.id)
 | 
						||
            BatchLog.g_create(source=source_b, target=target, handover=handover, relation_type="split")
 | 
						||
        else:
 | 
						||
            batch = wm_from.batch
 | 
						||
            batches.append(batch)
 | 
						||
 | 
						||
        if wm_from is None:
 | 
						||
            raise ParseError(f'{wm_from.batch} 找不到车间库存')
 | 
						||
        
 | 
						||
        count_x = wm_from.count - xcount
 | 
						||
        if count_x < 0:
 | 
						||
            raise ParseError(f'{wm_from.batch} 车间库存不足!')
 | 
						||
        else:
 | 
						||
            wm_from.count = count_x
 | 
						||
            wm_from.save()
 | 
						||
 | 
						||
        if need_add:
 | 
						||
            # 开始变动
 | 
						||
            if handover.type == Handover.H_NORMAL:
 | 
						||
                if mtype == Handover.H_MERGE and handover.new_wm:
 | 
						||
                    wm_to = handover.new_wm
 | 
						||
                else:
 | 
						||
                    wm_to, _ = WMaterial.objects.get_or_create(
 | 
						||
                        batch=batch,
 | 
						||
                        material=material,
 | 
						||
                        mgroup=recive_mgroup,
 | 
						||
                        belong_dept=recive_dept,
 | 
						||
                        state=wm_from.state,
 | 
						||
                        notok_sign=wm_from.notok_sign,
 | 
						||
                        defect=wm_from.defect,
 | 
						||
                        defaults={
 | 
						||
                            "batch_ofrom": wm_from.batch_ofrom,
 | 
						||
                            "material_ofrom": wm_from.material_ofrom,
 | 
						||
                            "create_by": user,
 | 
						||
                            "number_from": wm_from.number_from
 | 
						||
                        }
 | 
						||
                    )
 | 
						||
            elif handover.type == Handover.H_REPAIR:
 | 
						||
                # 返修交接
 | 
						||
                recive_mgroup = handover.recive_mgroup
 | 
						||
                if recive_mgroup:
 | 
						||
                    # if recive_mgroup.process.type == Process.PRO_TEST:
 | 
						||
                    #     wm_state = WMaterial.WM_REPAIRED
 | 
						||
                    # else:
 | 
						||
                    wm_state = WMaterial.WM_REPAIR
 | 
						||
                    wm_to, _ = WMaterial.objects.get_or_create(
 | 
						||
                        batch=batch,
 | 
						||
                        material=material,
 | 
						||
                        mgroup=recive_mgroup,
 | 
						||
                        belong_dept=recive_dept,
 | 
						||
                        notok_sign=wm_from.notok_sign,
 | 
						||
                        defect=wm_from.defect,
 | 
						||
                        material_origin=material,
 | 
						||
                        state=wm_state,
 | 
						||
                        defaults={
 | 
						||
                        "batch_ofrom": wm_from.batch_ofrom,
 | 
						||
                        "material_ofrom": wm_from.material_ofrom,
 | 
						||
                        "create_by": user,
 | 
						||
                        "number_from": wm_from.number_from
 | 
						||
                        }
 | 
						||
                    )
 | 
						||
                else:
 | 
						||
                    raise ParseError("返工交接必须指定接收工段")
 | 
						||
            elif handover.type == Handover.H_TEST:
 | 
						||
                wm_to, _ = WMaterial.objects.get_or_create(
 | 
						||
                    batch=batch,
 | 
						||
                    material=material,
 | 
						||
                    mgroup=recive_mgroup,
 | 
						||
                    state=WMaterial.WM_TEST,
 | 
						||
                    belong_dept=recive_dept,
 | 
						||
                    defaults={
 | 
						||
                        "count_xtest": 0,
 | 
						||
                        "batch_ofrom": wm_from.batch_ofrom,
 | 
						||
                        "material_ofrom": wm_from.material_ofrom,
 | 
						||
                        "create_by": user
 | 
						||
                    },
 | 
						||
                )
 | 
						||
            elif handover.type == Handover.H_SCRAP:
 | 
						||
                if recive_mgroup:
 | 
						||
                    wm_to, _ = WMaterial.objects.get_or_create(
 | 
						||
                        batch=batch,
 | 
						||
                        material=material,
 | 
						||
                        mgroup=recive_mgroup,
 | 
						||
                        belong_dept=recive_dept,
 | 
						||
                        notok_sign=wm_from.notok_sign,
 | 
						||
                        defect=wm_from.defect,
 | 
						||
                        state=WMaterial.WM_SCRAP,
 | 
						||
                        defaults={
 | 
						||
                        "batch_ofrom": wm_from.batch_ofrom,
 | 
						||
                        "material_ofrom": wm_from.material_ofrom,
 | 
						||
                        "create_by": user,
 | 
						||
                        "number_from": wm_from.number_from
 | 
						||
                        }
 | 
						||
                    )
 | 
						||
                else:
 | 
						||
                    raise ParseError("不支持非工段报废")
 | 
						||
            elif handover.type == Handover.H_CHANGE:
 | 
						||
                if handover.recive_mgroup:
 | 
						||
                    wm_to, _ = WMaterial.objects.get_or_create(
 | 
						||
                        batch=batch,
 | 
						||
                        material=handover.material_changed,
 | 
						||
                        mgroup=recive_mgroup,
 | 
						||
                        belong_dept=recive_dept,
 | 
						||
                        notok_sign=None,
 | 
						||
                        defect=None,
 | 
						||
                        material_origin=material,
 | 
						||
                        state=WMaterial.WM_OK,
 | 
						||
                        defaults={
 | 
						||
                        "batch_ofrom": wm_from.batch_ofrom,
 | 
						||
                        "material_ofrom": wm_from.material_ofrom,
 | 
						||
                        "create_by": user,
 | 
						||
                        "number_from": wm_from.number_from
 | 
						||
                        }
 | 
						||
                    )
 | 
						||
                else:
 | 
						||
                    raise ParseError("改版交接必须指定接收工段")
 | 
						||
            elif handover.type == Handover.H_BACK:
 | 
						||
                if wm_from and wm_from.state != WMaterial.WM_OK:
 | 
						||
                    raise ParseError("仅合格品支持退回")
 | 
						||
                if mtype == Handover.H_MERGE and handover.new_wm:
 | 
						||
                    wm_to = handover.new_wm
 | 
						||
                else:
 | 
						||
                    wm_to, _ = WMaterial.objects.get_or_create(
 | 
						||
                        batch=batch,
 | 
						||
                        material=material,
 | 
						||
                        mgroup=recive_mgroup,
 | 
						||
                        belong_dept=recive_dept,
 | 
						||
                        state=wm_from.state,
 | 
						||
                        notok_sign=wm_from.notok_sign,
 | 
						||
                        defect=wm_from.defect,
 | 
						||
                        defaults={
 | 
						||
                            "batch_ofrom": wm_from.batch_ofrom,
 | 
						||
                            "material_ofrom": wm_from.material_ofrom,
 | 
						||
                            "create_by": user,
 | 
						||
                            "number_from": wm_from.number_from
 | 
						||
                        }
 | 
						||
                    )
 | 
						||
            else:
 | 
						||
                raise ParseError("不支持该交接类型")
 | 
						||
 | 
						||
            wm_to.count = wm_to.count + xcount
 | 
						||
            wm_to.count_eweight = handover.count_eweight  # 这行代码有隐患
 | 
						||
            wm_to.save()
 | 
						||
            handover_or_b.wm_to = wm_to
 | 
						||
            handover_or_b.save()
 | 
						||
            if material.tracking == Material.MA_TRACKING_SINGLE:
 | 
						||
                handoverbws = Handoverbw.objects.filter(handoverb=handover_or_b)
 | 
						||
                if handoverbws.count() != xcount:
 | 
						||
                    raise ParseError("交接与明细数量不一致,操作失败")
 | 
						||
                wm_ids = list(Wpr.objects.filter(wpr_handoverbw__in=handoverbws).values_list("wm_id", flat=True).distinct())
 | 
						||
                if len(wm_ids) == 1 and wm_ids[0] == wm_from.id:
 | 
						||
                    pass
 | 
						||
                else:
 | 
						||
                    raise ParseError(f'{batch}物料明细中存在{len(wm_ids)}个不同物料批次')
 | 
						||
                for item in handoverbws:
 | 
						||
                    wpr:Wpr = item.wpr
 | 
						||
                    Wpr.change_or_new(wpr=wpr, wm=wm_to, old_wm=wpr.wm, old_mb=wpr.mb)
 | 
						||
                if wm_to.count != Wpr.objects.filter(wm=wm_to).count():
 | 
						||
                    raise ParseError("交接与明细数量不一致2,操作失败")
 | 
						||
 | 
						||
    handover.submit_user = user
 | 
						||
    handover.submit_time = now
 | 
						||
    if handover.recive_user is None:
 | 
						||
        handover.recive_user = user
 | 
						||
    handover.save()
 | 
						||
 | 
						||
    # 如果是改版交接需要触发统计数量
 | 
						||
    if handover.type == Handover.H_CHANGE:
 | 
						||
        mids.append(handover.material_changed.id)
 | 
						||
        cal_material_count(mids)
 | 
						||
    
 | 
						||
    ana_batch_thread(xbatchs=batches)
 | 
						||
 | 
						||
@lock_model_record_d_func(Handover)
 | 
						||
def handover_revert(handover:Handover, handler:User=None):
 | 
						||
    if handover.submit_time is None:
 | 
						||
        raise ParseError('该交接单未提交!')
 | 
						||
    ticket:Ticket = handover.ticket
 | 
						||
    if ticket:
 | 
						||
        # 首先把ticket改回开始状态
 | 
						||
        WfService.retreat(ticket=ticket, suggestion="撤销交接单", handler=handler, next_handler=handover.create_by)
 | 
						||
    mids = []
 | 
						||
    # handover_type = handover.type
 | 
						||
    # handover_mtype = handover.mtype
 | 
						||
    # if handover_type in [Handover.H_NORMAL, Handover.H_REPAIR] and handover_mtype == Handover.H_NORMAL:
 | 
						||
    #     pass
 | 
						||
    # else:
 | 
						||
    #     raise ParseError('该交接单不支持撤销!')
 | 
						||
    xbatchs = []
 | 
						||
    if handover.new_batch:
 | 
						||
        xbatchs.append(handover.new_batch)
 | 
						||
    handoverb_qs = Handoverb.objects.filter(handover=handover)
 | 
						||
    material = handover.material
 | 
						||
    for item in handoverb_qs:
 | 
						||
        if item.wm:
 | 
						||
            mids.append(item.wm.material.id)
 | 
						||
        if item.batch:
 | 
						||
            xbatchs.append(item.batch)
 | 
						||
        wm = item.wm
 | 
						||
        wm_to = item.wm_to
 | 
						||
        if wm is None or wm_to is None:
 | 
						||
            raise ParseError('该交接单不支持撤销2!')
 | 
						||
        if wm == wm_to:
 | 
						||
            # 此时是自己交给自己,不需要做任何操作
 | 
						||
            pass
 | 
						||
        else:
 | 
						||
            wm.count = wm.count + item.count
 | 
						||
            wm.save()
 | 
						||
            wm_to.count = wm_to.count - item.count
 | 
						||
            if wm_to.count < 0:
 | 
						||
                raise ParseError('库存不足无法撤回!')
 | 
						||
            wm_to.save()
 | 
						||
        if material.tracking == Material.MA_TRACKING_SINGLE:
 | 
						||
                handoverbws = Handoverbw.objects.filter(handoverb=item)
 | 
						||
                if handoverbws.count() != item.count:
 | 
						||
                    raise ParseError("交接与明细数量不一致,操作失败")
 | 
						||
                for item in handoverbws:
 | 
						||
                    wpr:Wpr = item.wpr
 | 
						||
                    Wpr.change_or_new(wpr=wpr, wm=wm, old_wm=wpr.wm, old_mb=wpr.mb, add_version=False)
 | 
						||
 | 
						||
    handover.submit_time = None
 | 
						||
    handover.submit_user = None
 | 
						||
    handover.save()
 | 
						||
    # 删除追踪链
 | 
						||
    BatchLog.clear(handover=handover)
 | 
						||
 | 
						||
    # 如果是改版交接需要触发统计数量
 | 
						||
    if handover.type == Handover.H_CHANGE:
 | 
						||
        mids.append(handover.material_changed.id)
 | 
						||
        cal_material_count(mids)
 | 
						||
    
 | 
						||
    ana_batch_thread(xbatchs=xbatchs)
 | 
						||
 | 
						||
def mlog_submit_validate(ins: Mlog):
 | 
						||
    if ins.submit_time:
 | 
						||
        raise ParseError('该日志已提交!')
 | 
						||
    if ins.mtask and ins.mtask.state == Mtask.MTASK_STOP:
 | 
						||
        raise ParseError('该任务已停止!')
 | 
						||
    if ins.fill_way == Mlog.MLOG_23:
 | 
						||
        if not Mlogb.objects.filter(material_out__isnull=False, mlog=ins).exists():
 | 
						||
            raise ParseError('该日志未指定产出!')
 | 
						||
        if not Mlogb.objects.filter(material_in__isnull=False, mlog=ins).exists():
 | 
						||
            raise ParseError('该日志未指定消耗!')
 | 
						||
        if Mlogb.objects.filter(material_out__isnull=False, count_real=0, mlog=ins).exists():
 | 
						||
            raise ParseError('产出数量不能为0!')
 | 
						||
    if ins.is_fix is False and ins.route:
 | 
						||
        process = ins.route.process
 | 
						||
        if Process.objects.filter(parent=process).exists() and not MlogUser.objects.filter(mlog=ins).exists():
 | 
						||
            raise ParseError('该日志子工序信息未完善!')
 | 
						||
 | 
						||
def bind_mlog(ticket: Ticket, transition, new_ticket_data: dict):
 | 
						||
    ins = Mlog.objects.get(id=new_ticket_data['t_id'])
 | 
						||
    mlog_submit_validate(ins) # 校验是否可submit
 | 
						||
    ticket_data = ticket.ticket_data
 | 
						||
    ticket_data.update({
 | 
						||
        't_model': 'mlog',
 | 
						||
        't_id': ins.id,
 | 
						||
    })
 | 
						||
    ticket.ticket_data = ticket_data
 | 
						||
    ticket.create_by = ins.create_by
 | 
						||
    ticket.save()
 | 
						||
    if ins.ticket is None:
 | 
						||
        ins.ticket = ticket
 | 
						||
        ins.save()
 | 
						||
 | 
						||
def bind_handover(ticket: Ticket, transition, new_ticket_data: dict):
 | 
						||
    ins = Handover.objects.get(id=new_ticket_data['t_id'])
 | 
						||
    ticket_data = ticket.ticket_data
 | 
						||
    ticket_data.update({
 | 
						||
        't_model': 'handover',
 | 
						||
        't_id': ins.id,
 | 
						||
    })
 | 
						||
    ticket.ticket_data = ticket_data
 | 
						||
    ticket.create_by = ins.create_by
 | 
						||
    ticket.save()
 | 
						||
    if ins.ticket is None:
 | 
						||
        ins.ticket = ticket
 | 
						||
        ins.save()
 | 
						||
 | 
						||
def mlog_audit_end(ticket: Ticket):
 | 
						||
    now = timezone.now()
 | 
						||
    ins = Mlog.objects.get(id=ticket.ticket_data['t_id'])
 | 
						||
    mlog_submit(ins, ticket.create_by, now)
 | 
						||
 | 
						||
 | 
						||
def get_batch_dag(batch_number: str, method="full"):
 | 
						||
    try:
 | 
						||
        batch_ins = BatchSt.objects.get(batch=batch_number, version=1)
 | 
						||
    except Exception:
 | 
						||
        raise ParseError("该批次号未构建关系链")
 | 
						||
 | 
						||
    r_dict = {
 | 
						||
        "split": "分",
 | 
						||
        "merge": "合"
 | 
						||
    }
 | 
						||
 | 
						||
    if method == "full":
 | 
						||
        # 完整DAG模式 - 收集所有相关批次和边(原逻辑)
 | 
						||
        nodes_set = {batch_ins.id}
 | 
						||
        edges = []
 | 
						||
        prev_size = 0
 | 
						||
 | 
						||
        while len(nodes_set) > prev_size:
 | 
						||
            prev_size = len(nodes_set)
 | 
						||
            logs = BatchLog.objects.filter(Q(source__id__in=nodes_set) | Q(target__id__in=nodes_set)).select_related(
 | 
						||
                "source", "target"
 | 
						||
            ).order_by("update_time")
 | 
						||
            
 | 
						||
            for log in logs:
 | 
						||
                source = log.source.id
 | 
						||
                target = log.target.id
 | 
						||
                nodes_set.update([source, target])
 | 
						||
                edges.append({
 | 
						||
                    'source': source,
 | 
						||
                    'target': target,
 | 
						||
                    "handover": log.handover.id if log.handover else None,
 | 
						||
                    "mlog": log.mlog.id if log.mlog else None,
 | 
						||
                    'label': r_dict.get(log.relation_type, ""),
 | 
						||
                })
 | 
						||
 | 
						||
    elif method == "direct":
 | 
						||
        # 直接关系模式 - 只查询直接相连的批次
 | 
						||
        nodes_set = {batch_ins.id}
 | 
						||
        edges = []
 | 
						||
 | 
						||
        # 查询作为source或target的直接关系
 | 
						||
        logs = BatchLog.objects.filter(Q(source__id=batch_ins.id) | Q(target__id=batch_ins.id)).select_related(
 | 
						||
            "source", "target"
 | 
						||
        ).order_by("update_time")
 | 
						||
        
 | 
						||
        for log in logs:
 | 
						||
            source = log.source.id
 | 
						||
            target = log.target.id
 | 
						||
            nodes_set.update([source, target])
 | 
						||
            edges.append({
 | 
						||
                'id': log.id,
 | 
						||
                'source': source,
 | 
						||
                'target': target,
 | 
						||
                "handover": log.handover.id if log.handover else None,
 | 
						||
                "mlog": log.mlog.id if log.mlog else None,
 | 
						||
                'label': r_dict.get(log.relation_type, ""),
 | 
						||
            })
 | 
						||
 | 
						||
    else:
 | 
						||
        raise ParseError("不支持的查询方法,请使用'full'或'direct'")
 | 
						||
 | 
						||
    # 将批次号排序
 | 
						||
    nodes_qs = BatchSt.objects.filter(id__in=nodes_set).order_by('update_time')
 | 
						||
    
 | 
						||
    nodes = [{
 | 
						||
        'id': item.id,
 | 
						||
        'label': item.batch,
 | 
						||
        'shape': 'rect'
 | 
						||
    } for item in nodes_qs]
 | 
						||
 | 
						||
    return {'nodes': nodes, 'edges': edges}
 | 
						||
 |