686 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			686 lines
		
	
	
		
			30 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.inm.models import MIO, MIOItem, MIOItemA
 | 
						||
from apps.pm.models import Mtask
 | 
						||
from apps.mtm.models import Mgroup, Shift, Material, Route, RoutePack
 | 
						||
 | 
						||
from .models import SfLog, WMaterial, Mlog, Mlogb, Handover
 | 
						||
from apps.mtm.services import cal_material_count
 | 
						||
from apps.wf.models import Ticket
 | 
						||
from django.db import transaction
 | 
						||
from apps.utils.thread import MyThread
 | 
						||
 | 
						||
def generate_new_batch(old_batch: str, mlog: Mlog):
 | 
						||
    new_batch = old_batch
 | 
						||
    supplier = mlog.supplier
 | 
						||
    process = mlog.mgroup.process
 | 
						||
    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}'
 | 
						||
    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 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)  # 根据排班规则制定排班记录
 | 
						||
    for shift in shifts:
 | 
						||
        start_time_o = shift.start_time_o
 | 
						||
        end_time_o = shift.end_time_o
 | 
						||
        current_date = start_date
 | 
						||
        while current_date <= end_date:
 | 
						||
            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:
 | 
						||
                if end_time.hour == 0:
 | 
						||
                    end_time += datetime.timedelta(days=1)
 | 
						||
                else:
 | 
						||
                    start_time -= datetime.timedelta(days=1)
 | 
						||
            total_sec = (end_time - start_time).total_seconds()
 | 
						||
            SfLog.objects.get_or_create(mgroup=mgroup, shift=shift, start_time=start_time, defaults={
 | 
						||
                "mgroup": mgroup,
 | 
						||
                "shift": shift,
 | 
						||
                "work_date": current_date,
 | 
						||
                "start_time": start_time,
 | 
						||
                "end_time": end_time,
 | 
						||
                "total_sec_now": total_sec,
 | 
						||
                "total_sec": total_sec,
 | 
						||
                "create_by": create_by
 | 
						||
            })
 | 
						||
            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:
 | 
						||
                qs.pcoal_heat = 6000
 | 
						||
                qs.save(update_fields=['pcoal_heat'])
 | 
						||
            cache.set(f'pcoal_heat_{year_s}_{month_s}_{day_s}', qs.pcoal_heat)
 | 
						||
            return qs.pcoal_heat
 | 
						||
        except Exception:
 | 
						||
            return 6000
 | 
						||
 | 
						||
 | 
						||
def do_out(mio: MIO):
 | 
						||
    """
 | 
						||
    生产领料到车间
 | 
						||
    """
 | 
						||
    belong_dept = mio.belong_dept
 | 
						||
    mgroup = mio.mgroup
 | 
						||
    do_user = mio.do_user
 | 
						||
    mioitems = MIOItem.objects.filter(mio=mio)
 | 
						||
    for item in mioitems:
 | 
						||
        material = item.material
 | 
						||
        if material.into_wm is False:
 | 
						||
            continue
 | 
						||
        # 用于混料的原料不与车间库存交互
 | 
						||
        # if material.type in [Material.MA_TYPE_MAINSO, Material.MA_TYPE_HELPSO]: # hard code
 | 
						||
        #     continue
 | 
						||
        action_list = []
 | 
						||
        mias = MIOItemA.objects.filter(mioitem=item)
 | 
						||
        if mias.exists():
 | 
						||
            mias_list = list(mias.values_list('material', 'batch', 'rate'))
 | 
						||
            for i in range(len(mias_list)):
 | 
						||
                material, batch, rate = mias_list[i]
 | 
						||
                new_count = rate * item.count  # 假设 item.count 存在
 | 
						||
                action_list.append([material, batch, new_count])
 | 
						||
        else:
 | 
						||
            action_list = [[item.material, item.batch, item.count]]
 | 
						||
        for al in action_list:
 | 
						||
            xmaterial, xbatch, xcount = al
 | 
						||
            # 领到车间库存(或工段)
 | 
						||
            wm, new_create = WMaterial.objects.get_or_create(batch=xbatch, material=xmaterial,
 | 
						||
                                                             belong_dept=belong_dept, mgroup=mgroup, 
 | 
						||
                                                             state=WMaterial.WM_OK,
 | 
						||
                                                             defaults={
 | 
						||
                                                                 "batch": xbatch,
 | 
						||
                                                                 "material": xmaterial,
 | 
						||
                                                                 "count": xcount,
 | 
						||
                                                                 "create_by": do_user,
 | 
						||
                                                                 "belong_dept": belong_dept,
 | 
						||
                                                                 "mgroup": mgroup,
 | 
						||
                                                                 "state": WMaterial.WM_OK,
 | 
						||
                                                             })
 | 
						||
            if not new_create:
 | 
						||
                wm.count = wm.count + item.count
 | 
						||
                wm.update_by = do_user
 | 
						||
                wm.save()
 | 
						||
 | 
						||
 | 
						||
