755 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			755 lines
		
	
	
		
			34 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
 | ||
| from apps.mtm.services import cal_material_count
 | ||
| from apps.wf.models import Ticket
 | ||
| from apps.utils.thread import MyThread
 | ||
| import logging
 | ||
| from apps.wpm.services_2 import get_alldata_with_batch_and_store
 | ||
| from datetime import timedelta
 | ||
| from apps.wpmw.models import Wpr, WprDefect
 | ||
| from ..qm.models import Defect, FtestDefect
 | ||
| from django.db.models import Count
 | ||
| 
 | ||
| myLogger = logging.getLogger('log')
 | ||
| 
 | ||
| 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}'
 | ||
|         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:
 | ||
|                     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
 | ||
| 
 | ||
| 
 | ||
| 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: Material = mlog.material_out
 | ||
|     material_in: Material = mlog.material_in
 | ||
|     supplier = mlog.supplier  # 外协
 | ||
|     m_ins_list = []
 | ||
|     if material_in:  # 需要进行车间库存管理
 | ||
|         m_ins_list = []
 | ||
|         m_ins_bl_list = []
 | ||
|         m_ins = Mlogb.objects.filter(mlog=mlog, material_in__isnull=False)
 | ||
|         if m_ins.exists():
 | ||
|             for mi in m_ins.all():
 | ||
|                 m_ins_list.append((mi.material_in, mi.batch, mi.count_use, mi))
 | ||
|                 if mi.count_pn_jgqbl > 0:
 | ||
|                     m_ins_bl_list.append((mi.material_in, mi.batch, mi.count_pn_jgqbl, mi))
 | ||
|         else:
 | ||
|             m_ins_list = [(material_in, mlog.batch, mlog.count_use, mlog)]
 | ||
|         for mi in m_ins_list:
 | ||
|             mi_ma, mi_batch, mi_count, mlog_or_b = mi
 | ||
|             # 需要判断领用数是否合理
 | ||
|             # 优先使用工段库存
 | ||
|             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 material_in.tracking == Material.MA_TRACKING_SINGLE:
 | ||
|                     mlogbws = Mlogbw.objects.filter(mlogb=mlog_or_b)
 | ||
|                     if mlogbws.count() != mi_count:
 | ||
|                         raise ParseError("日志与明细数量不一致,操作失败")
 | ||
|                     for item in mlogbws:
 | ||
|                         if item.ftest:
 | ||
|                             raise ParseError("不支持消耗物料的检验")
 | ||
|                         Wpr.change_or_new(wpr=item.wpr, old_wm=wm)
 | ||
| 
 | ||
|         # 针对加工前不良的暂时额外处理 
 | ||
|         for item in m_ins_bl_list:
 | ||
|             material, batch, count_pn_jgqbl, mi_ = item
 | ||
|             if count_pn_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 + count_pn_jgqbl
 | ||
|                 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_in.tracking == Material.MA_TRACKING_SINGLE:
 | ||
|                     raise ParseError("加工前不良的物料暂不支持单件追踪")
 | ||
|                 
 | ||
| 
 | ||
|     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, mo) for mo in m_outs.all()]
 | ||
|             if need_store_notok:
 | ||
|                 for item in m_outs:
 | ||
|                     if Mlogbw.objects.filter(mlogb=item).exists():
 | ||
|                         # 获取所有主要的不合格项
 | ||
|                         bw_qs = Mlogbw.objects.filter(mlogb=item)
 | ||
|                         defectIds= FtestDefect.objects.filter(ftest__mlogbw_ftest__in=bw_qs, is_main=True, has=True).values_list("defect__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__defects_ftest__defect__id__in=defects_map.keys(),
 | ||
|                             ftest__defects_ftest__has=True,
 | ||
|                             ftest__defects_ftest__is_main=True
 | ||
|                         ).values('ftest__defects_ftest__defect__id').annotate(xcount=Count('id'))
 | ||
|                         # 整理结果
 | ||
|                         for defect_data in filtered_bw_qs:
 | ||
