diff --git a/alembic/versions/20260805_performing_partner.py b/alembic/versions/20260805_performing_partner.py
new file mode 100644
index 0000000..f65b1f4
--- /dev/null
+++ b/alembic/versions/20260805_performing_partner.py
@@ -0,0 +1,44 @@
+"""add performing partner to clients and engagements
+
+Revision ID: 20260805_performing_partner
+Revises: 20260725_client_groups_family_tracking
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy import inspect
+
+revision = "20260805_performing_partner"
+down_revision = "20260725_client_groups_family_tracking"
+branch_labels = None
+depends_on = None
+
+def _has_column(table, column):
+ return column in {c["name"] for c in inspect(op.get_bind()).get_columns(table)}
+
+def _has_index(table, name):
+ return name in {i["name"] for i in inspect(op.get_bind()).get_indexes(table)}
+
+def upgrade():
+ if not _has_column("clients", "default_performing_partner_user_id"):
+ op.add_column("clients", sa.Column("default_performing_partner_user_id", sa.Integer(), nullable=True))
+ op.create_foreign_key("fk_clients_default_performing_partner", "clients", "users", ["default_performing_partner_user_id"], ["id"], ondelete="SET NULL")
+ if not _has_index("clients", "ix_clients_default_performing_partner_user_id"):
+ op.create_index("ix_clients_default_performing_partner_user_id", "clients", ["default_performing_partner_user_id"])
+ if not _has_column("client_service_subscriptions", "performing_partner_user_id"):
+ op.add_column("client_service_subscriptions", sa.Column("performing_partner_user_id", sa.Integer(), nullable=True))
+ op.create_foreign_key("fk_css_performing_partner", "client_service_subscriptions", "users", ["performing_partner_user_id"], ["id"], ondelete="SET NULL")
+ if not _has_index("client_service_subscriptions", "ix_client_service_subscriptions_performing_partner_user_id"):
+ op.create_index("ix_client_service_subscriptions_performing_partner_user_id", "client_service_subscriptions", ["performing_partner_user_id"])
+ op.execute("""UPDATE client_service_subscriptions SET performing_partner_user_id = assigned_partner_user_id WHERE performing_partner_user_id IS NULL""")
+
+def downgrade():
+ if _has_index("client_service_subscriptions", "ix_client_service_subscriptions_performing_partner_user_id"):
+ op.drop_index("ix_client_service_subscriptions_performing_partner_user_id", table_name="client_service_subscriptions")
+ if _has_column("client_service_subscriptions", "performing_partner_user_id"):
+ op.drop_constraint("fk_css_performing_partner", "client_service_subscriptions", type_="foreignkey")
+ op.drop_column("client_service_subscriptions", "performing_partner_user_id")
+ if _has_index("clients", "ix_clients_default_performing_partner_user_id"):
+ op.drop_index("ix_clients_default_performing_partner_user_id", table_name="clients")
+ if _has_column("clients", "default_performing_partner_user_id"):
+ op.drop_constraint("fk_clients_default_performing_partner", "clients", type_="foreignkey")
+ op.drop_column("clients", "default_performing_partner_user_id")
diff --git a/app/modules/clients/models.py b/app/modules/clients/models.py
index bbea0a1..411112d 100644
--- a/app/modules/clients/models.py
+++ b/app/modules/clients/models.py
@@ -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)
diff --git a/app/modules/clients/repository.py b/app/modules/clients/repository.py
index 72a2263..77be726 100644
--- a/app/modules/clients/repository.py
+++ b/app/modules/clients/repository.py
@@ -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())
diff --git a/app/modules/clients/schemas.py b/app/modules/clients/schemas.py
index d60e0a7..c88929f 100644
--- a/app/modules/clients/schemas.py
+++ b/app/modules/clients/schemas.py
@@ -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
diff --git a/app/modules/clients/templates/clients/detail.html b/app/modules/clients/templates/clients/detail.html
index 9a01f15..91de7f6 100644
--- a/app/modules/clients/templates/clients/detail.html
+++ b/app/modules/clients/templates/clients/detail.html
@@ -117,6 +117,7 @@
Audit Firm: {{ row.assoc_firm_tenant_id or row.tenant_id or '-' }}
Branch: {{ row.branch_id or '-' }}
Partner: {{ row.assoc_partner_user_id or row.partner_id or '-' }}
+ Default Performing Partner: {{ row.default_performing_partner_user_id or row.partner_id or '-' }}
Default Review Partner: {{ row.default_review_partner_user_id or '-' }}
Referred by: {{ consultant_summary.referred_by.contact_person if consultant_summary and consultant_summary.referred_by else 'Direct / Not recorded' }}
Primary consultant: {{ consultant_summary.primary.contact_person if consultant_summary and consultant_summary.primary else 'Firm managed' }}
diff --git a/app/modules/clients/templates/clients/partials/form.html b/app/modules/clients/templates/clients/partials/form.html
index 03f1584..693f0fa 100644
--- a/app/modules/clients/templates/clients/partials/form.html
+++ b/app/modules/clients/templates/clients/partials/form.html
@@ -316,6 +316,19 @@
+
+
Default Performing Partner
+
+ -- Same as Engagement Partner --
+ {% for p in form_options.performing_partners or [] %}
+
+ {{ p.full_name or p.email }}{% if p.tenant_name %} ({{ p.tenant_name }}){% endif %}
+
+ {% endfor %}
+
+
Defaults the Partner who directly performs or supervises the engagement. When blank, the Engagement Partner is used.
+
+
Default Review Partner
diff --git a/app/modules/clients/ui.py b/app/modules/clients/ui.py
index 3d45d68..9d2c2d4 100644
--- a/app/modules/clients/ui.py
+++ b/app/modules/clients/ui.py
@@ -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,
diff --git a/app/modules/partner_dashboard/service.py b/app/modules/partner_dashboard/service.py
index f3d973a..53a991b 100644
--- a/app/modules/partner_dashboard/service.py
+++ b/app/modules/partner_dashboard/service.py
@@ -138,6 +138,7 @@ def _task_scope(stmt, tenant_id: int | None, branch_id: int | None, current_user
ClientServiceTaskInstance.subscription.has(
or_(
ClientServiceSubscription.assigned_partner_user_id == current_user.id,
+ ClientServiceSubscription.performing_partner_user_id == current_user.id,
ClientServiceSubscription.review_partner_user_id == current_user.id,
)
)
@@ -157,6 +158,7 @@ def _subscription_scope(stmt, tenant_id: int | None, branch_id: int | None, curr
stmt = stmt.where(
or_(
ClientServiceSubscription.assigned_partner_user_id == current_user.id,
+ ClientServiceSubscription.performing_partner_user_id == current_user.id,
ClientServiceSubscription.review_partner_user_id == current_user.id,
)
)
@@ -258,23 +260,10 @@ def _load_clients(db: Session, tenant_id: int | None, branch_id: int | None, cur
def _staff_rows(db: Session, tenant_id: int | None, branch_id: int | None, tasks: list[ClientServiceTaskInstance]) -> list[dict[str, Any]]:
if not tenant_id:
return []
- stmt = (
- select(User)
- .join(UserRole, UserRole.user_id == User.id)
- .join(Role, Role.id == UserRole.role_id)
- .where(
- User.tenant_id == tenant_id,
- User.is_active.is_(True),
- Role.is_active.is_(True),
- Role.name.in_(("Staff", "Branch Manager")),
- )
- .distinct()
- )
+ stmt = select(User).where(User.tenant_id == tenant_id, User.is_active.is_(True))
if branch_id is not None:
stmt = stmt.where(User.branch_id == branch_id)
- users = db.execute(
- stmt.order_by(User.full_name.asc(), User.email.asc()).limit(100)
- ).scalars().all()
+ users = db.execute(stmt.order_by(User.full_name.asc(), User.email.asc()).limit(100)).scalars().all()
by_user: dict[int, dict[str, int]] = {}
for task in tasks:
uid = getattr(task, "assigned_to_user_id", None)
diff --git a/app/modules/services/client_services.py b/app/modules/services/client_services.py
index 89963fb..72f567f 100644
--- a/app/modules/services/client_services.py
+++ b/app/modules/services/client_services.py
@@ -70,6 +70,7 @@ def list_subscription_payload(
selectinload(ClientServiceSubscription.client),
selectinload(ClientServiceSubscription.catalogue),
selectinload(ClientServiceSubscription.assigned_partner),
+ selectinload(ClientServiceSubscription.performing_partner),
selectinload(ClientServiceSubscription.assigned_manager),
selectinload(ClientServiceSubscription.assigned_staff),
selectinload(ClientServiceSubscription.review_partner),
@@ -118,6 +119,7 @@ def get_subscription(db: Session, *, subscription_id: int, tenant_id: int) -> Cl
selectinload(ClientServiceSubscription.client),
selectinload(ClientServiceSubscription.catalogue),
selectinload(ClientServiceSubscription.assigned_partner),
+ selectinload(ClientServiceSubscription.performing_partner),
selectinload(ClientServiceSubscription.assigned_manager),
selectinload(ClientServiceSubscription.assigned_staff),
selectinload(ClientServiceSubscription.review_partner),
diff --git a/app/modules/services/engagements_ui.py b/app/modules/services/engagements_ui.py
index 06cc8d0..4fd7299 100644
--- a/app/modules/services/engagements_ui.py
+++ b/app/modules/services/engagements_ui.py
@@ -223,6 +223,7 @@ def subscription_create_page(request: Request, client_id: int | None = None):
)
enabled_services = list_enabled_services_for_assignment(db, tenant_id=tenant_id)
assignable_users = list_assignable_users(db, tenant_id=tenant_id, branch_id=branch_id)
+ performing_partners = list_assignable_users(db, tenant_id=tenant_id, branch_id=branch_id, role_names=("Partner",))
review_partners = list_review_partners(db, tenant_id=tenant_id, branch_id=branch_id)
return _render(
@@ -236,6 +237,7 @@ def subscription_create_page(request: Request, client_id: int | None = None):
clients=clients,
enabled_services=enabled_services,
assignable_users=assignable_users,
+ performing_partners=performing_partners,
review_partners=review_partners,
selected_client_id=client_id,
financial_year=_active_financial_year(request),
@@ -252,6 +254,7 @@ def subscription_create_submit(
assigned_partner_user_id: str = Form(""),
assigned_manager_user_id: str = Form(""),
assigned_staff_user_id: str = Form(""),
+ performing_partner_user_id: str = Form(""),
review_partner_user_id: str = Form(""),
financial_year: str = Form(""),
start_date: str = Form(""),
@@ -317,6 +320,8 @@ def subscription_create_submit(
row.branch_id = firm_selection.default_branch_id or getattr(user, "branch_id", None)
row.firm_service_selection_id = firm_selection.id
row.assigned_partner_user_id = int(assigned_partner_user_id) if assigned_partner_user_id.strip() else None
+ requested_performing_partner_id = int(performing_partner_user_id) if performing_partner_user_id.strip() else None
+ row.performing_partner_user_id = requested_performing_partner_id or getattr(client, "default_performing_partner_user_id", None) or row.assigned_partner_user_id
row.assigned_manager_user_id = int(assigned_manager_user_id) if assigned_manager_user_id.strip() else None
row.assigned_staff_user_id = int(assigned_staff_user_id) if assigned_staff_user_id.strip() else None
if review_partner_required_for_engagement(db, tenant_id=tenant_id, engagement_type=row.engagement_type):
@@ -364,6 +369,7 @@ def subscription_bulk_create_page(request: Request, error: str = ""):
)
enabled_services = list_enabled_services_for_assignment(db, tenant_id=tenant_id)
partners = list_assignable_users(db, tenant_id=tenant_id, role_names=("Partner",))
+ performing_partners = partners
managers = list_assignable_users(db, tenant_id=tenant_id, role_names=("Branch Manager",))
staff_users = list_assignable_users(db, tenant_id=tenant_id, role_names=("Staff",))
review_partners = list_review_partners(db, tenant_id=tenant_id)
@@ -377,6 +383,7 @@ def subscription_bulk_create_page(request: Request, error: str = ""):
clients=clients,
enabled_services=enabled_services,
partners=partners,
+ performing_partners=performing_partners,
client_partner_names={row.id: (row.full_name or row.email) for row in partners},
managers=managers,
staff_users=staff_users,
@@ -385,6 +392,7 @@ def subscription_bulk_create_page(request: Request, error: str = ""):
error_message={
"service": "Select a valid enabled firm service.",
"partner": "Select a valid active Partner.",
+ "performing_partner": "The selected Performing Partner is not available for this firm.",
"manager": "The selected Manager is not available for this firm.",
"staff": "The selected Staff member is not available for this firm.",
"review_partner": "The selected Review Partner is not available for this firm.",
@@ -404,6 +412,7 @@ def subscription_bulk_create_submit(
assigned_partner_user_id: int = Form(...),
assigned_manager_user_id: str = Form(""),
assigned_staff_user_id: str = Form(""),
+ performing_partner_user_id: str = Form(""),
review_partner_user_id: str = Form(""),
financial_year: str = Form(""),
remarks: str = Form(""),
@@ -448,6 +457,7 @@ def subscription_bulk_create_submit(
review_partners = list_review_partners(db, tenant_id=tenant_id)
partner_ids = {row.id for row in partners}
+ performing_partner_ids = partner_ids
manager_ids = {row.id for row in managers}
staff_ids = {row.id for row in staff_users}
review_partner_ids = {row.id for row in review_partners}
@@ -455,9 +465,12 @@ def subscription_bulk_create_submit(
if assigned_partner_user_id not in partner_ids:
return RedirectResponse(url="/services/engagements/bulk-new?error=partner", status_code=303)
+ performing_partner_id = int(performing_partner_user_id) if performing_partner_user_id.strip() else assigned_partner_user_id
manager_id = int(assigned_manager_user_id) if assigned_manager_user_id.strip() else None
staff_id = int(assigned_staff_user_id) if assigned_staff_user_id.strip() else None
review_partner_id = int(review_partner_user_id) if review_partner_user_id.strip() else None
+ if performing_partner_id not in performing_partner_ids:
+ return RedirectResponse(url="/services/engagements/bulk-new?error=performing_partner", status_code=303)
if manager_id is not None and manager_id not in manager_ids:
return RedirectResponse(url="/services/engagements/bulk-new?error=manager", status_code=303)
if staff_id is not None and staff_id not in staff_ids:
@@ -531,6 +544,7 @@ def subscription_bulk_create_submit(
service_catalogue_id=service_catalogue_id,
firm_service_selection_id=firm_selection.id,
assigned_partner_user_id=assigned_partner_user_id,
+ performing_partner_user_id=performing_partner_id or getattr(client, "default_performing_partner_user_id", None) or assigned_partner_user_id,
assigned_manager_user_id=manager_id,
assigned_staff_user_id=staff_id,
review_partner_user_id=(
@@ -980,6 +994,7 @@ def subscription_edit_page(request: Request, subscription_id: int):
)
enabled_services = list_enabled_services_for_assignment(db, tenant_id=tenant_id)
assignable_users = list_assignable_users(db, tenant_id=tenant_id, branch_id=branch_id)
+ performing_partners = list_assignable_users(db, tenant_id=tenant_id, branch_id=branch_id, role_names=("Partner",))
review_partners = list_review_partners(db, tenant_id=tenant_id, branch_id=branch_id)
return _render(
request,
@@ -992,6 +1007,7 @@ def subscription_edit_page(request: Request, subscription_id: int):
clients=clients,
enabled_services=enabled_services,
assignable_users=assignable_users,
+ performing_partners=performing_partners,
review_partners=review_partners,
selected_client_id=row.client_id,
financial_year=row.financial_year,
@@ -1007,6 +1023,7 @@ def subscription_edit_submit(
assigned_partner_user_id: str = Form(""),
assigned_manager_user_id: str = Form(""),
assigned_staff_user_id: str = Form(""),
+ performing_partner_user_id: str = Form(""),
review_partner_user_id: str = Form(""),
start_date: str = Form(""),
end_date: str = Form(""),
@@ -1038,6 +1055,8 @@ def subscription_edit_submit(
return RedirectResponse(url=f"/services/engagements/{row.id}?year_locked=1", status_code=303)
row.assigned_partner_user_id = int(assigned_partner_user_id) if assigned_partner_user_id.strip() else None
+ requested_performing_partner_id = int(performing_partner_user_id) if performing_partner_user_id.strip() else None
+ row.performing_partner_user_id = requested_performing_partner_id or getattr(row.client, "default_performing_partner_user_id", None) or row.assigned_partner_user_id
row.assigned_manager_user_id = int(assigned_manager_user_id) if assigned_manager_user_id.strip() else None
row.assigned_staff_user_id = int(assigned_staff_user_id) if assigned_staff_user_id.strip() else None
if review_partner_required_for_engagement(db, tenant_id=tenant_id, engagement_type=row.engagement_type):
diff --git a/app/modules/services/models.py b/app/modules/services/models.py
index e264576..f81631e 100644
--- a/app/modules/services/models.py
+++ b/app/modules/services/models.py
@@ -394,6 +394,7 @@ class ClientServiceSubscription(CommonBase):
firm_service_selection_id: Mapped[int | None] = mapped_column(ForeignKey("firm_service_selections.id", ondelete="SET NULL"), nullable=True, index=True)
assigned_partner_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True, index=True)
+ performing_partner_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True, index=True)
assigned_manager_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True, index=True)
assigned_staff_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True, index=True)
review_partner_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True, index=True)
@@ -449,6 +450,7 @@ class ClientServiceSubscription(CommonBase):
due_date_rule = relationship("ServiceDueDateRule", foreign_keys=[due_date_rule_id])
firm_selection = relationship("FirmServiceSelection")
assigned_partner = relationship("User", foreign_keys=[assigned_partner_user_id])
+ performing_partner = relationship("User", foreign_keys=[performing_partner_user_id])
assigned_manager = relationship("User", foreign_keys=[assigned_manager_user_id])
assigned_staff = relationship("User", foreign_keys=[assigned_staff_user_id])
review_partner = relationship("User", foreign_keys=[review_partner_user_id])
diff --git a/app/modules/services/templates/services/engagements/bulk_form.html b/app/modules/services/templates/services/engagements/bulk_form.html
index 7f5c858..0265705 100644
--- a/app/modules/services/templates/services/engagements/bulk_form.html
+++ b/app/modules/services/templates/services/engagements/bulk_form.html
@@ -44,6 +44,15 @@
The engagement branch is derived automatically from the selected Partner’s branch.
+
+
Performing Partner
+
+ Same as Engagement Partner
+ {% for u in performing_partners %}{{ u.full_name or u.email }} {% endfor %}
+
+
Defaults to the Engagement Partner when left blank.
+
+
Default Manager
diff --git a/app/modules/services/templates/services/engagements/detail.html b/app/modules/services/templates/services/engagements/detail.html
index a761d3b..a0bffe1 100644
--- a/app/modules/services/templates/services/engagements/detail.html
+++ b/app/modules/services/templates/services/engagements/detail.html
@@ -11,7 +11,7 @@
{% if row.is_locked %}This engagement is locked as historical record. It cannot be edited.
{% endif %}
Client & Service
Client {{ row.client.client_name if row.client else '-' }}
Service {{ row.catalogue.service_name if row.catalogue else '-' }}
Financial Year {{ row.financial_year or '-' }}
Assessment Year {{ row.assessment_year or '-' }}
Engagement Type {{ 'Assurance' if row.engagement_type == 'assurance' else 'Non-Assurance' }}
Original Due Date {{ row.original_due_date or '-' }}
Expiry Date {{ row.expiry_date or '-' }}
Current Due Date {{ row.current_due_date or '-' }}{% if row.due_date_source %}{{ row.due_date_source|replace('_',' ')|title }} {% endif %}
Status {{ 'Locked' if row.is_locked else row.status|replace('_',' ')|title }}{% if not row.is_active %} / Inactive{% endif %}
Period {{ row.start_date or '-' }} to {{ row.end_date or '-' }}
-
Assignment
Partner {{ row.assigned_partner.full_name if row.assigned_partner and row.assigned_partner.full_name else (row.assigned_partner.email if row.assigned_partner else '-') }}
Manager {{ row.assigned_manager.full_name if row.assigned_manager and row.assigned_manager.full_name else (row.assigned_manager.email if row.assigned_manager else '-') }}
Staff {{ row.assigned_staff.full_name if row.assigned_staff and row.assigned_staff.full_name else (row.assigned_staff.email if row.assigned_staff else '-') }}
Review Partner {{ row.review_partner.full_name if row.review_partner and row.review_partner.full_name else (row.review_partner.email if row.review_partner else '-') }}
+
Assignment
Partner {{ row.assigned_partner.full_name if row.assigned_partner and row.assigned_partner.full_name else (row.assigned_partner.email if row.assigned_partner else '-') }}
Performing Partner {{ row.performing_partner.full_name if row.performing_partner and row.performing_partner.full_name else (row.performing_partner.email if row.performing_partner else (row.assigned_partner.full_name if row.assigned_partner and row.assigned_partner.full_name else (row.assigned_partner.email if row.assigned_partner else '-'))) }}
Manager {{ row.assigned_manager.full_name if row.assigned_manager and row.assigned_manager.full_name else (row.assigned_manager.email if row.assigned_manager else '-') }}
Staff {{ row.assigned_staff.full_name if row.assigned_staff and row.assigned_staff.full_name else (row.assigned_staff.email if row.assigned_staff else '-') }}
Review Partner {{ row.review_partner.full_name if row.review_partner and row.review_partner.full_name else (row.review_partner.email if row.review_partner else '-') }}
{% if row.quality_workflow_required %}
diff --git a/app/modules/services/templates/services/engagements/form.html b/app/modules/services/templates/services/engagements/form.html
index 49bea6c..4cbfe84 100644
--- a/app/modules/services/templates/services/engagements/form.html
+++ b/app/modules/services/templates/services/engagements/form.html
@@ -4,7 +4,7 @@
{% if mode == 'edit' %}Edit Engagement{% else %}Assign Service to Client{% endif %}
-
Engagements are maintained financial-year wise. Review partner is used only for assurance engagements of partnership audit firms.
+
Engagement Partner owns the assignment, Performing Partner executes or supervises it, and Review Partner independently reviews applicable assurance engagements.
Back
@@ -50,6 +50,7 @@
Assigned Partner Not assigned {% for u in assignable_users %}{{ u.full_name or u.email }} {% endfor %}
+ Performing Partner Use client default / Engagement Partner {% for u in performing_partners or [] %}{{ u.full_name or u.email }} {% endfor %}The Partner who directly performs or supervises this engagement.
Assigned Manager Not assigned {% for u in assignable_users %}{{ u.full_name or u.email }} {% endfor %}
Assigned Staff Not assigned {% for u in assignable_users %}{{ u.full_name or u.email }} {% endfor %}
Review Partner Use client default / Not required {% for u in review_partners or [] %}{{ u.full_name or u.email }} {% endfor %}Saved only for assurance engagements of partnership audit firms.
diff --git a/app/modules/services/templates/services/engagements/list.html b/app/modules/services/templates/services/engagements/list.html
index 1d38add..4a4294c 100644
--- a/app/modules/services/templates/services/engagements/list.html
+++ b/app/modules/services/templates/services/engagements/list.html
@@ -87,7 +87,7 @@
FY: {{ row.financial_year or '-' }}
AY: {{ row.assessment_year or '-' }}
Current: {{ row.current_due_date or '-' }}
Original: {{ row.original_due_date or '-' }}
{% if row.expiry_date %}Expiry: {{ row.expiry_date }}
{% endif %}{% if row.due_date_source %}{{ row.due_date_source|replace('_',' ')|title }}
{% endif %}
{{ 'Assurance' if row.engagement_type == 'assurance' else 'Non-Assurance' }}
- Partner: {{ row.assigned_partner.full_name if row.assigned_partner and row.assigned_partner.full_name else (row.assigned_partner.email if row.assigned_partner else '-') }}
Manager: {{ row.assigned_manager.full_name if row.assigned_manager and row.assigned_manager.full_name else (row.assigned_manager.email if row.assigned_manager else '-') }}
Staff: {{ row.assigned_staff.full_name if row.assigned_staff and row.assigned_staff.full_name else (row.assigned_staff.email if row.assigned_staff else '-') }}
Review: {{ row.review_partner.full_name if row.review_partner and row.review_partner.full_name else (row.review_partner.email if row.review_partner else '-') }}
+ Partner: {{ row.assigned_partner.full_name if row.assigned_partner and row.assigned_partner.full_name else (row.assigned_partner.email if row.assigned_partner else '-') }}
Performing: {{ row.performing_partner.full_name if row.performing_partner and row.performing_partner.full_name else (row.performing_partner.email if row.performing_partner else (row.assigned_partner.full_name if row.assigned_partner and row.assigned_partner.full_name else (row.assigned_partner.email if row.assigned_partner else '-'))) }}
Manager: {{ row.assigned_manager.full_name if row.assigned_manager and row.assigned_manager.full_name else (row.assigned_manager.email if row.assigned_manager else '-') }}
Staff: {{ row.assigned_staff.full_name if row.assigned_staff and row.assigned_staff.full_name else (row.assigned_staff.email if row.assigned_staff else '-') }}
Review: {{ row.review_partner.full_name if row.review_partner and row.review_partner.full_name else (row.review_partner.email if row.review_partner else '-') }}
{{ 'Locked' if row.is_locked else row.status|replace('_',' ')|title }}{% if not row.is_active %} / Inactive{% endif %}
View {% if can_manage and not row.is_locked %}Edit {% endif %}
diff --git a/app/modules/work_detail/service.py b/app/modules/work_detail/service.py
index dc868aa..eb44ab7 100644
--- a/app/modules/work_detail/service.py
+++ b/app/modules/work_detail/service.py
@@ -112,6 +112,7 @@ def _is_manager_for_engagement(engagement: ClientServiceSubscription, user) -> b
def _is_partner_for_engagement(engagement: ClientServiceSubscription, user) -> bool:
return user.id in {
_safe_int(getattr(engagement, "assigned_partner_user_id", None)),
+ _safe_int(getattr(engagement, "performing_partner_user_id", None)),
_safe_int(getattr(engagement, "review_partner_user_id", None)),
}