dograh/api/services/workflow/configuration_policy.py
Abhishek d8cd34e8d2
feat: add vici dial controls from UI (#563)
* ViciDial Working

* feat(telephony): add FreeSWITCH provider to upstream-PBX seam

Generalize the upstream-PBX capture and control paths to dispatch by
provider. FreeSWITCH bridges in with X-PBX-* headers and is driven over
the Event Socket Library (uuid_kill / uuid_transfer) by the channel UUID,
alongside the existing VICIdial ra_call_control path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* add vici specific configs

* feat(telephony): env-based VICIdial config + address3 post-call routing

Move VICIdial agent/non-agent API and FreeSWITCH ESL connection settings
out of hardcoded POC values into environment variables so the same image
works against a PBX on another server.

Add VICIdial update_lead forwarding: X-VICI-UPDATE-LEAD_* extracted
variables are mapped to lead columns and pushed via the non-agent API
before a transfer, with reserved API-control params dropped.

Add hardcoded address3 disposition routing (Y/N -> in-group transfer,
else hang up) and a synchronous final variable extraction so the
transfer path sees the freshest conversation state. Skip upstream_pbx
capture on non-PJSIP channels to avoid Asterisk 500s.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: run formatter

* feat: make vici configurable from UI

* chore: clean up PR

* fix: incorporate review comments

* chore: incorporate review comments

* chore: generate client

* chore: incporporate review comments

* chore: incorporate review comments

---------

Co-authored-by: Dograh POC <payment@dograh.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:57:34 +05:30

70 lines
2.4 KiB
Python

"""Workflow-configuration policies shared by workflow update surfaces."""
from __future__ import annotations
from copy import deepcopy
from typing import Any
from api.db import db_client
from api.services.organization_preferences import external_pbx_integrations_enabled
class WorkflowConfigurationNotFoundError(LookupError):
"""Raised when configuration policy cannot resolve the target workflow."""
class ExternalPBXConfigurationDisabledError(PermissionError):
"""Raised when a disabled organization changes external-PBX mappings."""
async def apply_external_pbx_mapping_policy(
workflow_configurations: dict[str, Any] | None,
*,
workflow_id: int,
organization_id: int,
) -> dict[str, Any] | None:
"""Preserve hidden mappings and reject edits while external PBX is disabled.
Workflow configuration updates replace the stored configuration document. When
the External PBX UI is hidden, its field mappings are absent from the request,
so they must be copied from the active draft or published definition before the
update is persisted.
"""
if workflow_configurations is None or await external_pbx_integrations_enabled(
organization_id
):
return workflow_configurations
workflow = await db_client.get_workflow(
workflow_id, organization_id=organization_id
)
if workflow is None:
raise WorkflowConfigurationNotFoundError(
f"Workflow with id {workflow_id} not found"
)
draft = await db_client.get_draft_version(workflow_id)
stored_configurations = (
draft.workflow_configurations
if draft
else workflow.released_definition.workflow_configurations
)
stored_mappings = (stored_configurations or {}).get(
"external_pbx_field_mappings", []
)
incoming_mappings = workflow_configurations.get(
"external_pbx_field_mappings", stored_mappings
)
if incoming_mappings != stored_mappings:
raise ExternalPBXConfigurationDisabledError(
"External PBX integrations are disabled for this organization. "
"Enable them in Platform Settings before changing field mappings."
)
if not stored_mappings:
return workflow_configurations
prepared_configurations = dict(workflow_configurations)
prepared_configurations["external_pbx_field_mappings"] = deepcopy(stored_mappings)
return prepared_configurations