Files
2026-07-29 06:05:10 +05:30

124 lines
6.6 KiB
Python

from __future__ import annotations
from sqlalchemy import exists, func, or_, select
from sqlalchemy.orm import Session
from app.modules.client_groups.models import ClientGroup
from app.modules.clients.models import Client
from app.modules.services.models import ClientServiceSubscription
GROUP_TYPES = ("Family", "Business Group", "Promoter Group", "Trust Group", "Common Management", "Other")
def normalise_group_code(value: str | None) -> str:
return (value or "").strip().upper()
def list_groups(db: Session, *, tenant_id: int, include_inactive: bool = False, viewer_partner_id: int | None = None):
stmt = (
select(ClientGroup, func.count(Client.id).label("client_count"))
.outerjoin(Client, Client.client_group_id == ClientGroup.id)
.where(ClientGroup.tenant_id == tenant_id)
.group_by(ClientGroup.id)
.order_by(ClientGroup.group_name.asc())
)
if viewer_partner_id is not None:
review_access = exists(select(ClientServiceSubscription.id).where(
ClientServiceSubscription.tenant_id == tenant_id,
ClientServiceSubscription.client_id == Client.id,
ClientServiceSubscription.review_partner_user_id == viewer_partner_id,
ClientServiceSubscription.is_active.is_(True),
))
stmt = stmt.where(or_(Client.partner_id == viewer_partner_id, review_access))
if not include_inactive:
stmt = stmt.where(ClientGroup.is_active.is_(True))
return [{"group": group, "client_count": int(count or 0)} for group, count in db.execute(stmt).all()]
def get_group(db: Session, *, tenant_id: int, group_id: int):
return db.execute(select(ClientGroup).where(ClientGroup.id == group_id, ClientGroup.tenant_id == tenant_id)).scalar_one_or_none()
def get_group_by_code(db: Session, *, tenant_id: int, group_code: str):
code = normalise_group_code(group_code)
if not code:
return None
return db.execute(select(ClientGroup).where(ClientGroup.tenant_id == tenant_id, ClientGroup.group_code == code)).scalar_one_or_none()
def list_group_clients(db: Session, *, tenant_id: int, group_id: int, viewer_partner_id: int | None = None):
stmt = select(Client).where(Client.tenant_id == tenant_id, Client.client_group_id == group_id, Client.is_archived.is_(False))
if viewer_partner_id is not None:
review_access = exists(select(ClientServiceSubscription.id).where(
ClientServiceSubscription.tenant_id == tenant_id,
ClientServiceSubscription.client_id == Client.id,
ClientServiceSubscription.review_partner_user_id == viewer_partner_id,
ClientServiceSubscription.is_active.is_(True),
))
stmt = stmt.where(or_(Client.partner_id == viewer_partner_id, review_access))
return db.execute(stmt.order_by(Client.is_group_head.desc(), Client.client_name.asc())).scalars().all()
def create_group(db: Session, *, tenant_id: int, actor_user_id: int, payload: dict):
code = normalise_group_code(payload.get("group_code"))
name = (payload.get("group_name") or "").strip()
if not code or not name:
raise ValueError("Group code and group name are required.")
if get_group_by_code(db, tenant_id=tenant_id, group_code=code):
raise ValueError("Client group code already exists in this firm.")
group_type = (payload.get("group_type") or "Family").strip()
if group_type not in GROUP_TYPES:
raise ValueError("Invalid group type.")
row = ClientGroup(
tenant_id=tenant_id, group_code=code, group_name=name, group_type=group_type,
primary_contact_name=(payload.get("primary_contact_name") or "").strip() or None,
primary_contact_mobile=(payload.get("primary_contact_mobile") or "").strip() or None,
primary_contact_email=(payload.get("primary_contact_email") or "").strip().lower() or None,
assigned_partner_user_id=payload.get("assigned_partner_user_id") or None,
primary_consultant_id=payload.get("primary_consultant_id") or None,
notes=(payload.get("notes") or "").strip() or None,
is_active=bool(payload.get("is_active", True)), created_by_user_id=actor_user_id, updated_by_user_id=actor_user_id,
)
db.add(row); db.commit(); db.refresh(row); return row
def update_group(db: Session, *, row: ClientGroup, actor_user_id: int, payload: dict):
code = normalise_group_code(payload.get("group_code"))
name = (payload.get("group_name") or "").strip()
if not code or not name:
raise ValueError("Group code and group name are required.")
duplicate = get_group_by_code(db, tenant_id=row.tenant_id, group_code=code)
if duplicate and duplicate.id != row.id:
raise ValueError("Client group code already exists in this firm.")
group_type = (payload.get("group_type") or "Family").strip()
if group_type not in GROUP_TYPES:
raise ValueError("Invalid group type.")
for key, value in {
"group_code": code, "group_name": name, "group_type": group_type,
"primary_contact_name": (payload.get("primary_contact_name") or "").strip() or None,
"primary_contact_mobile": (payload.get("primary_contact_mobile") or "").strip() or None,
"primary_contact_email": (payload.get("primary_contact_email") or "").strip().lower() or None,
"assigned_partner_user_id": payload.get("assigned_partner_user_id") or None,
"primary_consultant_id": payload.get("primary_consultant_id") or None,
"notes": (payload.get("notes") or "").strip() or None,
"is_active": bool(payload.get("is_active", False)), "updated_by_user_id": actor_user_id,
}.items(): setattr(row, key, value)
db.add(row); db.commit(); db.refresh(row); return row
def resolve_or_create_group(db: Session, *, tenant_id: int, actor_user_id: int, group_code: str | None, group_name: str | None, group_type: str | None = None):
code = normalise_group_code(group_code)
name = (group_name or "").strip()
if not code and not name:
return None
if not code:
raise ValueError("client_group_code is required when client_group_name is supplied.")
existing = get_group_by_code(db, tenant_id=tenant_id, group_code=code)
if existing:
if name and existing.group_name.strip().lower() != name.lower():
raise ValueError(f"Client group code {code} already exists with name {existing.group_name}.")
return existing
if not name:
raise ValueError(f"Client group {code} does not exist; provide client_group_name to create it.")
return create_group(db, tenant_id=tenant_id, actor_user_id=actor_user_id, payload={"group_code": code, "group_name": name, "group_type": group_type or "Family", "is_active": True})