Prepare ERP source for Gitea deployment
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Bulk Imports</h2>
|
||||
<p class="text-sm text-slate-500">Download Excel templates, fill data, and upload to configure services faster.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-2">
|
||||
{% if can_client_assignment %}
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Bulk assign services to clients</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">For Firm Admin / Partner. Services must already be enabled for the active audit firm. Use expiry_date for renewal-before-expiry services like DSC renewal.</p>
|
||||
<a href="/services/bulk-imports/templates/engagement-assignments.xlsx" class="mt-4 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>
|
||||
<form method="post" action="/services/bulk-imports/engagement-assignments" enctype="multipart/form-data" class="mt-4 space-y-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input type="file" name="file" accept=".xlsx" required class="block w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-slate-700"><input type="checkbox" name="update_existing" checked> Update existing subscriptions</label>
|
||||
<div><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Upload Assignments</button></div>
|
||||
</form>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if can_due_date_extensions %}
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Import due date extensions</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">Imports government deadline extensions for the active audit firm and updates matching unlocked engagements.</p>
|
||||
<a href="/services/bulk-imports/templates/due-date-extensions.xlsx" class="mt-4 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>
|
||||
<form method="post" action="/services/bulk-imports/due-date-extensions" enctype="multipart/form-data" class="mt-4 space-y-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input type="file" name="file" accept=".xlsx" required class="block w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-slate-700"><input type="checkbox" name="update_existing" checked> Update duplicate extension rows</label>
|
||||
<p class="text-xs text-slate-500">Multiple extensions are stored as separate history rows. Duplicate rows are detected by service, rule, FY/AY, period, extended date and notification reference.</p>
|
||||
<div><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Upload Extensions</button></div>
|
||||
</form>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if can_firm_tasks %}
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Import firm task templates</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">For Firm Admin. Service must be enabled for active audit firm.</p>
|
||||
<a href="/services/bulk-imports/templates/firm-task-templates.xlsx" class="mt-4 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>
|
||||
<form method="post" action="/services/bulk-imports/firm-task-templates" enctype="multipart/form-data" class="mt-4 space-y-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input type="file" name="file" accept=".xlsx" required class="block w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-slate-700"><input type="checkbox" name="update_existing" checked> Update existing sequence rows</label>
|
||||
<div><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Upload Firm Tasks</button></div>
|
||||
</form>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if can_system_import %}
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Import system service master</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">System Admin only. Creates/updates service categories, catalogue services, and optional due date rules/extensions in the workbook.</p>
|
||||
<a href="/services/bulk-imports/templates/service-master.xlsx" class="mt-4 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>
|
||||
<form method="post" action="/services/bulk-imports/service-master" enctype="multipart/form-data" class="mt-4 space-y-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input type="file" name="file" accept=".xlsx" required class="block w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-slate-700"><input type="checkbox" name="update_existing" checked> Update existing services/rules</label>
|
||||
<p class="text-xs text-slate-500">Template contains optional sheets: service_master, due_date_rules, due_date_extensions. Due date rules support renewal_based using renewal_days_before_expiry.</p>
|
||||
<div><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Upload Service Master</button></div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Import system default tasks</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">System Admin only. Creates/updates default task templates by service code and sequence no.</p>
|
||||
<a href="/services/bulk-imports/templates/system-default-tasks.xlsx" class="mt-4 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>
|
||||
<form method="post" action="/services/bulk-imports/system-default-tasks" enctype="multipart/form-data" class="mt-4 space-y-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input type="file" name="file" accept=".xlsx" required class="block w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-slate-700"><input type="checkbox" name="update_existing" checked> Update existing sequence rows</label>
|
||||
<div><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Upload Default Tasks</button></div>
|
||||
</form>
|
||||
</section>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,37 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">{{ title }}</h2>
|
||||
<p class="text-sm text-slate-500">Import summary and row-level validation errors.</p>
|
||||
</div>
|
||||
<a href="{{ back_url }}" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Back</a>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-4">
|
||||
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs text-slate-500">Created</div><div class="mt-1 text-2xl font-semibold text-slate-900">{{ result.created }}</div></div>
|
||||
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs text-slate-500">Updated</div><div class="mt-1 text-2xl font-semibold text-slate-900">{{ result.updated }}</div></div>
|
||||
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs text-slate-500">Skipped</div><div class="mt-1 text-2xl font-semibold text-slate-900">{{ result.skipped }}</div></div>
|
||||
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs text-slate-500">Errors</div><div class="mt-1 text-2xl font-semibold {% if result.errors %}text-rose-700{% else %}text-emerald-700{% endif %}">{{ result.errors|length }}</div></div>
|
||||
</div>
|
||||
|
||||
{% if result.errors %}
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<h3 class="text-sm font-semibold text-rose-700">Errors found. No rows were committed.</h3>
|
||||
<div class="mt-4 overflow-hidden rounded-xl 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">Row</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Message</th></tr></thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for err in result.errors %}
|
||||
<tr><td class="px-4 py-3 text-sm text-slate-700">{{ err.row }}</td><td class="px-4 py-3 text-sm text-slate-700">{{ err.message }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
{% else %}
|
||||
<div class="rounded-2xl border border-emerald-200 bg-emerald-50 p-5 text-sm text-emerald-800">Import completed successfully.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,156 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">{{ catalogue.service_name }}</h2>
|
||||
<p class="text-sm text-slate-500">{{ catalogue.service_code }} · {{ catalogue.service_category.name if catalogue.service_category else (catalogue.category or 'Uncategorised') }}</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
{% if can_edit %}<a href="/services/catalogue/{{ catalogue.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 %}
|
||||
<a href="/services" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Back to Services</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-3">
|
||||
<div class="rounded-2xl bg-white p-5 shadow-soft lg:col-span-2">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Catalogue Details</h3>
|
||||
<dl class="mt-4 grid gap-3 md:grid-cols-2 text-sm text-slate-700">
|
||||
<div><dt class="font-medium text-slate-500">Recurrence</dt><dd>{{ catalogue.recurrence_type or '-' }}</dd></div>
|
||||
<div><dt class="font-medium text-slate-500">Engagement Type</dt><dd>{{ 'Assurance' if catalogue.engagement_type == 'assurance' else 'Non-Assurance' }}</dd></div>
|
||||
<div><dt class="font-medium text-slate-500">Sort order</dt><dd>{{ catalogue.sort_order }}</dd></div>
|
||||
<div class="md:col-span-2"><dt class="font-medium text-slate-500">Description</dt><dd>{{ catalogue.description or 'No description added.' }}</dd></div>
|
||||
<div class="md:col-span-2"><dt class="font-medium text-slate-500">Applicability</dt><dd>
|
||||
{% set labels = [] %}
|
||||
{% if catalogue.applicable_individual %}{% set _ = labels.append('Individual') %}{% endif %}
|
||||
{% if catalogue.applicable_proprietorship %}{% set _ = labels.append('Proprietorship') %}{% endif %}
|
||||
{% if catalogue.applicable_partnership %}{% set _ = labels.append('Partnership') %}{% endif %}
|
||||
{% if catalogue.applicable_llp %}{% set _ = labels.append('LLP') %}{% endif %}
|
||||
{% if catalogue.applicable_company %}{% set _ = labels.append('Company') %}{% endif %}
|
||||
{% if catalogue.applicable_trust %}{% set _ = labels.append('Trust') %}{% endif %}
|
||||
{% if catalogue.applicable_society %}{% set _ = labels.append('Society') %}{% endif %}
|
||||
{{ labels|join(', ') if labels else '-' }}
|
||||
</dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Firm Service Selection</h3>
|
||||
<div class="mt-4 text-sm text-slate-700">
|
||||
{% if current_selection and current_selection.is_enabled %}
|
||||
<span class="rounded-full bg-emerald-100 px-2 py-1 text-xs font-medium text-emerald-700">Selected for Firm</span>
|
||||
<p class="mt-3">Firm task templates configured: {{ current_templates|length }}</p>
|
||||
{% if current_selection.default_branch_id %}<p class="mt-1 text-xs text-slate-500">Default Branch ID: {{ current_selection.default_branch_id }}</p>{% endif %}
|
||||
{% else %}
|
||||
<span class="rounded-full bg-slate-200 px-2 py-1 text-xs font-medium text-slate-700">Not Selected</span>
|
||||
<p class="mt-3 text-xs text-slate-500">Select this service for the active firm to customise firm task templates.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if can_manage_firm_services %}
|
||||
<form method="post" action="/services/catalogue/{{ catalogue.id }}/toggle" class="mt-5 space-y-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Default Branch</label>
|
||||
{% if branches %}
|
||||
<select name="default_branch_id" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
<option value="">No default branch</option>
|
||||
{% for branch in branches %}
|
||||
<option value="{{ branch.id }}" {% if current_selection and current_selection.default_branch_id == branch.id %}selected{% endif %}>{{ branch.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<input type="hidden" name="default_branch_id" value="">
|
||||
<p class="rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-700">No active branch found for this firm.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<button class="w-full rounded-xl {% if current_selection and current_selection.is_enabled %}border border-amber-300 text-amber-700 hover:bg-amber-50{% else %}bg-emerald-600 text-white hover:bg-emerald-700{% endif %} px-4 py-2 text-sm font-semibold">
|
||||
{{ 'Disable Service for Firm' if current_selection and current_selection.is_enabled else 'Select Service for Firm' }}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if current_selection and current_selection.is_enabled %}
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
<a href="/services/templates/{{ catalogue.id }}" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Customise Firm Tasks</a>
|
||||
<a href="/services" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">View Firm Services</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-slate-900">Due Date Rules</h3>
|
||||
<p class="mt-1 text-xs text-slate-500">Rules define statutory due dates copied into engagements. Extensions are stored separately as history.</p>
|
||||
</div>
|
||||
{% if can_edit %}<a href="/services/catalogue/{{ catalogue.id }}/due-rules/new" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Add Rule</a>{% endif %}
|
||||
</div>
|
||||
<div class="mt-4 overflow-hidden rounded-xl 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">Rule</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Type</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Due Logic</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Status</th><th></th></tr></thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for rule in due_rules %}
|
||||
<tr>
|
||||
<td class="px-4 py-3 text-sm font-medium text-slate-900">{{ rule.rule_name }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ rule.period_type|replace('_',' ')|title }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">
|
||||
{% if rule.period_type in ['yearly', 'one_time'] %}
|
||||
{{ '%02d'|format(rule.due_day or 0) }}-{{ '%02d'|format(rule.due_month or 0) }} based on {{ rule.due_year_basis|replace('_',' ') }}
|
||||
{% elif rule.period_type in ['monthly', 'quarterly'] %}
|
||||
Day {{ rule.due_day or '-' }} with month offset {{ rule.due_month_offset }}
|
||||
{% elif rule.period_type == 'renewal_based' %}
|
||||
Expiry date minus {{ rule.renewal_days_before_expiry or 0 }} day(s)
|
||||
{% else %}
|
||||
Manual / event based
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm"><span class="rounded-full px-2 py-1 text-xs font-medium {% if rule.is_active %}bg-emerald-100 text-emerald-700{% else %}bg-slate-200 text-slate-700{% endif %}">{{ 'Active' if rule.is_active else 'Inactive' }}</span></td>
|
||||
<td class="px-4 py-3 text-right">{% if can_edit %}<a href="/services/catalogue/{{ catalogue.id }}/due-rules/{{ rule.id }}/edit" class="text-sm font-medium text-brand-700 hover:underline">Edit</a>{% endif %}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5" class="px-4 py-8 text-center text-sm text-slate-500">No due date rules configured yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-slate-900">Due Date Extensions</h3>
|
||||
<p class="mt-1 text-xs text-slate-500">Each extension is preserved. New extensions update current due dates for matching unlocked engagements.</p>
|
||||
</div>
|
||||
{% if can_edit %}<a href="/services/catalogue/{{ catalogue.id }}/due-extensions/new" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Add Extension</a>{% endif %}
|
||||
</div>
|
||||
<div class="mt-4 overflow-hidden rounded-xl 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">FY / AY / Period</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Sequence</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Previous</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Extended To</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Reference</th></tr></thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for ext in due_extensions %}
|
||||
<tr>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">FY {{ ext.financial_year }}{% if ext.assessment_year %} / AY {{ ext.assessment_year }}{% endif %}{% if ext.period_label %} / {{ ext.period_label }}{% endif %}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ ext.extension_sequence }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ ext.previous_due_date or '-' }}</td>
|
||||
<td class="px-4 py-3 text-sm font-medium text-slate-900">{{ ext.extended_due_date }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700"><div>{{ ext.notification_reference or '-' }}</div>{% if ext.notification_date %}<div class="text-xs text-slate-500">{{ ext.notification_date }}</div>{% endif %}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5" class="px-4 py-8 text-center text-sm text-slate-500">No due date extensions recorded yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<div class="flex items-center justify-between"><h3 class="text-sm font-semibold text-slate-900">System Default Tasks</h3>{% if can_edit %}<a href="/services/catalogue/{{ catalogue.id }}/defaults" class="text-sm font-medium text-brand-700 hover:underline">Manage</a>{% endif %}</div>
|
||||
<div class="mt-4 space-y-3">
|
||||
{% for task in default_templates %}
|
||||
<div class="rounded-xl border border-slate-200 p-4"><div class="text-sm font-semibold text-slate-900">{{ task.sequence_no }}. {{ task.task_name }}</div><div class="mt-1 text-xs text-slate-500">Role: {{ task.default_role_name or '-' }} · Mandatory: {{ 'Yes' if task.is_mandatory else 'No' }} · Review: {{ 'Yes' if task.requires_review else 'No' }}</div>{% if task.description %}<p class="mt-2 text-sm text-slate-700">{{ task.description }}</p>{% endif %}</div>
|
||||
{% else %}<div class="rounded-xl border border-dashed border-slate-300 p-6 text-sm text-slate-500">No system default tasks configured yet.</div>{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,30 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="mx-auto max-w-5xl rounded-2xl bg-white p-6 shadow-soft">
|
||||
<h2 class="text-xl font-semibold text-slate-900">{% if mode == 'edit' %}Edit Service Catalogue{% else %}Create Service Catalogue{% endif %}</h2>
|
||||
<form method="post" class="mt-6 grid gap-5 md:grid-cols-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Service Code</label><input type="text" name="service_code" value="{{ catalogue.service_code if catalogue else '' }}" {% if mode == 'edit' %}readonly{% endif %} required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm {% if mode == 'edit' %}bg-slate-50{% endif %}"></div>
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Service Name</label><input type="text" name="service_name" value="{{ catalogue.service_name if catalogue else '' }}" required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></div>
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Category</label><select name="category_id" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"><option value="">Select category</option>{% for cat in categories %}<option value="{{ cat.id }}" {% if catalogue and catalogue.category_id == cat.id %}selected{% endif %}>{{ cat.name }}</option>{% endfor %}</select></div>
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Recurrence Type</label><select name="recurrence_type" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"><option value="">Select recurrence</option>{% for value, label in recurrence_choices %}<option value="{{ value }}" {% if catalogue and catalogue.recurrence_type == value %}selected{% endif %}>{{ label }}</option>{% endfor %}</select></div>
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Engagement Type</label><select name="engagement_type" required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">{% for value, label in engagement_type_choices %}<option value="{{ value }}" {% if catalogue and catalogue.engagement_type == value %}selected{% elif not catalogue and value == "non_assurance" %}selected{% endif %}>{{ label }}</option>{% endfor %}</select><p class="mt-1 text-xs text-slate-500">This value will be copied to client engagements when the service is assigned.</p></div>
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Sort Order</label><input type="number" name="sort_order" value="{{ catalogue.sort_order if catalogue else 100 }}" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></div>
|
||||
<div class="flex items-center gap-6 pt-7 text-sm text-slate-700"><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_active" {% if not catalogue or catalogue.is_active %}checked{% endif %}> Active</label><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_client_requestable" {% if catalogue and catalogue.is_client_requestable %}checked{% endif %}> Client Requestable</label><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_consultant_requestable" {% if catalogue and catalogue.is_consultant_requestable %}checked{% endif %}> Consultant Requestable</label></div>
|
||||
<div class="md:col-span-2"><label class="mb-2 block text-sm font-medium text-slate-700">Description</label><textarea name="description" rows="4" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">{{ catalogue.description if catalogue else '' }}</textarea></div>
|
||||
<div class="md:col-span-2">
|
||||
<h3 class="mb-3 text-sm font-semibold text-slate-900">Applicability</h3>
|
||||
<div class="grid gap-3 md:grid-cols-4 text-sm text-slate-700">
|
||||
<label class="inline-flex items-center gap-2"><input type="checkbox" name="applicable_individual" {% if catalogue and catalogue.applicable_individual %}checked{% endif %}> Individual</label>
|
||||
<label class="inline-flex items-center gap-2"><input type="checkbox" name="applicable_proprietorship" {% if catalogue and catalogue.applicable_proprietorship %}checked{% endif %}> Proprietorship</label>
|
||||
<label class="inline-flex items-center gap-2"><input type="checkbox" name="applicable_partnership" {% if catalogue and catalogue.applicable_partnership %}checked{% endif %}> Partnership</label>
|
||||
<label class="inline-flex items-center gap-2"><input type="checkbox" name="applicable_llp" {% if catalogue and catalogue.applicable_llp %}checked{% endif %}> LLP</label>
|
||||
<label class="inline-flex items-center gap-2"><input type="checkbox" name="applicable_company" {% if catalogue and catalogue.applicable_company %}checked{% endif %}> Company</label>
|
||||
<label class="inline-flex items-center gap-2"><input type="checkbox" name="applicable_trust" {% if catalogue and catalogue.applicable_trust %}checked{% endif %}> Trust</label>
|
||||
<label class="inline-flex items-center gap-2"><input type="checkbox" name="applicable_society" {% if catalogue and catalogue.applicable_society %}checked{% endif %}> Society</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md:col-span-2 flex items-center justify-end gap-3"><a href="/services/catalogue" 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</button></div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,110 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Service Catalogue</h2>
|
||||
<p class="text-sm text-slate-500">Standard service master. Firm Admin can select the services applicable for the active firm.</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="/services" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Firm Services</a>
|
||||
<a href="/services/categories" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Categories</a>
|
||||
<a href="/services/templates" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Firm Task Templates</a>
|
||||
{% if can_create %}
|
||||
<a href="/services/defaults" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">System Default Tasks</a>
|
||||
<a href="/services/catalogue/new" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Add Catalogue Service</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if can_manage_firm_services %}
|
||||
<div class="rounded-2xl border border-emerald-200 bg-emerald-50 p-4 text-sm text-emerald-800">
|
||||
Select the services your firm provides. Once selected, you can open <span class="font-semibold">Firm Task Templates</span> and customise tasks for your firm.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="get" class="rounded-2xl bg-white p-4 shadow-soft">
|
||||
<div class="grid gap-3 md:grid-cols-5">
|
||||
<input type="text" name="q" value="{{ q }}" placeholder="Search code, name, category" class="rounded-xl border border-slate-300 px-4 py-2 text-sm md:col-span-2">
|
||||
<select name="category_id" class="rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
<option value="">All categories</option>
|
||||
{% for cat in categories %}<option value="{{ cat.id }}" {% if category_id == cat.id %}selected{% endif %}>{{ cat.name }}</option>{% endfor %}
|
||||
</select>
|
||||
<select name="recurrence_type" class="rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
<option value="">All recurrence</option>
|
||||
{% for value, label in recurrence_choices %}<option value="{{ value }}" {% if recurrence_type == value %}selected{% endif %}>{{ label }}</option>{% endfor %}
|
||||
</select>
|
||||
<select name="engagement_type" class="rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
<option value="">All engagement types</option>
|
||||
{% for value, label in engagement_type_choices %}<option value="{{ value }}" {% if engagement_type == value %}selected{% endif %}>{{ label }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mt-3 flex justify-end"><button class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium hover:bg-slate-50">Search</button></div>
|
||||
</form>
|
||||
|
||||
<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">Name</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Category</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Recurrence</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Firm Selection</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-semibold uppercase tracking-wide text-slate-500">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for row in rows %}
|
||||
{% set selection = firm_selection_by_catalogue.get(row.id) if firm_selection_by_catalogue else None %}
|
||||
<tr>
|
||||
<td class="px-4 py-3 text-sm font-medium text-slate-900">{{ row.service_code }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">
|
||||
<div class="font-medium text-slate-900">{{ row.service_name }}</div>
|
||||
<div class="mt-1 text-xs text-slate-500">{{ 'Assurance' if row.engagement_type == 'assurance' else 'Non-Assurance' }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ row.service_category.name if row.service_category else (row.category or '-') }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ row.recurrence_type|replace('_',' ')|title if row.recurrence_type else '-' }}</td>
|
||||
<td class="px-4 py-3 text-sm">
|
||||
{% if selection and selection.is_enabled %}
|
||||
<div class="mb-2"><span class="rounded-full bg-emerald-100 px-2 py-1 text-xs font-medium text-emerald-700">Selected for Firm</span></div>
|
||||
{% if selection.default_branch_id %}<div class="mb-2 text-xs text-slate-500">Default Branch ID: {{ selection.default_branch_id }}</div>{% endif %}
|
||||
{% else %}
|
||||
<div class="mb-2"><span class="rounded-full bg-slate-200 px-2 py-1 text-xs font-medium text-slate-700">Not Selected</span></div>
|
||||
{% endif %}
|
||||
|
||||
{% if can_manage_firm_services %}
|
||||
<form method="post" action="/services/catalogue/{{ row.id }}/toggle" class="flex flex-wrap items-center gap-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
{% if branches %}
|
||||
<select name="default_branch_id" class="max-w-44 rounded-lg border border-slate-300 px-2 py-1 text-xs">
|
||||
<option value="">Default branch optional</option>
|
||||
{% for branch in branches %}
|
||||
<option value="{{ branch.id }}" {% if selection and selection.default_branch_id == branch.id %}selected{% endif %}>{{ branch.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<input type="hidden" name="default_branch_id" value="">
|
||||
{% endif %}
|
||||
<button class="rounded-lg {% if selection and selection.is_enabled %}border border-amber-300 text-amber-700 hover:bg-amber-50{% else %}bg-emerald-600 text-white hover:bg-emerald-700{% endif %} px-3 py-1.5 text-xs font-semibold">
|
||||
{{ 'Disable for Firm' if selection and selection.is_enabled else 'Select for Firm' }}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right text-sm">
|
||||
<a href="/services/catalogue/{{ row.id }}" class="font-medium text-brand-700 hover:underline">Open</a>
|
||||
{% if selection and selection.is_enabled %}
|
||||
<a href="/services/templates/{{ row.id }}" class="ml-3 font-medium text-brand-700 hover:underline">Firm Tasks</a>
|
||||
{% endif %}
|
||||
{% if can_create %}<a href="/services/catalogue/{{ row.id }}/edit" class="ml-3 font-medium text-brand-700 hover:underline">Edit</a>{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="6" class="px-4 py-8 text-center text-sm text-slate-500">No catalogue services found.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,28 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="mx-auto max-w-3xl rounded-2xl bg-white p-6 shadow-soft">
|
||||
<h2 class="text-xl font-semibold text-slate-900">{% if mode == 'edit' %}Edit Service Category{% else %}Create Service Category{% endif %}</h2>
|
||||
<form method="post" class="mt-6 grid gap-5 md:grid-cols-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Category Code</label>
|
||||
<input type="text" name="code" value="{{ category.code if category else '' }}" {% if mode == 'edit' %}readonly{% endif %} required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm {% if mode == 'edit' %}bg-slate-50{% endif %}">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Category Name</label>
|
||||
<input type="text" name="name" value="{{ category.name if category else '' }}" required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Sort Order</label>
|
||||
<input type="number" name="sort_order" value="{{ category.sort_order if category else 100 }}" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
</div>
|
||||
<div class="flex items-center gap-6 pt-7 text-sm text-slate-700">
|
||||
<label class="inline-flex items-center gap-2"><input type="checkbox" name="is_active" {% if not category or category.is_active %}checked{% endif %}> Active</label>
|
||||
</div>
|
||||
<div class="md:col-span-2 flex items-center justify-end gap-3">
|
||||
<a href="/services/categories" 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</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,34 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Service Categories</h2>
|
||||
<p class="text-sm text-slate-500">System-level grouping for service catalogue entries.</p>
|
||||
</div>
|
||||
{% if can_create %}<a href="/services/categories/new" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Add Category</a>{% endif %}
|
||||
</div>
|
||||
<form method="get" class="rounded-2xl bg-white p-4 shadow-soft">
|
||||
<div class="flex gap-3">
|
||||
<input type="text" name="q" value="{{ q }}" placeholder="Search code or name" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
<button class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium hover:bg-slate-50">Search</button>
|
||||
</div>
|
||||
</form>
|
||||
<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">Name</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Order</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Status</th><th></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.code }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ row.name }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ row.sort_order }}</td>
|
||||
<td class="px-4 py-3 text-sm">{% if row.is_active %}<span class="rounded-full bg-emerald-100 px-2 py-1 text-xs font-medium text-emerald-700">Active</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="/services/categories/{{ row.id }}/edit" class="text-sm font-medium text-brand-700 hover:underline">Edit</a></td>
|
||||
</tr>
|
||||
{% else %}<tr><td colspan="5" class="px-4 py-8 text-center text-sm text-slate-500">No categories found.</td></tr>{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,60 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="mx-auto max-w-3xl rounded-2xl bg-white p-6 shadow-soft">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Edit System Default Task</h2>
|
||||
<p class="text-sm text-slate-500">{{ service.service_code }} · {{ service.service_name }}</p>
|
||||
</div>
|
||||
<a href="/services/catalogue/{{ service.id }}/defaults" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Back</a>
|
||||
</div>
|
||||
|
||||
<form method="post" class="mt-6 grid gap-5 md:grid-cols-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Task Name</label>
|
||||
<input type="text" name="task_name" value="{{ task.task_name }}" required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Default Role</label>
|
||||
<input type="text" name="default_role_name" value="{{ task.default_role_name or '' }}" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Sequence No</label>
|
||||
<input type="number" min="1" name="sequence_no" value="{{ task.sequence_no }}" required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-6 pt-7 text-sm text-slate-700">
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<input type="checkbox" name="is_mandatory" {% if task.is_mandatory %}checked{% endif %}>
|
||||
Mandatory
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<input type="checkbox" name="requires_review" {% if task.requires_review %}checked{% endif %}>
|
||||
Review
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<input type="checkbox" name="is_active" {% if task.is_active %}checked{% endif %}>
|
||||
Active
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2">
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Description</label>
|
||||
<textarea name="description" rows="4" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">{{ task.description or '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2 rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800">
|
||||
To deactivate this system default task, untick <strong>Active</strong> and save. Existing firm-copied tasks will not be automatically changed.
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2 flex justify-end gap-3">
|
||||
<a href="/services/catalogue/{{ service.id }}/defaults" 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,53 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">{{ service.service_name }}</h2>
|
||||
<p class="text-sm text-slate-500">System default task templates for {{ service.service_code }}</p>
|
||||
</div>
|
||||
<div class="flex gap-2"><a href="/services/defaults" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Back</a><a href="/services/catalogue/{{ service.id }}" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Catalogue Detail</a></div>
|
||||
</div>
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<div class="space-y-3">
|
||||
{% for task in default_tasks %}
|
||||
{% if task.is_active or is_system_admin %}
|
||||
<div class="rounded-xl border border-slate-200 p-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<div class="text-sm font-semibold text-slate-900">{{ task.sequence_no }}. {{ task.task_name }}</div>
|
||||
<div class="mt-1 text-xs text-slate-500">
|
||||
Role: {{ task.default_role_name or '-' }} ·
|
||||
Mandatory: {{ 'Yes' if task.is_mandatory else 'No' }} ·
|
||||
Review: {{ 'Yes' if task.requires_review else 'No' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="rounded-full px-2 py-1 text-xs font-medium {% if task.is_active %}bg-emerald-100 text-emerald-700{% else %}bg-slate-200 text-slate-700{% endif %}">
|
||||
{{ 'Active' if task.is_active else 'Inactive' }}
|
||||
</span>
|
||||
<a href="/services/catalogue/{{ service.id }}/defaults/{{ task.id }}/edit" class="text-xs font-medium text-brand-700 hover:underline">Edit</a>
|
||||
</div>
|
||||
</div>
|
||||
{% if task.description %}
|
||||
<p class="mt-2 text-sm text-slate-700">{{ task.description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}<div class="rounded-xl border border-dashed border-slate-300 p-6 text-sm text-slate-500">No default tasks configured yet.</div>{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Add Default Task Template</h3>
|
||||
<form method="post" action="/services/catalogue/{{ service.id }}/defaults/new" class="mt-5 grid gap-4 rounded-2xl border border-slate-200 p-4 md:grid-cols-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Task Name</label><input type="text" name="task_name" required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></div>
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Default Role</label><input type="text" name="default_role_name" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></div>
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Sequence No</label><input type="number" min="1" name="sequence_no" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></div>
|
||||
<div class="flex items-center gap-6 pt-7 text-sm text-slate-700"><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_mandatory" checked> Mandatory</label><label class="inline-flex items-center gap-2"><input type="checkbox" name="requires_review"> Review</label><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_active" checked> Active</label></div>
|
||||
<div class="md:col-span-2"><label class="mb-2 block text-sm font-medium text-slate-700">Description</label><textarea name="description" rows="4" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></textarea></div>
|
||||
<div class="md:col-span-2 flex justify-end"><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Add Default Task</button></div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,18 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Default Task Templates</h2>
|
||||
<p class="text-sm text-slate-500">System-level default task templates by service catalogue.</p>
|
||||
</div>
|
||||
<a href="/services/catalogue" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Back to Catalogue</a>
|
||||
</div>
|
||||
<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">Service</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Default Tasks</th><th></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.service_code }}</td><td class="px-4 py-3 text-sm text-slate-700">{{ row.service_name }}</td><td class="px-4 py-3 text-sm text-slate-700">{{ row.default_task_templates|length }}</td><td class="px-4 py-3 text-right"><a href="/services/catalogue/{{ row.id }}/defaults" class="text-sm font-medium text-brand-700 hover:underline">Manage</a></td></tr>{% else %}<tr><td colspan="4" class="px-4 py-8 text-center text-sm text-slate-500">No catalogue services found.</td></tr>{% endfor %}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,20 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div><h2 class="text-xl font-semibold text-slate-900">Engagement</h2><p class="text-sm text-slate-500">{{ row.client.client_name if row.client else '-' }} · {{ row.catalogue.service_name if row.catalogue else '-' }} · FY {{ row.financial_year }}</p></div>
|
||||
<div class="flex gap-2">
|
||||
{% if can_view_documents(current_user, current_user_permissions, current_user_roles) %}<a href="/documents/engagements/{{ row.id }}" class="rounded-xl border border-brand-300 px-4 py-2 text-sm font-medium text-brand-700 hover:bg-brand-50">Documents</a>{% endif %}
|
||||
<a href="/services/engagements?financial_year={{ row.financial_year }}" 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 can_manage and not row.is_locked %}<a href="/services/engagements/{{ row.id }}/edit" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Edit</a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if row.is_locked %}<div class="rounded-2xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800">This engagement is locked as historical record. It cannot be edited.</div>{% endif %}
|
||||
<div class="grid gap-6 lg:grid-cols-2">
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft"><h3 class="text-sm font-semibold text-slate-900">Client & Service</h3><dl class="mt-4 space-y-3 text-sm"><div><dt class="text-slate-500">Client</dt><dd class="font-medium text-slate-900">{{ row.client.client_name if row.client else '-' }}</dd></div><div><dt class="text-slate-500">Service</dt><dd class="font-medium text-slate-900">{{ row.catalogue.service_name if row.catalogue else '-' }}</dd></div><div><dt class="text-slate-500">Financial Year</dt><dd>{{ row.financial_year or '-' }}</dd></div><div><dt class="text-slate-500">Assessment Year</dt><dd>{{ row.assessment_year or '-' }}</dd></div><div><dt class="text-slate-500">Engagement Type</dt><dd>{{ 'Assurance' if row.engagement_type == 'assurance' else 'Non-Assurance' }}</dd></div><div><dt class="text-slate-500">Original Due Date</dt><dd>{{ row.original_due_date or '-' }}</dd></div><div><dt class="text-slate-500">Expiry Date</dt><dd>{{ row.expiry_date or '-' }}</dd></div><div><dt class="text-slate-500">Current Due Date</dt><dd class="font-medium text-slate-900">{{ row.current_due_date or '-' }}{% if row.due_date_source %}<span class="ml-2 rounded-full bg-slate-100 px-2 py-1 text-xs font-medium text-slate-600">{{ row.due_date_source|replace('_',' ')|title }}</span>{% endif %}</dd></div><div><dt class="text-slate-500">Status</dt><dd>{{ 'Locked' if row.is_locked else row.status|replace('_',' ')|title }}{% if not row.is_active %} / Inactive{% endif %}</dd></div><div><dt class="text-slate-500">Period</dt><dd>{{ row.start_date or '-' }} to {{ row.end_date or '-' }}</dd></div></dl></section>
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft"><h3 class="text-sm font-semibold text-slate-900">Assignment</h3><dl class="mt-4 space-y-3 text-sm"><div><dt class="text-slate-500">Partner</dt><dd>{{ row.assigned_partner.full_name if row.assigned_partner and row.assigned_partner.full_name else (row.assigned_partner.email if row.assigned_partner else '-') }}</dd></div><div><dt class="text-slate-500">Manager</dt><dd>{{ row.assigned_manager.full_name if row.assigned_manager and row.assigned_manager.full_name else (row.assigned_manager.email if row.assigned_manager else '-') }}</dd></div><div><dt class="text-slate-500">Staff</dt><dd>{{ row.assigned_staff.full_name if row.assigned_staff and row.assigned_staff.full_name else (row.assigned_staff.email if row.assigned_staff else '-') }}</dd></div><div><dt class="text-slate-500">Review Partner</dt><dd>{{ row.review_partner.full_name if row.review_partner and row.review_partner.full_name else (row.review_partner.email if row.review_partner else '-') }}</dd></div></dl></section>
|
||||
</div>
|
||||
{% if can_manage and not row.is_locked %}<form method="post" action="/services/engagements/{{ row.id }}/lock" class="rounded-2xl border border-amber-200 bg-white p-5 shadow-soft"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><h3 class="text-sm font-semibold text-slate-900">Year-end Lock</h3><p class="mt-2 text-sm text-slate-600">Lock this engagement when the year is complete. After locking, it becomes read-only history.</p><button class="mt-4 rounded-xl bg-amber-600 px-4 py-2 text-sm font-medium text-white hover:bg-amber-700">Lock Engagement</button></form>{% endif %}
|
||||
{% if row.remarks %}<section class="rounded-2xl bg-white p-5 shadow-soft"><h3 class="text-sm font-semibold text-slate-900">Remarks</h3><p class="mt-3 text-sm leading-6 text-slate-700">{{ row.remarks }}</p></section>{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,59 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="mx-auto max-w-4xl rounded-2xl bg-white p-6 shadow-soft">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Add Due Date Extension</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">{{ catalogue.service_name }} · {{ catalogue.service_code }}</p>
|
||||
</div>
|
||||
<a href="/services/catalogue/{{ catalogue.id }}" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Back</a>
|
||||
</div>
|
||||
|
||||
<form method="post" class="mt-6 grid gap-5 md:grid-cols-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Due Date Rule</label>
|
||||
<select name="due_date_rule_id" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
<option value="">Use first active rule</option>
|
||||
{% for rule in rules %}<option value="{{ rule.id }}">{{ rule.rule_name }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Financial Year</label>
|
||||
<input type="text" name="financial_year" required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" placeholder="2025-26">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Assessment Year</label>
|
||||
<input type="text" name="assessment_year" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" placeholder="2026-27">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Period Label</label>
|
||||
<input type="text" name="period_label" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" placeholder="Apr / Q1 / 2026-04">
|
||||
<p class="mt-1 text-xs text-slate-500">Leave blank for annual services.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Extended Due Date</label>
|
||||
<input type="date" name="extended_due_date" required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Notification Date</label>
|
||||
<input type="date" name="notification_date" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Notification / Circular Reference</label>
|
||||
<input type="text" name="notification_reference" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" placeholder="CBDT Circular / GST Notification reference">
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Remarks</label>
|
||||
<textarea name="remarks" rows="3" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></textarea>
|
||||
</div>
|
||||
<div class="md:col-span-2 rounded-xl bg-amber-50 p-4 text-sm text-amber-800">
|
||||
This extension will be stored as a new sequence. Matching unlocked engagements for the active audit firm will be updated to the new current due date. Locked engagements will be skipped.
|
||||
</div>
|
||||
<div class="md:col-span-2 flex items-center justify-end gap-3">
|
||||
<a href="/services/catalogue/{{ catalogue.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 Extension</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,73 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="mx-auto max-w-4xl rounded-2xl bg-white p-6 shadow-soft">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">{% if mode == 'edit' %}Edit Due Date Rule{% else %}Add Due Date Rule{% endif %}</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">{{ catalogue.service_name }} · {{ catalogue.service_code }}</p>
|
||||
</div>
|
||||
<a href="/services/catalogue/{{ catalogue.id }}" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Back</a>
|
||||
</div>
|
||||
|
||||
<form method="post" class="mt-6 grid gap-5 md:grid-cols-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Rule Name</label>
|
||||
<input type="text" name="rule_name" value="{{ rule.rule_name if rule else '' }}" required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" placeholder="Tax Audit due date">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Period Type</label>
|
||||
<select name="period_type" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
{% for value, label in due_period_types %}<option value="{{ value }}" {% if rule and rule.period_type == value %}selected{% elif not rule and value == 'yearly' %}selected{% endif %}>{{ label }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Due Year Basis</label>
|
||||
<select name="due_year_basis" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
{% for value, label in due_year_basis_choices %}<option value="{{ value }}" {% if rule and rule.due_year_basis == value %}selected{% elif not rule and value == 'assessment_year_start' %}selected{% endif %}>{{ label }}</option>{% endfor %}
|
||||
</select>
|
||||
<p class="mt-1 text-xs text-slate-500">For Tax Audit AY 2026-27 due 30 Sep 2026, use assessment year start year.</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Due Day</label>
|
||||
<input type="number" min="1" max="31" name="due_day" value="{{ rule.due_day if rule and rule.due_day else '' }}" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" placeholder="30">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Due Month</label>
|
||||
<input type="number" min="1" max="12" name="due_month" value="{{ rule.due_month if rule and rule.due_month else '' }}" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" placeholder="9">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Due Month Offset</label>
|
||||
<input type="number" name="due_month_offset" value="{{ rule.due_month_offset if rule else 0 }}" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
<p class="mt-1 text-xs text-slate-500">For monthly GST 20th of next month, use period type Monthly, due day 20, offset 1.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Days After Event</label>
|
||||
<input type="number" name="days_offset_after_event" value="{{ rule.days_offset_after_event if rule and rule.days_offset_after_event is not none else '' }}" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" placeholder="30">
|
||||
<p class="mt-1 text-xs text-slate-500">For event-based rules like 30 days from AGM. Event-date calculation can be handled later.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Renewal Days Before Expiry</label>
|
||||
<input type="number" min="0" name="renewal_days_before_expiry" value="{{ rule.renewal_days_before_expiry if rule and rule.renewal_days_before_expiry is not none else '' }}" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" placeholder="30">
|
||||
<p class="mt-1 text-xs text-slate-500">For renewal-before-expiry services like DSC/FSSAI. Due date = engagement expiry date minus these days.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Sort Order</label>
|
||||
<input type="number" name="sort_order" value="{{ rule.sort_order if rule else 100 }}" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
</div>
|
||||
<div class="flex items-center gap-3 pt-7 text-sm text-slate-700">
|
||||
<label class="inline-flex items-center gap-2"><input type="checkbox" name="is_active" {% if not rule or rule.is_active %}checked{% endif %}> Active</label>
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Remarks</label>
|
||||
<textarea name="remarks" rows="3" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">{{ rule.remarks if rule else '' }}</textarea>
|
||||
</div>
|
||||
<div class="md:col-span-2 flex items-center justify-end gap-3">
|
||||
<a href="/services/catalogue/{{ catalogue.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 Rule</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,41 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div><h2 class="text-xl font-semibold text-slate-900">Engagement</h2><p class="text-sm text-slate-500">{{ row.client.client_name if row.client else '-' }} · {{ row.catalogue.service_name if row.catalogue else '-' }} · FY {{ row.financial_year }}</p></div>
|
||||
<div class="flex gap-2">
|
||||
<a href="/services/engagements?financial_year={{ row.financial_year }}" 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 can_manage and not row.is_locked %}<a href="/services/engagements/{{ row.id }}/edit" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Edit</a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if row.is_locked %}<div class="rounded-2xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800">This engagement is locked as historical record. It cannot be edited.</div>{% endif %}
|
||||
<div class="grid gap-6 lg:grid-cols-2">
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft"><h3 class="text-sm font-semibold text-slate-900">Client & Service</h3><dl class="mt-4 space-y-3 text-sm"><div><dt class="text-slate-500">Client</dt><dd class="font-medium text-slate-900">{{ row.client.client_name if row.client else '-' }}</dd></div><div><dt class="text-slate-500">Service</dt><dd class="font-medium text-slate-900">{{ row.catalogue.service_name if row.catalogue else '-' }}</dd></div><div><dt class="text-slate-500">Financial Year</dt><dd>{{ row.financial_year or '-' }}</dd></div><div><dt class="text-slate-500">Assessment Year</dt><dd>{{ row.assessment_year or '-' }}</dd></div><div><dt class="text-slate-500">Engagement Type</dt><dd>{{ 'Assurance' if row.engagement_type == 'assurance' else 'Non-Assurance' }}</dd></div><div><dt class="text-slate-500">Original Due Date</dt><dd>{{ row.original_due_date or '-' }}</dd></div><div><dt class="text-slate-500">Expiry Date</dt><dd>{{ row.expiry_date or '-' }}</dd></div><div><dt class="text-slate-500">Current Due Date</dt><dd class="font-medium text-slate-900">{{ row.current_due_date or '-' }}{% if row.due_date_source %}<span class="ml-2 rounded-full bg-slate-100 px-2 py-1 text-xs font-medium text-slate-600">{{ row.due_date_source|replace('_',' ')|title }}</span>{% endif %}</dd></div><div><dt class="text-slate-500">Status</dt><dd>{{ 'Locked' if row.is_locked else row.status|replace('_',' ')|title }}{% if not row.is_active %} / Inactive{% endif %}</dd></div><div><dt class="text-slate-500">Period</dt><dd>{{ row.start_date or '-' }} to {{ row.end_date or '-' }}</dd></div></dl></section>
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft"><h3 class="text-sm font-semibold text-slate-900">Assignment</h3><dl class="mt-4 space-y-3 text-sm"><div><dt class="text-slate-500">Partner</dt><dd>{{ row.assigned_partner.full_name if row.assigned_partner and row.assigned_partner.full_name else (row.assigned_partner.email if row.assigned_partner else '-') }}</dd></div><div><dt class="text-slate-500">Manager</dt><dd>{{ row.assigned_manager.full_name if row.assigned_manager and row.assigned_manager.full_name else (row.assigned_manager.email if row.assigned_manager else '-') }}</dd></div><div><dt class="text-slate-500">Staff</dt><dd>{{ row.assigned_staff.full_name if row.assigned_staff and row.assigned_staff.full_name else (row.assigned_staff.email if row.assigned_staff else '-') }}</dd></div><div><dt class="text-slate-500">Review Partner</dt><dd>{{ row.review_partner.full_name if row.review_partner and row.review_partner.full_name else (row.review_partner.email if row.review_partner else '-') }}</dd></div></dl></section>
|
||||
</div>
|
||||
{% if can_manage and not row.is_locked %}<form method="post" action="/services/engagements/{{ row.id }}/lock" class="rounded-2xl border border-amber-200 bg-white p-5 shadow-soft"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><h3 class="text-sm font-semibold text-slate-900">Year-end Lock</h3><p class="mt-2 text-sm text-slate-600">Lock this engagement when the year is complete. After locking, it becomes read-only history.</p><button class="mt-4 rounded-xl bg-amber-600 px-4 py-2 text-sm font-medium text-white hover:bg-amber-700">Lock Engagement</button></form>{% endif %}
|
||||
|
||||
<section class="overflow-hidden rounded-2xl bg-white shadow-soft">
|
||||
<div class="border-b border-slate-100 px-5 py-4">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Engagement Tasks & Task Documents</h3>
|
||||
<p class="text-sm text-slate-500">Open each task to upload documents against its configured document requirements.</p>
|
||||
</div>
|
||||
<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">Seq</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Task</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 text-right text-xs font-semibold uppercase tracking-wide text-slate-500">Documents</th></tr></thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for task in tasks or [] %}
|
||||
<tr>
|
||||
<td class="px-4 py-3 text-sm font-medium text-slate-900">{{ task.sequence_no }}</td>
|
||||
<td class="px-4 py-3 text-sm"><div class="font-medium text-slate-900">{{ task.task_name }}</div>{% if task.description %}<div class="text-xs text-slate-500">{{ task.description }}</div>{% endif %}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ task.status.replace('_',' ').title() }}</td>
|
||||
<td class="px-4 py-3 text-right"><a href="/documents/tasks/{{ task.id }}" class="text-sm font-medium text-brand-700 hover:underline">Open Task Documents</a></td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="4" class="px-4 py-8 text-center text-sm text-slate-500">No execution tasks generated yet. Generate work tracker tasks first.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{% if row.remarks %}<section class="rounded-2xl bg-white p-5 shadow-soft"><h3 class="text-sm font-semibold text-slate-900">Remarks</h3><p class="mt-3 text-sm leading-6 text-slate-700">{{ row.remarks }}</p></section>{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,66 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="mx-auto max-w-4xl rounded-2xl bg-white p-6 shadow-soft">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">{% if mode == 'edit' %}Edit Engagement{% else %}Assign Service to Client{% endif %}</h2>
|
||||
<p class="text-sm text-slate-500">Engagements are maintained financial-year wise. Review partner is used only for assurance engagements of partnership audit firms.</p>
|
||||
</div>
|
||||
<a href="/services/engagements" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Back</a>
|
||||
</div>
|
||||
|
||||
<form method="post" class="mt-6 grid gap-5 md:grid-cols-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Financial Year</label>
|
||||
{% if mode == 'edit' %}
|
||||
<div class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-2 text-sm text-slate-700">{{ subscription.financial_year }}</div>
|
||||
{% else %}
|
||||
<input type="text" name="financial_year" value="{{ financial_year or '2025-26' }}" required placeholder="2025-26" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Client</label>
|
||||
{% if mode == 'edit' %}
|
||||
<div class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-2 text-sm text-slate-700">{{ subscription.client.client_name if subscription and subscription.client else '-' }}</div>
|
||||
{% else %}
|
||||
<select name="client_id" required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
<option value="">Select client</option>
|
||||
{% for client in clients %}<option value="{{ client.id }}" {% if selected_client_id and client.id == selected_client_id %}selected{% endif %}>{{ client.client_name }} ({{ client.client_code }})</option>{% endfor %}
|
||||
</select>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Enabled Service</label>
|
||||
{% if mode == 'edit' %}
|
||||
<div class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-2 text-sm text-slate-700">
|
||||
{{ subscription.catalogue.service_name if subscription and subscription.catalogue else '-' }} · {{ 'Assurance' if subscription and subscription.engagement_type == 'assurance' else 'Non-Assurance' }}
|
||||
</div>
|
||||
{% else %}
|
||||
<select name="service_catalogue_id" required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
<option value="">Select service</option>
|
||||
{% for selection in enabled_services %}
|
||||
<option value="{{ selection.catalogue.id }}">{{ selection.catalogue.service_name }} ({{ selection.catalogue.service_code }}) - {{ 'Assurance' if selection.catalogue.engagement_type == 'assurance' else 'Non-Assurance' }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Assigned Partner</label><select name="assigned_partner_user_id" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"><option value="">Not assigned</option>{% for u in assignable_users %}<option value="{{ u.id }}" {% if subscription and subscription.assigned_partner_user_id == u.id %}selected{% endif %}>{{ u.full_name or u.email }}</option>{% endfor %}</select></div>
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Assigned Manager</label><select name="assigned_manager_user_id" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"><option value="">Not assigned</option>{% for u in assignable_users %}<option value="{{ u.id }}" {% if subscription and subscription.assigned_manager_user_id == u.id %}selected{% endif %}>{{ u.full_name or u.email }}</option>{% endfor %}</select></div>
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Assigned Staff</label><select name="assigned_staff_user_id" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"><option value="">Not assigned</option>{% for u in assignable_users %}<option value="{{ u.id }}" {% if subscription and subscription.assigned_staff_user_id == u.id %}selected{% endif %}>{{ u.full_name or u.email }}</option>{% endfor %}</select></div>
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Review Partner</label><select name="review_partner_user_id" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"><option value="">Use client default / Not required</option>{% for u in review_partners or [] %}<option value="{{ u.id }}" {% if subscription and subscription.review_partner_user_id == u.id %}selected{% endif %}>{{ u.full_name or u.email }}</option>{% endfor %}</select><p class="mt-1 text-xs text-slate-500">Saved only for assurance engagements of partnership audit firms.</p></div>
|
||||
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Status</label><select name="status" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">{% for value, label in subscription_statuses %}<option value="{{ value }}" {% if subscription and subscription.status == value %}selected{% elif not subscription and value == 'active' %}selected{% endif %}>{{ label }}</option>{% endfor %}</select></div>
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Start Date</label><input type="date" name="start_date" value="{{ subscription.start_date if subscription and subscription.start_date else '' }}" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></div>
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">End Date</label><input type="date" name="end_date" value="{{ subscription.end_date if subscription and subscription.end_date else '' }}" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></div>
|
||||
<div><label class="mb-2 block text-sm font-medium text-slate-700">Expiry Date</label><input type="date" name="expiry_date" value="{{ subscription.expiry_date if subscription and subscription.expiry_date else '' }}" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"><p class="mt-1 text-xs text-slate-500">Use for renewal-before-expiry services like DSC/FSSAI. Current due date is calculated from the service due-date rule.</p></div>
|
||||
<div class="flex items-center gap-6 pt-7 text-sm text-slate-700"><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_active" {% if not subscription or subscription.is_active %}checked{% endif %}> Active</label></div>
|
||||
<div class="md:col-span-2"><label class="mb-2 block text-sm font-medium text-slate-700">Remarks</label><textarea name="remarks" rows="4" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">{{ subscription.remarks if subscription and subscription.remarks else '' }}</textarea></div>
|
||||
<div class="md:col-span-2 flex justify-end gap-3"><a href="/services/engagements" 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</button></div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,88 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Engagements</h2>
|
||||
<p class="text-sm text-slate-500">Year-wise client service engagements with locking for completed years.</p>
|
||||
</div>
|
||||
{% if can_manage %}
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="/services/bulk-imports" class="rounded-xl border border-emerald-300 px-4 py-2 text-sm font-medium text-emerald-700 hover:bg-emerald-50">Bulk Assign by Excel</a>
|
||||
<a href="/services/engagements/new" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Assign Service</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if locked_count or skipped_count %}
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-4 text-sm shadow-soft">
|
||||
{% if locked_count %}<span class="font-medium text-emerald-700">{{ locked_count }} engagement{{ 's' if locked_count != 1 else '' }} locked.</span>{% endif %}
|
||||
{% if skipped_count %}<span class="ml-2 font-medium text-amber-700">{{ skipped_count }} skipped because already locked or not permitted.</span>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="get" class="flex flex-wrap items-end gap-3 rounded-2xl bg-white p-4 shadow-soft">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Financial Year</label>
|
||||
<input type="text" name="financial_year" value="{{ financial_year or '' }}" placeholder="2025-26" class="w-36 rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Search</label>
|
||||
<input type="text" name="q" value="{{ q or '' }}" placeholder="Client or service" class="w-72 rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
</div>
|
||||
<label class="inline-flex items-center gap-2 pb-2 text-sm text-slate-700">
|
||||
<input type="checkbox" name="include_inactive" value="true" {% if include_inactive %}checked{% endif %}>
|
||||
Include inactive
|
||||
</label>
|
||||
<button class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Filter</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/services/engagements/bulk-lock" class="space-y-3" onsubmit="return confirm('Lock selected engagements? Locked engagements become read-only history.');">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input type="hidden" name="financial_year" value="{{ financial_year or '' }}">
|
||||
<input type="hidden" name="q" value="{{ q or '' }}">
|
||||
{% if include_inactive %}<input type="hidden" name="include_inactive" value="true">{% endif %}
|
||||
{% if can_lock_engagements %}
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-slate-200 bg-white p-4 shadow-soft">
|
||||
<p class="text-sm text-slate-600">Select completed engagements and lock them in bulk. Locked engagements become read-only history.</p>
|
||||
<button type="submit" class="rounded-xl bg-amber-600 px-4 py-2 text-sm font-medium text-white hover:bg-amber-700">Lock Selected Engagements</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<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>
|
||||
{% if can_lock_engagements %}<th class="w-10 px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500"><input type="checkbox" onclick="document.querySelectorAll('.engagement-lock-checkbox').forEach(cb => cb.checked = this.checked)"></th>{% endif %}
|
||||
<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">Service</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">FY / AY</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Due Date</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Type</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Assigned Users</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 text-right text-xs font-semibold uppercase tracking-wide text-slate-500">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for row in rows %}
|
||||
<tr>
|
||||
{% if can_lock_engagements %}<td class="px-4 py-3 text-sm">{% if not row.is_locked %}<input type="checkbox" class="engagement-lock-checkbox rounded border-slate-300" name="subscription_ids" value="{{ row.id }}">{% endif %}</td>{% endif %}
|
||||
<td class="px-4 py-3 text-sm"><div class="font-medium text-slate-900">{{ row.client.client_name if row.client else '-' }}</div><div class="text-xs text-slate-500">{{ row.client.client_code if row.client else '' }}</div></td>
|
||||
<td class="px-4 py-3 text-sm"><div class="font-medium text-slate-900">{{ row.catalogue.service_name if row.catalogue else '-' }}</div><div class="text-xs text-slate-500">{{ row.catalogue.service_code if row.catalogue else '' }}</div></td>
|
||||
<td class="px-4 py-3 text-xs text-slate-600"><div>FY: {{ row.financial_year or '-' }}</div><div>AY: {{ row.assessment_year or '-' }}</div></td>
|
||||
<td class="px-4 py-3 text-xs text-slate-600"><div>Current: <span class="font-medium text-slate-900">{{ row.current_due_date or '-' }}</span></div><div>Original: {{ row.original_due_date or '-' }}</div>{% if row.expiry_date %}<div>Expiry: {{ row.expiry_date }}</div>{% endif %}{% if row.due_date_source %}<div class="text-slate-500">{{ row.due_date_source|replace('_',' ')|title }}</div>{% endif %}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ 'Assurance' if row.engagement_type == 'assurance' else 'Non-Assurance' }}</td>
|
||||
<td class="px-4 py-3 text-xs text-slate-600"><div>Partner: {{ row.assigned_partner.full_name if row.assigned_partner and row.assigned_partner.full_name else (row.assigned_partner.email if row.assigned_partner else '-') }}</div><div>Manager: {{ row.assigned_manager.full_name if row.assigned_manager and row.assigned_manager.full_name else (row.assigned_manager.email if row.assigned_manager else '-') }}</div><div>Staff: {{ row.assigned_staff.full_name if row.assigned_staff and row.assigned_staff.full_name else (row.assigned_staff.email if row.assigned_staff else '-') }}</div><div>Review: {{ row.review_partner.full_name if row.review_partner and row.review_partner.full_name else (row.review_partner.email if row.review_partner else '-') }}</div></td>
|
||||
<td class="px-4 py-3 text-sm"><span class="rounded-full px-2 py-1 text-xs font-medium {% if row.is_locked %}bg-amber-100 text-amber-700{% elif row.is_active and row.status == 'active' %}bg-emerald-100 text-emerald-700{% else %}bg-slate-200 text-slate-700{% endif %}">{{ 'Locked' if row.is_locked else row.status|replace('_',' ')|title }}{% if not row.is_active %} / Inactive{% endif %}</span></td>
|
||||
<td class="px-4 py-3 text-right text-sm"><a href="/services/engagements/{{ row.id }}" class="font-medium text-brand-700 hover:underline">View</a>{% if can_manage and not row.is_locked %}<a href="/services/engagements/{{ row.id }}/edit" class="ml-3 font-medium text-brand-700 hover:underline">Edit</a>{% endif %}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="{{ 9 if can_lock_engagements else 8 }}" class="px-4 py-8 text-center text-sm text-slate-500">No engagements found for this financial year.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,60 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="mx-auto max-w-3xl rounded-2xl bg-white p-6 shadow-soft">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Edit Firm Task Template</h2>
|
||||
<p class="text-sm text-slate-500">{{ service.service_code }} · {{ service.service_name }}</p>
|
||||
</div>
|
||||
<a href="/services/templates/{{ service.id }}" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Back</a>
|
||||
</div>
|
||||
|
||||
<form method="post" class="mt-6 grid gap-5 md:grid-cols-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Task Name</label>
|
||||
<input type="text" name="task_name" value="{{ task.task_name }}" required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Default Role</label>
|
||||
<input type="text" name="default_role_name" value="{{ task.default_role_name or '' }}" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Sequence No</label>
|
||||
<input type="number" min="1" name="sequence_no" value="{{ task.sequence_no }}" required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-6 pt-7 text-sm text-slate-700">
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<input type="checkbox" name="is_mandatory" {% if task.is_mandatory %}checked{% endif %}>
|
||||
Mandatory
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<input type="checkbox" name="requires_review" {% if task.requires_review %}checked{% endif %}>
|
||||
Review
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<input type="checkbox" name="is_active" {% if task.is_active %}checked{% endif %}>
|
||||
Active
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2">
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Description</label>
|
||||
<textarea name="description" rows="4" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">{{ task.description or '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2 rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800">
|
||||
To deactivate this task, untick <strong>Active</strong> and save. No hard delete is used.
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2 flex justify-end gap-3">
|
||||
<a href="/services/templates/{{ service.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,4 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="mx-auto max-w-3xl space-y-6"><div class="rounded-3xl bg-white p-6 shadow-soft"><h2 class="text-xl font-semibold text-slate-900">Import Service Catalogue + Firm Task Templates</h2><p class="mt-2 text-sm text-slate-500">This import updates the system-level service catalogue and creates or updates task templates for your current firm.</p><div class="mt-4 flex gap-3"><a href="/services/catalogue/import/template" class="rounded-xl border border-slate-300 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50">Download Template</a><a href="/services/catalogue" class="rounded-xl border border-slate-300 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50">Back to Catalogue</a></div><form method="post" action="/services/catalogue/import/preview" enctype="multipart/form-data" class="mt-6 space-y-4"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><div><label class="mb-2 block text-sm font-medium text-slate-700">Upload Filled Workbook (.xlsx)</label><input type="file" name="workbook" accept=".xlsx" required class="block w-full rounded-2xl border border-slate-300 px-4 py-2.5 text-sm"></div><div class="flex gap-3"><button class="rounded-xl bg-brand-600 px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-700">Preview Import</button><a href="/services/catalogue" class="rounded-xl border border-slate-300 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50">Cancel</a></div></form></div></div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,4 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6"><div class="rounded-3xl bg-white p-6 shadow-soft"><h2 class="text-xl font-semibold text-slate-900">Import Preview</h2><p class="mt-2 text-sm text-slate-500">Audit Firm ID {{ tenant_id }} • Branch {{ branch_id }}</p>{% if preview.errors %}<div class="mt-4 rounded-2xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-900"><div class="font-medium">Please fix these issues before importing:</div><ul class="mt-2 list-disc pl-5">{% for err in preview.errors %}<li>{{ err }}</li>{% endfor %}</ul></div><div class="mt-4"><a href="/services/catalogue/import" class="rounded-xl border border-slate-300 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50">Back</a></div>{% else %}<div class="grid gap-4 md:grid-cols-2"><div class="rounded-2xl bg-slate-50 p-4"><div class="text-xs uppercase text-slate-500">Catalogue Rows</div><div class="mt-1 text-xl font-semibold text-slate-900">{{ preview.catalogue_rows|length }}</div></div><div class="rounded-2xl bg-slate-50 p-4"><div class="text-xs uppercase text-slate-500">Task Template Rows</div><div class="mt-1 text-xl font-semibold text-slate-900">{{ preview.task_rows|length }}</div></div></div><div class="mt-6 rounded-2xl border border-slate-200"><div class="border-b border-slate-200 bg-slate-50 px-4 py-3 text-sm font-medium text-slate-700">Catalogue Preview</div><div class="overflow-x-auto"><table class="min-w-full divide-y divide-slate-200"><thead class="bg-white"><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">Name</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Category</th></tr></thead><tbody class="divide-y divide-slate-100">{% for row in preview.catalogue_rows[:15] %}<tr><td class="px-4 py-3 text-sm">{{ row.service_code }}</td><td class="px-4 py-3 text-sm">{{ row.service_name }}</td><td class="px-4 py-3 text-sm">{{ row.category or '-' }}</td></tr>{% endfor %}</tbody></table></div></div>{% if preview.task_rows %}<div class="rounded-2xl border border-slate-200"><div class="border-b border-slate-200 bg-slate-50 px-4 py-3 text-sm font-medium text-slate-700">Firm Task Template Preview</div><div class="overflow-x-auto"><table class="min-w-full divide-y divide-slate-200"><thead class="bg-white"><tr><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Service</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Seq</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Task</th></tr></thead><tbody class="divide-y divide-slate-100">{% for row in preview.task_rows[:20] %}<tr><td class="px-4 py-3 text-sm">{{ row.service_code }}</td><td class="px-4 py-3 text-sm">{{ row.sequence_no }}</td><td class="px-4 py-3 text-sm">{{ row.task_name }}</td></tr>{% endfor %}</tbody></table></div></div>{% endif %}<form method="post" action="/services/catalogue/import/confirm" class="flex gap-3"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="payload_json" value="{{ payload_json|e }}"><button class="rounded-xl bg-brand-600 px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-700">Confirm Import</button><a href="/services/catalogue/import" class="rounded-xl border border-slate-300 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50">Back</a></form>{% endif %}</div></div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,88 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Engagements</h2>
|
||||
<p class="text-sm text-slate-500">Year-wise client service engagements with locking for completed years.</p>
|
||||
</div>
|
||||
{% if can_manage %}
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="/services/bulk-imports" class="rounded-xl border border-emerald-300 px-4 py-2 text-sm font-medium text-emerald-700 hover:bg-emerald-50">Bulk Assign by Excel</a>
|
||||
<a href="/services/engagements/new" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Assign Service</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if locked_count or skipped_count %}
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-4 text-sm shadow-soft">
|
||||
{% if locked_count %}<span class="font-medium text-emerald-700">{{ locked_count }} engagement{{ 's' if locked_count != 1 else '' }} locked.</span>{% endif %}
|
||||
{% if skipped_count %}<span class="ml-2 font-medium text-amber-700">{{ skipped_count }} skipped because already locked or not permitted.</span>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="get" class="flex flex-wrap items-end gap-3 rounded-2xl bg-white p-4 shadow-soft">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Financial Year</label>
|
||||
<input type="text" name="financial_year" value="{{ financial_year or '' }}" placeholder="2025-26" class="w-36 rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Search</label>
|
||||
<input type="text" name="q" value="{{ q or '' }}" placeholder="Client or service" class="w-72 rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
</div>
|
||||
<label class="inline-flex items-center gap-2 pb-2 text-sm text-slate-700">
|
||||
<input type="checkbox" name="include_inactive" value="true" {% if include_inactive %}checked{% endif %}>
|
||||
Include inactive
|
||||
</label>
|
||||
<button class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Filter</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/services/engagements/bulk-lock" class="space-y-3" onsubmit="return confirm('Lock selected engagements? Locked engagements become read-only history.');">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input type="hidden" name="financial_year" value="{{ financial_year or '' }}">
|
||||
<input type="hidden" name="q" value="{{ q or '' }}">
|
||||
{% if include_inactive %}<input type="hidden" name="include_inactive" value="true">{% endif %}
|
||||
{% if can_lock_engagements %}
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-slate-200 bg-white p-4 shadow-soft">
|
||||
<p class="text-sm text-slate-600">Select completed engagements and lock them in bulk. Locked engagements become read-only history.</p>
|
||||
<button type="submit" class="rounded-xl bg-amber-600 px-4 py-2 text-sm font-medium text-white hover:bg-amber-700">Lock Selected Engagements</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<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>
|
||||
{% if can_lock_engagements %}<th class="w-10 px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500"><input type="checkbox" onclick="document.querySelectorAll('.engagement-lock-checkbox').forEach(cb => cb.checked = this.checked)"></th>{% endif %}
|
||||
<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">Service</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">FY / AY</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Due Date</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Type</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Assigned Users</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 text-right text-xs font-semibold uppercase tracking-wide text-slate-500">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for row in rows %}
|
||||
<tr>
|
||||
{% if can_lock_engagements %}<td class="px-4 py-3 text-sm">{% if not row.is_locked %}<input type="checkbox" class="engagement-lock-checkbox rounded border-slate-300" name="subscription_ids" value="{{ row.id }}">{% endif %}</td>{% endif %}
|
||||
<td class="px-4 py-3 text-sm"><div class="font-medium text-slate-900">{{ row.client.client_name if row.client else '-' }}</div><div class="text-xs text-slate-500">{{ row.client.client_code if row.client else '' }}</div></td>
|
||||
<td class="px-4 py-3 text-sm"><div class="font-medium text-slate-900">{{ row.catalogue.service_name if row.catalogue else '-' }}</div><div class="text-xs text-slate-500">{{ row.catalogue.service_code if row.catalogue else '' }}</div></td>
|
||||
<td class="px-4 py-3 text-xs text-slate-600"><div>FY: {{ row.financial_year or '-' }}</div><div>AY: {{ row.assessment_year or '-' }}</div></td>
|
||||
<td class="px-4 py-3 text-xs text-slate-600"><div>Current: <span class="font-medium text-slate-900">{{ row.current_due_date or '-' }}</span></div><div>Original: {{ row.original_due_date or '-' }}</div>{% if row.expiry_date %}<div>Expiry: {{ row.expiry_date }}</div>{% endif %}{% if row.due_date_source %}<div class="text-slate-500">{{ row.due_date_source|replace('_',' ')|title }}</div>{% endif %}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ 'Assurance' if row.engagement_type == 'assurance' else 'Non-Assurance' }}</td>
|
||||
<td class="px-4 py-3 text-xs text-slate-600"><div>Partner: {{ row.assigned_partner.full_name if row.assigned_partner and row.assigned_partner.full_name else (row.assigned_partner.email if row.assigned_partner else '-') }}</div><div>Manager: {{ row.assigned_manager.full_name if row.assigned_manager and row.assigned_manager.full_name else (row.assigned_manager.email if row.assigned_manager else '-') }}</div><div>Staff: {{ row.assigned_staff.full_name if row.assigned_staff and row.assigned_staff.full_name else (row.assigned_staff.email if row.assigned_staff else '-') }}</div><div>Review: {{ row.review_partner.full_name if row.review_partner and row.review_partner.full_name else (row.review_partner.email if row.review_partner else '-') }}</div></td>
|
||||
<td class="px-4 py-3 text-sm"><span class="rounded-full px-2 py-1 text-xs font-medium {% if row.is_locked %}bg-amber-100 text-amber-700{% elif row.is_active and row.status == 'active' %}bg-emerald-100 text-emerald-700{% else %}bg-slate-200 text-slate-700{% endif %}">{{ 'Locked' if row.is_locked else row.status|replace('_',' ')|title }}{% if not row.is_active %} / Inactive{% endif %}</span></td>
|
||||
<td class="px-4 py-3 text-right text-sm"><a href="/services/engagements/{{ row.id }}" class="font-medium text-brand-700 hover:underline">View</a>{% if can_view_documents(current_user, current_user_permissions, current_user_roles) %}<a href="/documents/engagements/{{ row.id }}" class="ml-3 font-medium text-brand-700 hover:underline">Docs</a>{% endif %}{% if can_manage and not row.is_locked %}<a href="/services/engagements/{{ row.id }}/edit" class="ml-3 font-medium text-brand-700 hover:underline">Edit</a>{% endif %}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="{{ 9 if can_lock_engagements else 8 }}" class="px-4 py-8 text-center text-sm text-slate-500">No engagements found for this financial year.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,181 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Firm Task Templates</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
{{ service.service_code }} · {{ service.service_name }}
|
||||
{% if selection and selection.default_branch_id %} · Default Branch ID {{ selection.default_branch_id }}{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="/services/templates" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Back to Templates</a>
|
||||
<a href="/services/catalogue/{{ service.id }}" class="rounded-xl border border-brand-300 px-4 py-2 text-sm font-medium text-brand-700 hover:bg-brand-50">View Catalogue</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if request.query_params.get('requirement_added') %}<div class="rounded-2xl border border-emerald-200 bg-emerald-50 p-4 text-sm text-emerald-800">Document requirement added successfully.</div>{% endif %}
|
||||
{% if request.query_params.get('template_uploaded') %}<div class="rounded-2xl border border-emerald-200 bg-emerald-50 p-4 text-sm text-emerald-800">Template file uploaded successfully.</div>{% endif %}
|
||||
{% if request.query_params.get('error') %}<div class="rounded-2xl border border-red-200 bg-red-50 p-4 text-sm text-red-800">Action failed. Please check the selected task, file and permissions.</div>{% endif %}
|
||||
|
||||
<section class="grid gap-4 md:grid-cols-3">
|
||||
<div class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Firm Status</div>
|
||||
<div class="mt-2 text-lg font-semibold text-slate-900">{{ 'Enabled' if selection and selection.is_enabled else 'Not Enabled' }}</div>
|
||||
<p class="mt-1 text-sm text-slate-500">Firm-level service selection controls whether these task templates are used.</p>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Firm Tasks</div>
|
||||
<div class="mt-2 text-lg font-semibold text-slate-900">{{ task_templates|length }}</div>
|
||||
<p class="mt-1 text-sm text-slate-500">Tasks customised for the active firm.</p>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">System Defaults</div>
|
||||
<div class="mt-2 text-lg font-semibold text-slate-900">{{ default_tasks|length }}</div>
|
||||
<p class="mt-1 text-sm text-slate-500">Defaults may be copied and customised for the firm.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% if can_manage_tasks %}
|
||||
<section class="rounded-2xl bg-white p-5 shadow-soft">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-slate-900">Add Firm Task</h3>
|
||||
<p class="text-sm text-slate-500">Create a task template for this firm and service.</p>
|
||||
</div>
|
||||
{% if default_tasks|length > 0 %}
|
||||
<form method="post" action="/services/catalogue/{{ service.id }}/defaults/copy-to-firm">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<button class="rounded-xl border border-brand-300 px-4 py-2 text-sm font-medium text-brand-700 hover:bg-brand-50" type="submit">Copy System Defaults</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<form method="post" action="/services/templates/{{ service.id }}/tasks/new" class="mt-5 grid gap-4 lg:grid-cols-12">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<div class="lg:col-span-4">
|
||||
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Task Name</label>
|
||||
<input name="task_name" required class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" placeholder="e.g. Verify books with GSTR-2B">
|
||||
</div>
|
||||
<div class="lg:col-span-3">
|
||||
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Default Role</label>
|
||||
<input name="default_role_name" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" placeholder="Staff / Manager / Partner">
|
||||
</div>
|
||||
<div class="lg:col-span-2">
|
||||
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Sequence</label>
|
||||
<input name="sequence_no" type="number" min="1" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" placeholder="Auto">
|
||||
</div>
|
||||
<div class="lg:col-span-3">
|
||||
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Options</label>
|
||||
<div class="flex flex-wrap gap-3 rounded-xl border border-slate-200 px-3 py-2 text-xs text-slate-700">
|
||||
<label class="inline-flex items-center gap-1"><input type="checkbox" name="is_mandatory" checked> Mandatory</label>
|
||||
<label class="inline-flex items-center gap-1"><input type="checkbox" name="requires_review"> Review</label>
|
||||
<label class="inline-flex items-center gap-1"><input type="checkbox" name="is_active" checked> Active</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lg:col-span-10">
|
||||
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Description</label>
|
||||
<textarea name="description" rows="2" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" placeholder="Optional instructions for staff or reviewer"></textarea>
|
||||
</div>
|
||||
<div class="flex items-end lg:col-span-2">
|
||||
<button class="w-full rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700" type="submit">Add Task</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<section class="overflow-hidden rounded-2xl bg-white shadow-soft">
|
||||
<div class="border-b border-slate-100 px-5 py-4">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Firm Task List</h3>
|
||||
<p class="text-sm text-slate-500">These tasks will be used when work is generated for this firm.</p>
|
||||
</div>
|
||||
<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">Seq</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Task</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Role</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Flags</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Document Requirements</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Template Uploads</th>
|
||||
<th class="px-4 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for task in task_templates %}
|
||||
<tr>
|
||||
<td class="px-4 py-3 text-sm font-medium text-slate-900">{{ task.sequence_no }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">
|
||||
<div class="font-medium text-slate-900">{{ task.task_name }}</div>
|
||||
{% if task.description %}<div class="mt-1 text-xs text-slate-500">{{ task.description }}</div>{% endif %}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ task.default_role_name or '-' }}</td>
|
||||
<td class="px-4 py-3 text-xs text-slate-600">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{% if task.is_mandatory %}<span class="rounded-full bg-slate-100 px-2 py-1">Mandatory</span>{% endif %}
|
||||
{% if task.requires_review %}<span class="rounded-full bg-amber-100 px-2 py-1 text-amber-800">Review</span>{% endif %}
|
||||
<span class="rounded-full px-2 py-1 {{ 'bg-emerald-100 text-emerald-800' if task.is_active else 'bg-slate-100 text-slate-500' }}">{{ 'Active' if task.is_active else 'Inactive' }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-xs text-slate-600 align-top">
|
||||
{% set reqs = task_requirement_map.get(task.id, []) if task_requirement_map else [] %}
|
||||
<div class="space-y-2">
|
||||
{% for req in reqs %}
|
||||
<div class="rounded-lg border border-slate-200 p-2">
|
||||
<div class="font-medium text-slate-800">{{ req.document_name }}</div>
|
||||
<div class="text-[11px] text-slate-500">{{ req.document_type.replace('_',' ').title() }} · {{ 'Mandatory' if req.is_mandatory else 'Optional' }} · {{ 'Active' if req.is_active else 'Inactive' }}</div>
|
||||
{% if can_manage_tasks %}
|
||||
<form method="post" action="/services/templates/{{ service.id }}/tasks/{{ task.id }}/document-requirements/{{ req.id }}/toggle" class="mt-1">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<button class="text-[11px] font-semibold text-brand-700">{{ 'Deactivate' if req.is_active else 'Activate' }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}<span class="text-slate-400">No requirements</span>{% endfor %}
|
||||
</div>
|
||||
{% if can_manage_tasks %}
|
||||
<form method="post" action="/services/templates/{{ service.id }}/tasks/{{ task.id }}/document-requirements/new" class="mt-3 space-y-2 rounded-xl bg-slate-50 p-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input name="document_name" required placeholder="Document name" class="w-full rounded-lg border border-slate-300 px-2 py-1 text-xs">
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<input name="document_type" value="GENERAL" placeholder="Type" class="rounded-lg border border-slate-300 px-2 py-1 text-xs">
|
||||
<input name="sort_order" type="number" value="100" class="rounded-lg border border-slate-300 px-2 py-1 text-xs">
|
||||
</div>
|
||||
<input name="allowed_file_types" placeholder="Allowed: pdf,docx,xlsx" class="w-full rounded-lg border border-slate-300 px-2 py-1 text-xs">
|
||||
<textarea name="instructions" rows="2" placeholder="Instructions" class="w-full rounded-lg border border-slate-300 px-2 py-1 text-xs"></textarea>
|
||||
<label class="inline-flex items-center gap-1 text-[11px]"><input type="checkbox" name="is_mandatory" checked> Mandatory</label>
|
||||
<button class="block rounded-lg bg-slate-900 px-3 py-1 text-xs font-semibold text-white">Add Requirement</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-xs text-slate-600 align-top">
|
||||
{% set files = task_template_file_map.get(task.id, []) if task_template_file_map else [] %}
|
||||
<div class="space-y-1">
|
||||
{% for tpl in files %}
|
||||
<div><a href="/services/document-templates/{{ tpl.id }}/download" class="font-medium text-brand-700 hover:underline">{{ tpl.template_name }}</a><div class="text-[11px] text-slate-500">{{ tpl.template_category or 'Template' }} · {{ tpl.original_filename }}</div></div>
|
||||
{% else %}<span class="text-slate-400">No template files</span>{% endfor %}
|
||||
</div>
|
||||
{% if can_manage_tasks %}
|
||||
<form method="post" action="/services/templates/{{ service.id }}/tasks/{{ task.id }}/document-templates/upload" enctype="multipart/form-data" class="mt-3 space-y-2 rounded-xl bg-slate-50 p-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input name="template_name" placeholder="Template name" class="w-full rounded-lg border border-slate-300 px-2 py-1 text-xs">
|
||||
<input name="template_category" placeholder="NOC / Agreement / Deed" class="w-full rounded-lg border border-slate-300 px-2 py-1 text-xs">
|
||||
<input type="file" name="file" required class="w-full rounded-lg border border-slate-300 px-2 py-1 text-xs">
|
||||
<textarea name="description" rows="2" placeholder="Description" class="w-full rounded-lg border border-slate-300 px-2 py-1 text-xs"></textarea>
|
||||
<button class="block rounded-lg bg-brand-600 px-3 py-1 text-xs font-semibold text-white">Upload Template</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right align-top">
|
||||
{% if can_manage_tasks %}<a href="/services/templates/{{ service.id }}/tasks/{{ task.id }}/edit" class="text-sm font-medium text-brand-700 hover:underline">Edit</a>{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7" class="px-4 py-8 text-center text-sm text-slate-500">No firm task templates yet. Copy defaults or add tasks manually.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,31 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Firm Service Task Templates</h2>
|
||||
<p class="text-sm text-slate-500">Each firm can maintain its own execution template for enabled services.</p>
|
||||
</div>
|
||||
<a href="/services" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Back to Services</a>
|
||||
</div>
|
||||
<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">Service</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Task Count</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.catalogue.service_code }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ row.catalogue.service_name }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ row.task_count }}</td>
|
||||
<td class="px-4 py-3 text-right"><a href="/services/templates/{{ row.catalogue.id }}" class="text-sm font-medium text-brand-700 hover:underline">Open</a></td>
|
||||
</tr>
|
||||
{% else %}<tr><td colspan="4" class="px-4 py-8 text-center text-sm text-slate-500">Enable a service for the firm first.</td></tr>{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,119 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Work Tracker Dashboard</h2>
|
||||
<p class="text-sm text-slate-500">Track engagement tasks, assigned staff, internal target dates and execution status.</p>
|
||||
</div>
|
||||
<a href="/services/work-tracker/subscriptions" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Generate Tasks</a>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-4 xl:grid-cols-8">
|
||||
<a href="/services/work-tracker" class="rounded-2xl bg-white p-4 shadow-soft hover:bg-slate-50"><div class="text-xs text-slate-500">Total</div><div class="mt-1 text-2xl font-semibold text-slate-900">{{ stats.total }}</div></a>
|
||||
<a href="/services/work-tracker?status=pending" class="rounded-2xl bg-white p-4 shadow-soft hover:bg-slate-50"><div class="text-xs text-slate-500">Pending</div><div class="mt-1 text-2xl font-semibold text-slate-900">{{ stats.pending }}</div></a>
|
||||
<a href="/services/work-tracker?status=in_progress" class="rounded-2xl bg-white p-4 shadow-soft hover:bg-slate-50"><div class="text-xs text-slate-500">In Progress</div><div class="mt-1 text-2xl font-semibold text-slate-900">{{ stats.in_progress }}</div></a>
|
||||
<a href="/services/work-tracker?status=blocked" class="rounded-2xl bg-white p-4 shadow-soft hover:bg-slate-50"><div class="text-xs text-slate-500">Blocked</div><div class="mt-1 text-2xl font-semibold text-slate-900">{{ stats.blocked }}</div></a>
|
||||
<a href="/services/work-tracker?status=overdue" class="rounded-2xl bg-white p-4 shadow-soft hover:bg-red-50"><div class="text-xs text-slate-500">Overdue</div><div class="mt-1 text-2xl font-semibold text-red-700">{{ stats.overdue }}</div></a>
|
||||
<a href="/services/work-tracker?status=due_today" class="rounded-2xl bg-white p-4 shadow-soft hover:bg-amber-50"><div class="text-xs text-slate-500">Due Today</div><div class="mt-1 text-2xl font-semibold text-amber-700">{{ stats.due_today }}</div></a>
|
||||
<a href="/services/work-tracker?status=unassigned" class="rounded-2xl bg-white p-4 shadow-soft hover:bg-slate-50"><div class="text-xs text-slate-500">Unassigned</div><div class="mt-1 text-2xl font-semibold text-slate-900">{{ stats.unassigned }}</div></a>
|
||||
<a href="/services/work-tracker?status=completed" class="rounded-2xl bg-white p-4 shadow-soft hover:bg-green-50"><div class="text-xs text-slate-500">Completed</div><div class="mt-1 text-2xl font-semibold text-green-700">{{ stats.completed }}</div></a>
|
||||
</div>
|
||||
|
||||
<form method="get" class="flex flex-wrap items-end gap-3 rounded-2xl bg-white p-4 shadow-soft">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Search</label>
|
||||
<input type="text" name="q" value="{{ q }}" class="rounded-xl border border-slate-300 px-3 py-2 text-sm" placeholder="Client / service / task">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Status / Filter</label>
|
||||
<select name="status" class="rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
<option value="">All</option>
|
||||
<option value="overdue" {% if status == 'overdue' %}selected{% endif %}>Overdue</option>
|
||||
<option value="due_today" {% if status == 'due_today' %}selected{% endif %}>Due Today</option>
|
||||
<option value="unassigned" {% if status == 'unassigned' %}selected{% endif %}>Unassigned</option>
|
||||
{% for code, label in task_statuses %}
|
||||
<option value="{{ code }}" {% if status == code %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Filter</button>
|
||||
{% if q or status %}<a href="/services/work-tracker" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Clear</a>{% endif %}
|
||||
</form>
|
||||
|
||||
<form method="post" action="/services/work-tracker/tasks/bulk-update" class="space-y-4">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
{% if can_bulk_manage %}
|
||||
<div class="rounded-2xl bg-white p-4 shadow-soft">
|
||||
<div class="flex flex-wrap items-end gap-3">
|
||||
<div class="min-w-44">
|
||||
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Bulk Status</label>
|
||||
<select name="bulk_status" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
<option value="">No change</option>
|
||||
{% for code, label in task_statuses %}<option value="{{ code }}">{{ label }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% if can_assign_staff %}
|
||||
<div class="min-w-56">
|
||||
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Bulk Assignee</label>
|
||||
<select name="bulk_assigned_to_user_id" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
<option value="__no_change__">No change</option>
|
||||
<option value="">Unassigned</option>
|
||||
{% for user in assignees %}<option value="{{ user.id }}">{{ user.full_name or user.email }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Internal Target Date</label>
|
||||
<input type="date" name="bulk_internal_target_date" class="rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
<label class="mt-2 flex items-center gap-2 text-xs text-slate-600"><input type="checkbox" name="update_internal_target_date"> Apply target date</label>
|
||||
</div>
|
||||
<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700" onclick="return confirm('Update selected tasks?')">Update Selected Tasks</button>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-slate-500">Select tasks below, then apply status, assignee or internal target date. Locked engagements/tasks are skipped.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<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>
|
||||
{% if can_bulk_manage %}<th class="px-4 py-3 text-left"><input type="checkbox" onclick="document.querySelectorAll('.task-check').forEach(cb => cb.checked = this.checked)"></th>{% endif %}
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Task</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">Service</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Assignee</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Internal Target</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Engagement Due</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 task in tasks %}
|
||||
<tr class="{% if task.is_task_overdue %}bg-red-50{% elif task.is_due_today %}bg-amber-50{% endif %}">
|
||||
{% if can_bulk_manage %}<td class="px-4 py-3"><input type="checkbox" class="task-check" name="task_ids" value="{{ task.id }}"></td>{% endif %}
|
||||
<td class="px-4 py-3 text-sm font-medium text-slate-900">{{ task.sequence_no }}. {{ task.task_name }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ task.client.client_name if task.client else '-' }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ task.catalogue.service_name if task.catalogue else '-' }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ task.assigned_to.full_name if task.assigned_to and task.assigned_to.full_name else (task.assigned_to.email if task.assigned_to else '-') }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">
|
||||
{% if task.internal_target_date %}
|
||||
<span class="{% if task.is_task_overdue %}font-semibold text-red-700{% elif task.is_due_today %}font-semibold text-amber-700{% endif %}">{{ task.internal_target_date }}</span>
|
||||
{% if task.is_task_overdue %}<span class="ml-2 rounded-full bg-red-100 px-2 py-1 text-xs font-medium text-red-700">Overdue</span>{% endif %}
|
||||
{% if task.is_due_today %}<span class="ml-2 rounded-full bg-amber-100 px-2 py-1 text-xs font-medium text-amber-700">Today</span>{% endif %}
|
||||
{% else %}-{% endif %}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{% if task.subscription and task.subscription.current_due_date %}{{ task.subscription.current_due_date }}{% else %}-{% endif %}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700"><span class="rounded-full bg-slate-100 px-2 py-1 text-xs font-medium text-slate-700">{{ task.tracker_status_label }}</span></td>
|
||||
<td class="px-4 py-3 text-right"><a href="/services/work-tracker/tasks/{{ task.id }}/edit" class="text-sm font-medium text-brand-700 hover:underline">Open</a></td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="{% if can_bulk_manage %}9{% else %}8{% endif %}" class="px-4 py-8 text-center text-sm text-slate-500">No execution tasks found. Generate tasks from subscriptions first.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,41 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Generate Service Tasks</h2>
|
||||
<p class="text-sm text-slate-500">Generate execution tasks from firm task templates for active engagement subscriptions.</p>
|
||||
</div>
|
||||
<a href="/services/work-tracker" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Dashboard</a>
|
||||
</div>
|
||||
|
||||
<form method="get" class="rounded-2xl bg-white p-4 shadow-soft">
|
||||
<input type="text" name="q" value="{{ q }}" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" placeholder="Search client or service">
|
||||
</form>
|
||||
|
||||
<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">Client</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Service</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">Tasks</th><th></th></tr></thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for row in rows %}
|
||||
<tr>
|
||||
<td class="px-4 py-3 text-sm text-slate-900">{{ row.subscription.client.client_name if row.subscription.client else '-' }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ row.subscription.catalogue.service_name if row.subscription.catalogue else '-' }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ row.completed_tasks }}/{{ row.total_tasks }} completed · {{ row.open_tasks }} open</td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
{% if can_generate %}
|
||||
<form method="post" action="/services/work-tracker/subscriptions/{{ row.subscription.id }}/generate" class="inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<button class="rounded-xl bg-brand-600 px-3 py-2 text-xs font-medium text-white hover:bg-brand-700">Generate / Sync Tasks</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="4" class="px-4 py-8 text-center text-sm text-slate-500">No active subscriptions found.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,137 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="mx-auto max-w-3xl rounded-2xl bg-white p-6 shadow-soft">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Update Service Task</h2>
|
||||
<p class="text-sm text-slate-500">{{ task.client.client_name if task.client else '-' }} · {{ task.catalogue.service_name if task.catalogue else '-' }}</p>
|
||||
</div>
|
||||
<a href="/services/work-tracker" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Back</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 rounded-xl border border-slate-200 p-4">
|
||||
<div class="text-sm font-semibold text-slate-900">{{ task.sequence_no }}. {{ task.task_name }}</div>
|
||||
{% if task.description %}<p class="mt-2 text-sm text-slate-700">{{ task.description }}</p>{% endif %}
|
||||
<div class="mt-3 grid gap-3 text-sm text-slate-600 md:grid-cols-2">
|
||||
<div><span class="font-medium text-slate-700">Engagement Due Date:</span> {{ task.subscription.current_due_date if task.subscription and task.subscription.current_due_date else '-' }}</div>
|
||||
<div><span class="font-medium text-slate-700">Internal Target Date:</span> {{ task.internal_target_date or '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if task.is_task_overdue %}
|
||||
<div class="mt-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm font-medium text-red-700">This task is overdue based on the internal target date.</div>
|
||||
{% elif task.is_due_today %}
|
||||
<div class="mt-4 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm font-medium text-amber-700">This task is due today based on the internal target date.</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" class="mt-6 grid gap-5 md:grid-cols-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Status</label>
|
||||
<select name="status" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" {% if not can_edit %}disabled{% endif %}>
|
||||
{% for code, label in task_statuses %}<option value="{{ code }}" {% if task.status == code %}selected{% endif %}>{{ label }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Priority</label>
|
||||
<select name="priority" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" {% if not can_manage_fields %}disabled{% endif %}>
|
||||
{% for code, label in task_priorities %}<option value="{{ code }}" {% if task.priority == code %}selected{% endif %}>{{ label }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Internal Target Date</label>
|
||||
<input type="date" name="internal_target_date" value="{{ task.internal_target_date or '' }}" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" {% if not can_manage_fields %}readonly{% endif %}>
|
||||
<p class="mt-1 text-xs text-slate-500">Internal office target date. Statutory due date remains at engagement level.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Assigned To</label>
|
||||
<select name="assigned_to_user_id" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" {% if not can_reassign %}disabled{% endif %}>
|
||||
<option value="">Unassigned</option>
|
||||
{% for user in assignees %}<option value="{{ user.id }}" {% if task.assigned_to_user_id == user.id %}selected{% endif %}>{{ user.full_name or user.email }}</option>{% endfor %}
|
||||
</select>
|
||||
{% if not can_reassign and task.assigned_to %}<p class="mt-1 text-xs text-slate-500">Assignee changes are restricted for this role.</p>{% endif %}
|
||||
{% if can_edit and not can_manage_fields %}<p class="mt-1 text-xs text-slate-500">You can update status and work note only for your own assigned task.</p>{% endif %}
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Remarks / Work Note</label>
|
||||
<textarea name="remarks" rows="4" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" {% if not can_edit %}readonly{% endif %}>{{ task.remarks or '' }}</textarea>
|
||||
</div>
|
||||
<div class="md:col-span-2 flex items-center justify-between gap-3">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-slate-700"><input type="checkbox" name="is_active" {% if task.is_active %}checked{% endif %} {% if not can_manage_fields %}disabled{% endif %}> Active</label>
|
||||
<div class="flex gap-3">
|
||||
<a href="/services/work-tracker" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Cancel</a>
|
||||
{% if can_edit %}<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Save</button>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto mt-6 max-w-3xl rounded-2xl bg-white p-6 shadow-soft">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-slate-900">Task Communication Timeline</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">Record internal notes, client clarifications, consultant clarifications, and partner review notes for this task.</p>
|
||||
</div>
|
||||
{% if task.subscription and task.subscription.is_locked %}
|
||||
<span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-medium text-slate-600">Locked</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if can_add_comment and comment_type_options %}
|
||||
<form method="post" action="/services/work-tracker/tasks/{{ task.id }}/comments" class="mt-5 grid gap-4">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Comment Type</label>
|
||||
<select name="comment_type" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
{% for code, label in comment_type_options %}
|
||||
<option value="{{ code }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Visibility</label>
|
||||
<select name="visibility" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">
|
||||
{% for code, label in task_comment_visibilities %}
|
||||
<option value="{{ code }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="mt-1 text-xs text-slate-500">Client/consultant visibility is stored now and will be used when portals are enabled.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Message</label>
|
||||
<textarea name="message" rows="4" required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm" placeholder="Type the communication note here..."></textarea>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Add Communication</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="mt-5 rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">Communication entry is not available for this task or role.</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-6 space-y-4">
|
||||
{% if comments %}
|
||||
{% for comment in comments %}
|
||||
<div class="rounded-2xl border border-slate-200 p-4">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
{% set type_label = comment.comment_type.replace('_', ' ').title() %}
|
||||
{% set visibility_label = comment.visibility.replace('_', ' ').title() %}
|
||||
<span class="rounded-full bg-slate-900 px-3 py-1 text-xs font-medium text-white">{{ type_label }}</span>
|
||||
<span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-medium text-slate-700">{{ visibility_label }}</span>
|
||||
</div>
|
||||
<div class="text-xs text-slate-500">{{ comment.created_at_utc }}</div>
|
||||
</div>
|
||||
<div class="mt-2 text-sm font-medium text-slate-700">{{ comment.created_by.full_name or comment.created_by.email if comment.created_by else 'System' }}</div>
|
||||
<p class="mt-3 whitespace-pre-wrap text-sm leading-6 text-slate-700">{{ comment.message }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="rounded-xl border border-dashed border-slate-300 px-4 py-6 text-center text-sm text-slate-500">No task communication recorded yet.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user