# Copyright (c) 2026 Patrick Motsch # All rights reserved. """Shared helpers for Redmine workflow actions. Keeps each action file focused on the business logic -- parameter resolution, services lookup and ActionResult shaping live here. """ from typing import Any, Dict, Optional, Tuple def resolveInstanceContext(services, parameters: Dict[str, Any]) -> Tuple[Any, Optional[str], str]: """Resolve ``(user, mandateId, featureInstanceId)`` for a workflow action. The workflow runtime wires up ``services.user`` / ``services.mandateId`` / ``services.featureInstanceId``. The action may override the instance explicitly via ``parameters['featureInstanceId']`` so that the same workflow template can be reused against different Redmine instances. """ featureInstanceId = parameters.get("featureInstanceId") or getattr( services, "featureInstanceId", None ) if not featureInstanceId: raise ValueError("featureInstanceId is required") mandateId = getattr(services, "mandateId", None) user = getattr(services, "user", None) if user is None: raise ValueError("services.user is not available") return user, mandateId, str(featureInstanceId) def ticketToDict(ticket) -> Dict[str, Any]: """Compact dict representation for AI consumption -- strips ``raw``.""" if ticket is None: return {} payload = ticket.model_dump(exclude_none=True) payload.pop("raw", None) return payload