def do_in(mio: MIO):
 | 
						||
    """
 | 
						||
    生产入库后更新车间物料
 | 
						||
    """
 | 
						||
    belong_dept = mio.belong_dept
 | 
						||
    mgroup = mio.mgroup
 | 
						||
    do_user = mio.do_user
 | 
						||
    mioitems = MIOItem.objects.filter(mio=mio)
 | 
						||
    for item in mioitems:
 | 
						||
        material = item.material
 | 
						||
        if material.into_wm is False:
 | 
						||
            continue
 | 
						||
        # 用于混料的原料不与车间库存交互
 | 
						||
        # if material.type in [Material.MA_TYPE_MAINSO, Material.MA_TYPE_HELPSO]: # hard code
 | 
						||
        #     continue
 | 
						||
        action_list = []
 | 
						||
        mias = MIOItemA.objects.filter(mioitem=item)
 | 
						||
        if mias.exists():  # 组合件入库
 | 
						||
            mias_list = list(mias.values_list('material', 'batch', 'rate'))
 | 
						||
            for i in range(len(mias_list)):
 | 
						||
                material, batch, rate = mias_list[i]
 | 
						||
                new_count = rate * item.count  # 假设 item.count 存在
 | 
						||
                action_list.append([material, batch, new_count])
 | 
						||
        else:
 | 
						||
            action_list = [[item.material, item.batch, item.count]]
 | 
						||
        for al in action_list:
 | 
						||
            xmaterial, xbatch, xcount = al
 | 
						||
            # 优先从车间库存里拿
 | 
						||
            wm_qs = WMaterial.objects.filter(batch=xbatch, material=xmaterial, belong_dept=belong_dept, mgroup=mgroup, state=WMaterial.WM_OK)
 | 
						||
            # if not wm_qs.exists():
 | 
						||
            #     wm_qs = WMaterial.objects.filter(batch=xbatch, material=xmaterial, belong_dept=belong_dept, mgroup__isnull=False, notok_sign=None, material_origin=None, count_xtest=None)
 | 
						||
            
 | 
						||
            count_x = wm_qs.count()
 | 
						||
            if count_x == 1:
 | 
						||
                wm = wm_qs.first()
 | 
						||
            elif count_x == 0:
 | 
						||
                raise ParseError(
 | 
						||
                    f'{str(xmaterial)}-{xbatch}-批次库存不存在!')
 | 
						||
            else:
 | 
						||
                raise ParseError(
 | 
						||
                    f'{str(xmaterial)}-{xbatch}-存在多个相同批次!')
 | 
						||
            
 | 
						||
            new_count = wm.count - xcount
 | 
						||
            if new_count >= 0:
 | 
						||
                wm.count = new_count
 | 
						||
                wm.update_by = do_user
 | 
						||
                wm.save()
 | 
						||
            else:
 | 
						||
                raise ParseError(f'{str(xmaterial)}-{xbatch}车间物料不足')
 | 
						||
 | 
						||
 | 
						||
