All checks were successful
Deploy MES Core / deploy (push) Successful in 14s
134 lines
4.5 KiB
Python
134 lines
4.5 KiB
Python
import logging
|
|
|
|
from django.db import transaction
|
|
from django.db.models import Q, Sum
|
|
from django.db.models.functions import Coalesce
|
|
from django.utils import timezone
|
|
|
|
from manufacturing.models import EntityOperation
|
|
from shiftflow.models import DealEntityProgress, DealItem, ProductionTask, WorkItem
|
|
|
|
logger = logging.getLogger('mes')
|
|
|
|
|
|
def _workitem_op_code(wi: WorkItem) -> str:
|
|
if getattr(wi, 'operation_id', None) and getattr(wi, 'operation', None):
|
|
code = (wi.operation.code or '').strip()
|
|
if code:
|
|
return code
|
|
return (wi.stage or '').strip()
|
|
|
|
|
|
def _target_qty_for_workitem(wi: WorkItem) -> int | None:
|
|
if getattr(wi, 'delivery_batch_id', None):
|
|
qty = (
|
|
ProductionTask.objects.filter(
|
|
deal_id=wi.deal_id,
|
|
delivery_batch_id=wi.delivery_batch_id,
|
|
entity_id=wi.entity_id,
|
|
)
|
|
.values_list('quantity_ordered', flat=True)
|
|
.first()
|
|
)
|
|
return int(qty) if qty is not None else None
|
|
|
|
di = DealItem.objects.filter(deal_id=wi.deal_id, entity_id=wi.entity_id).first()
|
|
return int(di.quantity) if di else None
|
|
|
|
|
|
@transaction.atomic
|
|
def advance_progress_and_generate_next_workitem(*, workitem_id: int) -> int | None:
|
|
wi = (
|
|
WorkItem.objects.select_for_update(of=('self',))
|
|
.select_related('operation')
|
|
.filter(id=int(workitem_id))
|
|
.first()
|
|
)
|
|
if not wi:
|
|
return None
|
|
|
|
op_code = _workitem_op_code(wi)
|
|
if not op_code:
|
|
return None
|
|
|
|
target_qty = _target_qty_for_workitem(wi)
|
|
if target_qty is None:
|
|
return None
|
|
|
|
progress, _ = DealEntityProgress.objects.select_for_update(of=('self',)).get_or_create(
|
|
deal_id=wi.deal_id,
|
|
delivery_batch_id=(int(wi.delivery_batch_id) if getattr(wi, 'delivery_batch_id', None) else None),
|
|
entity_id=wi.entity_id,
|
|
defaults={'current_seq': 1},
|
|
)
|
|
|
|
cur = int(progress.current_seq or 1)
|
|
cur_eo = EntityOperation.objects.select_related('operation').filter(entity_id=wi.entity_id, seq=cur).first()
|
|
if not cur_eo or not cur_eo.operation:
|
|
return None
|
|
|
|
cur_code = (cur_eo.operation.code or '').strip()
|
|
if cur_code != op_code:
|
|
return None
|
|
|
|
wi_qs = WorkItem.objects.filter(deal_id=wi.deal_id, entity_id=wi.entity_id).filter(Q(operation__code=op_code) | Q(stage=op_code))
|
|
if getattr(wi, 'delivery_batch_id', None):
|
|
wi_qs = wi_qs.filter(delivery_batch_id=wi.delivery_batch_id)
|
|
else:
|
|
wi_qs = wi_qs.filter(delivery_batch_id__isnull=True)
|
|
|
|
total_done = wi_qs.aggregate(s=Coalesce(Sum('quantity_done'), 0))['s']
|
|
if int(total_done or 0) < int(target_qty):
|
|
return None
|
|
|
|
progress.current_seq = cur + 1
|
|
progress.save(update_fields=['current_seq'])
|
|
|
|
next_eo = (
|
|
EntityOperation.objects.select_related('operation', 'operation__workshop')
|
|
.filter(entity_id=wi.entity_id, seq=int(progress.current_seq))
|
|
.first()
|
|
)
|
|
if not next_eo or not next_eo.operation:
|
|
return None
|
|
|
|
next_op = next_eo.operation
|
|
next_code = (next_op.code or '').strip()
|
|
|
|
planned_qs = WorkItem.objects.filter(deal_id=wi.deal_id, entity_id=wi.entity_id)
|
|
if getattr(wi, 'delivery_batch_id', None):
|
|
planned_qs = planned_qs.filter(delivery_batch_id=wi.delivery_batch_id)
|
|
else:
|
|
planned_qs = planned_qs.filter(delivery_batch_id__isnull=True)
|
|
|
|
planned_total = planned_qs.filter(Q(operation_id=next_op.id) | Q(operation__code=next_code) | Q(stage=next_code)).aggregate(
|
|
s=Coalesce(Sum('quantity_plan'), 0)
|
|
)['s']
|
|
|
|
remaining_to_plan = max(0, int(target_qty) - int(planned_total or 0))
|
|
if remaining_to_plan <= 0:
|
|
return None
|
|
|
|
created = WorkItem.objects.create(
|
|
deal_id=wi.deal_id,
|
|
delivery_batch_id=(int(wi.delivery_batch_id) if getattr(wi, 'delivery_batch_id', None) else None),
|
|
entity_id=wi.entity_id,
|
|
operation_id=next_op.id,
|
|
stage=(next_code or next_op.name or '')[:32],
|
|
workshop_id=(int(next_op.workshop_id) if getattr(next_op, 'workshop_id', None) else None),
|
|
machine_id=None,
|
|
quantity_plan=int(remaining_to_plan),
|
|
quantity_done=0,
|
|
status='planned',
|
|
date=timezone.localdate(),
|
|
)
|
|
logger.info(
|
|
'route_flow:created_next_workitem id=%s deal_id=%s batch_id=%s entity_id=%s op=%s qty=%s',
|
|
created.id,
|
|
created.deal_id,
|
|
getattr(created, 'delivery_batch_id', None),
|
|
created.entity_id,
|
|
next_code or '',
|
|
created.quantity_plan,
|
|
)
|
|
return int(created.id) |