diff --git a/app/modules/client_groups/ui.py b/app/modules/client_groups/ui.py index af45324..751f507 100644 --- a/app/modules/client_groups/ui.py +++ b/app/modules/client_groups/ui.py @@ -5,9 +5,10 @@ from fastapi.responses import RedirectResponse from app.core.db.common import CommonSessionLocal from app.core.security.csrf import get_or_create_csrf_token, validate_csrf -from app.core.security.deps import get_current_user +from app.core.security.session_auth import get_current_user from app.core.templating import templates -from app.modules.core.rbac.service import get_user_permissions, get_user_roles, require_permission +from app.modules.core.rbac.deps import get_user_permissions, get_user_roles +from app.modules.core.rbac.permission_guard import require_permission from app.modules.client_groups.service import GROUP_TYPES, create_group, get_group, list_group_clients, list_groups, update_group from app.modules.clients.access import build_scope from app.modules.clients import repository diff --git a/app/modules/clients/__init__.py b/app/modules/clients/__init__.py index 335a310..2c30557 100644 --- a/app/modules/clients/__init__.py +++ b/app/modules/clients/__init__.py @@ -1,4 +1,29 @@ -from .api import router as api_router -from .ui import router as ui_router +"""Clients module. + +Routers are exposed lazily so importing ``app.modules.clients.models`` does +not initialise the clients API, UI, importer, and related modules. This keeps +the existing ``api_router`` and ``ui_router`` public exports while preventing +circular imports with client groups. +""" + +from __future__ import annotations + +from typing import Any __all__ = ["api_router", "ui_router"] + + +def __getattr__(name: str) -> Any: + if name == "api_router": + from .api import router + + return router + + if name == "ui_router": + from .ui import router + + return router + + raise AttributeError( + f"module {__name__!r} has no attribute {name!r}" + ) \ No newline at end of file diff --git a/app/modules/clients/service.py b/app/modules/clients/service.py index 4869e98..4029e0f 100644 --- a/app/modules/clients/service.py +++ b/app/modules/clients/service.py @@ -30,11 +30,16 @@ from app.modules.core.iam.models import User from app.modules.core.rbac.models import Role, UserRole from app.modules.documents.models import PermanentClientDocument from app.modules.consultants.service import sync_primary_client_consultant_link -from app.modules.client_groups.service import get_group + from app.modules.clients.models import Client + +def _get_client_group(*args, **kwargs): + # Imported lazily to prevent clients ↔ client_groups circular imports. + from app.modules.client_groups.service import get_group + return get_group(*args, **kwargs) def _payload_from_schema(data): payload = data.model_dump(exclude_none=True) if hasattr(data, "model_dump") else data.dict(exclude_none=True) # primary_consultant_id belongs to ClientConsultantLink, not the clients table. @@ -50,7 +55,7 @@ def _validate_client_group_assignment(db, *, payload: dict, existing_row=None): group_id = payload.get("client_group_id") tenant_id = payload.get("tenant_id") or getattr(existing_row, "tenant_id", None) if group_id: - group = get_group(db, tenant_id=int(tenant_id), group_id=int(group_id)) + group = _get_client_group(db, tenant_id=int(tenant_id), group_id=int(group_id)) if not group or not group.is_active: raise HTTPException(status_code=400, detail="Selected client group is invalid or inactive for this firm.") if payload.get("is_group_head") and group_id: