REFACTOR production scheduler
This commit is contained in:
@@ -309,6 +309,9 @@ class ProcurementService:
|
||||
elif status == "cancelled":
|
||||
updates["execution_completed_at"] = datetime.utcnow()
|
||||
|
||||
# Handle plan rejection workflow - trigger notification and potential regeneration
|
||||
await self._handle_plan_rejection(tenant_id, plan_id, approval_notes, updated_by)
|
||||
|
||||
plan = await self.plan_repo.update_plan(plan_id, tenant_id, updates)
|
||||
if plan:
|
||||
await self.db.commit()
|
||||
@@ -1238,6 +1241,168 @@ class ProcurementService:
|
||||
except Exception as e:
|
||||
logger.warning("Failed to publish event", error=str(e))
|
||||
|
||||
async def _handle_plan_rejection(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
plan_id: uuid.UUID,
|
||||
rejection_notes: Optional[str],
|
||||
rejected_by: Optional[uuid.UUID]
|
||||
) -> None:
|
||||
"""
|
||||
Handle plan rejection workflow with notifications and optional regeneration
|
||||
|
||||
When a plan is rejected:
|
||||
1. Send notifications to stakeholders
|
||||
2. Analyze rejection reason
|
||||
3. Offer regeneration option
|
||||
4. Publish rejection event
|
||||
"""
|
||||
try:
|
||||
logger.info("Processing plan rejection",
|
||||
tenant_id=str(tenant_id),
|
||||
plan_id=str(plan_id),
|
||||
rejected_by=str(rejected_by) if rejected_by else None)
|
||||
|
||||
# Get plan details
|
||||
plan = await self.plan_repo.get_plan_by_id(plan_id, tenant_id)
|
||||
if not plan:
|
||||
logger.error("Plan not found for rejection handling", plan_id=plan_id)
|
||||
return
|
||||
|
||||
# Send notification to stakeholders
|
||||
await self._send_plan_rejection_notification(
|
||||
tenant_id, plan_id, plan.plan_number, rejection_notes, rejected_by
|
||||
)
|
||||
|
||||
# Publish rejection event with details
|
||||
await self._publish_plan_rejection_event(
|
||||
tenant_id, plan_id, rejection_notes, rejected_by
|
||||
)
|
||||
|
||||
# Check if we should auto-regenerate (e.g., if rejection due to stale data)
|
||||
should_regenerate = self._should_auto_regenerate_plan(rejection_notes)
|
||||
if should_regenerate:
|
||||
logger.info("Auto-regenerating plan after rejection",
|
||||
plan_id=plan_id, reason="stale data detected")
|
||||
|
||||
# Schedule regeneration (async task to not block rejection)
|
||||
await self._schedule_plan_regeneration(tenant_id, plan.plan_date)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error handling plan rejection",
|
||||
error=str(e),
|
||||
plan_id=plan_id,
|
||||
tenant_id=str(tenant_id))
|
||||
|
||||
def _should_auto_regenerate_plan(self, rejection_notes: Optional[str]) -> bool:
|
||||
"""Determine if plan should be auto-regenerated based on rejection reason"""
|
||||
if not rejection_notes:
|
||||
return False
|
||||
|
||||
# Auto-regenerate if rejection mentions stale data or outdated forecasts
|
||||
auto_regenerate_keywords = [
|
||||
"stale", "outdated", "old data", "datos antiguos",
|
||||
"desactualizado", "obsoleto"
|
||||
]
|
||||
|
||||
rejection_lower = rejection_notes.lower()
|
||||
return any(keyword in rejection_lower for keyword in auto_regenerate_keywords)
|
||||
|
||||
async def _send_plan_rejection_notification(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
plan_id: uuid.UUID,
|
||||
plan_number: str,
|
||||
rejection_notes: Optional[str],
|
||||
rejected_by: Optional[uuid.UUID]
|
||||
) -> None:
|
||||
"""Send notifications about plan rejection"""
|
||||
try:
|
||||
notification_data = {
|
||||
"type": "procurement_plan_rejected",
|
||||
"severity": "medium",
|
||||
"title": f"Plan de Aprovisionamiento Rechazado: {plan_number}",
|
||||
"message": f"El plan {plan_number} ha sido rechazado. {rejection_notes or 'Sin motivo especificado.'}",
|
||||
"metadata": {
|
||||
"tenant_id": str(tenant_id),
|
||||
"plan_id": str(plan_id),
|
||||
"plan_number": plan_number,
|
||||
"rejection_notes": rejection_notes,
|
||||
"rejected_by": str(rejected_by) if rejected_by else None,
|
||||
"rejected_at": datetime.utcnow().isoformat(),
|
||||
"action_required": "review_and_regenerate"
|
||||
}
|
||||
}
|
||||
|
||||
await self.rabbitmq_client.publish_event(
|
||||
exchange_name="bakery_events",
|
||||
routing_key="procurement.plan.rejected",
|
||||
event_data=notification_data
|
||||
)
|
||||
|
||||
logger.info("Plan rejection notification sent",
|
||||
tenant_id=str(tenant_id),
|
||||
plan_id=str(plan_id))
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to send plan rejection notification", error=str(e))
|
||||
|
||||
async def _publish_plan_rejection_event(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
plan_id: uuid.UUID,
|
||||
rejection_notes: Optional[str],
|
||||
rejected_by: Optional[uuid.UUID]
|
||||
) -> None:
|
||||
"""Publish plan rejection event for downstream systems"""
|
||||
try:
|
||||
event_data = {
|
||||
"tenant_id": str(tenant_id),
|
||||
"plan_id": str(plan_id),
|
||||
"rejection_notes": rejection_notes,
|
||||
"rejected_by": str(rejected_by) if rejected_by else None,
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"event_type": "procurement.plan.rejected"
|
||||
}
|
||||
|
||||
await self.rabbitmq_client.publish_event(
|
||||
exchange_name="procurement.events",
|
||||
routing_key="procurement.plan.rejected",
|
||||
event_data=event_data
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("Failed to publish plan rejection event", error=str(e))
|
||||
|
||||
async def _schedule_plan_regeneration(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
plan_date: date
|
||||
) -> None:
|
||||
"""Schedule automatic plan regeneration after rejection"""
|
||||
try:
|
||||
logger.info("Scheduling plan regeneration",
|
||||
tenant_id=str(tenant_id),
|
||||
plan_date=str(plan_date))
|
||||
|
||||
# Publish regeneration request event
|
||||
event_data = {
|
||||
"tenant_id": str(tenant_id),
|
||||
"plan_date": plan_date.isoformat(),
|
||||
"trigger": "rejection_auto_regenerate",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"event_type": "procurement.plan.regeneration_requested"
|
||||
}
|
||||
|
||||
await self.rabbitmq_client.publish_event(
|
||||
exchange_name="procurement.events",
|
||||
routing_key="procurement.plan.regeneration_requested",
|
||||
event_data=event_data
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to schedule plan regeneration", error=str(e))
|
||||
|
||||
async def _publish_plan_status_changed_event(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
|
||||
Reference in New Issue
Block a user