Add client groups and family tracking

This commit is contained in:
A R R R Associates
2026-07-25 22:32:35 +05:30
parent 5e6584702c
commit 2dc2a712e8
21 changed files with 444 additions and 161 deletions
+32
View File
@@ -30,6 +30,8 @@ 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
@@ -43,6 +45,26 @@ def _payload_from_schema(data):
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))
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:
stmt = select(Client).where(Client.tenant_id == int(tenant_id), Client.client_group_id == int(group_id), Client.is_group_head.is_(True))
if existing_row is not None:
stmt = stmt.where(Client.id != existing_row.id)
existing_head = db.execute(stmt).scalar_one_or_none()
if existing_head:
raise HTTPException(status_code=400, detail=f"This group already has group head {existing_head.client_code} - {existing_head.client_name}.")
if not group_id:
payload["group_relationship"] = None
payload["is_group_head"] = False
def _is_high_risk(risk_category: str | None) -> bool:
return (risk_category or "").strip().lower() in CLIENT_ACCEPTANCE_APPROVAL_REQUIRED_RISKS
@@ -271,6 +293,7 @@ def create_client_service(db, *, data, actor_user_id: int, scope, current_user_r
payload = _payload_from_schema(data)
_enforce_client_acceptance_controls(payload)
_validate_client_group_assignment(db, payload=payload)
row = repository.create_client(db, payload)
row = _sync_client_portal_user(db, row=row, portal_password=portal_password, portal_password_confirm=portal_password_confirm)
sync_primary_client_consultant_link(db, client=row, consultant_id=getattr(data, "primary_consultant_id", None), actor_user_id=actor_user_id)
@@ -301,6 +324,7 @@ def update_client_service(db, *, row, data, actor_user_id: int, scope, current_u
payload = _payload_from_schema(data)
_enforce_client_acceptance_controls(payload, existing_row=row)
_validate_client_group_assignment(db, payload=payload, existing_row=row)
if payload.get("pan"):
existing_pan = repository.get_client_by_pan(db, tenant_id=payload["tenant_id"], pan=payload["pan"])
@@ -441,6 +465,10 @@ def export_clients_csv(payload: dict) -> str:
"pan",
"gstin",
"partner",
"client_group_code",
"client_group_name",
"group_relationship",
"is_group_head",
"association_type",
"association_source",
]
@@ -455,6 +483,10 @@ def export_clients_csv(payload: dict) -> str:
row.get("pan"),
row.get("gstin"),
row.get("partner_name") or row.get("effective_partner_id"),
row.get("client_group_code"),
row.get("client_group_name"),
row.get("group_relationship"),
row.get("is_group_head"),
row.get("association_type"),
row.get("assoc_created_source"),
]