Add digital client acceptance workflow
This commit is contained in:
@@ -151,6 +151,115 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="mt-6 rounded-2xl border border-indigo-100 bg-indigo-50/50 p-4">
|
||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold uppercase tracking-wide text-indigo-900">Digital Acceptance Workflow</h4>
|
||||
<p class="mt-1 text-sm text-indigo-800">Declarations, KYC from permanent documents and engagement letter acceptance are captured as audit evidence.</p>
|
||||
</div>
|
||||
<span class="rounded-full px-3 py-1 text-xs font-semibold {% if workflow_ready %}bg-emerald-100 text-emerald-700{% else %}bg-amber-100 text-amber-800{% endif %}">{{ 'Ready for Approval' if workflow_ready else 'Pending Evidence' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid gap-4 lg:grid-cols-3">
|
||||
<div class="rounded-xl border border-white bg-white p-4 shadow-sm">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<h5 class="text-sm font-semibold text-slate-900">Independence / Conflict Declarations</h5>
|
||||
{% if can_manage_acceptance %}
|
||||
<form method="post" action="/clients/{{ row.id }}/acceptance/request-declarations">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<button class="rounded-lg bg-indigo-600 px-3 py-1.5 text-xs font-semibold text-white hover:bg-indigo-700">Request</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mt-3 space-y-2">
|
||||
{% if acceptance_declarations %}
|
||||
{% for d in acceptance_declarations %}
|
||||
<div class="rounded-lg border border-slate-200 p-3 text-xs">
|
||||
<div class="font-semibold text-slate-800">{{ d.declaration_type.replace('_',' ').title() }} · User #{{ d.assigned_user_id }}</div>
|
||||
<div class="mt-1 {% if d.status == 'declared_clear' %}text-emerald-700{% elif d.status == 'declared_issue' %}text-rose-700{% else %}text-amber-700{% endif %}">{{ d.status.replace('_',' ').title() }}</div>
|
||||
{% if d.assigned_user_id == current_user.id and d.status == 'pending' %}
|
||||
<form method="post" action="/clients/{{ row.id }}/acceptance/declarations/{{ d.id }}/submit" class="mt-2 space-y-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<select name="declaration_result" class="w-full rounded-lg border border-slate-300 px-2 py-1 text-xs">
|
||||
<option value="clear">Declare Clear / No Issue</option>
|
||||
<option value="issue">Declare Issue / Conflict</option>
|
||||
</select>
|
||||
<textarea name="response_notes" rows="2" placeholder="Notes" class="w-full rounded-lg border border-slate-300 px-2 py-1 text-xs"></textarea>
|
||||
<textarea name="issue_details" rows="2" placeholder="Issue details if any" class="w-full rounded-lg border border-slate-300 px-2 py-1 text-xs"></textarea>
|
||||
<button class="rounded-lg bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white">Submit Declaration</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="text-xs text-slate-500">No declaration requests have been created yet.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-white bg-white p-4 shadow-sm">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<h5 class="text-sm font-semibold text-slate-900">KYC from Permanent Documents</h5>
|
||||
{% if can_manage_acceptance %}
|
||||
<form method="post" action="/clients/{{ row.id }}/acceptance/kyc/sync">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<button class="rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-semibold text-slate-700 hover:bg-slate-50">Sync</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mt-3 text-xs text-slate-600">Permanent documents found: <span class="font-semibold">{{ permanent_document_count }}</span></div>
|
||||
<div class="mt-2 rounded-lg bg-slate-50 p-3 text-xs text-slate-600 whitespace-pre-wrap max-h-40 overflow-auto">{{ permanent_document_summary or 'No permanent document summary available. Click Sync after client uploads permanent documents.' }}</div>
|
||||
{% if kyc_verification %}
|
||||
<div class="mt-3 text-xs font-semibold {% if kyc_verification.status == 'verified' %}text-emerald-700{% elif kyc_verification.status == 'rejected' %}text-rose-700{% else %}text-amber-700{% endif %}">KYC Status: {{ kyc_verification.status.replace('_',' ').title() }}</div>
|
||||
{% endif %}
|
||||
{% if can_manage_acceptance or can_approve_acceptance %}
|
||||
<div class="mt-3 grid gap-2">
|
||||
<form method="post" action="/clients/{{ row.id }}/acceptance/kyc/verify" class="space-y-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<textarea name="verification_notes" rows="2" placeholder="KYC verification notes" class="w-full rounded-lg border border-slate-300 px-2 py-1 text-xs"></textarea>
|
||||
<button class="w-full rounded-lg bg-emerald-600 px-3 py-1.5 text-xs font-semibold text-white hover:bg-emerald-700">Verify KYC</button>
|
||||
</form>
|
||||
<form method="post" action="/clients/{{ row.id }}/acceptance/kyc/reject" class="space-y-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<textarea name="verification_notes" rows="2" placeholder="Resubmission remarks" class="w-full rounded-lg border border-slate-300 px-2 py-1 text-xs"></textarea>
|
||||
<button class="w-full rounded-lg border border-rose-300 px-3 py-1.5 text-xs font-semibold text-rose-700 hover:bg-rose-50">Reject / Request Resubmission</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-white bg-white p-4 shadow-sm">
|
||||
<h5 class="text-sm font-semibold text-slate-900">Engagement Letter</h5>
|
||||
{% if engagement_letter %}
|
||||
<div class="mt-2 text-xs text-slate-600">Version {{ engagement_letter.version_no }} · <span class="font-semibold">{{ engagement_letter.status.replace('_',' ').title() }}</span></div>
|
||||
<a href="/clients/{{ row.id }}/acceptance/engagement-letter/{{ engagement_letter.id }}/download" target="_blank" class="mt-3 inline-flex rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-semibold text-slate-700 hover:bg-slate-50">View / Print PDF</a>
|
||||
{% if can_approve_acceptance and engagement_letter.status == 'draft' %}
|
||||
<form method="post" action="/clients/{{ row.id }}/acceptance/engagement-letter/{{ engagement_letter.id }}/approve-send" class="mt-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<button class="w-full rounded-lg bg-indigo-600 px-3 py-1.5 text-xs font-semibold text-white hover:bg-indigo-700">Partner Approve & Send</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if (can_manage_acceptance or can_approve_acceptance) and engagement_letter.status == 'manual_uploaded' %}
|
||||
<form method="post" action="/clients/{{ row.id }}/acceptance/engagement-letter/{{ engagement_letter.id }}/verify-manual" class="mt-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<button class="w-full rounded-lg bg-emerald-600 px-3 py-1.5 text-xs font-semibold text-white hover:bg-emerald-700">Verify Manual Signed Copy</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="mt-2 text-xs text-slate-500">No engagement letter drafted yet.</div>
|
||||
{% endif %}
|
||||
{% if can_manage_acceptance %}
|
||||
<form method="post" action="/clients/{{ row.id }}/acceptance/engagement-letter/draft" class="mt-4 space-y-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input name="title" placeholder="Letter title" class="w-full rounded-lg border border-slate-300 px-2 py-1 text-xs" value="Engagement Letter - {{ row.client_name }}">
|
||||
<textarea name="body_text" rows="4" placeholder="Leave blank to use standard engagement letter draft" class="w-full rounded-lg border border-slate-300 px-2 py-1 text-xs"></textarea>
|
||||
<button class="w-full rounded-lg border border-indigo-300 px-3 py-1.5 text-xs font-semibold text-indigo-700 hover:bg-indigo-50">Create New Draft</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl bg-white p-6 shadow-soft">
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="mx-auto max-w-5xl space-y-6">
|
||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-slate-900">Engagement Letter</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Accept digitally with OTP/session evidence or upload a manually signed copy.</p>
|
||||
</div>
|
||||
<a href="/client/dashboard" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Back to Dashboard</a>
|
||||
</div>
|
||||
|
||||
{% if not engagement_letter %}
|
||||
<section class="rounded-2xl border border-amber-200 bg-amber-50 p-6 text-sm text-amber-800">
|
||||
Engagement letter is not yet sent by the firm.
|
||||
</section>
|
||||
{% else %}
|
||||
<section class="rounded-2xl bg-white p-6 shadow-soft">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-slate-900">{{ engagement_letter.title }}</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">Version {{ engagement_letter.version_no }} · {{ engagement_letter.status.replace('_',' ').title() }}</p>
|
||||
</div>
|
||||
<a href="/clients/{{ client_row.id if client_row.id is defined else client_row['id'] }}/acceptance/engagement-letter/{{ engagement_letter.id }}/download" target="_blank" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">View / Download / Print</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 rounded-xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-700 whitespace-pre-wrap max-h-96 overflow-auto">{{ engagement_letter.body_text }}</div>
|
||||
|
||||
{% if engagement_letter.status in ['sent_to_client', 'manual_uploaded', 'rejected'] %}
|
||||
<div class="mt-6 grid gap-4 lg:grid-cols-2">
|
||||
<div class="rounded-xl border border-emerald-200 bg-emerald-50 p-4 space-y-3">
|
||||
<h4 class="text-sm font-semibold text-emerald-900">Accept Digitally with OTP</h4>
|
||||
<p class="text-xs text-emerald-800">Use this option when you accept the engagement letter electronically. The ERP stores OTP verification, user, time, IP/device and declaration evidence.</p>
|
||||
<form method="post" action="/client/engagement-letter/{{ engagement_letter.id }}/send-otp">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<button class="w-full rounded-xl border border-emerald-300 bg-white px-4 py-2 text-sm font-semibold text-emerald-700 hover:bg-emerald-100">Send OTP</button>
|
||||
</form>
|
||||
<form method="post" action="/client/engagement-letter/{{ engagement_letter.id }}/accept-digital" class="space-y-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input name="otp" inputmode="numeric" required placeholder="Enter OTP" class="w-full rounded-xl border border-emerald-200 px-3 py-2 text-sm">
|
||||
<textarea name="declaration_text" rows="3" class="w-full rounded-xl border border-emerald-200 px-3 py-2 text-sm">I have read and accept this engagement letter for and on behalf of the client.</textarea>
|
||||
<button class="w-full rounded-xl bg-emerald-600 px-4 py-2 text-sm font-semibold text-white hover:bg-emerald-700">Verify OTP & Accept Digitally</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" action="/client/engagement-letter/{{ engagement_letter.id }}/upload-signed" class="rounded-xl border border-indigo-200 bg-indigo-50 p-4 space-y-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<h4 class="text-sm font-semibold text-indigo-900">Upload Manually Signed Copy</h4>
|
||||
<p class="text-xs text-indigo-800">Download/print the engagement letter, sign it manually and upload the signed PDF/image. The firm will verify it before marking it accepted.</p>
|
||||
<input type="file" name="signed_file" accept=".pdf,.jpg,.jpeg,.png" required class="w-full rounded-xl border border-indigo-200 bg-white px-3 py-2 text-sm">
|
||||
<button class="w-full rounded-xl bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-700">Upload Signed Copy</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="mt-6 rounded-xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-700">
|
||||
Current status: {{ engagement_letter.status.replace('_',' ').title() }}.
|
||||
{% if engagement_letter.accepted_at_utc %}Accepted at: {{ engagement_letter.accepted_at_utc }}{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user