Add client acceptance controls for AQMM and peer review workflow
This commit is contained in:
+127
-2
@@ -10,7 +10,7 @@ from app.core.security.session_auth import get_current_user
|
||||
from app.core.templating import templates
|
||||
from app.modules.clients import repository
|
||||
from app.modules.clients.access import build_scope, can_view_client_row
|
||||
from app.modules.clients.constants import CLIENT_CATEGORY_OPTIONS, CLIENT_STATUS, CLIENT_TYPES, RISK_CATEGORIES
|
||||
from app.modules.clients.constants import CLIENT_ACCEPTANCE_STATUS, CLIENT_CATEGORY_OPTIONS, CLIENT_STATUS, CLIENT_TYPES, RISK_CATEGORIES
|
||||
from app.modules.clients.filters import ClientListFilters
|
||||
from app.modules.clients.import_service import (
|
||||
build_client_import_template_bytes,
|
||||
@@ -22,6 +22,7 @@ from app.modules.clients.import_service import (
|
||||
from app.modules.clients.schemas import ClientCreate, ClientUpdate
|
||||
from app.modules.clients.service import (
|
||||
activate_client_service,
|
||||
approve_client_acceptance_service,
|
||||
archive_client_service,
|
||||
create_client_service,
|
||||
deactivate_client_service,
|
||||
@@ -29,7 +30,9 @@ from app.modules.clients.service import (
|
||||
get_client_or_404,
|
||||
list_client_audit_logs,
|
||||
list_clients_payload,
|
||||
reject_client_acceptance_service,
|
||||
restore_client_service,
|
||||
mark_client_acceptance_pending_service,
|
||||
update_client_service,
|
||||
update_client_self_profile_service,
|
||||
)
|
||||
@@ -77,6 +80,7 @@ def _base_ctx(request: Request, user, db, **ctx):
|
||||
"client_statuses": CLIENT_STATUS,
|
||||
"client_categories": CLIENT_CATEGORY_OPTIONS,
|
||||
"risk_categories": RISK_CATEGORIES,
|
||||
"client_acceptance_statuses": CLIENT_ACCEPTANCE_STATUS,
|
||||
}
|
||||
base.update(ctx)
|
||||
return base
|
||||
@@ -179,6 +183,15 @@ def _build_form_payload(request: Request, user, scope, *, include_client_code: b
|
||||
"risk_category": form.get("risk_category"),
|
||||
"onboarding_date": form.get("onboarding_date") or None,
|
||||
"closing_date": form.get("closing_date") or None,
|
||||
"acceptance_status": form.get("acceptance_status") or "pending_review",
|
||||
"acceptance_required": _form_bool(form.get("acceptance_required")),
|
||||
"independence_check_completed": _form_bool(form.get("independence_check_completed")),
|
||||
"conflict_check_completed": _form_bool(form.get("conflict_check_completed")),
|
||||
"kyc_completed": _form_bool(form.get("kyc_completed")),
|
||||
"engagement_letter_required": _form_bool(form.get("engagement_letter_required")),
|
||||
"engagement_letter_received": _form_bool(form.get("engagement_letter_received")),
|
||||
"acceptance_review_notes": form.get("acceptance_review_notes"),
|
||||
"acceptance_rejection_reason": form.get("acceptance_rejection_reason"),
|
||||
"notes": form.get("notes"),
|
||||
"gst_applicable": _form_bool(form.get("gst_applicable")),
|
||||
"income_tax_applicable": _form_bool(form.get("income_tax_applicable")),
|
||||
@@ -535,10 +548,13 @@ def client_new_page(request: Request):
|
||||
"status": "active",
|
||||
"client_type": "Other",
|
||||
"country": "India",
|
||||
"engagement_mode": "internal_managed",
|
||||
"engagement_mode": "hybrid" if form_mode == "firm_admin" else "internal_managed",
|
||||
"partner_id": scope.locked_partner_id or getattr(user, "id", None),
|
||||
"branch_id": scope.branch_id,
|
||||
"tenant_id": scope.tenant_id,
|
||||
"acceptance_status": "pending_review",
|
||||
"acceptance_required": True,
|
||||
"engagement_letter_required": True,
|
||||
}
|
||||
|
||||
return _render(
|
||||
@@ -645,6 +661,8 @@ def client_detail(request: Request, client_id: int):
|
||||
can_activate=has("clients.activate"),
|
||||
can_archive=has("clients.archive"),
|
||||
can_restore=has("clients.restore"),
|
||||
can_manage_acceptance=has("clients.acceptance.manage"),
|
||||
can_approve_acceptance=has("clients.acceptance.approve"),
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
@@ -757,6 +775,113 @@ async def client_update(request: Request, client_id: int):
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/{client_id}/acceptance/approve")
|
||||
async def client_acceptance_approve(request: Request, client_id: int):
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user = get_current_user(request, db=db)
|
||||
if not user:
|
||||
return RedirectResponse(url="/login", status_code=303)
|
||||
has = _has_perm_factory(db, user)
|
||||
if not has("clients.acceptance.approve"):
|
||||
return _redirect_denied()
|
||||
role_names = _role_names(db, user)
|
||||
scope = build_scope(request, user, has)
|
||||
scope = _elevate_scope_for_system_admin(scope, role_names)
|
||||
form = await request.form()
|
||||
validate_csrf(request, form.get("csrf_token"))
|
||||
row = get_client_or_404(
|
||||
db,
|
||||
client_id=client_id,
|
||||
tenant_id=scope.tenant_id,
|
||||
branch_id=scope.branch_id,
|
||||
allow_cross_branch=scope.allow_cross_branch,
|
||||
allow_all_clients=scope.allow_all_clients,
|
||||
)
|
||||
try:
|
||||
row = approve_client_acceptance_service(db, row=row, actor_user_id=user.id, review_notes=form.get("acceptance_review_notes"))
|
||||
return RedirectResponse(url=f"/clients/{row.id}", status_code=303)
|
||||
except Exception as exc:
|
||||
return _render(
|
||||
request,
|
||||
"modules/clients/templates/clients/detail.html",
|
||||
db,
|
||||
user,
|
||||
title=f"Client • {row.client_name}",
|
||||
row=repository.get_client_detail_payload(db, client_id),
|
||||
audit_logs=list_client_audit_logs(db, row=type("Tmp", (), {"id": client_id})(), limit=10) if has("clients.audit_log.view") else [],
|
||||
scope=scope,
|
||||
can_edit=has("clients.edit"),
|
||||
can_deactivate=has("clients.deactivate"),
|
||||
can_activate=has("clients.activate"),
|
||||
can_archive=has("clients.archive"),
|
||||
can_restore=has("clients.restore"),
|
||||
can_manage_acceptance=has("clients.acceptance.manage"),
|
||||
can_approve_acceptance=has("clients.acceptance.approve"),
|
||||
form_errors=_field_errors(exc),
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/{client_id}/acceptance/reject")
|
||||
async def client_acceptance_reject(request: Request, client_id: int):
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user = get_current_user(request, db=db)
|
||||
if not user:
|
||||
return RedirectResponse(url="/login", status_code=303)
|
||||
has = _has_perm_factory(db, user)
|
||||
if not has("clients.acceptance.approve"):
|
||||
return _redirect_denied()
|
||||
role_names = _role_names(db, user)
|
||||
scope = build_scope(request, user, has)
|
||||
scope = _elevate_scope_for_system_admin(scope, role_names)
|
||||
form = await request.form()
|
||||
validate_csrf(request, form.get("csrf_token"))
|
||||
row = get_client_or_404(
|
||||
db,
|
||||
client_id=client_id,
|
||||
tenant_id=scope.tenant_id,
|
||||
branch_id=scope.branch_id,
|
||||
allow_cross_branch=scope.allow_cross_branch,
|
||||
allow_all_clients=scope.allow_all_clients,
|
||||
)
|
||||
row = reject_client_acceptance_service(db, row=row, actor_user_id=user.id, rejection_reason=form.get("acceptance_rejection_reason"))
|
||||
return RedirectResponse(url=f"/clients/{row.id}", status_code=303)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/{client_id}/acceptance/pending")
|
||||
async def client_acceptance_pending(request: Request, client_id: int):
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user = get_current_user(request, db=db)
|
||||
if not user:
|
||||
return RedirectResponse(url="/login", status_code=303)
|
||||
has = _has_perm_factory(db, user)
|
||||
if not has("clients.acceptance.manage"):
|
||||
return _redirect_denied()
|
||||
role_names = _role_names(db, user)
|
||||
scope = build_scope(request, user, has)
|
||||
scope = _elevate_scope_for_system_admin(scope, role_names)
|
||||
form = await request.form()
|
||||
validate_csrf(request, form.get("csrf_token"))
|
||||
row = get_client_or_404(
|
||||
db,
|
||||
client_id=client_id,
|
||||
tenant_id=scope.tenant_id,
|
||||
branch_id=scope.branch_id,
|
||||
allow_cross_branch=scope.allow_cross_branch,
|
||||
allow_all_clients=scope.allow_all_clients,
|
||||
)
|
||||
row = mark_client_acceptance_pending_service(db, row=row, actor_user_id=user.id, review_notes=form.get("acceptance_review_notes"))
|
||||
return RedirectResponse(url=f"/clients/{row.id}", status_code=303)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/{client_id}/deactivate")
|
||||
async def client_deactivate(request: Request, client_id: int):
|
||||
db = CommonSessionLocal()
|
||||
|
||||
Reference in New Issue
Block a user