import datetime from django.core.cache import cache from django.db.models import Sum from django.utils import timezone from typing import Union from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned 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 from .models import SfLog, SfLogExp, WMaterial, Mlog, Mlogb, Handover from apps.mtm.models import Process from apps.mtm.services import cal_material_count 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=1)).date(), end_date=happen_time.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 do_user = mio.do_user mioitems = MIOItem.objects.filter(mio=mio) for item in mioitems: # 用于混料的原料不与车间库存交互 material = item.material 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=None, defaults={ "batch": xbatch, "material": xmaterial, "count": xcount, "create_by": do_user, "belong_dept": belong_dept, "mgroup": None, }) 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 do_user = mio.do_user mioitems = MIOItem.objects.filter(mio=mio) for item in mioitems: # 用于混料的原料不与车间库存交互 material = item.material 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=None) if not wm_qs.exists(): wm_qs = WMaterial.objects.filter(batch=xbatch, material=xmaterial, belong_dept=belong_dept, mgroup__isnull=False) 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() elif new_count == 0: wm.delete() 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 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 if material_in: # 需要进行车间库存管理 m_ins = Mlogb.objects.filter(mlog=mlog, material_in__isnull=False) if m_ins.exists(): m_ins_list = [(mi.material_in, mi.batch, mi.count_use) for mi in m_ins.all()] else: m_ins_list = [(material_in, mlog.batch, mlog.count_use)] for mi in m_ins_list: mi_ma, mi_batch, mi_count = mi # 需要判断领用数是否合理 # 优先使用工段库存 wm_qs = WMaterial.objects.filter(batch=mi_batch, material=mi_ma, mgroup=mgroup) if not wm_qs.exists(): wm_qs = WMaterial.objects.filter(batch=mi_batch, material=mi_ma, belong_dept=belong_dept, mgroup=None) 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 if wm.count == 0: wm.delete() 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 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) for mo in m_outs.all()] else: m_outs_list = [(material_out, mlog.batch, mlog.count_ok, mlog.count_real_eweight)] for mo in m_outs_list: mo_ma, mo_batch, mo_count, mo_count_eweight = mo lookup = {'batch': mo_batch, 'material': mo_ma, 'mgroup': None} if into_wm_mgroup: lookup['mgroup'] = mgroup else: lookup['belong_dept'] = belong_dept if mo_count > 0: wm, _ = WMaterial.objects.get_or_create(**lookup, defaults=lookup) wm.count = wm.count + mo_count wm.count_eweight = mo_count_eweight wm.update_by = user wm.save() mlog.submit_time = now mlog.submit_user = user 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 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 = [(mi.material_in, mi.batch, mi.count_use) for mi in m_ins.all()] else: m_ins_list = [(material_in, mlog.batch, mlog.count_use)] for mi in m_ins_list: mi_ma, mi_batch, mi_count = mi lookup = {'batch': mi_batch, 'material': mi_ma, 'mgroup': None} if into_wm_mgroup: lookup['mgroup'] = mgroup else: lookup['belong_dept'] = belong_dept wm, _ = WMaterial.objects.get_or_create(**lookup, defaults=lookup) wm.count = wm.count + mi_count wm.update_by = user wm.save() if material_out: # 产物退回 # 有多个产物的情况 into_wm_mgroup = material_out.process.into_wm_mgroup if material_out.process else False 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) for mo in m_outs.all()] else: m_outs_list = [(material_out, mlog.batch, mlog.count_ok, mlog.count_real_eweight)] for mo in m_outs_list: mo_ma, mo_batch, mo_count, _ = mo lookup = {'batch': mo_batch, 'material': mo_ma, 'mgroup': None} if into_wm_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.delete() else: wm.update_by = user wm.save() mlog.submit_time = None mlog.submit_user = None mlog.save() def cal_mtask_progress(mtask: Mtask): """ 计算任务进度 """ def cal_mtask_progress_from_mlog(mlog: Mlog): """ 更新mlog关联的任务进度(可线程中执行) """ 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): from apps.pm.models import Utask 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() 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 wm_from_need_delete = False 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('车间库存不足!') elif count_x == 0: wm_from_need_delete = True else: wm_from.count = count_x wm_from.save() if need_add: if handover.recive_mgroup: wm_to, _ = WMaterial.objects.get_or_create(batch=batch, material=material, mgroup=handover.recive_mgroup, 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, defaults={ 'batch': batch, 'material': material, 'belong_dept': handover.recive_dept }) 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() if wm_from_need_delete: wm_from.delete()