factory/apps/wpm/services.py

511 lines
22 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 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, notok_sign=None)
if not wm_qs.exists():
wm_qs = WMaterial.objects.filter(batch=xbatch, material=xmaterial, belong_dept=belong_dept, mgroup__isnull=False, notok_sign=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 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, mi.wm_in) for mi in m_ins.all()]
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, notok_sign=None)
if not wm_qs.exists():
wm_qs = WMaterial.objects.filter(batch=mi_batch, material=mi_ma, belong_dept=belong_dept, mgroup=None, notok_sign=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
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
lookup = {'batch': mo_batch, 'material': mo_ma, 'mgroup': None, 'notok_sign': notok_sign}
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)
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.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 = [(mi.material_in, mi.batch, mi.count_use, mi.wm_in) for mi in m_ins.all()]
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:
lookup = {'batch': mi_batch, 'material': mi_ma, 'mgroup': None, 'notok_sign': 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: # 产物退回
# 有多个产物的情况
# 需要考虑不合格品退回的情况
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
lookup = {'batch': mo_batch, 'material': mo_ma, 'mgroup': None, 'notok_sign': notok_sign}
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(status=Mtask.MTASK_ASSGINED)
def cal_mlog_count_from_mlogb(mlog: Mlog):
"""
通过mlogb计算mlog count 合计
"""
if mlog.fill_way == Mlog.MLOG_STEP:
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 == Mlog.MLOG_ONETIME and mlog.mtask:
update_mtask(mlog.mtask, fill_way=Mlog.MLOG_ONETIME)
elif mlog.fill_way == Mlog.MLOG_STEP:
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.MLOG_STEP)
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_ONETIME:
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 == Mlog.MLOG_STEP:
# 已经提交的日志
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.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()