Add client groups and family tracking
This commit is contained in:
@@ -9,6 +9,7 @@ from app.core.security.passwords import hash_password
|
||||
from app.modules.clients.association_models import ClientAssociation
|
||||
from app.modules.clients.constants import CLIENT_SORT_FIELDS
|
||||
from app.modules.clients.models import Client, ClientAuditLog
|
||||
from app.modules.client_groups.models import ClientGroup
|
||||
from app.modules.core.iam.models import User
|
||||
from app.modules.core.rbac.models import Role, UserRole
|
||||
from app.modules.core.tenancy.models import Branch, Tenant
|
||||
@@ -30,16 +31,18 @@ def build_clients_query(
|
||||
q: str = "",
|
||||
status: str = "",
|
||||
client_type: str = "",
|
||||
client_group_id: int | None = None,
|
||||
include_archived: bool = False,
|
||||
):
|
||||
assoc = ClientAssociation
|
||||
|
||||
stmt = (
|
||||
select(
|
||||
Client,
|
||||
User.full_name.label("partner_name"),
|
||||
Branch.name.label("branch_name"),
|
||||
Tenant.name.label("tenant_name"),
|
||||
ClientGroup.group_name.label("client_group_name"),
|
||||
ClientGroup.group_code.label("client_group_code"),
|
||||
assoc.association_type.label("association_type"),
|
||||
assoc.firm_tenant_id.label("assoc_firm_tenant_id"),
|
||||
assoc.consultant_id.label("assoc_consultant_id"),
|
||||
@@ -47,189 +50,85 @@ def build_clients_query(
|
||||
assoc.created_source.label("assoc_created_source"),
|
||||
)
|
||||
.outerjoin(assoc, assoc.client_id == Client.id)
|
||||
.outerjoin(ClientGroup, ClientGroup.id == Client.client_group_id)
|
||||
.join(User, User.id == Client.partner_id, isouter=True)
|
||||
.join(Branch, Branch.id == Client.branch_id, isouter=True)
|
||||
.join(Tenant, Tenant.id == Client.tenant_id, isouter=True)
|
||||
)
|
||||
|
||||
if not allow_all_clients:
|
||||
stmt = stmt.where(Client.tenant_id == tenant_id)
|
||||
|
||||
if not include_archived:
|
||||
stmt = stmt.where(Client.is_archived.is_(False))
|
||||
|
||||
if branch_id and not allow_all_clients and not allow_cross_branch:
|
||||
stmt = stmt.where(Client.branch_id == branch_id)
|
||||
|
||||
if partner_id:
|
||||
stmt = stmt.where(
|
||||
(Client.partner_id == partner_id) | (assoc.partner_user_id == partner_id)
|
||||
)
|
||||
|
||||
stmt = stmt.where((Client.partner_id == partner_id) | (assoc.partner_user_id == partner_id))
|
||||
if status:
|
||||
stmt = stmt.where(Client.status == status)
|
||||
|
||||
if client_type:
|
||||
stmt = stmt.where(Client.client_type == client_type)
|
||||
|
||||
if client_group_id:
|
||||
stmt = stmt.where(Client.client_group_id == client_group_id)
|
||||
if q:
|
||||
like = f"%{q.strip()}%"
|
||||
stmt = stmt.where(
|
||||
or_(
|
||||
Client.client_code.ilike(like),
|
||||
Client.client_name.ilike(like),
|
||||
Client.trade_name.ilike(like),
|
||||
Client.pan.ilike(like),
|
||||
Client.gstin.ilike(like),
|
||||
Client.mobile.ilike(like),
|
||||
Client.email.ilike(like),
|
||||
)
|
||||
)
|
||||
|
||||
stmt = stmt.where(or_(
|
||||
Client.client_code.ilike(like), Client.client_name.ilike(like), Client.trade_name.ilike(like),
|
||||
Client.pan.ilike(like), Client.gstin.ilike(like), Client.mobile.ilike(like), Client.email.ilike(like),
|
||||
ClientGroup.group_name.ilike(like), ClientGroup.group_code.ilike(like),
|
||||
))
|
||||
return stmt
|
||||
|
||||
|
||||
def list_clients(
|
||||
db: Session,
|
||||
*,
|
||||
tenant_id: int,
|
||||
branch_id: int | None = None,
|
||||
allow_cross_branch: bool = False,
|
||||
allow_all_clients: bool = False,
|
||||
partner_id: int | None = None,
|
||||
q: str = "",
|
||||
status: str = "",
|
||||
client_type: str = "",
|
||||
include_archived: bool = False,
|
||||
page: int = 1,
|
||||
per_page: int = 10,
|
||||
sort_by: str = "client_name",
|
||||
sort_order: str = "asc",
|
||||
db: Session, *, tenant_id: int, branch_id: int | None = None,
|
||||
allow_cross_branch: bool = False, allow_all_clients: bool = False,
|
||||
partner_id: int | None = None, q: str = "", status: str = "", client_type: str = "",
|
||||
client_group_id: int | None = None, include_archived: bool = False,
|
||||
page: int = 1, per_page: int = 10, sort_by: str = "client_name", sort_order: str = "asc",
|
||||
) -> dict:
|
||||
stmt = build_clients_query(
|
||||
tenant_id=tenant_id,
|
||||
branch_id=branch_id,
|
||||
allow_cross_branch=allow_cross_branch,
|
||||
allow_all_clients=allow_all_clients,
|
||||
partner_id=partner_id,
|
||||
q=q,
|
||||
status=status,
|
||||
client_type=client_type,
|
||||
include_archived=include_archived,
|
||||
tenant_id=tenant_id, branch_id=branch_id, allow_cross_branch=allow_cross_branch,
|
||||
allow_all_clients=allow_all_clients, partner_id=partner_id, q=q, status=status,
|
||||
client_type=client_type, client_group_id=client_group_id, include_archived=include_archived,
|
||||
)
|
||||
|
||||
total = db.execute(select(func.count()).select_from(stmt.subquery())).scalar_one()
|
||||
result = db.execute(
|
||||
stmt.order_by(_safe_sort(sort_by, sort_order))
|
||||
.offset((page - 1) * per_page)
|
||||
.limit(per_page)
|
||||
).all()
|
||||
|
||||
rows = []
|
||||
for (
|
||||
client,
|
||||
partner_name,
|
||||
branch_name,
|
||||
tenant_name,
|
||||
association_type,
|
||||
assoc_firm_tenant_id,
|
||||
assoc_consultant_id,
|
||||
assoc_partner_user_id,
|
||||
assoc_created_source,
|
||||
) in result:
|
||||
row = {**client.__dict__}
|
||||
row.pop("_sa_instance_state", None)
|
||||
row.update(
|
||||
{
|
||||
"partner_name": partner_name,
|
||||
"branch_name": branch_name,
|
||||
"tenant_name": tenant_name,
|
||||
"association_type": association_type,
|
||||
"assoc_firm_tenant_id": assoc_firm_tenant_id,
|
||||
"assoc_consultant_id": assoc_consultant_id,
|
||||
"assoc_partner_user_id": assoc_partner_user_id,
|
||||
"assoc_created_source": assoc_created_source,
|
||||
"effective_partner_id": assoc_partner_user_id or row.get("partner_id"),
|
||||
}
|
||||
)
|
||||
rows.append(row)
|
||||
|
||||
stats_stmt = select(
|
||||
func.count(Client.id),
|
||||
func.sum(case((Client.status == "active", 1), else_=0)),
|
||||
func.sum(case((Client.status == "inactive", 1), else_=0)),
|
||||
func.sum(case((Client.status == "archived", 1), else_=0)),
|
||||
)
|
||||
|
||||
result = db.execute(stmt.order_by(_safe_sort(sort_by, sort_order)).offset((page - 1) * per_page).limit(per_page)).all()
|
||||
rows=[]
|
||||
for client, partner_name, branch_name, tenant_name, client_group_name, client_group_code, association_type, assoc_firm_tenant_id, assoc_consultant_id, assoc_partner_user_id, assoc_created_source in result:
|
||||
row={**client.__dict__}; row.pop("_sa_instance_state",None)
|
||||
row.update({
|
||||
"partner_name":partner_name,"branch_name":branch_name,"tenant_name":tenant_name,
|
||||
"client_group_name":client_group_name,"client_group_code":client_group_code,
|
||||
"association_type":association_type,"assoc_firm_tenant_id":assoc_firm_tenant_id,
|
||||
"assoc_consultant_id":assoc_consultant_id,"assoc_partner_user_id":assoc_partner_user_id,
|
||||
"assoc_created_source":assoc_created_source,"effective_partner_id":assoc_partner_user_id or row.get("partner_id"),
|
||||
}); rows.append(row)
|
||||
stats_stmt=select(func.count(Client.id),func.sum(case((Client.status=="active",1),else_=0)),func.sum(case((Client.status=="inactive",1),else_=0)),func.sum(case((Client.status=="archived",1),else_=0)))
|
||||
if not allow_all_clients:
|
||||
stats_stmt = stats_stmt.where(Client.tenant_id == tenant_id)
|
||||
if branch_id and not allow_cross_branch:
|
||||
stats_stmt = stats_stmt.where(Client.branch_id == branch_id)
|
||||
|
||||
if partner_id:
|
||||
stats_stmt = stats_stmt.where(Client.partner_id == partner_id)
|
||||
|
||||
total_all, active, inactive, archived = db.execute(stats_stmt).one()
|
||||
|
||||
pages = ceil(total / per_page) if per_page else 1
|
||||
return {
|
||||
"rows": rows,
|
||||
"meta": {
|
||||
"total": total,
|
||||
"page": page,
|
||||
"per_page": per_page,
|
||||
"pages": max(pages, 1),
|
||||
},
|
||||
"stats": {
|
||||
"total": int(total_all or 0),
|
||||
"active": int(active or 0),
|
||||
"inactive": int(inactive or 0),
|
||||
"archived": int(archived or 0),
|
||||
},
|
||||
}
|
||||
stats_stmt=stats_stmt.where(Client.tenant_id==tenant_id)
|
||||
if branch_id and not allow_cross_branch: stats_stmt=stats_stmt.where(Client.branch_id==branch_id)
|
||||
if partner_id: stats_stmt=stats_stmt.where(Client.partner_id==partner_id)
|
||||
total_all,active,inactive,archived=db.execute(stats_stmt).one()
|
||||
pages=ceil(total/per_page) if per_page else 1
|
||||
return {"rows":rows,"meta":{"total":total,"page":page,"per_page":per_page,"pages":max(pages,1)},"stats":{"total":int(total_all or 0),"active":int(active or 0),"inactive":int(inactive or 0),"archived":int(archived or 0)}}
|
||||
|
||||
|
||||
def get_client_detail_payload(db: Session, client_id: int):
|
||||
assoc = ClientAssociation
|
||||
|
||||
stmt = (
|
||||
select(
|
||||
Client,
|
||||
assoc.association_type.label("association_type"),
|
||||
assoc.firm_tenant_id.label("assoc_firm_tenant_id"),
|
||||
assoc.consultant_id.label("assoc_consultant_id"),
|
||||
assoc.partner_user_id.label("assoc_partner_user_id"),
|
||||
assoc.created_source.label("assoc_created_source"),
|
||||
)
|
||||
.outerjoin(assoc, assoc.client_id == Client.id)
|
||||
.where(Client.id == client_id)
|
||||
)
|
||||
|
||||
result = db.execute(stmt).one_or_none()
|
||||
if not result:
|
||||
return None
|
||||
|
||||
(
|
||||
client,
|
||||
association_type,
|
||||
assoc_firm_tenant_id,
|
||||
assoc_consultant_id,
|
||||
assoc_partner_user_id,
|
||||
assoc_created_source,
|
||||
) = result
|
||||
|
||||
row = {**client.__dict__}
|
||||
row.pop("_sa_instance_state", None)
|
||||
row.update(
|
||||
{
|
||||
"association_type": association_type,
|
||||
"assoc_firm_tenant_id": assoc_firm_tenant_id,
|
||||
"assoc_consultant_id": assoc_consultant_id,
|
||||
"assoc_partner_user_id": assoc_partner_user_id,
|
||||
"assoc_created_source": assoc_created_source,
|
||||
"effective_partner_id": assoc_partner_user_id or row.get("partner_id"),
|
||||
}
|
||||
)
|
||||
assoc=ClientAssociation
|
||||
stmt=(select(
|
||||
Client, ClientGroup.group_name.label("client_group_name"), ClientGroup.group_code.label("client_group_code"),
|
||||
assoc.association_type.label("association_type"), assoc.firm_tenant_id.label("assoc_firm_tenant_id"),
|
||||
assoc.consultant_id.label("assoc_consultant_id"), assoc.partner_user_id.label("assoc_partner_user_id"),
|
||||
assoc.created_source.label("assoc_created_source"),
|
||||
).outerjoin(assoc,assoc.client_id==Client.id).outerjoin(ClientGroup,ClientGroup.id==Client.client_group_id).where(Client.id==client_id))
|
||||
result=db.execute(stmt).one_or_none()
|
||||
if not result: return None
|
||||
client,client_group_name,client_group_code,association_type,assoc_firm_tenant_id,assoc_consultant_id,assoc_partner_user_id,assoc_created_source=result
|
||||
row={**client.__dict__}; row.pop("_sa_instance_state",None)
|
||||
row.update({"client_group_name":client_group_name,"client_group_code":client_group_code,"association_type":association_type,
|
||||
"assoc_firm_tenant_id":assoc_firm_tenant_id,"assoc_consultant_id":assoc_consultant_id,
|
||||
"assoc_partner_user_id":assoc_partner_user_id,"assoc_created_source":assoc_created_source,
|
||||
"effective_partner_id":assoc_partner_user_id or row.get("partner_id")})
|
||||
return row
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user