def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
 | 
						||
    """
 | 
						||
    生产日志提交后需要执行的操作
 | 
						||
    """
 | 
						||
    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
 | 
						||
    belong_dept = mgroup.belong_dept
 | 
						||
    material_out = mlog.material_out
 | 
						||
    material_in = mlog.material_in
 | 
						||
    supplier = mlog.supplier  # 外协
 | 
						||
    if material_in:  # 需要进行车间库存管理
 | 
						||
        m_ins = Mlogb.objects.filter(mlog=mlog, material_in__isnull=False)
 | 
						||
        if m_ins.exists():
 | 
						||
            m_ins_list = []
 | 
						||
            m_ins_bl_list = []
 | 
						||
            for mi in m_ins.all():
 | 
						||
                m_ins_list.append((mi.material_in, mi.batch, mi.count_use, mi.wm_in))
 | 
						||
                if mi.count_n_jgqbl > 0:
 | 
						||
                    m_ins_bl_list.append((mi.material_in, mi.batch, mi.count_n_jgqbl))
 | 
						||
        else:
 | 
						||
            m_ins_list = [(material_in, mlog.batch, mlog.count_use, mlog.wm_in)]
 | 
						||
        for mi in m_ins_list:
 | 
						||
            mi_ma, mi_batch, mi_count, mi_wm_in = mi
 | 
						||
            # 需要判断领用数是否合理
 | 
						||
            # 优先使用工段库存
 | 
						||
            if mi_wm_in:
 | 
						||
                wm_qs = WMaterial.objects.filter(id=mi_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()
 | 
						||
        # 针对加工前不良的暂时额外处理 
 | 
						||
        for item in m_ins_bl_list:
 | 
						||
            material, batch, count_n_jgqbl = item
 | 
						||
            if count_n_jgqbl> 0:
 | 
						||
                lookup = {'batch': batch, 'material': material, 'mgroup': mgroup, 'notok_sign': 'jgqbl', 'state': WMaterial.WM_NOTOK}
 | 
						||
                wm, is_create = WMaterial.objects.get_or_create(**lookup, defaults={**lookup, "belong_dept": belong_dept})
 | 
						||
                wm.count = wm.count + item.count_n_jgqbl
 | 
						||
                if is_create:
 | 
						||
                    wm.create_by = user
 | 
						||
                else:
 | 
						||
                    wm.update_by = user
 | 
						||
                wm.save()
 | 
						||
                
 | 
						||
 | 
						||
    if material_out:  # 需要入车间库存
 | 
						||
        into_wm_mgroup = material_out.process.into_wm_mgroup if material_out.process else False
 | 
						||
        need_store_notok = material_out.process.store_notok if material_out.process else False
 | 
						||
        m_outs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False)
 | 
						||
        stored_notok = False
 | 
						||
        stored_mgroup = False
 | 
						||
        if m_outs.exists(): 
 | 
						||
            m_outs_list = [(mo.material_out, mo.batch if mo.batch else mlog.batch, mo.count_ok, mlog.count_real_eweight, None) for mo in m_outs.all()]
 | 
						||
            if need_store_notok:
 | 
						||
                for item in m_outs:
 | 
						||
                    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))
 | 
						||
                            stored_notok = True
 | 
						||
        else:
 | 
						||
            m_outs_list = [(material_out, mlog.batch, mlog.count_ok, mlog.count_real_eweight, None)]
 | 
						||
            # 一次填写的暂时不处理不合格品
 | 
						||
        
 | 
						||
        for mo in m_outs_list:
 | 
						||
            mo_ma, mo_batch, mo_count, mo_count_eweight, notok_sign = mo
 | 
						||
            wm_state = WMaterial.WM_OK if notok_sign is None else WMaterial.WM_NOTOK
 | 
						||
            lookup = {'batch': mo_batch, 'material': mo_ma, 'mgroup': None, 'notok_sign': notok_sign, 'state': wm_state}
 | 
						||
            if into_wm_mgroup:
 | 
						||
                lookup['mgroup'] = mgroup
 | 
						||
                stored_mgroup = True
 | 
						||
            else:
 | 
						||
                lookup['belong_dept'] = belong_dept
 | 
						||
            if mo_count > 0:
 | 
						||
                wm, _ = 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
 | 
						||
                wm.save()
 | 
						||
        
 | 
						||
    mlog.submit_time = now
 | 
						||
    mlog.submit_user = user
 | 
						||
    mlog.stored_notok = stored_notok
 | 
						||
    mlog.stored_mgroup = stored_mgroup
 | 
						||
    mlog.save()
 | 
						||
 | 
						||
 | 
						||
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
 | 
						||
    belong_dept = mgroup.belong_dept
 | 
						||
    material_out = mlog.material_out
 | 
						||
    material_in = mlog.material_in
 | 
						||
    stored_notok = mlog.stored_notok
 | 
						||
    stored_mgroup = mlog.stored_mgroup
 | 
						||
    if material_in:
 | 
						||
        # 领用数退回
 | 
						||
        into_wm_mgroup = material_in.process.into_wm_mgroup if material_in.process else False
 | 
						||
        m_ins = Mlogb.objects.filter(mlog=mlog, material_in__isnull=False)
 | 
						||
        if m_ins.exists():
 | 
						||
            m_ins_list = []
 | 
						||
            m_ins_bl_list = []
 | 
						||
            for mi in m_ins.all():
 | 
						||
                m_ins_list.append((mi.material_in, mi.batch, mi.count_use, mi.wm_in))
 | 
						||
                if mi.count_n_jgqbl > 0:
 | 
						||
                    m_ins_bl_list.append((mi.material_in, mi.batch, mi.count_n_jgqbl))
 | 
						||
        else:
 | 
						||
            m_ins_list = [(material_in, mlog.batch, mlog.count_use, mlog.wm_in)]
 | 
						||
        for mi in m_ins_list:
 | 
						||
            mi_ma, mi_batch, mi_count, mi_wm_in = mi
 | 
						||
            if mi_wm_in:
 | 
						||
                mi_wm_in.count = mi_wm_in.count + mi_count
 | 
						||
                mi_wm_in.update_by = user
 | 
						||
                mi_wm_in.save()
 | 
						||
            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()
 | 
						||
        # 针对加工前不良的暂时额外处理 
 | 
						||
        for item in m_ins_bl_list:
 | 
						||
            material, batch, count_n_jgqbl = item
 | 
						||
            if count_n_jgqbl> 0:
 | 
						||
                lookup = {'batch': batch, 'material': material, 'mgroup': mgroup, 'notok_sign': 'jgqbl', 'state': WMaterial.WM_NOTOK}
 | 
						||
                wm, is_create = WMaterial.objects.get_or_create(**lookup, defaults={**lookup, "belong_dept": belong_dept})
 | 
						||
                wm.count = wm.count - item.count_n_jgqbl
 | 
						||
                if wm.count < 0:
 | 
						||
                    raise ParseError('加工前不良数量大于库存量')
 | 
						||
                if is_create:
 | 
						||
                    wm.create_by = user
 | 
						||
                else:
 | 
						||
                    wm.update_by = user
 | 
						||
                wm.save()
 | 
						||
 | 
						||
    if material_out:  # 产物退回
 | 
						||
        # 有多个产物的情况
 | 
						||
        # 需要考虑不合格品退回的情况
 | 
						||
        m_outs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False)
 | 
						||
        if m_outs.exists():
 | 
						||
            m_outs_list = [(mo.material_out, mo.batch if mo.batch else mlog.batch, mo.count_ok, mlog.count_real_eweight, None) for mo in m_outs.all()]
 | 
						||
            if stored_notok:
 | 
						||
                for item in m_outs:
 | 
						||
                    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))
 | 
						||
        else:
 | 
						||
            m_outs_list = [(material_out, mlog.batch, mlog.count_ok, mlog.count_real_eweight, None)]
 | 
						||
            # 一次填写的暂时不处理不合格品
 | 
						||
            
 | 
						||
        for mo in m_outs_list:
 | 
						||
            mo_ma, mo_batch, mo_count, _, notok_sign = mo
 | 
						||
            if mo_count == 0:
 | 
						||
                continue
 | 
						||
            wm_state = WMaterial.WM_OK if notok_sign is None else WMaterial.WM_NOTOK
 | 
						||
            lookup = {'batch': mo_batch, 'material': mo_ma, 'mgroup': None, 'notok_sign': notok_sign, 'state': wm_state}
 | 
						||
            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('车间库存不足, 产物无法回退')
 | 
						||
            elif wm.count >= 0:
 | 
						||
                wm.update_by = user
 | 
						||
                wm.save()
 | 
						||
    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)
 | 
						||
 | 
						||
 | 
						||
