diff --git a/app/modules/consultants/service.py b/app/modules/consultants/service.py index 36b82e3..a9265cf 100644 --- a/app/modules/consultants/service.py +++ b/app/modules/consultants/service.py @@ -326,6 +326,122 @@ def list_client_links_for_consultant(db: Session, *, consultant_id: int) -> list ).scalars().all() +def consultant_linked_clients_page( + db: Session, + *, + consultant: ConsultantProfile, + q: str = "", + client_group_id: int | None = None, + status: str = "active", + page: int = 1, + per_page: int = 25, + sort_by: str = "client_name", + sort_order: str = "asc", +) -> dict: + """Return a compact, searchable and paginated list of linked firm clients. + + Link permission columns remain unchanged and continue to govern the existing + service, due-date, communication and document workflows. They are deliberately + not exposed in the client-list UI. + """ + q = (q or "").strip() + status = (status or "").strip().lower() + per_page = min(max(int(per_page or 25), 1), 100) + page = max(int(page or 1), 1) + + stmt = ( + select(ClientConsultantLink, Client, ClientGroup) + .join(Client, Client.id == ClientConsultantLink.client_id) + .outerjoin(ClientGroup, ClientGroup.id == Client.client_group_id) + .where( + ClientConsultantLink.tenant_id == consultant.tenant_id, + ClientConsultantLink.consultant_id == consultant.id, + ClientConsultantLink.is_active.is_(True), + Client.tenant_id == consultant.tenant_id, + Client.is_archived.is_(False), + ) + ) + if status: + stmt = stmt.where(Client.status == status) + if client_group_id: + stmt = stmt.where(Client.client_group_id == int(client_group_id)) + if q: + like = f"%{q}%" + 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), + ClientGroup.group_name.ilike(like), + ClientGroup.group_code.ilike(like), + ) + ) + + total = int(db.execute(select(func.count()).select_from(stmt.subquery())).scalar_one() or 0) + pages = max(ceil(total / per_page), 1) + page = min(page, pages) + + sort_columns = { + "client_code": Client.client_code, + "client_name": Client.client_name, + "pan": Client.pan, + "client_group": ClientGroup.group_name, + "status": Client.status, + } + sort_column = sort_columns.get(sort_by, Client.client_name) + ordering = sort_column.desc() if (sort_order or "asc").lower() == "desc" else sort_column.asc() + + rows = [] + result = db.execute( + stmt.order_by(ordering, Client.id.asc()) + .offset((page - 1) * per_page) + .limit(per_page) + ).all() + for link, client, group in result: + rows.append({ + "link": link, + "client": client, + "client_group_id": getattr(group, "id", None), + "client_group_code": getattr(group, "group_code", None), + "client_group_name": getattr(group, "group_name", None), + "relationship_label": (link.relationship_type or "accounts_consultant").replace("_", " ").title(), + }) + + groups_stmt = ( + select(ClientGroup.id, ClientGroup.group_code, ClientGroup.group_name) + .join(Client, Client.client_group_id == ClientGroup.id) + .join(ClientConsultantLink, ClientConsultantLink.client_id == Client.id) + .where( + ClientConsultantLink.tenant_id == consultant.tenant_id, + ClientConsultantLink.consultant_id == consultant.id, + ClientConsultantLink.is_active.is_(True), + Client.tenant_id == consultant.tenant_id, + Client.is_archived.is_(False), + ClientGroup.is_active.is_(True), + ) + .distinct() + .order_by(ClientGroup.group_name.asc()) + ) + groups = [ + {"id": int(group_id), "code": code, "name": name} + for group_id, code, name in db.execute(groups_stmt).all() + ] + return { + "rows": rows, + "groups": groups, + "meta": {"total": total, "page": page, "per_page": per_page, "pages": pages}, + "filters": { + "q": q, + "client_group_id": client_group_id, + "status": status, + "sort_by": sort_by if sort_by in sort_columns else "client_name", + "sort_order": "desc" if (sort_order or "asc").lower() == "desc" else "asc", + }, + } + + def link_client_to_consultant( db: Session, *, diff --git a/app/modules/consultants/templates/consultants/portal_dashboard.html b/app/modules/consultants/templates/consultants/portal_dashboard.html index c8d018e..6f2e313 100644 --- a/app/modules/consultants/templates/consultants/portal_dashboard.html +++ b/app/modules/consultants/templates/consultants/portal_dashboard.html @@ -38,6 +38,8 @@ {% include "modules/consultants/templates/consultants/portal_partials/clients_requests.html" %} {% elif active_tab == 'documents-clarifications' %} {% include "modules/consultants/templates/consultants/portal_partials/documents_clarifications.html" %} + {% elif active_tab == 'reports' %} + {% include "modules/consultants/templates/consultants/portal_partials/reports.html" %} {% else %} {% include "modules/consultants/templates/consultants/portal_partials/overview.html" %} {% endif %} diff --git a/app/modules/consultants/templates/consultants/portal_partials/clients.html b/app/modules/consultants/templates/consultants/portal_partials/clients.html index 0cc9f7f..876fb3c 100644 --- a/app/modules/consultants/templates/consultants/portal_partials/clients.html +++ b/app/modules/consultants/templates/consultants/portal_partials/clients.html @@ -1,10 +1,148 @@ -
Firm clients explicitly linked to your consultant profile.
+| Code | +Client Name | +PAN | +Client Group | +Role | +Status | +
|---|---|---|---|---|---|
| {{ row.client.client_code or '-' }} | +
+ {{ row.client.client_name }}
+ {% if row.client.trade_name and row.client.trade_name != row.client.client_name %}{{ row.client.trade_name }} {% endif %}
+ |
+ {{ row.client.pan or '-' }} | +
+ {% if row.client_group_name %}
+ {{ row.client_group_name }}
+ {% if row.client_group_code %}{{ row.client_group_code }} {% endif %}
+ {% else %}-{% endif %}
+ |
+ {{ row.relationship_label }} | ++ {% if row.client.status == 'active' %} + Active + {% else %} + {{ (row.client.status or 'inactive').replace('_',' ').title() }} + {% endif %} + | +
| No linked firm clients match the selected filters. | |||||
Clients maintained by the consultant workspace.
Clients maintained separately within the consultant workspace.
+Only explicitly linked firm clients are visible.