Files
MES_Core/shiftflow/services/closing.py
2026-04-13 07:36:57 +03:00

223 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)