def cal_mlog_count_from_mlogb(mlog: Mlog):
 | 
						||
    """
 | 
						||
    通过mlogb计算mlog count 合计
 | 
						||
    """
 | 
						||
    if mlog.fill_way == Mlog.MLOG_23:
 | 
						||
        a_dict = {
 | 
						||
            "total_count_use": Sum('count_use'),
 | 
						||
            "total_count_break": Sum('count_break'),
 | 
						||
            "total_count_break_t": Sum('count_break_t'),
 | 
						||
            "total_count_real": Sum('count_real'),
 | 
						||
            "total_count_ok": Sum('count_ok'),
 | 
						||
            "total_count_notok": Sum('count_notok'),
 | 
						||
        }
 | 
						||
        f_names = [f.name for f in Mlogb._meta.fields if 'count_n_' in f.name]
 | 
						||
        for f in f_names:
 | 
						||
            a_dict[f'total_{f}'] = Sum(f)
 | 
						||
        mlogb_summary = Mlogb.objects.filter(mlog=mlog).aggregate(
 | 
						||
            **a_dict
 | 
						||
        )
 | 
						||
        # 更新Mlog对象的相应字段
 | 
						||
        mlog.count_use = mlogb_summary['total_count_use'] or 0
 | 
						||
        mlog.count_break = mlogb_summary['total_count_break'] or 0
 | 
						||
        mlog.count_break_t = mlogb_summary['total_count_break_t'] or 0
 | 
						||
        mlog.count_real = mlogb_summary['total_count_real'] or 0
 | 
						||
        mlog.count_ok = mlogb_summary['total_count_ok'] or 0
 | 
						||
        mlog.count_notok = mlogb_summary['total_count_notok'] or 0
 | 
						||
        for f in f_names:
 | 
						||
            setattr(mlog, f, mlogb_summary[f'total_{f}'] or 0)
 | 
						||
        # 保存更新后的Mlog对象
 | 
						||
        mlog.save()
 | 
						||
 | 
						||
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:
 | 
						||
        cal_mlog_count_from_mlogb(mlog)
 | 
						||
        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 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])
 | 
						||
    cal_material_count(matid_list)
 | 
						||
    
 | 
						||
 | 
						||
