Add Excel import export and code linking for task categories

This commit is contained in:
A R R R Associates
2026-08-31 12:52:50 +05:30
parent af4f6e8b28
commit fbb8607842
6 changed files with 551 additions and 14 deletions
@@ -1,11 +1,105 @@
{% 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">Task Categories</h2><p class="mt-1 text-sm text-slate-500">{{ service.service_code }} · {{ service.service_name }} · {{ 'System defaults' if system_scope else 'Current firm' }}</p></div><a href="{{ '/services/catalogue/' ~ service.id ~ '/defaults' if system_scope else '/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 to Tasks</a></div>
{% if request.query_params.get('created') %}<div class="rounded-xl border border-emerald-200 bg-emerald-50 p-3 text-sm text-emerald-800">Task category created.</div>{% endif %}
{% if request.query_params.get('updated') %}<div class="rounded-xl border border-emerald-200 bg-emerald-50 p-3 text-sm text-emerald-800">Task category updated. Linked task templates keep the same grouping name.</div>{% endif %}
{% if request.query_params.get('error') %}<div class="rounded-xl border border-rose-200 bg-rose-50 p-3 text-sm text-rose-800">Unable to save this category. Check the code/name and try again.</div>{% endif %}
{% if can_manage %}<section class="rounded-2xl bg-white p-5 shadow-soft"><h3 class="text-sm font-semibold text-slate-900">Add Task Category</h3><form method="post" action="{{ '/services/catalogue/' ~ service.id ~ '/task-categories/new' if system_scope else '/services/templates/' ~ service.id ~ '/categories/new' }}" class="mt-4 grid gap-3 md:grid-cols-4"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input name="name" required placeholder="Category name" class="rounded-xl border border-slate-300 px-3 py-2 text-sm"><input name="code" placeholder="Code (auto if blank)" class="rounded-xl border border-slate-300 px-3 py-2 text-sm"><input name="sort_order" type="number" value="100" class="rounded-xl border border-slate-300 px-3 py-2 text-sm"><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Add Category</button></form></section>{% endif %}
<section 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 text-slate-500">Order</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Code</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Category</th><th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Status</th><th class="px-4 py-3"></th></tr></thead><tbody class="divide-y divide-slate-100">{% for row in rows %}<tr>{% if can_manage %}<form method="post" action="{{ '/services/catalogue/' ~ service.id ~ '/task-categories/' ~ row.id ~ '/update' if system_scope else '/services/templates/' ~ service.id ~ '/categories/' ~ row.id ~ '/update' }}"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><td class="px-4 py-3"><input name="sort_order" type="number" value="{{ row.sort_order }}" class="w-20 rounded-lg border border-slate-300 px-2 py-1 text-sm"></td><td class="px-4 py-3"><input name="code" value="{{ row.code }}" required class="w-36 rounded-lg border border-slate-300 px-2 py-1 text-sm"></td><td class="px-4 py-3"><input name="name" value="{{ row.name }}" required class="w-full rounded-lg border border-slate-300 px-2 py-1 text-sm"></td><td class="px-4 py-3 text-sm"><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_active" {% if row.is_active %}checked{% endif %}> Active</label></td><td class="px-4 py-3 text-right"><button class="rounded-lg border border-brand-200 bg-brand-50 px-3 py-1.5 text-xs font-semibold text-brand-700">Save</button></td></form>{% else %}<td class="px-4 py-3 text-sm">{{ row.sort_order }}</td><td class="px-4 py-3 text-sm">{{ row.code }}</td><td class="px-4 py-3 text-sm font-medium">{{ row.name }}</td><td class="px-4 py-3 text-sm">{{ 'Active' if row.is_active else 'Inactive' }}</td><td></td>{% endif %}</tr>{% else %}<tr><td colspan="5" class="px-4 py-8 text-center text-sm text-slate-500">No task categories yet. Existing uncategorised tasks continue to work normally.</td></tr>{% endfor %}</tbody></table></section>
<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">Task Categories</h2>
<p class="mt-1 text-sm text-slate-500">{{ service.service_code }} · {{ service.service_name }} · {{ 'System defaults' if system_scope else 'Current firm' }}</p>
</div>
<div class="flex flex-wrap gap-2">
<a href="{{ '/services/catalogue/' ~ service.id ~ '/task-categories/export' if system_scope else '/services/templates/' ~ service.id ~ '/categories/export' }}" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Export Excel</a>
<a href="{{ '/services/catalogue/' ~ service.id ~ '/defaults' if system_scope else '/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 to Tasks</a>
</div>
</div>
{% if request.query_params.get('created') %}
<div class="rounded-xl border border-emerald-200 bg-emerald-50 p-3 text-sm text-emerald-800">Task category created.</div>
{% endif %}
{% if request.query_params.get('updated') %}
<div class="rounded-xl border border-emerald-200 bg-emerald-50 p-3 text-sm text-emerald-800">Task category updated. Linked task templates keep the same grouping name.</div>
{% endif %}
{% if request.query_params.get('imported') %}
<div class="rounded-xl border border-emerald-200 bg-emerald-50 p-3 text-sm text-emerald-800">
Task Category Excel imported successfully. Created {{ request.query_params.get('created', '0') }} and updated {{ request.query_params.get('updated', '0') }} categories.
</div>
{% endif %}
{% if request.query_params.get('import_error') %}
<div class="rounded-xl border border-rose-200 bg-rose-50 p-3 text-sm text-rose-800">
Task Category import was not applied because {{ request.query_params.get('import_error') }} row(s) failed validation. Correct the workbook and import it again; no partial category changes were committed.
</div>
{% endif %}
{% if request.query_params.get('error') %}
<div class="rounded-xl border border-rose-200 bg-rose-50 p-3 text-sm text-rose-800">Unable to save this category. Check the code/name and try again.</div>
{% endif %}
{% if can_manage %}
<section class="rounded-2xl bg-white p-5 shadow-soft">
<div class="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
<div>
<h3 class="text-sm font-semibold text-slate-900">Excel Import / Export</h3>
<p class="mt-1 max-w-3xl text-sm text-slate-500">
Export the current category master, edit it in Excel and import it back. Categories are matched by <strong>service_code + category_code</strong>; the stable category code can then be used in Task Excel through <strong>task_category_code</strong>. Existing name-only task imports remain supported.
</p>
</div>
<form method="post" enctype="multipart/form-data" action="{{ '/services/catalogue/' ~ service.id ~ '/task-categories/import' if system_scope else '/services/templates/' ~ service.id ~ '/categories/import' }}" class="flex flex-col gap-2 sm:flex-row sm:items-center">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="file" name="file" accept=".xlsx" required class="block max-w-sm rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm text-slate-700 file:mr-3 file:rounded-lg file:border-0 file:bg-slate-100 file:px-3 file:py-1.5 file:text-xs file:font-semibold file:text-slate-700">
<button class="whitespace-nowrap rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Import Categories</button>
</form>
</div>
<div class="mt-4 rounded-xl border border-blue-200 bg-blue-50 p-3 text-xs text-blue-800">
Excel columns: <strong>service_code</strong>, <strong>category_code</strong>, <strong>category_name</strong>, <strong>sort_order</strong>, <strong>is_active</strong>. Import never deletes categories omitted from the workbook.
</div>
</section>
<section class="rounded-2xl bg-white p-5 shadow-soft">
<h3 class="text-sm font-semibold text-slate-900">Add Task Category</h3>
<form method="post" action="{{ '/services/catalogue/' ~ service.id ~ '/task-categories/new' if system_scope else '/services/templates/' ~ service.id ~ '/categories/new' }}" class="mt-4 grid gap-3 md:grid-cols-4">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input name="name" required placeholder="Category name" class="rounded-xl border border-slate-300 px-3 py-2 text-sm">
<input name="code" placeholder="Code (auto if blank)" class="rounded-xl border border-slate-300 px-3 py-2 text-sm">
<input name="sort_order" type="number" value="100" class="rounded-xl border border-slate-300 px-3 py-2 text-sm">
<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Add Category</button>
</form>
</section>
{% endif %}
<section 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 text-slate-500">Order</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Code</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Category</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Status</th>
<th class="px-4 py-3"></th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{% for row in rows %}
<tr>
{% if can_manage %}
<form method="post" action="{{ '/services/catalogue/' ~ service.id ~ '/task-categories/' ~ row.id ~ '/update' if system_scope else '/services/templates/' ~ service.id ~ '/categories/' ~ row.id ~ '/update' }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<td class="px-4 py-3"><input name="sort_order" type="number" value="{{ row.sort_order }}" class="w-20 rounded-lg border border-slate-300 px-2 py-1 text-sm"></td>
<td class="px-4 py-3"><input name="code" value="{{ row.code }}" required class="w-36 rounded-lg border border-slate-300 px-2 py-1 text-sm"></td>
<td class="px-4 py-3"><input name="name" value="{{ row.name }}" required class="w-full rounded-lg border border-slate-300 px-2 py-1 text-sm"></td>
<td class="px-4 py-3 text-sm"><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_active" {% if row.is_active %}checked{% endif %}> Active</label></td>
<td class="px-4 py-3 text-right"><button class="rounded-lg border border-brand-200 bg-brand-50 px-3 py-1.5 text-xs font-semibold text-brand-700">Save</button></td>
</form>
{% else %}
<td class="px-4 py-3 text-sm">{{ row.sort_order }}</td>
<td class="px-4 py-3 text-sm">{{ row.code }}</td>
<td class="px-4 py-3 text-sm font-medium">{{ row.name }}</td>
<td class="px-4 py-3 text-sm">{{ 'Active' if row.is_active else 'Inactive' }}</td>
<td></td>
{% endif %}
</tr>
{% else %}
<tr><td colspan="5" class="px-4 py-8 text-center text-sm text-slate-500">No task categories yet. Export Excel to get the correct column format, fill the categories and import it here, or add them manually above.</td></tr>
{% endfor %}
</tbody>
</table>
</section>
</div>
{% endblock %}