Prepare ERP source for Gitea deployment

This commit is contained in:
A R R R Associates
2026-06-20 15:01:44 +05:30
commit 5c75eb6bd9
450 changed files with 67698 additions and 0 deletions
@@ -0,0 +1,12 @@
<div class="mb-6 overflow-x-auto rounded-2xl border border-slate-200 bg-white p-2 shadow-soft">
<div class="flex min-w-max items-center gap-2">
{% set path = request.url.path %}
<a href="/client/dashboard" class="rounded-xl px-4 py-2 text-sm font-semibold transition {{ 'bg-brand-600 text-white' if path == '/client/dashboard' else 'text-slate-700 hover:bg-slate-100' }}">Overview</a>
<a href="/client/compliance" class="rounded-xl px-4 py-2 text-sm font-semibold transition {{ 'bg-brand-600 text-white' if path.startswith('/client/compliance') or path.startswith('/client/engagements') else 'text-slate-700 hover:bg-slate-100' }}">My Compliance</a>
<a href="/client/documents" class="rounded-xl px-4 py-2 text-sm font-semibold transition {{ 'bg-brand-600 text-white' if path.startswith('/client/documents') else 'text-slate-700 hover:bg-slate-100' }}">My Documents</a>
<a href="/client/messages" class="rounded-xl px-4 py-2 text-sm font-semibold transition {{ 'bg-brand-600 text-white' if path.startswith('/client/messages') else 'text-slate-700 hover:bg-slate-100' }}">My Messages</a>
<a href="/client/billing" class="rounded-xl px-4 py-2 text-sm font-semibold transition {{ 'bg-brand-600 text-white' if path.startswith('/client/billing') else 'text-slate-700 hover:bg-slate-100' }}">My Bills</a>
<a href="/client/profile" class="rounded-xl px-4 py-2 text-sm font-semibold transition {{ 'bg-brand-600 text-white' if path.startswith('/client/profile') else 'text-slate-700 hover:bg-slate-100' }}">My Profile</a>
<a href="/alerts" class="rounded-xl px-4 py-2 text-sm font-semibold transition {{ 'bg-brand-600 text-white' if path == '/alerts' else 'text-slate-700 hover:bg-slate-100' }}">My Alert</a>
</div>
</div>
@@ -0,0 +1 @@
{% extends "ui/templates/base/layout.html" %}{% block content %}<div class="space-y-6"><div><h2 class="text-2xl font-semibold text-slate-900">Add Client</h2><p class="text-sm text-slate-500">Create a validated client master with ownership and branch-safe rules.</p></div>{% if form_errors %}<div class="rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-900 shadow-soft">{% for err in form_errors %}<div>{{ err }}</div>{% endfor %}</div>{% endif %}<form method="post" action="/clients" class="space-y-6"><input type="hidden" name="csrf_token" value="{{ csrf_token }}">{% include "modules/clients/templates/clients/partials/form.html" %}<div class="flex justify-end gap-3"><a href="/clients" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Cancel</a><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Save Client</button></div></form></div>{% endblock %}
@@ -0,0 +1,23 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
{% include "modules/clients/templates/clients/_client_tabs.html" %}
<div class="space-y-6">
<div><h2 class="text-2xl font-semibold text-slate-900">My Compliance</h2><p class="text-sm text-slate-500">Service-wise status and pending actions visible to you.</p></div>
<div class="grid gap-4 md:grid-cols-4">
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-sm text-slate-500">Pending from Client</div><div class="mt-2 text-2xl font-semibold">{{ pending_from_client or 0 }}</div></div>
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-sm text-slate-500">With Firm</div><div class="mt-2 text-2xl font-semibold">{{ with_firm or 0 }}</div></div>
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-sm text-slate-500">Clarification Required</div><div class="mt-2 text-2xl font-semibold">{{ clarification_required or 0 }}</div></div>
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-sm text-slate-500">Completed</div><div class="mt-2 text-2xl font-semibold">{{ completed_engagements or 0 }}</div></div>
</div>
<div class="grid gap-4 lg:grid-cols-2">
{% for row in engagements %}
<a href="/work/engagements/{{ row.id }}" class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft hover:bg-slate-50">
<div class="flex items-start justify-between gap-3"><div><h3 class="font-semibold text-slate-900">{{ row.catalogue.service_name if row.catalogue else 'Service' }}</h3><p class="text-xs text-slate-500">FY {{ row.financial_year }}{% if row.assessment_year %} • AY {{ row.assessment_year }}{% endif %}</p></div><span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-700">{{ row.status|replace('_',' ')|title }}</span></div>
<div class="mt-4 grid gap-3 text-sm md:grid-cols-3"><div><div class="text-xs text-slate-500">Due Date</div><div>{{ row.current_due_date.strftime('%d-%m-%Y') if row.current_due_date else '-' }}</div></div><div><div class="text-xs text-slate-500">Firm Contact</div><div>{{ row.assigned_manager.full_name if row.assigned_manager else (row.assigned_partner.full_name if row.assigned_partner else 'Firm team') }}</div></div><div><div class="text-xs text-slate-500">Status</div><div>{{ row.status|replace('_',' ')|title }}</div></div></div>
</a>
{% else %}
<div class="rounded-2xl border border-dashed border-slate-300 bg-white p-8 text-sm text-slate-500 lg:col-span-2">No active compliance services are assigned yet.</div>
{% endfor %}
</div>
</div>
{% endblock %}
@@ -0,0 +1,92 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
<div class="space-y-6">
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<h2 class="text-2xl font-semibold text-slate-900">{{ row.client_name }}</h2>
<p class="text-sm text-slate-500">{{ row.client_code }} • {{ row.client_type }} • {{ row.status|title }}</p>
</div>
<div class="flex flex-wrap gap-3">
{% if can_edit %}
<a href="/clients/{{ row.id }}/edit"
class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">
Edit
</a>
{% endif %}
{% if can_activate and row.status != 'active' and row.status != 'archived' %}
<form method="post" action="/clients/{{ row.id }}/activate">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button class="rounded-xl border border-emerald-300 px-4 py-2 text-sm font-medium text-emerald-700 hover:bg-emerald-50">
Activate
</button>
</form>
{% endif %}
{% if can_deactivate and row.status == 'active' %}
<form method="post" action="/clients/{{ row.id }}/deactivate">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button class="rounded-xl border border-rose-300 px-4 py-2 text-sm font-medium text-rose-700 hover:bg-rose-50">
Deactivate
</button>
</form>
{% endif %}
{% if can_archive and row.status != 'archived' %}
<form method="post" action="/clients/{{ row.id }}/archive">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button class="rounded-xl border border-amber-300 px-4 py-2 text-sm font-medium text-amber-800 hover:bg-amber-50">
Archive
</button>
</form>
{% endif %}
{% if can_restore and row.status == 'archived' %}
<form method="post" action="/clients/{{ row.id }}/restore">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button class="rounded-xl border border-sky-300 px-4 py-2 text-sm font-medium text-sky-700 hover:bg-sky-50">
Restore
</button>
</form>
{% endif %}
</div>
</div>
<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>
<div class="mt-4 grid gap-4 md:grid-cols-2">
{% for label, value in [
('Trade Name', row.trade_name),
('PAN', row.pan),
('GSTIN', row.gstin),
('TAN', row.tan),
('Contact Person', row.contact_person_name),
('Designation', row.contact_person_designation),
('Mobile', row.mobile),
('Email', row.email)
] %}
<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 text-slate-800">{{ value or '-' }}</div>
</div>
{% endfor %}
</div>
</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">
<div><span class="font-medium">Type:</span> {{ row.association_type or 'legacy_firm' }}</div>
<div><span class="font-medium">Source:</span> {{ row.assoc_created_source or 'legacy' }}</div>
<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 Review Partner:</span> {{ row.default_review_partner_user_id or '-' }}</div>
<div><span class="font-medium">Consultant:</span> {{ row.assoc_consultant_id or '-' }}</div>
</div>
</section>
</div>
</div>
{% endblock %}
@@ -0,0 +1,9 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
{% include "modules/clients/templates/clients/_client_tabs.html" %}
<div class="space-y-6">
<div><h2 class="text-2xl font-semibold text-slate-900">My Documents</h2><p class="text-sm text-slate-500">View documents shared by your audit firm.</p></div>
<section class="rounded-2xl bg-white p-6 shadow-soft"><h3 class="font-semibold text-slate-900">Engagement Documents</h3><div class="mt-4 overflow-hidden rounded-2xl border border-slate-200"><table class="min-w-full divide-y divide-slate-200 text-sm"><thead class="bg-slate-50 text-left text-xs font-semibold uppercase tracking-wide text-slate-500"><tr><th class="px-4 py-3">Document</th><th class="px-4 py-3">Service</th><th class="px-4 py-3">Type</th><th class="px-4 py-3">Version</th><th class="px-4 py-3 text-right">Action</th></tr></thead><tbody class="divide-y divide-slate-100">{% for doc in engagement_documents %}{% set ver = doc.versions[0] if doc.versions else None %}<tr><td class="px-4 py-3 font-medium text-slate-900">{{ doc.title }}</td><td class="px-4 py-3 text-slate-600">{{ doc.engagement.catalogue.service_name if doc.engagement and doc.engagement.catalogue else '-' }}</td><td class="px-4 py-3 text-slate-600">{{ doc.document_type }}</td><td class="px-4 py-3 text-slate-600">v{{ doc.current_version_no }}</td><td class="px-4 py-3 text-right">{% if ver %}<a href="/client/documents/engagement-versions/{{ ver.id }}/download" class="font-semibold text-brand-700 hover:underline">Download</a>{% endif %}</td></tr>{% else %}<tr><td colspan="5" class="px-4 py-8 text-center text-slate-500">No engagement documents shared yet.</td></tr>{% endfor %}</tbody></table></div></section>
<section class="rounded-2xl bg-white p-6 shadow-soft"><h3 class="font-semibold text-slate-900">Permanent Documents</h3><div class="mt-4 overflow-hidden rounded-2xl border border-slate-200"><table class="min-w-full divide-y divide-slate-200 text-sm"><thead class="bg-slate-50 text-left text-xs font-semibold uppercase tracking-wide text-slate-500"><tr><th class="px-4 py-3">Document</th><th class="px-4 py-3">Category</th><th class="px-4 py-3">Version</th><th class="px-4 py-3 text-right">Action</th></tr></thead><tbody class="divide-y divide-slate-100">{% for doc in permanent_documents %}{% set ver = doc.versions[0] if doc.versions else None %}<tr><td class="px-4 py-3 font-medium text-slate-900">{{ doc.title }}</td><td class="px-4 py-3 text-slate-600">{{ doc.category }}</td><td class="px-4 py-3 text-slate-600">v{{ doc.current_version_no }}</td><td class="px-4 py-3 text-right">{% if ver %}<a href="/client/documents/permanent-versions/{{ ver.id }}/download" class="font-semibold text-brand-700 hover:underline">Download</a>{% endif %}</td></tr>{% else %}<tr><td colspan="4" class="px-4 py-8 text-center text-slate-500">No permanent documents shared yet.</td></tr>{% endfor %}</tbody></table></div></section>
</div>
{% endblock %}
@@ -0,0 +1,26 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
<div class="space-y-6">
<div>
<h2 class="text-2xl font-semibold text-slate-900">Edit Client</h2>
<p class="text-sm text-slate-500">B5 role-aware edit flow.</p>
</div>
{% if form_errors %}
<div class="rounded-2xl border border-rose-200 bg-rose-50 p-4">
<ul class="list-disc pl-5 text-sm text-rose-700">
{% for err in form_errors %}<li>{{ err }}</li>{% endfor %}
</ul>
</div>
{% endif %}
<form method="post" action="/clients/{{ row.id }}/edit" class="space-y-6">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
{% include "modules/clients/templates/clients/partials/form.html" %}
<div class="flex items-center justify-end gap-3">
<a href="/clients/{{ row.id }}" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Cancel</a>
<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Save Changes</button>
</div>
</form>
</div>
{% endblock %}
@@ -0,0 +1,14 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
{% include "modules/clients/templates/clients/_client_tabs.html" %}
<div class="space-y-6">
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between"><div><h2 class="text-2xl font-semibold text-slate-900">{{ engagement.catalogue.service_name if engagement.catalogue else 'Engagement' }}</h2><p class="text-sm text-slate-500">FY {{ engagement.financial_year }}{% if engagement.assessment_year %} • AY {{ engagement.assessment_year }}{% endif %} • Due {{ engagement.current_due_date.strftime('%d-%m-%Y') if engagement.current_due_date else '-' }}</p></div><a href="/client/compliance" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Back to My Compliance</a></div>
<div class="grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px]">
<section class="space-y-4">
<div class="rounded-2xl bg-white p-6 shadow-soft"><h3 class="font-semibold text-slate-900">Task / Action Status</h3><div class="mt-4 space-y-3">{% for task in tasks %}<details class="rounded-2xl border border-slate-200 p-4" {% if loop.first %}open{% endif %}><summary class="cursor-pointer list-none"><div class="flex flex-col gap-2 md:flex-row md:items-center md:justify-between"><div><div class="font-semibold text-slate-900">{{ task.task_name }}</div><div class="text-xs text-slate-500">{{ task.description or '' }}</div></div><span class="w-fit rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-700">{{ task.status|replace('_',' ')|title }}</span></div></summary><div class="mt-4 border-t border-slate-100 pt-4"><form method="post" action="/client/tasks/{{ task.id }}/reply" class="space-y-3"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><label class="block text-sm font-medium text-slate-700">Reply / clarification for firm</label><textarea name="message" rows="3" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" placeholder="Type your clarification, confirmation or query for the firm..."></textarea><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">Send Reply</button></form></div></details>{% else %}<div class="rounded-2xl border border-dashed border-slate-300 p-6 text-sm text-slate-500">No task details available.</div>{% endfor %}</div></div>
<div class="rounded-2xl bg-white p-6 shadow-soft"><h3 class="font-semibold text-slate-900">Communication Timeline</h3><div class="mt-4 space-y-3">{% for note in comments %}<div class="rounded-2xl border border-slate-200 p-4 text-sm"><div class="flex justify-between gap-3"><div class="font-semibold text-slate-900">{{ note.task.task_name if note.task else 'Message' }}</div><div class="text-xs text-slate-500">{{ note.created_at_utc.strftime('%d-%m-%Y %I:%M %p') if note.created_at_utc else '' }}</div></div><p class="mt-2 whitespace-pre-line text-slate-700">{{ note.message }}</p></div>{% else %}<div class="rounded-2xl border border-dashed border-slate-300 p-6 text-sm text-slate-500">No visible communication yet.</div>{% endfor %}</div></div>
</section>
<aside class="rounded-2xl bg-white p-6 shadow-soft"><h3 class="font-semibold text-slate-900">Engagement Documents</h3><div class="mt-4 space-y-3">{% for doc in documents %}{% set ver = doc.versions[0] if doc.versions else None %}<div class="rounded-2xl border border-slate-200 p-4"><div class="font-semibold text-slate-900">{{ doc.title }}</div><div class="text-xs text-slate-500">{{ doc.document_type }} • v{{ doc.current_version_no }}</div>{% if ver %}<a href="/client/documents/engagement-versions/{{ ver.id }}/download" class="mt-3 inline-flex rounded-xl border border-slate-300 px-3 py-1.5 text-xs font-semibold text-slate-700 hover:bg-slate-50">Download</a>{% endif %}</div>{% else %}<div class="rounded-2xl border border-dashed border-slate-300 p-6 text-sm text-slate-500">No documents uploaded for this engagement yet.</div>{% endfor %}</div></aside>
</div>
</div>
{% endblock %}
@@ -0,0 +1,59 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
<div class="space-y-6">
<div class="flex items-start justify-between gap-4">
<div>
<h2 class="text-2xl font-semibold text-slate-900">Import Clients</h2>
<p class="text-sm text-slate-500">Bulk upload clients for the active audit firm with partner validation.</p>
</div>
<a href="/clients/import/template" class="inline-flex rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Download Template</a>
</div>
<div class="rounded-2xl bg-white p-6 shadow-soft space-y-4">
<div class="grid gap-4 md:grid-cols-2">
<div class="rounded-xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-700">
<div class="font-semibold text-slate-900">Active Audit Firm</div>
<div class="mt-1">{{ current_tenant.name if current_tenant else scope.tenant_id }} (ID: {{ scope.tenant_id }})</div>
</div>
<div class="rounded-xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-700">
<div class="font-semibold text-slate-900">Logged-in uploader</div>
<div class="mt-1">{{ current_user.full_name or current_user.email }} — User ID {{ current_user.id }}</div>
</div>
</div>
<div class="rounded-xl border border-sky-200 bg-sky-50 p-4 text-sm text-sky-900">
Template includes <strong>uploader_user_id</strong>, <strong>firm_tenant_id</strong>, and <strong>partner_user_id</strong>.
Validation checks that uploader_user_id matches the logged-in user, firm_tenant_id matches the active audit firm, and partner_user_id belongs to an active Partner in that same audit firm.
</div>
<div class="rounded-xl border border-slate-200 p-4">
<div class="text-sm font-semibold text-slate-900">Partners available in this audit firm</div>
<div class="mt-2 text-sm text-slate-600">
{% for p in partners %}
<div>{{ p.id }} — {{ p.full_name or p.email }}</div>
{% else %}
<div>No active partners found for this audit firm.</div>
{% endfor %}
</div>
</div>
{% if import_errors %}
<div class="rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-900">
{% for err in import_errors %}<div>{{ err }}</div>{% endfor %}
</div>
{% endif %}
<form method="post" action="/clients/import/preview" enctype="multipart/form-data" class="space-y-4">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div>
<label class="block text-sm font-medium text-slate-700">Excel file</label>
<input type="file" name="excel_file" accept=".xlsx" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" required>
</div>
<div class="flex justify-end gap-3">
<a href="/clients" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Back</a>
<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Validate File</button>
</div>
</form>
</div>
</div>
{% endblock %}
@@ -0,0 +1,71 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
<div class="space-y-6">
<div>
<h2 class="text-2xl font-semibold text-slate-900">Import Clients Preview</h2>
<p class="text-sm text-slate-500">Review validation result before final import.</p>
</div>
{% if import_result is defined and import_result %}
<div class="rounded-2xl bg-white p-6 shadow-soft space-y-4">
<div class="rounded-xl border border-emerald-200 bg-emerald-50 p-4 text-sm text-emerald-900">Created {{ import_result.created|length }} clients.</div>
{% if import_result.created %}
<div class="rounded-xl border border-slate-200 p-4 text-sm">
{% for row in import_result.created %}<div>{{ row.client_code }} — {{ row.client_name }} (ID {{ row.id }})</div>{% endfor %}
</div>
{% endif %}
{% if import_result.failures %}
<div class="rounded-xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-900">
{% for err in import_result.failures %}<div>Row {{ err.row_number }}: {{ err.message }}</div>{% endfor %}
</div>
{% endif %}
<div class="flex justify-end"><a href="/clients" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Back to Clients</a></div>
</div>
{% else %}
<div class="grid gap-6 lg:grid-cols-2">
<section class="rounded-2xl bg-white p-6 shadow-soft">
<h3 class="text-base font-semibold text-slate-900">Valid rows</h3>
<div class="mt-3 text-sm text-slate-600">{{ preview.valid_rows|length }} of {{ preview.total_rows }} rows are ready to import.</div>
<div class="mt-4 max-h-[28rem] overflow-auto rounded-xl border border-slate-200">
<table class="min-w-full divide-y divide-slate-200 text-sm">
<thead class="bg-slate-50"><tr><th class="px-4 py-2 text-left">Row</th><th class="px-4 py-2 text-left">Audit Firm ID</th><th class="px-4 py-2 text-left">Partner</th><th class="px-4 py-2 text-left">Client Code</th><th class="px-4 py-2 text-left">Client Name</th></tr></thead>
<tbody class="divide-y divide-slate-100 bg-white">
{% for item in preview.valid_rows %}
<tr><td class="px-4 py-2">{{ item.row_number }}</td><td class="px-4 py-2">{{ item.tenant_id }}</td><td class="px-4 py-2">{{ item.partner_id }}</td><td class="px-4 py-2">{{ item.client_payload.client_code }}</td><td class="px-4 py-2">{{ item.client_payload.client_name }}</td></tr>
{% else %}
<tr><td colspan="5" class="px-4 py-6 text-center text-slate-500">No valid rows found.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
<section class="rounded-2xl bg-white p-6 shadow-soft">
<h3 class="text-base font-semibold text-slate-900">Validation errors</h3>
<div class="mt-3 text-sm text-slate-600">{{ preview.errors|length }} rows have issues.</div>
<div class="mt-4 max-h-[28rem] space-y-3 overflow-auto">
{% for err in preview.errors %}
<div class="rounded-xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-900">
<div class="font-semibold">Row {{ err.row_number }}</div>
<ul class="mt-2 list-disc space-y-1 pl-5">{% for msg in err.messages %}<li>{{ msg }}</li>{% endfor %}</ul>
</div>
{% else %}
<div class="rounded-xl border border-emerald-200 bg-emerald-50 p-4 text-sm text-emerald-900">No validation errors found.</div>
{% endfor %}
</div>
</section>
</div>
<div class="flex justify-end gap-3">
<a href="/clients/import" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Back</a>
{% if preview.valid_rows %}
<form method="post" action="/clients/import/commit">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<textarea name="preview_payload" hidden>{{ preview_payload }}</textarea>
<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Import {{ preview.valid_rows|length }} Valid Rows</button>
</form>
{% endif %}
</div>
{% endif %}
</div>
{% endblock %}
@@ -0,0 +1,36 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
<div class="space-y-6">
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<h2 class="text-2xl font-semibold text-slate-900">Clients</h2>
<p class="text-sm text-slate-500">Association-aware list view.</p>
</div>
<div class="flex gap-3">
{% if can_export %}
<a href="/clients/export?q={{ q }}&status={{ status }}&client_type={{ client_type }}&include_archived={{ include_archived }}&sort_by={{ sort_by }}&sort_order={{ sort_order }}"
class="inline-flex rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">
Export CSV
</a>
{% endif %}
{% if can_import %}
<a href="/clients/import"
class="inline-flex rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">
Import Clients
</a>
{% endif %}
{% if can_create %}
<a href="/clients/new"
class="inline-flex rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">
Add Client
</a>
{% endif %}
</div>
</div>
{% include "modules/clients/templates/clients/partials/table.html" %}
</div>
{% endblock %}
@@ -0,0 +1,5 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
{% include "modules/clients/templates/clients/_client_tabs.html" %}
<div class="space-y-6"><div><h2 class="text-2xl font-semibold text-slate-900">My Messages</h2><p class="text-sm text-slate-500">Client-visible communications and clarifications from your firm.</p></div><section class="rounded-2xl bg-white p-6 shadow-soft"><div class="space-y-4">{% for note in comments %}<article class="rounded-2xl border border-slate-200 p-4"><div class="flex flex-col gap-2 md:flex-row md:items-start md:justify-between"><div><div class="text-xs font-semibold uppercase tracking-wide text-brand-700">{{ note.comment_type|replace('_',' ')|title }}</div><h3 class="mt-1 font-semibold text-slate-900">{{ note.task.task_name if note.task else 'Message' }}</h3><div class="text-xs text-slate-500">{{ note.subscription.catalogue.service_name if note.subscription and note.subscription.catalogue else '' }}</div></div><div class="text-xs text-slate-500">{{ note.created_at_utc.strftime('%d-%m-%Y %I:%M %p') if note.created_at_utc else '' }}</div></div><p class="mt-3 whitespace-pre-line text-sm leading-6 text-slate-700">{{ note.message }}</p><div class="mt-3 text-xs text-slate-500">From: {% if note.created_by %}{{ note.created_by.full_name or note.created_by.email }}{% else %}Firm team{% endif %}</div>{% if note.task %}<a href="/client/engagements/{{ note.task.subscription_id }}" class="mt-3 inline-flex text-sm font-semibold text-brand-700 hover:underline">Open related work</a>{% endif %}</article>{% else %}<div class="rounded-2xl border border-dashed border-slate-300 p-8 text-center text-sm text-slate-500">No messages found.</div>{% endfor %}</div></section></div>
{% endblock %}
@@ -0,0 +1 @@
<div class="overflow-hidden rounded-2xl border border-slate-200"><table class="min-w-full divide-y divide-slate-200"><thead class="bg-slate-50"><tr><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">When</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Action</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Summary</th></tr></thead><tbody class="divide-y divide-slate-100">{% for log in audit_logs %}<tr><td class="px-4 py-3 text-sm text-slate-700">{{ log.created_at_utc }}</td><td class="px-4 py-3 text-sm text-slate-700">{{ log.action }}</td><td class="px-4 py-3 text-sm text-slate-700">{{ log.summary }}</td></tr>{% else %}<tr><td colspan="3" class="px-4 py-6 text-center text-sm text-slate-500">No audit entries yet.</td></tr>{% endfor %}</tbody></table></div>
@@ -0,0 +1 @@
<div class="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">{% for label, value in [('GST', row.gst_applicable),('Income Tax', row.income_tax_applicable),('TDS', row.tds_applicable),('ROC', row.roc_applicable),('Audit', row.audit_applicable),('PF', row.pf_applicable),('ESI', row.esi_applicable),('Professional Tax', row.professional_tax_applicable),('Payroll', row.payroll_applicable),('MSME', row.msme_applicable),('Import / Export', row.import_export_applicable)] %}<div class="rounded-xl border border-slate-200 px-3 py-3 text-sm"><div class="font-medium text-slate-700">{{ label }}</div><div class="mt-1 {% if value %}text-emerald-700{% else %}text-slate-500{% endif %}">{% if value %}Applicable{% else %}Not Applicable{% endif %}</div></div>{% endfor %}</div>
@@ -0,0 +1,372 @@
{% set is_edit = row is defined and row %}
<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">Basic Profile</h3>
<div class="mt-4 grid gap-4 md:grid-cols-2">
<div>
<label class="block text-sm font-medium text-slate-700">Client Code</label>
<input name="client_code" value="{{ form_data.client_code or (row.client_code if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" {% if is_edit %}readonly{% endif %}>
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Client Name</label>
<input name="client_name" value="{{ form_data.client_name or (row.client_name if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Trade Name</label>
<input name="trade_name" value="{{ form_data.trade_name or (row.trade_name if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Client Type</label>
<select name="client_type" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
{% for opt in client_types %}
<option value="{{ opt }}" {% if (form_data.client_type or (row.client_type if is_edit else 'Other')) == opt %}selected{% endif %}>{{ opt }}</option>
{% endfor %}
</select>
</div>
<div>
<label class="block text-sm font-medium text-slate-700">PAN</label>
<input name="pan" value="{{ form_data.pan or (row.pan if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">GSTIN</label>
<input name="gstin" value="{{ form_data.gstin or (row.gstin if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">TAN</label>
<input name="tan" value="{{ form_data.tan or (row.tan if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">CIN / LLPIN</label>
<input name="cin_llpin" value="{{ form_data.cin_llpin or (row.cin_llpin if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">MSME Number</label>
<input name="msme_no" value="{{ form_data.msme_no or (row.msme_no if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">IEC Code</label>
<input name="iec_code" value="{{ form_data.iec_code or (row.iec_code if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Mobile</label>
<input name="mobile" value="{{ form_data.mobile or (row.mobile if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Alternate Mobile</label>
<input name="alternate_mobile" value="{{ form_data.alternate_mobile or (row.alternate_mobile if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Email (used for frontend login)</label>
<input name="email" value="{{ form_data.email or (row.email if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Alternate Email</label>
<input name="alternate_email" value="{{ form_data.alternate_email or (row.alternate_email if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Contact Person</label>
<input name="contact_person_name" value="{{ form_data.contact_person_name or (row.contact_person_name if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Designation</label>
<input name="contact_person_designation" value="{{ form_data.contact_person_designation or (row.contact_person_designation if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Client Category</label>
<select name="client_category" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
<option value="">-- Select --</option>
{% for opt in client_categories %}
<option value="{{ opt }}" {% if (form_data.client_category or (row.client_category if is_edit else '')) == opt %}selected{% endif %}>{{ opt }}</option>
{% endfor %}
</select>
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Risk Category</label>
<select name="risk_category" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
<option value="">-- Select --</option>
{% for opt in risk_categories %}
<option value="{{ opt }}" {% if (form_data.risk_category or (row.risk_category if is_edit else '')) == opt %}selected{% endif %}>{{ opt }}</option>
{% endfor %}
</select>
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Onboarding Date</label>
<input type="date" name="onboarding_date" value="{{ form_data.onboarding_date or (row.onboarding_date if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Closing Date</label>
<input type="date" name="closing_date" value="{{ form_data.closing_date or (row.closing_date if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium text-slate-700">Address Line 1</label>
<input name="address_line_1" value="{{ form_data.address_line_1 or (row.address_line_1 if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium text-slate-700">Address Line 2</label>
<input name="address_line_2" value="{{ form_data.address_line_2 or (row.address_line_2 if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">City</label>
<input name="city" value="{{ form_data.city or (row.city if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">State</label>
<input name="state" value="{{ form_data.state or (row.state if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Pincode</label>
<input name="pincode" value="{{ form_data.pincode or (row.pincode if is_edit else '') }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Country</label>
<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">
<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>
</div>
</div>
</section>
<section class="rounded-2xl bg-white p-6 shadow-soft">
<h3 class="text-base font-semibold text-slate-900">Assignment & Scope</h3>
<div class="mt-4 space-y-4">
<div>
<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>
{% endfor %}
</select>
</div>
{% if form_mode == 'firm_admin' %}
<div>
<label class="block text-sm font-medium text-slate-700">Audit Firm</label>
<div class="mt-1 rounded-xl border border-slate-300 bg-slate-50 px-4 py-2 text-sm text-slate-700">
{% for t in form_options.tenants %}
{{ t.name }}
{% endfor %}
</div>
<input type="hidden" name="tenant_id" value="{{ form_data.tenant_id or (row.tenant_id if is_edit else form_options.active_tenant_id) }}">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Branch</label>
<select name="branch_id" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
{% for b in form_options.branches %}
<option value="{{ b.id }}" {% if (form_data.branch_id or (row.branch_id if is_edit else form_options.active_branch_id)) == b.id %}selected{% endif %}>{{ b.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Partner</label>
<select name="partner_id" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
<option value="">-- Select Partner --</option>
{% for p in form_options.partners %}
<option value="{{ p.id }}" {% if (form_data.partner_id or (row.partner_id if is_edit else None)) == p.id %}selected{% endif %}>{{ p.full_name or p.email }}</option>
{% endfor %}
</select>
</div>
{% elif form_mode == 'system_admin' %}
<div>
<label class="block text-sm font-medium text-slate-700">Audit Firm</label>
<select name="tenant_id" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
{% for t in form_options.tenants %}
<option value="{{ t.id }}" {% if (form_data.tenant_id or (row.tenant_id if is_edit else form_options.active_tenant_id)) == t.id %}selected{% endif %}>{{ t.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Branch</label>
<select name="branch_id" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
<option value="">-- Select Branch --</option>
{% for b in form_options.branches %}
<option value="{{ b.id }}" {% if (form_data.branch_id or (row.branch_id if is_edit else form_options.active_branch_id)) == b.id %}selected{% endif %}>
{{ b.name }}{% if b.tenant_name %} ({{ b.tenant_name }}){% endif %}
</option>
{% endfor %}
</select>
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Partner</label>
<select name="partner_id" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
<option value="">-- Select Partner --</option>
{% for p in form_options.partners %}
<option value="{{ p.id }}" {% if (form_data.partner_id or (row.partner_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>
</div>
{% elif form_mode == 'partner' %}
<div class="rounded-xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-700">
Partner assignment is locked to your own user.
<input type="hidden" name="tenant_id" value="{{ form_data.tenant_id or (row.tenant_id if is_edit else form_options.active_tenant_id) }}">
<input type="hidden" name="branch_id" value="{{ form_data.branch_id or (row.branch_id if is_edit else form_options.active_branch_id) }}">
<input type="hidden" name="partner_id" value="{{ current_user.id }}">
</div>
{% elif form_mode == 'consultant' %}
<div class="rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800">
Consultant users cannot assign or reassign partner mappings.
<input type="hidden" name="tenant_id" value="{{ form_data.tenant_id or (row.tenant_id if is_edit else form_options.active_tenant_id) }}">
<input type="hidden" name="branch_id" value="{{ form_data.branch_id or (row.branch_id if is_edit else form_options.active_branch_id) }}">
<input type="hidden" name="partner_id" value="{{ form_data.partner_id or (row.partner_id if is_edit else '') }}">
</div>
{% elif form_mode == 'self_service' %}
<div class="rounded-xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-700">
This client is currently unassigned. Initial association to firm, branch, and partner must be done by System Admin.
</div>
{% endif %}
<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">
<option value="">-- Not required / Select later --</option>
{% for p in form_options.review_partners or [] %}
<option value="{{ p.id }}" {% if (form_data.default_review_partner_user_id or (row.default_review_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">Used automatically for assurance engagements only when the audit firm is a partnership firm.</p>
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Status</label>
<select name="status" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
{% for opt in client_statuses %}
<option value="{{ opt }}" {% if (form_data.status or (row.status if is_edit else 'active')) == opt %}selected{% endif %}>{{ opt|title }}</option>
{% endfor %}
</select>
</div>
<div class="border-t border-slate-200 pt-4">
<h4 class="mb-3 text-sm font-semibold text-slate-900">Client Frontend Login</h4>
<div class="rounded-xl border border-sky-200 bg-sky-50 p-3 text-xs text-sky-800">
{% if is_edit and row.portal_user_id %}
Linked portal user already exists. Leave password blank to keep the current password, or enter a new password to reset it.
{% elif is_edit %}
This existing client does not yet have a linked login. Enter email and password below to create the client login now.
{% else %}
Creating a client will also create a frontend login using the client email and password below.
{% endif %}
</div>
<div class="mt-4 grid gap-4">
<div>
<label class="block text-sm font-medium text-slate-700">{% if is_edit and row.portal_user_id %}New Password{% else %}Password{% endif %}</label>
<input name="portal_password" type="password" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" {% if not is_edit %}required{% endif %}>
</div>
<div>
<label class="block text-sm font-medium text-slate-700">{% if is_edit and row.portal_user_id %}Confirm New Password{% else %}Confirm Password{% endif %}</label>
<input name="portal_password_confirm" type="password" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" {% if not is_edit %}required{% endif %}>
</div>
{% if is_edit and row.portal_user_id %}
<div class="text-xs text-slate-500">
Portal user id linked: {{ row.portal_user_id }}
</div>
{% endif %}
</div>
</div>
<div class="border-t border-slate-200 pt-4">
<h4 class="mb-3 text-sm font-semibold text-slate-900">Compliance Applicability</h4>
<div class="grid gap-3">
<label class="flex items-center gap-2 text-sm text-slate-700">
<input type="checkbox" name="gst_applicable" value="1" {% if form_data.gst_applicable or (row.gst_applicable if is_edit else false) %}checked{% endif %}>
GST Applicable
</label>
<label class="flex items-center gap-2 text-sm text-slate-700">
<input type="checkbox" name="income_tax_applicable" value="1" {% if form_data.income_tax_applicable or (row.income_tax_applicable if is_edit else false) %}checked{% endif %}>
Income Tax Applicable
</label>
<label class="flex items-center gap-2 text-sm text-slate-700">
<input type="checkbox" name="tds_applicable" value="1" {% if form_data.tds_applicable or (row.tds_applicable if is_edit else false) %}checked{% endif %}>
TDS Applicable
</label>
<label class="flex items-center gap-2 text-sm text-slate-700">
<input type="checkbox" name="roc_applicable" value="1" {% if form_data.roc_applicable or (row.roc_applicable if is_edit else false) %}checked{% endif %}>
ROC Applicable
</label>
<label class="flex items-center gap-2 text-sm text-slate-700">
<input type="checkbox" name="audit_applicable" value="1" {% if form_data.audit_applicable or (row.audit_applicable if is_edit else false) %}checked{% endif %}>
Audit Applicable
</label>
<label class="flex items-center gap-2 text-sm text-slate-700">
<input type="checkbox" name="pf_applicable" value="1" {% if form_data.pf_applicable or (row.pf_applicable if is_edit else false) %}checked{% endif %}>
PF Applicable
</label>
<label class="flex items-center gap-2 text-sm text-slate-700">
<input type="checkbox" name="esi_applicable" value="1" {% if form_data.esi_applicable or (row.esi_applicable if is_edit else false) %}checked{% endif %}>
ESI Applicable
</label>
<label class="flex items-center gap-2 text-sm text-slate-700">
<input type="checkbox" name="professional_tax_applicable" value="1" {% if form_data.professional_tax_applicable or (row.professional_tax_applicable if is_edit else false) %}checked{% endif %}>
Professional Tax Applicable
</label>
<label class="flex items-center gap-2 text-sm text-slate-700">
<input type="checkbox" name="payroll_applicable" value="1" {% if form_data.payroll_applicable or (row.payroll_applicable if is_edit else false) %}checked{% endif %}>
Payroll Applicable
</label>
<label class="flex items-center gap-2 text-sm text-slate-700">
<input type="checkbox" name="msme_applicable" value="1" {% if form_data.msme_applicable or (row.msme_applicable if is_edit else false) %}checked{% endif %}>
MSME Applicable
</label>
<label class="flex items-center gap-2 text-sm text-slate-700">
<input type="checkbox" name="import_export_applicable" value="1" {% if form_data.import_export_applicable or (row.import_export_applicable if is_edit else false) %}checked{% endif %}>
Import / Export Applicable
</label>
</div>
</div>
</div>
</section>
</div>
@@ -0,0 +1,2 @@
<div class="overflow-hidden rounded-2xl bg-white shadow-soft"><table class="min-w-full divide-y divide-slate-200"><thead class="bg-slate-50"><tr><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Code</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Client</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Association</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Partner</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Branch</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Status</th><th class="px-4 py-3"></th></tr></thead><tbody class="divide-y divide-slate-100">{% for row in rows %}<tr><td class="px-4 py-3 text-sm font-medium text-slate-900">{{ row.client_code }}</td><td class="px-4 py-3 text-sm text-slate-700"><div class="font-medium">{{ row.client_name }}</div><div class="text-xs text-slate-500">{{ row.pan or row.gstin or '-' }}</div></td><td class="px-4 py-3 text-sm text-slate-700"><div>{{ row.association_type or 'legacy_firm' }}</div><div class="text-xs text-slate-500">{{ row.assoc_created_source or 'legacy' }}</div></td><td class="px-4 py-3 text-sm text-slate-700">{{ row.partner_name or row.effective_partner_id or '-' }}</td><td class="px-4 py-3 text-sm text-slate-700">{{ row.branch_name or row.assoc_firm_branch_id or row.branch_id or '-' }}</td><td class="px-4 py-3 text-sm">{% if row.status == 'active' %}<span class="rounded-full bg-emerald-100 px-2 py-1 text-xs font-medium text-emerald-700">Active</span>{% elif row.status == 'archived' %}<span class="rounded-full bg-amber-100 px-2 py-1 text-xs font-medium text-amber-800">Archived</span>{% else %}<span class="rounded-full bg-slate-200 px-2 py-1 text-xs font-medium text-slate-700">Inactive</span>{% endif %}</td><td class="px-4 py-3 text-right"><a href="/clients/{{ row.id }}" class="text-sm font-medium text-brand-700 hover:underline">Open</a></td></tr>{% else %}<tr><td colspan="7" class="px-4 py-8 text-center text-sm text-slate-500">No clients found.</td></tr>{% endfor %}</tbody></table></div>
@@ -0,0 +1,93 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
{% include "modules/clients/templates/clients/_client_tabs.html" %}
<div class="space-y-6">
<section class="rounded-3xl bg-gradient-to-r from-brand-700 to-slate-900 p-6 text-white shadow-soft">
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.22em] text-brand-100">Client Portal</p>
<h2 class="mt-2 text-2xl font-semibold">My Compliance & Firm Communication</h2>
<p class="mt-2 max-w-3xl text-sm text-brand-100">Track your compliance status, pending actions, required documents, messages and firm updates.</p>
</div>
{% if client_row %}<a href="/client/profile" class="rounded-xl bg-white px-4 py-2 text-sm font-semibold text-brand-700 hover:bg-brand-50">Update My Profile</a>{% endif %}
</div>
</section>
{% if not client_row %}
<div class="rounded-2xl border border-amber-200 bg-amber-50 p-5 text-sm text-amber-900 shadow-soft">
We could not find a client master linked to your login email in the current audit firm. Please contact your firm admin to map this login to the correct client record.
</div>
{% else %}
<section class="grid gap-4 md:grid-cols-2 xl:grid-cols-5">
<a href="/client/compliance" class="af-metric-card hover:border-brand-200"><div class="text-xs font-semibold uppercase text-slate-500">Active Compliance</div><div class="mt-2 text-3xl font-semibold text-slate-900">{{ total_engagements or 0 }}</div><div class="mt-1 text-xs text-slate-500">Services / filings</div></a>
<a href="/client/compliance" class="af-metric-card border-amber-200 bg-amber-50 hover:border-amber-300"><div class="text-xs font-semibold uppercase text-amber-700">Pending Action</div><div class="mt-2 text-3xl font-semibold text-amber-700">{{ pending_from_client or 0 }}</div><div class="mt-1 text-xs text-amber-700">Required from you</div></a>
<a href="/client/compliance" class="af-metric-card hover:border-brand-200"><div class="text-xs font-semibold uppercase text-slate-500">With Firm</div><div class="mt-2 text-3xl font-semibold text-brand-700">{{ with_firm or 0 }}</div><div class="mt-1 text-xs text-slate-500">Being handled</div></a>
<a href="/client/documents" class="af-metric-card hover:border-brand-200"><div class="text-xs font-semibold uppercase text-slate-500">Documents</div><div class="mt-2 text-3xl font-semibold text-slate-900">{{ recent_documents|length if recent_documents else 0 }}</div><div class="mt-1 text-xs text-slate-500">Recent uploads</div></a>
<a href="/client/billing" class="af-metric-card border-amber-200 bg-amber-50 hover:border-amber-300"><div class="text-xs font-semibold uppercase text-amber-700">Outstanding Bills</div><div class="mt-2 text-3xl font-semibold text-amber-700">₹ {{ '%.2f'|format(billing_outstanding_amount or 0) }}</div><div class="mt-1 text-xs text-amber-700">{{ billing_open_count or 0 }} open bill(s)</div></a>
</section>
<section class="grid gap-6 xl:grid-cols-[minmax(0,1fr)_380px]">
<div class="space-y-6">
<div class="af-card">
<div class="flex items-center justify-between gap-3"><div><h3 class="text-lg font-semibold text-slate-900">Compliance Status</h3><p class="mt-1 text-sm text-slate-500">Simple client-facing status of your active services.</p></div><a href="/client/compliance" class="af-btn af-btn-primary">View All</a></div>
<div class="mt-5 grid gap-3 md:grid-cols-4 text-sm">
<div class="rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3"><div class="text-amber-700">Pending from You</div><div class="mt-1 text-2xl font-semibold text-amber-700">{{ pending_from_client or 0 }}</div></div>
<div class="rounded-2xl border border-brand-200 bg-brand-50 px-4 py-3"><div class="text-brand-700">With Firm</div><div class="mt-1 text-2xl font-semibold text-brand-700">{{ with_firm or 0 }}</div></div>
<div class="rounded-2xl border border-blue-200 bg-blue-50 px-4 py-3"><div class="text-blue-700">Clarification</div><div class="mt-1 text-2xl font-semibold text-blue-700">{{ clarification_required or 0 }}</div></div>
<div class="rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-3"><div class="text-emerald-700">Completed</div><div class="mt-1 text-2xl font-semibold text-emerald-700">{{ completed_engagements or 0 }}</div></div>
</div>
<div class="mt-5 space-y-3">
{% for row in due_soon_engagements[:6] %}
<a href="/client/engagements/{{ row.id }}" class="block rounded-2xl border border-slate-200 p-4 hover:bg-slate-50">
<div class="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
<div><div class="font-semibold text-slate-900">{{ row.catalogue.service_name if row.catalogue else 'Service' }}</div><div class="text-xs text-slate-500">FY {{ row.financial_year }}{% if row.assessment_year %} • AY {{ row.assessment_year }}{% endif %}</div></div>
<div class="text-sm text-slate-600">Due: {{ row.current_due_date.strftime('%d-%m-%Y') if row.current_due_date else '-' }}</div>
</div>
</a>
{% else %}
<div class="rounded-2xl border border-dashed border-slate-300 p-6 text-sm text-slate-500">No active compliance items found.</div>
{% endfor %}
</div>
</div>
<div class="af-card">
<div class="flex items-center justify-between gap-3"><h3 class="text-lg font-semibold text-slate-900">Latest Messages</h3><a href="/client/messages" class="text-sm font-semibold text-brand-700 hover:underline">View all</a></div>
<div class="mt-4 space-y-3">
{% for note in client_visible_comments[:5] %}
<div class="rounded-2xl border border-slate-200 p-4 text-sm"><div class="font-semibold text-slate-900">{{ note.task.task_name if note.task else 'Message' }}</div><p class="mt-2 text-slate-700">{{ note.message }}</p><div class="mt-2 text-xs text-slate-500">{{ note.created_at_utc.strftime('%d-%m-%Y %I:%M %p') if note.created_at_utc else '' }}</div></div>
{% else %}
<div class="rounded-2xl border border-dashed border-slate-300 p-6 text-sm text-slate-500">No messages yet.</div>
{% endfor %}
</div>
</div>
</div>
<aside class="space-y-6">
<div class="af-card">
<h3 class="text-base font-semibold text-slate-900">My Client Profile</h3>
<div class="mt-4 space-y-3 text-sm">
<div><div class="text-xs uppercase tracking-wide text-slate-500">Client</div><div class="font-semibold">{{ client_row.client_name }}</div></div>
<div><div class="text-xs uppercase tracking-wide text-slate-500">PAN / GSTIN</div><div>{{ client_row.pan or '-' }}{% if client_row.gstin %} / {{ client_row.gstin }}{% endif %}</div></div>
<div><div class="text-xs uppercase tracking-wide text-slate-500">Firm Contact</div><div>{{ client_row.partner_name or 'Firm team' }}</div></div>
<div><div class="text-xs uppercase tracking-wide text-slate-500">Branch</div><div>{{ client_row.branch_name or '-' }}</div></div>
</div>
<a href="/client/profile" class="mt-5 inline-flex af-btn af-btn-secondary">Update Profile</a>
</div>
<div class="af-card">
<h3 class="font-semibold text-slate-900">Quick Actions</h3>
<div class="mt-4 grid gap-2 text-sm">
<a href="/client/compliance" class="rounded-xl border border-slate-200 px-4 py-3 font-semibold text-slate-700 hover:bg-slate-50">View My Compliance</a>
<a href="/client/documents" class="rounded-xl border border-slate-200 px-4 py-3 font-semibold text-slate-700 hover:bg-slate-50">Upload / View Documents</a>
<a href="/client/messages" class="rounded-xl border border-slate-200 px-4 py-3 font-semibold text-slate-700 hover:bg-slate-50">Messages from Firm</a>
<a href="/client/billing" class="rounded-xl border border-slate-200 px-4 py-3 font-semibold text-slate-700 hover:bg-slate-50">View Bills & Receipts</a>
{% if billing_latest_due_invoice %}<a href="/client/billing/{{ billing_latest_due_invoice.id }}/pay-now" class="rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 font-semibold text-amber-700 hover:bg-amber-100">Pay Latest Due</a>{% endif %}
</div>
</div>
</aside>
</section>
{% endif %}
</div>
{% endblock %}
@@ -0,0 +1,109 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
{% include "modules/clients/templates/clients/_client_tabs.html" %}
<div class="space-y-6">
<div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div>
<h2 class="text-2xl font-semibold text-slate-900">Edit My Profile</h2>
<p class="text-sm text-slate-500">You can update contact and communication details here. PAN, GSTIN and other compliance identity fields stay read-only.</p>
</div>
<div class="flex gap-3">
<a href="/change-password" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Change Password</a>
</div>
</div>
{% if form_errors %}
<div class="rounded-2xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-800">
<ul class="list-disc space-y-1 pl-5">
{% for error in form_errors %}<li>{{ error }}</li>{% endfor %}
</ul>
</div>
{% endif %}
<form method="post" action="/client/profile" class="space-y-6">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<section class="rounded-2xl bg-white p-6 shadow-soft">
<h3 class="text-base font-semibold text-slate-900">Read-only compliance identity</h3>
<div class="mt-4 grid gap-4 md:grid-cols-2 xl:grid-cols-4 text-sm">
<div class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-3"><div class="text-xs font-medium uppercase tracking-wide text-slate-500">PAN</div><div class="mt-1 text-slate-900">{{ client_row.pan or '-' }}</div></div>
<div class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-3"><div class="text-xs font-medium uppercase tracking-wide text-slate-500">GSTIN</div><div class="mt-1 text-slate-900">{{ client_row.gstin or '-' }}</div></div>
<div class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-3"><div class="text-xs font-medium uppercase tracking-wide text-slate-500">TAN</div><div class="mt-1 text-slate-900">{{ client_row.tan or '-' }}</div></div>
<div class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-3"><div class="text-xs font-medium uppercase tracking-wide text-slate-500">CIN / LLPIN</div><div class="mt-1 text-slate-900">{{ client_row.cin_llpin or '-' }}</div></div>
</div>
</section>
<section class="rounded-2xl bg-white p-6 shadow-soft">
<h3 class="text-base font-semibold text-slate-900">Editable profile details</h3>
<div class="mt-4 grid gap-4 md:grid-cols-2">
<div>
<label class="block text-sm font-medium text-slate-700">Client Name</label>
<input name="client_name" value="{{ form_data.client_name or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" required>
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Trade Name</label>
<input name="trade_name" value="{{ form_data.trade_name or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Contact Person Name</label>
<input name="contact_person_name" value="{{ form_data.contact_person_name or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Designation</label>
<input name="contact_person_designation" value="{{ form_data.contact_person_designation or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Mobile</label>
<input name="mobile" value="{{ form_data.mobile or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Alternate Mobile</label>
<input name="alternate_mobile" value="{{ form_data.alternate_mobile or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Login Email</label>
<input type="email" name="email" value="{{ form_data.email or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
<p class="mt-1 text-xs text-slate-500">If you change this, your next login will use the new email.</p>
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Alternate Email</label>
<input type="email" name="alternate_email" value="{{ form_data.alternate_email or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium text-slate-700">Address Line 1</label>
<input name="address_line_1" value="{{ form_data.address_line_1 or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium text-slate-700">Address Line 2</label>
<input name="address_line_2" value="{{ form_data.address_line_2 or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">City</label>
<input name="city" value="{{ form_data.city or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">State</label>
<input name="state" value="{{ form_data.state or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Pincode</label>
<input name="pincode" value="{{ form_data.pincode or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Country</label>
<input name="country" value="{{ form_data.country or '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">
<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 '' }}</textarea>
</div>
</div>
</section>
<div class="flex justify-end">
<button type="submit" class="rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-medium text-white hover:bg-brand-700">Save Profile</button>
</div>
</form>
</div>
{% endblock %}