Add performing partner to clients and engagements

This commit is contained in:
A R R R Associates
2026-08-05 10:52:36 +05:30
parent 9bfc666dad
commit 5a135a65a8
16 changed files with 122 additions and 21 deletions
+1
View File
@@ -23,6 +23,7 @@ class Client(CommonBase):
client_group_id: Mapped[int | None] = mapped_column(ForeignKey("client_groups.id", ondelete="SET NULL"), nullable=True, index=True)
group_relationship: Mapped[str | None] = mapped_column(String(100), nullable=True)
is_group_head: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, index=True)
default_performing_partner_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True, index=True)
default_review_partner_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True, index=True)
referred_by_consultant_id: Mapped[int | None] = mapped_column(ForeignKey("consultant_profiles.id", ondelete="SET NULL"), nullable=True, index=True)
referral_date: Mapped[date | None] = mapped_column(Date, nullable=True)
+12 -3
View File
@@ -41,7 +41,10 @@ def build_clients_query(
select(ClientServiceSubscription.id).where(
ClientServiceSubscription.tenant_id == tenant_id,
ClientServiceSubscription.client_id == Client.id,
ClientServiceSubscription.review_partner_user_id == viewer_partner_id,
or_(
ClientServiceSubscription.performing_partner_user_id == viewer_partner_id,
ClientServiceSubscription.review_partner_user_id == viewer_partner_id,
),
ClientServiceSubscription.is_active.is_(True),
)
) if viewer_partner_id is not None else None
@@ -133,7 +136,10 @@ def list_clients(
review_stats = exists(select(ClientServiceSubscription.id).where(
ClientServiceSubscription.tenant_id == tenant_id,
ClientServiceSubscription.client_id == Client.id,
ClientServiceSubscription.review_partner_user_id == viewer_partner_id,
or_(
ClientServiceSubscription.performing_partner_user_id == viewer_partner_id,
ClientServiceSubscription.review_partner_user_id == viewer_partner_id,
),
ClientServiceSubscription.is_active.is_(True),
))
stats_stmt = stats_stmt.where(or_(Client.partner_id == viewer_partner_id, review_stats))
@@ -149,7 +155,10 @@ def has_partner_review_access(db: Session, *, tenant_id: int, client_id: int, pa
select(ClientServiceSubscription.id).where(
ClientServiceSubscription.tenant_id == tenant_id,
ClientServiceSubscription.client_id == client_id,
ClientServiceSubscription.review_partner_user_id == partner_user_id,
or_(
ClientServiceSubscription.performing_partner_user_id == partner_user_id,
ClientServiceSubscription.review_partner_user_id == partner_user_id,
),
ClientServiceSubscription.is_active.is_(True),
).limit(1)
).scalar_one_or_none())
+3
View File
@@ -24,6 +24,7 @@ class ClientBase(BaseModel):
client_group_id: Optional[int] = None
group_relationship: Optional[str] = None
is_group_head: bool = False
default_performing_partner_user_id: Optional[int] = None
default_review_partner_user_id: Optional[int] = None
referred_by_consultant_id: Optional[int] = None
primary_consultant_id: Optional[int] = None
@@ -219,6 +220,7 @@ class ClientUpdate(BaseModel):
client_group_id: Optional[int] = None
group_relationship: Optional[str] = None
is_group_head: Optional[bool] = None
default_performing_partner_user_id: Optional[int] = None
default_review_partner_user_id: Optional[int] = None
referred_by_consultant_id: Optional[int] = None
primary_consultant_id: Optional[int] = None
@@ -398,6 +400,7 @@ class ClientOut(BaseModel):
tenant_id: int
branch_id: int
partner_id: Optional[int] = None
default_performing_partner_user_id: Optional[int] = None
default_review_partner_user_id: Optional[int] = None
engagement_mode: str
client_code: str
@@ -117,6 +117,7 @@
<div><span class="font-medium">Audit Firm:</span> {{ row.assoc_firm_tenant_id or row.tenant_id or '-' }}</div>
<div><span class="font-medium">Branch:</span> {{ row.branch_id or '-' }}</div>
<div><span class="font-medium">Partner:</span> {{ row.assoc_partner_user_id or row.partner_id or '-' }}</div>
<div><span class="font-medium">Default Performing Partner:</span> {{ row.default_performing_partner_user_id or row.partner_id or '-' }}</div>
<div><span class="font-medium">Default Review Partner:</span> {{ row.default_review_partner_user_id or '-' }}</div>
<div><span class="font-medium">Referred by:</span> {{ consultant_summary.referred_by.contact_person if consultant_summary and consultant_summary.referred_by else 'Direct / Not recorded' }}</div>
<div><span class="font-medium">Primary consultant:</span> {{ consultant_summary.primary.contact_person if consultant_summary and consultant_summary.primary else 'Firm managed' }}</div>
@@ -316,6 +316,19 @@
<input name="referral_reference" value="{{ form_data.referral_reference or (row.referral_reference if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" placeholder="Source, campaign, agreement or note">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Default Performing Partner</label>
<select name="default_performing_partner_user_id" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
<option value="">-- Same as Engagement Partner --</option>
{% for p in form_options.performing_partners or [] %}
<option value="{{ p.id }}" {% if (form_data.default_performing_partner_user_id or (row.default_performing_partner_user_id if is_edit else None)) == p.id %}selected{% endif %}>
{{ p.full_name or p.email }}{% if p.tenant_name %} ({{ p.tenant_name }}){% endif %}
</option>
{% endfor %}
</select>
<p class="mt-1 text-xs text-slate-500">Defaults the Partner who directly performs or supervises the engagement. When blank, the Engagement Partner is used.</p>
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Default Review Partner</label>
<select name="default_review_partner_user_id" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
+7
View File
@@ -186,6 +186,7 @@ def _build_form_payload(request: Request, user, scope, *, include_client_code: b
"client_group_id": int(form.get("client_group_id")) if form.get("client_group_id") not in (None, "", "None") else None,
"group_relationship": form.get("group_relationship"),
"is_group_head": _form_bool(form.get("is_group_head")),
"default_performing_partner_user_id": int(form.get("default_performing_partner_user_id")) if form.get("default_performing_partner_user_id") not in (None, "", "None") else None,
"default_review_partner_user_id": int(form.get("default_review_partner_user_id")) if form.get("default_review_partner_user_id") not in (None, "", "None") else None,
"referred_by_consultant_id": int(form.get("referred_by_consultant_id")) if form.get("referred_by_consultant_id") not in (None, "", "None") else None,
"primary_consultant_id": int(form.get("primary_consultant_id")) if form.get("primary_consultant_id") not in (None, "", "None") else None,
@@ -269,6 +270,7 @@ def _form_options(db, scope, form_mode: str):
"tenants": repository.list_tenants(db),
"branches": repository.list_all_branches(db),
"partners": repository.list_all_partners(db),
"performing_partners": repository.list_all_partners(db),
"review_partners": repository.list_all_partners(db),
"active_tenant_id": tenant_id,
"active_branch_id": branch_id,
@@ -286,6 +288,11 @@ def _form_options(db, scope, form_mode: str):
),
"consultants": list_consultants(db, tenant_id=tenant_id, branch_id=branch_id, include_inactive=False),
"client_groups": [item["group"] for item in list_groups(db, tenant_id=tenant_id)],
"performing_partners": repository.list_partners_for_scope(
db,
tenant_id=tenant_id,
branch_id=None if scope.allow_cross_branch else branch_id,
),
"review_partners": repository.list_partners_for_scope(
db,
tenant_id=tenant_id,