|                             defect_id = defect_data['ftest__defects_ftest__defect__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_count <= 0:
 | ||
|                 continue
 | ||
|             wm_state = WMaterial.WM_OK if notok_sign_or_defect is None 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
 | ||
|                 stored_mgroup = True
 | ||
|             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
 | ||
|             wm.save()
 | ||
|             if material_out.tracking == Material.MA_TRACKING_SINGLE:
 | ||
|                 mlogbws = Mlogbw.objects.filter(mlogb=mlog_or_b)
 | ||
|                 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 = Wpr.change_or_new(number=item.number, wm=wm, ftest=item.ftest)
 | ||
|                         item.wpr = wpr
 | ||
|                         item.save()
 | ||
|         
 | ||
|     mlog.submit_time = now
 | ||
|     mlog.submit_user = user
 | ||
|     mlog.stored_notok = stored_notok
 | ||
|     mlog.stored_mgroup = stored_mgroup
 | ||
|     mlog.save()
 | ||
| 
 | ||
|     # 更新任务进度
 | ||
|     cal_mtask_progress_from_mlog(mlog)
 | ||
| 
 | ||
|     # 更新物料数量
 | ||
|     MyThread(target=cal_material_count_from_mlog,args=(mlog,)).start()
 | ||
| 
 | ||
|     # 触发批次统计分析
 | ||
|     if mlog.batch:
 | ||
|         MyThread(target=get_alldata_with_batch_and_store, args=(mlog.batch,)).start()
 | ||
| 
 | ||
| 
 | ||
| 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:Material = mlog.material_out
 | ||
|     material_in:Material = mlog.material_in
 | ||
|     stored_notok = mlog.stored_notok
 | ||
|     stored_mgroup = mlog.stored_mgroup
 | ||
| 
 | ||
|     # 先回退产物
 | ||
|     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, mo)
 | ||
|                 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, 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, mlog_or_b = 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()
 | ||
|                 if material_out.tracking == Material.MA_TRACKING_SINGLE:
 | ||
|                     mlogbws = Mlogbw.objects.filter(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)
 | ||
| 
 | ||
|     # 再生成消耗
 | ||
|     if material_in:        # 领用数退回
 | ||
|         m_ins_list = []
 | ||
|         m_ins_bl_list = []
 | ||
|         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():
 | ||
|             for mi in m_ins.all():
 | ||
|                 m_ins_list.append((mi.material_in, mi.batch, mi.count_use, mi.wm_in, mi))
 | ||
|                 if mi.count_pn_jgqbl > 0:
 | ||
|                     m_ins_bl_list.append((mi.material_in, mi.batch, mi.count_pn_jgqbl, mi))
 | ||
|         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, mi_wm_in, mlog_or_b = mi
 | ||
|             if mi_wm_in:
 | ||
|                 wm = mi_wm_in
 | ||
|             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 material_in.tracking == Material.MA_TRACKING_SINGLE:
 | ||
|                 mlogbws = Mlogbw.objects.filter(mlogb=mlog_or_b)
 | ||
|                 if mlogbws.count() != mi_count:
 | ||
|                     raise ParseError("日志与明细数量不一致,操作失败")
 | ||
|                 for item in mlogbws:
 | ||
|                     if item.wpr:
 | ||
|                         Wpr.change_or_new(wpr=item.wpr, wm=wm)
 | ||
|                     else:
 | ||
|                         wpr = Wpr.change_or_new(number=item.number, wm=wm)
 | ||
|                         item.wpr = wpr
 | ||
|                         item.save()
 | ||
| 
 | ||
|         # 针对加工前不良的暂时额外处理 
 | ||
|         for item in m_ins_bl_list:
 | ||
|             material, batch, count_pn_jgqbl = item
 | ||
|             if count_pn_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 - count_pn_jgqbl
 | ||
|                 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:
 | ||
|                     raise ParseError("加工前不良的物料暂不支持单件回退")
 | ||
| 
 | ||
|     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)
 | ||
| 
 | ||
|     # 更新物料数量
 | ||
|     MyThread(target=cal_material_count_from_mlog,args=(mlog,)).start()
 | ||
| 
 | ||
|     # 触发批次统计分析
 | ||
|     if mlog.batch:
 | ||
|         MyThread(target=get_alldata_with_batch_and_store, args=(mlog.batch,)).start()
 | ||
