Add performing partner to clients and engagements
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user