def update_mtask(mtask: Mtask, fill_way: int = 10):
 | 
						||
    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()
 | 
						||
 | 
						||
 | 
						||
def handover_submit(handover: Handover, user: User, now: Union[datetime.datetime, None]):
 | 
						||
    """
 | 
						||
    交接提交后需要执行的操作
 | 
						||
    """
 | 
						||
    if handover.submit_time is not None:
 | 
						||
        return
 | 
						||
    now = timezone.now()
 | 
						||
    need_add = True
 | 
						||
    material = handover.material
 | 
						||
    batch = handover.batch
 | 
						||
    wm_from = handover.wm
 | 
						||
    if wm_from is None:
 | 
						||
        raise ParseError('找不到车间库存')
 | 
						||
    if '混料' in material.name:  # hard code
 | 
						||
        need_add = False
 | 
						||
    count_x = wm_from.count - handover.count
 | 
						||
    if count_x < 0:
 | 
						||
        raise ParseError('车间库存不足!')
 | 
						||
    else:
 | 
						||
        wm_from.count = count_x
 | 
						||
        wm_from.save()
 | 
						||
    if need_add:
 | 
						||
        if handover.type == Handover.H_NORMAL:
 | 
						||
            if handover.recive_mgroup:
 | 
						||
                wm_to, _ = WMaterial.objects.get_or_create(
 | 
						||
                    batch=batch,
 | 
						||
                    material=material,
 | 
						||
                    mgroup=handover.recive_mgroup,
 | 
						||
                    state=WMaterial.WM_OK,
 | 
						||
                    defaults={"batch": batch, "material": material, "mgroup": handover.recive_mgroup, "belong_dept": handover.recive_dept},
 | 
						||
                )
 | 
						||
            else:
 | 
						||
                wm_to, _ = WMaterial.objects.get_or_create(
 | 
						||
                    batch=batch, 
 | 
						||
                    material=material, 
 | 
						||
                    belong_dept=handover.recive_dept,
 | 
						||
                    mgroup=None,
 | 
						||
                    state=WMaterial.WM_OK,
 | 
						||
                    defaults={"batch": batch, "material": material, "belong_dept": handover.recive_dept}
 | 
						||
                )
 | 
						||
        elif handover.type == Handover.H_REPAIR:
 | 
						||
            if handover.recive_mgroup:
 | 
						||
                wm_to, _ = WMaterial.objects.get_or_create(
 | 
						||
                    batch=batch,
 | 
						||
                    material=handover.material_changed,
 | 
						||
                    mgroup=handover.recive_mgroup,
 | 
						||
                    notok_sign=handover.wm.notok_sign,
 | 
						||
                    material_origin=handover.material,
 | 
						||
                    state=WMaterial.WM_REPAIR,
 | 
						||
                    defaults={
 | 
						||
                        "batch": batch,
 | 
						||
                        "material": handover.material_changed,
 | 
						||
                        "mgroup": handover.recive_mgroup,
 | 
						||
                        "notok_sign": handover.wm.notok_sign,
 | 
						||
                        "material_origin": handover.material,
 | 
						||
                        "belong_dept": handover.recive_dept,
 | 
						||
                        "state": WMaterial.WM_REPAIR
 | 
						||
                    },
 | 
						||
                )
 | 
						||
            else:
 | 
						||
                raise ParseError("返工交接必须指定接收工段")
 | 
						||
        elif handover.type == Handover.H_TEST:
 | 
						||
            if handover.recive_mgroup:
 | 
						||
                wm_to, _ = WMaterial.objects.get_or_create(
 | 
						||
                    batch=batch,
 | 
						||
                    material=material,
 | 
						||
                    mgroup=handover.recive_mgroup,
 | 
						||
                    state=WMaterial.WM_TEST,
 | 
						||
                    defaults={
 | 
						||
                        "batch": batch, 
 | 
						||
                        "material": material, 
 | 
						||
                        "mgroup": handover.recive_mgroup, 
 | 
						||
                        "belong_dept": handover.recive_dept, 
 | 
						||
                        "count_xtest": 0,
 | 
						||
                        "state": WMaterial.WM_TEST},
 | 
						||
                )
 | 
						||
            else:
 | 
						||
                wm_to, _ = WMaterial.objects.get_or_create(
 | 
						||
                    batch=batch,
 | 
						||
                    material=material,
 | 
						||
                    belong_dept=handover.recive_dept,
 | 
						||
                    mgroup=None,
 | 
						||
                    state=WMaterial.WM_TEST,
 | 
						||
                    defaults={"batch": batch, "material": material, "belong_dept": handover.recive_dept, "count_xtest": 0, "state": WMaterial.WM_TEST},
 | 
						||
                )
 | 
						||
        else:
 | 
						||
            raise ParseError("不支持交接类型")
 | 
						||
 | 
						||
        wm_to.count = wm_to.count + handover.count
 | 
						||
        wm_to.count_eweight = handover.count_eweight  # 这行代码有隐患
 | 
						||
        wm_to.save()
 | 
						||
    handover.submit_user = user
 | 
						||
    handover.submit_time = now
 | 
						||
    handover.save()
 | 
						||
 | 
						||
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!')
 | 
						||
 | 
						||
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 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)
 | 
						||
    MyThread(target=cal_mtask_progress_from_mlog,args=(ins,)).start()
 | 
						||
    MyThread(target=cal_material_count_from_mlog,args=(ins,)).start()
 |