| 
 | ||
| def cal_mtask_progress_from_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_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_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):
 | ||
|     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()
 | ||
| 
 | ||
| 
 | ||
| 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
 | ||
|     if '混料' in material.name:  # hard code
 | ||
|         need_add = False
 | ||
|     if handoverb_qs.exists():
 | ||
|         handoverb_list = [(item.wm, item.count, item) for item in handoverb_qs]
 | ||
|     else:
 | ||
|         handoverb_list = [(handover.wm, handover.count, handover)]
 | ||
|     
 | ||
|     recive_mgroup = handover.recive_mgroup
 | ||
|     recive_dept = handover.recive_dept
 | ||
|     batches = []
 | ||
|     for item in handoverb_list:
 | ||
|         wm_from, xcount, handover_or_b = item
 | ||
|         batch = wm_from.batch
 | ||
| 
 | ||
|         if wm_from is None:
 | ||
|             raise ParseError('找不到车间库存')
 | ||
|         batches.append(batch)
 | ||
|     
 | ||
|         count_x = wm_from.count - xcount
 | ||
|         if count_x < 0:
 | ||
|             raise ParseError('车间库存不足!')
 | ||
|         else:
 | ||
|             wm_from.count = count_x
 | ||
|             wm_from.save()
 | ||
| 
 | ||
|         if need_add:
 | ||
|             # 开始变动
 | ||
|             if handover.type == Handover.H_NORMAL:
 | ||
|                 wm_to, _ = WMaterial.objects.get_or_create(
 | ||
|                     batch=batch,
 | ||
|                     material=material,
 | ||
|                     mgroup=recive_mgroup,
 | ||
|                     belong_dept=recive_dept,
 | ||
|                     state=WMaterial.WM_OK,
 | ||
|                     defaults={
 | ||
|                         "batch_ofrom": wm_from.batch_ofrom,
 | ||
|                         "material_ofrom": wm_from.material_ofrom,
 | ||
|                         "create_by": user
 | ||
|                     }
 | ||
|                 )
 | ||
|             elif handover.type == Handover.H_REPAIR:
 | ||
|                 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=wm_from.notok_sign,
 | ||
|                         material_origin=material,
 | ||
|                         state=WMaterial.WM_REPAIR,
 | ||
|                         defaults={
 | ||
|                         "batch_ofrom": wm_from.batch_ofrom,
 | ||
|                         "material_ofrom": wm_from.material_ofrom,
 | ||
|                         "create_by": user
 | ||
|                         }
 | ||
|                     )
 | ||
|                 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,
 | ||
|                         state=WMaterial.WM_SCRAP,
 | ||
|                         defaults={
 | ||
|                         "batch_ofrom": wm_from.batch_ofrom,
 | ||
|                         "material_ofrom": wm_from.material_ofrom,
 | ||
|                         "create_by": user
 | ||
|                         }
 | ||
|                     )
 | ||
|                 else:
 | ||
|                     raise ParseError("不支持非工段报废")
 | ||
|             else:
 | ||
|                 raise ParseError("不支持该交接类型")
 | ||
| 
 | ||
|             wm_to.count = wm_to.count + xcount
 | ||
|             wm_to.count_eweight = handover.count_eweight  # 这行代码有隐患
 | ||
|             wm_to.save()
 | ||
|             if material.tracking == Material.MA_TRACKING_SINGLE:
 | ||
|                 handoverbws = Handoverbw.objects.filter(handoverb=handover_or_b)
 | ||
|                 if handoverbws.count() != xcount:
 | ||
|                     raise ParseError("交接与明细数量不一致,操作失败")
 | ||
|                 for item in handoverbws:
 | ||
|                     Wpr.change_or_new(wpr=item.wpr, wm=wm_to)
 | ||
| 
 | ||
|     handover.submit_user = user
 | ||
|     handover.submit_time = now
 | ||
|     handover.save()
 | ||
| 
 | ||
|     batches = list(set(batches))
 | ||
|     if batches:
 | ||
|         for batch in batches:
 | ||
|             MyThread(target=get_alldata_with_batch_and_store, args=(batch,)).start()
 | ||
| 
 | ||
| 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)
 | ||
| 
 |