All checks were successful
Deploy MES Core / deploy (push) Successful in 13s
223 lines
7.4 KiB
Python
223 lines
7.4 KiB
Python
from django.db import transaction
|
||
from django.db.models import F
|
||
from django.utils import timezone
|
||
import logging
|
||
|
||
from shiftflow.models import (
|
||
CuttingSession,
|
||
Item,
|
||
ProductionReportConsumption,
|
||
ProductionReportRemnant,
|
||
ShiftItem,
|
||
)
|
||
from shiftflow.services.sessions import close_cutting_session
|
||
|
||
logger = logging.getLogger('mes')
|
||
|
||
|
||
@transaction.atomic
|
||
def apply_closing(
|
||
*,
|
||
user_id: int,
|
||
machine_id: int,
|
||
material_id: int,
|
||
item_actions: dict[int, dict],
|
||
consumptions: dict[int, float],
|
||
remnants: list[dict],
|
||
) -> None:
|
||
logger.info('apply_closing:start user=%s machine=%s material=%s items=%s consumptions=%s remnants=%s', user_id, machine_id, material_id, list(item_actions.keys()), list(consumptions.keys()), len(remnants))
|
||
|
||
items = list(
|
||
Item.objects.select_for_update(of=('self',))
|
||
.select_related('task', 'task__deal', 'task__material', 'machine')
|
||
.filter(id__in=list(item_actions.keys()), machine_id=machine_id, status='work', task__material_id=material_id)
|
||
)
|
||
if not items:
|
||
logger.error('apply_closing:no_items machine=%s material=%s', machine_id, material_id)
|
||
raise RuntimeError('Не найдено пунктов сменки для закрытия.')
|
||
|
||
report = CuttingSession.objects.create(
|
||
operator_id=user_id,
|
||
machine_id=machine_id,
|
||
used_stock_item=None,
|
||
date=timezone.localdate(),
|
||
is_closed=False,
|
||
)
|
||
|
||
logger.info('apply_closing:report_created id=%s', report.id)
|
||
logger.info('apply_closing:update_items start items=%s', [it.id for it in items])
|
||
for it in items:
|
||
spec = item_actions.get(it.id) or {}
|
||
action = (spec.get('action') or '').strip()
|
||
fact = int(spec.get('fact') or 0)
|
||
|
||
if action not in ['done', 'partial']:
|
||
continue
|
||
|
||
plan = int(it.quantity_plan or 0)
|
||
if plan <= 0:
|
||
continue
|
||
|
||
if action == 'done':
|
||
fact = plan
|
||
else:
|
||
fact = max(0, min(fact, plan))
|
||
if fact <= 0:
|
||
raise RuntimeError('При частичном закрытии факт должен быть больше 0.')
|
||
|
||
ShiftItem.objects.create(session=report, task=it.task, quantity_fact=fact)
|
||
|
||
logger.info('apply_closing:consumption_count=%s', len(consumptions))
|
||
for stock_item_id, qty in consumptions.items():
|
||
if qty <= 0:
|
||
continue
|
||
ProductionReportConsumption.objects.create(
|
||
report=report,
|
||
stock_item_id=stock_item_id,
|
||
material_id=None,
|
||
quantity=float(qty),
|
||
)
|
||
|
||
logger.info('apply_closing:remnants_count=%s', len(remnants))
|
||
for r in remnants:
|
||
qty = float(r.get('quantity') or 0)
|
||
if qty <= 0:
|
||
continue
|
||
ProductionReportRemnant.objects.create(
|
||
report=report,
|
||
material_id=material_id,
|
||
quantity=qty,
|
||
current_length=r.get('current_length'),
|
||
current_width=r.get('current_width'),
|
||
unique_id=None,
|
||
)
|
||
|
||
logger.info('apply_closing:close_session id=%s', report.id)
|
||
close_cutting_session(report.id)
|
||
|
||
for it in items:
|
||
spec = item_actions.get(it.id) or {}
|
||
action = (spec.get('action') or '').strip()
|
||
fact = int(spec.get('fact') or 0)
|
||
|
||
if action not in ['done', 'partial']:
|
||
continue
|
||
|
||
plan = int(it.quantity_plan or 0)
|
||
if plan <= 0:
|
||
continue
|
||
|
||
if action == 'done':
|
||
it.quantity_fact = plan
|
||
it.status = 'done'
|
||
it.save(update_fields=['quantity_fact', 'status'])
|
||
continue
|
||
|
||
fact = max(0, min(fact, plan))
|
||
residual = plan - fact
|
||
it.quantity_fact = fact
|
||
it.status = 'partial'
|
||
it.save(update_fields=['quantity_fact', 'status'])
|
||
|
||
if residual > 0:
|
||
Item.objects.create(
|
||
task=it.task,
|
||
date=it.date,
|
||
machine=it.machine,
|
||
quantity_plan=residual,
|
||
quantity_fact=0,
|
||
status='leftover',
|
||
is_synced_1c=False,
|
||
)
|
||
|
||
logger.info('apply_closing:done report=%s', report.id)
|
||
|
||
|
||
@transaction.atomic
|
||
def apply_closing_workitems(
|
||
*,
|
||
user_id: int,
|
||
machine_id: int,
|
||
material_id: int,
|
||
item_actions: dict[int, dict], # workitem_id -> {'action': 'done'|'partial', 'fact': int}
|
||
consumptions: dict[int, float],
|
||
remnants: list[dict],
|
||
) -> None:
|
||
logger.info('apply_closing_workitems:start user=%s machine=%s material=%s workitems=%s cons=%s rem=%s', user_id, machine_id, material_id, list(item_actions.keys()), list(consumptions.keys()), len(remnants))
|
||
|
||
from shiftflow.models import WorkItem, ProductionTask
|
||
|
||
wis = list(
|
||
WorkItem.objects.select_for_update(of=("self",))
|
||
.select_related('deal', 'entity', 'machine')
|
||
.filter(id__in=list(item_actions.keys()), machine_id=machine_id, status__in=['planned'], entity__planned_material_id=material_id)
|
||
.filter(quantity_done__lt=F('quantity_plan'))
|
||
)
|
||
if not wis:
|
||
raise RuntimeError('Не найдено сменных заданий для закрытия.')
|
||
|
||
report = CuttingSession.objects.create(
|
||
operator_id=user_id,
|
||
machine_id=machine_id,
|
||
used_stock_item=None,
|
||
date=timezone.localdate(),
|
||
is_closed=False,
|
||
)
|
||
|
||
created_shift = 0
|
||
for wi in wis:
|
||
spec = item_actions.get(wi.id) or {}
|
||
action = (spec.get('action') or '').strip()
|
||
fact = int(spec.get('fact') or 0)
|
||
if action not in ['done', 'partial']:
|
||
continue
|
||
|
||
plan_total = int(wi.quantity_plan or 0)
|
||
done_total = int(wi.quantity_done or 0)
|
||
remaining = max(0, plan_total - done_total)
|
||
if remaining <= 0:
|
||
continue
|
||
|
||
if action == 'done':
|
||
fact = remaining
|
||
else:
|
||
fact = max(0, min(fact, remaining))
|
||
if fact <= 0:
|
||
raise RuntimeError('При частичном закрытии факт должен быть больше 0.')
|
||
|
||
pt = ProductionTask.objects.filter(deal_id=wi.deal_id, entity_id=wi.entity_id).first()
|
||
if not pt:
|
||
raise RuntimeError('Не найден ProductionTask для задания.')
|
||
|
||
ShiftItem.objects.create(session=report, task=pt, quantity_fact=fact)
|
||
created_shift += 1
|
||
|
||
wi.quantity_done = done_total + fact
|
||
if wi.quantity_done >= plan_total:
|
||
wi.status = 'done'
|
||
elif wi.quantity_done > 0:
|
||
wi.status = 'leftover'
|
||
else:
|
||
wi.status = 'planned'
|
||
wi.save(update_fields=['quantity_done', 'status'])
|
||
|
||
for stock_item_id, qty in consumptions.items():
|
||
if qty and float(qty) > 0:
|
||
ProductionReportConsumption.objects.create(report=report, stock_item_id=stock_item_id, material_id=None, quantity=float(qty))
|
||
|
||
for r in remnants:
|
||
qty = float(r.get('quantity') or 0)
|
||
if qty <= 0:
|
||
continue
|
||
ProductionReportRemnant.objects.create(
|
||
report=report,
|
||
material_id=material_id,
|
||
quantity=qty,
|
||
current_length=r.get('current_length'),
|
||
current_width=r.get('current_width'),
|
||
unique_id=None,
|
||
)
|
||
|
||
close_cutting_session(report.id)
|
||
logger.info('apply_closing_workitems:done report=%s shift_items=%s', report.id, created_shift)
|