Add client acceptance controls for AQMM and peer review workflow
This commit is contained in:
@@ -53,6 +53,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if form_errors %}
|
||||
<div class="rounded-2xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-800">
|
||||
{% for err in form_errors %}<div>{{ err }}</div>{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid gap-6 xl:grid-cols-3">
|
||||
<section class="rounded-2xl bg-white p-6 shadow-soft xl:col-span-2">
|
||||
<h3 class="text-base font-semibold text-slate-900">Profile</h3>
|
||||
@@ -75,6 +81,78 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl bg-white p-6 shadow-soft xl:col-span-2">
|
||||
{% set acc_status = row.acceptance_status or 'pending_review' %}
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-slate-900">Client Acceptance Controls</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">Embedded AQMM / peer-review evidence for acceptance, independence, conflict, KYC and engagement letter controls.</p>
|
||||
</div>
|
||||
<span class="rounded-full px-3 py-1 text-xs font-semibold {% if acc_status == 'approved' %}bg-emerald-100 text-emerald-700{% elif acc_status == 'rejected' %}bg-rose-100 text-rose-700{% else %}bg-amber-100 text-amber-800{% endif %}">{{ acc_status.replace('_',' ').title() }}</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid gap-3 md:grid-cols-2">
|
||||
{% for label, ok in [
|
||||
('Acceptance Required', row.acceptance_required),
|
||||
('Independence Check', row.independence_check_completed),
|
||||
('Conflict Check', row.conflict_check_completed),
|
||||
('KYC Completed', row.kyc_completed),
|
||||
('Engagement Letter Required', row.engagement_letter_required),
|
||||
('Engagement Letter Received', row.engagement_letter_received)
|
||||
] %}
|
||||
<div class="rounded-xl border border-slate-200 px-4 py-3">
|
||||
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">{{ label }}</div>
|
||||
<div class="mt-1 text-sm font-medium {% if ok %}text-emerald-700{% else %}text-amber-700{% endif %}">{{ 'Yes' if ok else 'No' }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="rounded-xl border border-slate-200 px-4 py-3">
|
||||
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Approved By</div>
|
||||
<div class="mt-1 text-sm text-slate-800">{{ row.acceptance_approved_by_user_id or '-' }}</div>
|
||||
</div>
|
||||
<div class="rounded-xl border border-slate-200 px-4 py-3">
|
||||
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Approved At</div>
|
||||
<div class="mt-1 text-sm text-slate-800">{{ row.acceptance_approved_at_utc or '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if row.acceptance_review_notes %}
|
||||
<div class="mt-4 rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-700">
|
||||
<div class="font-semibold text-slate-900">Review Notes</div>
|
||||
<div class="mt-1 whitespace-pre-wrap">{{ row.acceptance_review_notes }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if row.acceptance_rejection_reason %}
|
||||
<div class="mt-4 rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-800">
|
||||
<div class="font-semibold text-rose-900">Rejection / Remediation Notes</div>
|
||||
<div class="mt-1 whitespace-pre-wrap">{{ row.acceptance_rejection_reason }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if can_approve_acceptance or can_manage_acceptance %}
|
||||
<div class="mt-5 grid gap-3 md:grid-cols-3">
|
||||
{% if can_approve_acceptance %}
|
||||
<form method="post" action="/clients/{{ row.id }}/acceptance/approve" class="space-y-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<textarea name="acceptance_review_notes" rows="2" placeholder="Approval notes" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">{{ row.acceptance_review_notes or '' }}</textarea>
|
||||
<button class="w-full rounded-xl bg-emerald-600 px-4 py-2 text-sm font-semibold text-white hover:bg-emerald-700">Approve Acceptance</button>
|
||||
</form>
|
||||
<form method="post" action="/clients/{{ row.id }}/acceptance/reject" class="space-y-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<textarea name="acceptance_rejection_reason" rows="2" placeholder="Rejection/remediation reason" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">{{ row.acceptance_rejection_reason or '' }}</textarea>
|
||||
<button class="w-full rounded-xl bg-rose-600 px-4 py-2 text-sm font-semibold text-white hover:bg-rose-700">Reject</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if can_manage_acceptance %}
|
||||
<form method="post" action="/clients/{{ row.id }}/acceptance/pending" class="space-y-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<textarea name="acceptance_review_notes" rows="2" placeholder="Pending review notes" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">{{ row.acceptance_review_notes or '' }}</textarea>
|
||||
<button class="w-full rounded-xl border border-amber-300 px-4 py-2 text-sm font-semibold text-amber-800 hover:bg-amber-50">Move to Pending</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl bg-white p-6 shadow-soft">
|
||||
<h3 class="text-base font-semibold text-slate-900">Association</h3>
|
||||
<div class="mt-4 space-y-2 text-sm text-slate-700">
|
||||
|
||||
@@ -147,6 +147,71 @@
|
||||
<input name="country" value="{{ form_data.country or (row.country if is_edit else 'India') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2 rounded-2xl border border-amber-200 bg-amber-50 p-4">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold text-amber-950">Client Acceptance / Continuance Controls</h4>
|
||||
<p class="mt-1 text-xs text-amber-800">Used for AQMM and peer review evidence. High/Critical risk clients cannot be activated until acceptance is approved.</p>
|
||||
</div>
|
||||
{% set acc_status = form_data.acceptance_status or (row.acceptance_status if is_edit else 'pending_review') %}
|
||||
<span class="rounded-full px-3 py-1 text-xs font-semibold {% if acc_status == 'approved' %}bg-emerald-100 text-emerald-700{% elif acc_status == 'rejected' %}bg-rose-100 text-rose-700{% else %}bg-amber-100 text-amber-800{% endif %}">{{ acc_status.replace('_',' ').title() }}</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">Acceptance Status</label>
|
||||
<select name="acceptance_status" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
{% for opt in client_acceptance_statuses %}
|
||||
<option value="{{ opt }}" {% if (form_data.acceptance_status or (row.acceptance_status if is_edit else 'pending_review')) == opt %}selected{% endif %}>{{ opt.replace('_',' ').title() }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="mt-1 text-xs text-slate-500">Formal approval can also be done from the client detail page.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-end">
|
||||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||||
<input type="checkbox" name="acceptance_required" value="1" {% if form_data.acceptance_required is not defined or form_data.acceptance_required or (row.acceptance_required if is_edit else true) %}checked{% endif %}>
|
||||
Client acceptance / continuance review required
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||||
<input type="checkbox" name="independence_check_completed" value="1" {% if form_data.independence_check_completed or (row.independence_check_completed if is_edit else false) %}checked{% endif %}>
|
||||
Independence check completed
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||||
<input type="checkbox" name="conflict_check_completed" value="1" {% if form_data.conflict_check_completed or (row.conflict_check_completed if is_edit else false) %}checked{% endif %}>
|
||||
Conflict check completed
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||||
<input type="checkbox" name="kyc_completed" value="1" {% if form_data.kyc_completed or (row.kyc_completed if is_edit else false) %}checked{% endif %}>
|
||||
KYC completed / verified
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||||
<input type="checkbox" name="engagement_letter_required" value="1" {% if form_data.engagement_letter_required is not defined or form_data.engagement_letter_required or (row.engagement_letter_required if is_edit else true) %}checked{% endif %}>
|
||||
Engagement letter required
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||||
<input type="checkbox" name="engagement_letter_received" value="1" {% if form_data.engagement_letter_received or (row.engagement_letter_received if is_edit else false) %}checked{% endif %}>
|
||||
Engagement letter received / accepted
|
||||
</label>
|
||||
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-medium text-slate-700">Acceptance Review Notes</label>
|
||||
<textarea name="acceptance_review_notes" rows="3" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">{{ form_data.acceptance_review_notes or (row.acceptance_review_notes if is_edit else '') }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-medium text-slate-700">Rejection Reason / Remediation Notes</label>
|
||||
<textarea name="acceptance_rejection_reason" rows="2" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">{{ form_data.acceptance_rejection_reason or (row.acceptance_rejection_reason if is_edit else '') }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-medium text-slate-700">Notes</label>
|
||||
<textarea name="notes" rows="4" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">{{ form_data.notes or (row.notes if is_edit else '') }}</textarea>
|
||||
@@ -161,7 +226,7 @@
|
||||
<label class="block text-sm font-medium text-slate-700">Engagement Mode</label>
|
||||
<select name="engagement_mode" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
{% for opt in ['internal_managed','self_tracked','hybrid'] %}
|
||||
<option value="{{ opt }}" {% if (form_data.engagement_mode or (row.engagement_mode if is_edit else 'internal_managed')) == opt %}selected{% endif %}>{{ opt }}</option>
|
||||
<option value="{{ opt }}" {% if (form_data.engagement_mode or (row.engagement_mode if is_edit else ('hybrid' if form_mode == 'firm_admin' else 'internal_managed'))) == opt %}selected{% endif %}>{{ opt }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user