Prepare ERP source for Gitea deployment
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold">Create Branch</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
System Admin can create branches for any audit firm. Firm Admin can create branches only for their own audit firm.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if flash %}
|
||||
<div class="rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900">
|
||||
{{ flash }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form class="rounded-2xl border bg-white p-5 shadow-soft max-w-5xl" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
|
||||
<div class="grid gap-5">
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Firm</span>
|
||||
|
||||
{% if can_change_tenant %}
|
||||
<select class="border rounded-xl px-3 py-2" name="tenant_id" required>
|
||||
<option value="">Select Firm</option>
|
||||
{% for t in tenants %}
|
||||
<option value="{{ t.id }}" {% if selected_tenant_id == t.id %}selected{% endif %}>
|
||||
{{ t.name }} ({{ t.code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<input type="hidden" name="tenant_id" value="{{ selected_tenant_id }}" />
|
||||
<div class="rounded-xl border bg-slate-50 px-3 py-2 text-sm text-slate-700">
|
||||
{% for t in tenants %}
|
||||
{% if t.id == selected_tenant_id %}
|
||||
{{ t.name }} ({{ t.code }})
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Branch Code</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="code" required />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Branch Name</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="name" required />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Timezone</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="timezone" value="Asia/Kolkata" required />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Office Start Time (HH:MM)</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="office_start_time" placeholder="09:30" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Office End Time (HH:MM)</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="office_end_time" placeholder="18:30" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="border-t pt-4">
|
||||
<div class="font-semibold">SMTP Credentials</div>
|
||||
<div class="mt-3 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">SMTP Host</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="smtp_host" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">SMTP Port</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="smtp_port" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">SMTP Username</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="smtp_username" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">SMTP Password</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="smtp_password" />
|
||||
</label>
|
||||
<label class="mt-2 flex items-center gap-2">
|
||||
<input type="checkbox" name="smtp_use_tls" checked />
|
||||
<span class="text-sm text-slate-700">Use TLS</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t pt-4">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Local Storage Path</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="local_storage_path" placeholder="D:\AuditFirm\Firm\Branch" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="border-t pt-4">
|
||||
<div class="font-semibold">Branch Identity (Compliance)</div>
|
||||
<div class="mt-3 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">GSTIN</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="gstin" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">PAN</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="pan" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm text-slate-600">Address Line 1</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="address_line1" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm text-slate-600">Address Line 2</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="address_line2" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">City</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="city" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">State</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="state" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">PIN Code</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="pin_code" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t pt-4">
|
||||
<div class="font-semibold">Attendance Geo/IP Controls</div>
|
||||
<p class="mt-1 text-xs text-slate-500">Enable geo-fenced attendance for this branch. Default radius is 100 meters. IP validation is optional and useful only for office LAN/static public IP.</p>
|
||||
<div class="mt-3 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm text-slate-600">Geo Address / Landmark</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="geo_address" placeholder="Office location / landmark" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Latitude</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="latitude" placeholder="13.082680" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Longitude</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="longitude" placeholder="80.270718" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Allowed Radius (meters)</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="attendance_geo_radius_meters" value="100" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Grace Minutes</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="attendance_grace_minutes" value="10" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Half Day After (HH:MM)</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="attendance_half_day_after_time" placeholder="13:00" />
|
||||
</label>
|
||||
<label class="mt-6 flex items-center gap-2">
|
||||
<input type="checkbox" name="attendance_rule_enabled" checked />
|
||||
<span class="text-sm text-slate-700">Enable timezone/late/half-day rules</span>
|
||||
</label>
|
||||
<label class="mt-6 flex items-center gap-2">
|
||||
<input type="checkbox" name="attendance_geo_enabled" />
|
||||
<span class="text-sm text-slate-700">Enable geo-fenced attendance</span>
|
||||
</label>
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm text-slate-600">Allowed Office IP / CIDR List</span>
|
||||
<textarea class="border rounded-xl px-3 py-2" name="attendance_allowed_ip_csv" rows="2" placeholder="Example: 103.x.x.x, 192.168.1.0/24"></textarea>
|
||||
</label>
|
||||
<label class="flex items-center gap-2">
|
||||
<input type="checkbox" name="attendance_ip_enabled" />
|
||||
<span class="text-sm text-slate-700">Enable optional IP/LAN validation</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button class="rounded-xl bg-slate-900 px-4 py-2 text-sm text-white" type="submit">Create Branch</button>
|
||||
<a class="rounded-xl border px-4 py-2 text-sm" href="/system-settings/branches">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,184 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold">Edit Branch</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
Branch remains mapped to its audit firm. Firm Admin can edit only branches of their own audit firm.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if flash %}
|
||||
<div class="rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900">
|
||||
{{ flash }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form class="rounded-2xl border bg-white p-5 shadow-soft max-w-5xl" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
|
||||
<div class="grid gap-5">
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Firm</span>
|
||||
<div class="rounded-xl border bg-slate-50 px-3 py-2 text-sm text-slate-700">
|
||||
{{ branch.tenant.name if branch.tenant else ('Audit Firm ' ~ branch.tenant_id) }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Branch Code</span>
|
||||
<input class="border rounded-xl bg-slate-50 px-3 py-2" value="{{ branch.code }}" readonly />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Branch Name</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="name" value="{{ branch.name or '' }}" required />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Timezone</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="timezone" value="{{ branch.timezone or 'Asia/Kolkata' }}" required />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Office Start Time (HH:MM)</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="office_start_time" value="{{ branch.office_start_time.strftime('%H:%M') if branch.office_start_time else '' }}" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Office End Time (HH:MM)</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="office_end_time" value="{{ branch.office_end_time.strftime('%H:%M') if branch.office_end_time else '' }}" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="border-t pt-4">
|
||||
<div class="font-semibold">SMTP Credentials</div>
|
||||
<div class="mt-3 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">SMTP Host</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="smtp_host" value="{{ branch.smtp_host or '' }}" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">SMTP Port</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="smtp_port" value="{{ branch.smtp_port or '' }}" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">SMTP Username</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="smtp_username" value="{{ branch.smtp_username or '' }}" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">SMTP Password</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="smtp_password" value="{{ branch.smtp_password or '' }}" />
|
||||
</label>
|
||||
<label class="mt-2 flex items-center gap-2">
|
||||
<input type="checkbox" name="smtp_use_tls" {% if branch.smtp_use_tls %}checked{% endif %} />
|
||||
<span class="text-sm text-slate-700">Use TLS</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t pt-4">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Local Storage Path</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="local_storage_path" value="{{ branch.local_storage_path or '' }}" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="border-t pt-4">
|
||||
<div class="font-semibold">Branch Identity (Compliance)</div>
|
||||
<div class="mt-3 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">GSTIN</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="gstin" value="{{ settings.gstin or '' }}" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">PAN</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="pan" value="{{ settings.pan or '' }}" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm text-slate-600">Address Line 1</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="address_line1" value="{{ settings.address_line1 or '' }}" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm text-slate-600">Address Line 2</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="address_line2" value="{{ settings.address_line2 or '' }}" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">City</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="city" value="{{ settings.city or '' }}" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">State</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="state" value="{{ settings.state or '' }}" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">PIN Code</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="pin_code" value="{{ settings.pin_code or '' }}" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t pt-4">
|
||||
<div class="font-semibold">Attendance Geo/IP Controls</div>
|
||||
<p class="mt-1 text-xs text-slate-500">If geo/IP validation fails, employee punch is still saved but marked pending approval for OD/client visit review.</p>
|
||||
<div class="mt-3 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm text-slate-600">Geo Address / Landmark</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="geo_address" value="{{ settings.geo_address or '' }}" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Latitude</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="latitude" value="{{ settings.latitude if settings.latitude is not none else '' }}" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Longitude</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="longitude" value="{{ settings.longitude if settings.longitude is not none else '' }}" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Allowed Radius (meters)</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="attendance_geo_radius_meters" value="{{ settings.attendance_geo_radius_meters or 100 }}" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Grace Minutes</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="attendance_grace_minutes" value="{{ settings.attendance_grace_minutes if settings.attendance_grace_minutes is not none else 10 }}" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Half Day After (HH:MM)</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="attendance_half_day_after_time" value="{{ settings.attendance_half_day_after_time.strftime('%H:%M') if settings.attendance_half_day_after_time else '' }}" placeholder="13:00" />
|
||||
</label>
|
||||
<label class="mt-6 flex items-center gap-2">
|
||||
<input type="checkbox" name="attendance_rule_enabled" {% if settings.attendance_rule_enabled is not defined or settings.attendance_rule_enabled %}checked{% endif %} />
|
||||
<span class="text-sm text-slate-700">Enable timezone/late/half-day rules</span>
|
||||
</label>
|
||||
<label class="mt-6 flex items-center gap-2">
|
||||
<input type="checkbox" name="attendance_geo_enabled" {% if settings.attendance_geo_enabled %}checked{% endif %} />
|
||||
<span class="text-sm text-slate-700">Enable geo-fenced attendance</span>
|
||||
</label>
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm text-slate-600">Allowed Office IP / CIDR List</span>
|
||||
<textarea class="border rounded-xl px-3 py-2" name="attendance_allowed_ip_csv" rows="2">{{ settings.attendance_allowed_ip_csv or '' }}</textarea>
|
||||
</label>
|
||||
<label class="flex items-center gap-2">
|
||||
<input type="checkbox" name="attendance_ip_enabled" {% if settings.attendance_ip_enabled %}checked{% endif %} />
|
||||
<span class="text-sm text-slate-700">Enable optional IP/LAN validation</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button class="rounded-xl bg-slate-900 px-4 py-2 text-sm text-white" type="submit">Save Branch</button>
|
||||
<a class="rounded-xl border px-4 py-2 text-sm" href="/system-settings/branches">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,63 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% import "ui/templates/components/macros.html" as ui %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
{% set perms = current_user_permissions or [] %}
|
||||
{% set roles = current_user_roles or [] %}
|
||||
{% set add_branch_cta = '<a href="/system-settings/branches/new" class="rounded-xl bg-slate-900 px-4 py-2 text-sm text-white">Add Branch</a>' if can_manage_branches(current_user, perms, roles) else '' %}
|
||||
|
||||
{{ ui.page_shell('Branches', 'Branch directory with search, paging, and quick navigation to branch settings.', add_branch_cta) }}
|
||||
|
||||
<div class="overflow-hidden rounded-3xl bg-white shadow-soft">
|
||||
{{ ui.search_bar('/system-settings/branches', filters.q, filters.per_page) }}
|
||||
|
||||
{% if branches %}
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-slate-50 text-slate-600">
|
||||
<tr>
|
||||
<th class="p-3 text-left">Branch</th>
|
||||
<th class="p-3 text-left">Audit Firm</th>
|
||||
<th class="p-3 text-left">Timezone</th>
|
||||
<th class="p-3 text-left">Flags</th>
|
||||
<th class="p-3 text-left">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for b in branches %}
|
||||
<tr class="border-t align-top">
|
||||
<td class="p-3">
|
||||
<div class="font-medium text-slate-900">{{ b.name }}</div>
|
||||
<div class="text-xs text-slate-500">{{ b.code }}</div>
|
||||
</td>
|
||||
<td class="p-3">
|
||||
{{ tenants[b.tenant_id].name if b.tenant_id in tenants else '-' }}
|
||||
</td>
|
||||
<td class="p-3">{{ b.timezone }}</td>
|
||||
<td class="p-3">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{{ ui.badge('Active','emerald') if b.is_active else ui.badge('Inactive','rose') }}
|
||||
{{ ui.badge('Login enabled','sky') if b.allow_login else ui.badge('Login disabled','amber') }}
|
||||
{% if b.is_head_office %}{{ ui.badge('Head Office','brand') }}{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-3">
|
||||
{% if can_manage_branches(current_user, perms, roles) %}
|
||||
<a class="text-slate-900 underline" href="/system-settings/branches/{{ b.id }}/edit">Edit</a>
|
||||
{% else %}
|
||||
<span class="text-slate-400">View only</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{ ui.pagination(branches_page, '/system-settings/branches', request.url.query) }}
|
||||
{% else %}
|
||||
<div class="p-6">
|
||||
{{ ui.empty_state('No branches found for the current scope.') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,167 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="af-page-shell space-y-6">
|
||||
<div class="flex flex-col gap-3 md:flex-row md:items-end md:justify-between">
|
||||
<div>
|
||||
<h1 class="af-page-title">Firm Branding Settings</h1>
|
||||
<p class="af-page-subtitle">Configure firm logo, favicon, standard colours, contact details and invoice presentation defaults.</p>
|
||||
</div>
|
||||
{% if saved %}
|
||||
<div class="af-badge af-badge-success">Saved successfully</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if can_change_tenant %}
|
||||
<div class="af-card p-4">
|
||||
<form method="get" action="/system-settings/branding" class="grid gap-3 md:grid-cols-3 md:items-end">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Audit Firm</span>
|
||||
<select name="tenant_id" class="af-select" onchange="this.form.submit()">
|
||||
{% for t in tenants %}
|
||||
<option value="{{ t.id }}" {% if tenant and t.id == tenant.id %}selected{% endif %}>{{ t.display_name or t.name }} ({{ t.code }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Branch</span>
|
||||
<select name="branch_id" class="af-select" onchange="this.form.submit()">
|
||||
{% for b in branches %}
|
||||
<option value="{{ b.id }}" {% if branch and b.id == branch.id %}selected{% endif %}>{{ b.name }} ({{ b.code }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<div class="text-sm text-slate-500">System Admin can preview/edit branding for any audit firm. Firm Admin is restricted to own firm.</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data" class="space-y-6">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<input type="hidden" name="tenant_id" value="{{ tenant.id }}" />
|
||||
{% if branch %}<input type="hidden" name="branch_id" value="{{ branch.id }}" />{% endif %}
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-[minmax(0,1fr)_22rem]">
|
||||
<div class="space-y-6">
|
||||
<section class="af-card p-5">
|
||||
<div class="af-section-heading">Brand Identity</div>
|
||||
<div class="mt-4 grid gap-4 md:grid-cols-2">
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm font-medium text-slate-600">Display Name</span>
|
||||
<input class="af-input" name="display_name" value="{{ tenant.display_name or tenant.name }}" placeholder="A R R R & ASSOCIATES" />
|
||||
<span class="text-xs text-slate-500">This name will be used in the sidebar, title bar and future invoice/report headers.</span>
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Primary Colour</span>
|
||||
<input class="af-input h-11" type="color" name="primary_color" value="{{ tenant.primary_color or '#2563eb' }}" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Accent / Dark Colour</span>
|
||||
<input class="af-input h-11" type="color" name="accent_color" value="{{ tenant.accent_color or '#0f172a' }}" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Firm Logo</span>
|
||||
<input class="af-input" type="file" name="logo_file" accept=".png,.jpg,.jpeg,.webp,.svg" />
|
||||
<span class="text-xs text-slate-500">Recommended: transparent PNG/SVG, square or horizontal.</span>
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Favicon</span>
|
||||
<input class="af-input" type="file" name="favicon_file" accept=".ico,.png,.svg" />
|
||||
<span class="text-xs text-slate-500">Recommended: ICO/PNG 32x32 or 64x64.</span>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="af-card p-5">
|
||||
<div class="af-section-heading">Contact & Statutory Details</div>
|
||||
<div class="mt-4 grid gap-4 md:grid-cols-2">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Website</span>
|
||||
<input class="af-input" name="website_url" value="{{ tenant.website_url or '' }}" placeholder="https://arrr.associates" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Contact Email</span>
|
||||
<input class="af-input" name="contact_email" value="{{ tenant.contact_email or '' }}" placeholder="office@example.com" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Contact Mobile</span>
|
||||
<input class="af-input" name="contact_mobile" value="{{ tenant.contact_mobile or '' }}" placeholder="+91 XXXXX XXXXX" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">GSTIN</span>
|
||||
<input class="af-input" name="gstin" value="{{ settings.gstin if settings else '' }}" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">PAN</span>
|
||||
<input class="af-input" name="pan" value="{{ settings.pan if settings else '' }}" />
|
||||
</label>
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm font-medium text-slate-600">Address Line 1</span>
|
||||
<input class="af-input" name="address_line1" value="{{ settings.address_line1 if settings else '' }}" />
|
||||
</label>
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm font-medium text-slate-600">Address Line 2</span>
|
||||
<input class="af-input" name="address_line2" value="{{ settings.address_line2 if settings else '' }}" />
|
||||
</label>
|
||||
<label class="grid gap-1"><span class="text-sm font-medium text-slate-600">City</span><input class="af-input" name="city" value="{{ settings.city if settings else '' }}" /></label>
|
||||
<label class="grid gap-1"><span class="text-sm font-medium text-slate-600">State</span><input class="af-input" name="state" value="{{ settings.state if settings else '' }}" /></label>
|
||||
<label class="grid gap-1"><span class="text-sm font-medium text-slate-600">PIN Code</span><input class="af-input" name="pin_code" value="{{ settings.pin_code if settings else '' }}" /></label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="af-card p-5">
|
||||
<div class="af-section-heading">Invoice / Payment Defaults</div>
|
||||
<div class="mt-4 grid gap-4 md:grid-cols-2">
|
||||
<label class="grid gap-1"><span class="text-sm font-medium text-slate-600">Bank Name</span><input class="af-input" name="bank_name" value="{{ settings.bank_name if settings else '' }}" /></label>
|
||||
<label class="grid gap-1"><span class="text-sm font-medium text-slate-600">Account Name</span><input class="af-input" name="bank_account_name" value="{{ settings.bank_account_name if settings else '' }}" /></label>
|
||||
<label class="grid gap-1"><span class="text-sm font-medium text-slate-600">Account Number</span><input class="af-input" name="bank_account_number" value="{{ settings.bank_account_number if settings else '' }}" /></label>
|
||||
<label class="grid gap-1"><span class="text-sm font-medium text-slate-600">IFSC</span><input class="af-input" name="bank_ifsc" value="{{ settings.bank_ifsc if settings else '' }}" /></label>
|
||||
<label class="grid gap-1 md:col-span-2"><span class="text-sm font-medium text-slate-600">UPI ID</span><input class="af-input" name="upi_id" value="{{ settings.upi_id if settings else '' }}" /></label>
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm font-medium text-slate-600">Invoice Footer Text</span>
|
||||
<textarea class="af-textarea min-h-24" name="invoice_footer_text">{{ settings.invoice_footer_text if settings else '' }}</textarea>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<button class="af-btn af-btn-primary" type="submit">Save Branding</button>
|
||||
<a class="af-btn af-btn-secondary" href="/system-settings">Back to Setup</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="space-y-4">
|
||||
<div class="af-card overflow-hidden">
|
||||
<div class="p-5" style="background: {{ tenant.primary_color or '#2563eb' }}; color: white;">
|
||||
<div class="flex items-center gap-3">
|
||||
{% if tenant.logo_path %}
|
||||
<img src="{{ tenant.logo_path }}" alt="Firm logo" class="h-12 w-12 rounded-2xl bg-white object-contain p-1" />
|
||||
{% else %}
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-white/20 text-lg font-bold">AF</div>
|
||||
{% endif %}
|
||||
<div class="min-w-0">
|
||||
<div class="truncate text-base font-semibold">{{ tenant.display_name or tenant.name }}</div>
|
||||
<div class="truncate text-xs opacity-90">{{ branch.name if branch else 'Default Branch' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-3 p-5 text-sm text-slate-600">
|
||||
<div><span class="font-medium text-slate-800">Email:</span> {{ tenant.contact_email or '-' }}</div>
|
||||
<div><span class="font-medium text-slate-800">Mobile:</span> {{ tenant.contact_mobile or '-' }}</div>
|
||||
<div><span class="font-medium text-slate-800">GSTIN:</span> {{ settings.gstin if settings and settings.gstin else '-' }}</div>
|
||||
<div><span class="font-medium text-slate-800">PAN:</span> {{ settings.pan if settings and settings.pan else '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="af-card-muted p-5 text-sm text-slate-600">
|
||||
<div class="font-semibold text-slate-900">Where this is used</div>
|
||||
<p class="mt-2">This phase immediately applies firm logo, favicon and theme colours to the common layout. Invoice and client portal usage will be refined in later phases.</p>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,108 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
{% set ui_perms = current_user_permissions or [] %}
|
||||
{% set ui_roles = current_user_roles or [] %}
|
||||
|
||||
<h1 class="text-2xl font-semibold">Workspace</h1>
|
||||
<p class="text-slate-600 mt-1">
|
||||
Welcome{% if current_user and current_user.full_name %}, {{ current_user.full_name }}{% endif %}.
|
||||
Use the options available for your access level.
|
||||
</p>
|
||||
|
||||
{% set show_admin_cards =
|
||||
can_view_tenants(current_user, ui_perms, ui_roles)
|
||||
or can_view_branches(current_user, ui_perms, ui_roles)
|
||||
or can_view_users(current_user, ui_perms, ui_roles)
|
||||
or can_view_audit(current_user, ui_perms, ui_roles)
|
||||
or can_view_rbac(current_user, ui_perms, ui_roles)
|
||||
or ("Firm Admin" in ui_roles)
|
||||
%}
|
||||
|
||||
{% if show_admin_cards %}
|
||||
<div class="mt-6 grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||
|
||||
{% if can_view_tenants(current_user, ui_perms, ui_roles) %}
|
||||
<a href="/system-settings/tenants" class="rounded-2xl bg-white border p-5 hover:shadow-sm">
|
||||
<div class="font-semibold">Audit Firms</div>
|
||||
<div class="text-sm text-slate-600 mt-1">Create and manage audit firms.</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if can_view_branches(current_user, ui_perms, ui_roles) %}
|
||||
<a href="/system-settings/branches" class="rounded-2xl bg-white border p-5 hover:shadow-sm">
|
||||
<div class="font-semibold">Branches</div>
|
||||
<div class="text-sm text-slate-600 mt-1">Branch settings, SMTP, storage, identity and policy controls.</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if can_view_settings(current_user, ui_perms, ui_roles) %}
|
||||
<a href="/system-settings/financial-years" class="rounded-2xl bg-white border p-5 hover:shadow-sm">
|
||||
<div class="font-semibold">Financial Years</div>
|
||||
<div class="text-sm text-slate-600 mt-1">Create, switch, lock, backup and manage financial year context.</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if "Firm Admin" in ui_roles or "System Admin" in ui_roles %}
|
||||
<a href="/system-settings/branding" class="rounded-2xl bg-white border p-5 hover:shadow-sm">
|
||||
<div class="font-semibold">Firm Branding</div>
|
||||
<div class="text-sm text-slate-600 mt-1">Logo, favicon, standard colours, contact details and invoice defaults.</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if "Firm Admin" in ui_roles or "System Admin" in ui_roles %}
|
||||
<a href="/email/settings" class="rounded-2xl bg-white border p-5 hover:shadow-sm">
|
||||
<div class="font-semibold">Firm Email / SMTP</div>
|
||||
<div class="text-sm text-slate-600 mt-1">Configure SMTP, IMAP, sender identity and authentication email delivery.</div>
|
||||
</a>
|
||||
<a href="/email/templates" class="rounded-2xl bg-white border p-5 hover:shadow-sm">
|
||||
<div class="font-semibold">Email Templates</div>
|
||||
<div class="text-sm text-slate-600 mt-1">Edit OTP, password reset, invite and notification email templates.</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if can_view_users(current_user, ui_perms, ui_roles) %}
|
||||
<a href="/system-settings/users" class="rounded-2xl bg-white border p-5 hover:shadow-sm">
|
||||
<div class="font-semibold">Users</div>
|
||||
<div class="text-sm text-slate-600 mt-1">Manage user accounts, access and lifecycle actions.</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if can_view_rbac(current_user, ui_perms, ui_roles) %}
|
||||
<a href="/system-settings/rbac/roles" class="rounded-2xl bg-white border p-5 hover:shadow-sm">
|
||||
<div class="font-semibold">RBAC</div>
|
||||
<div class="text-sm text-slate-600 mt-1">Configure roles and permissions.</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if can_view_audit(current_user, ui_perms, ui_roles) %}
|
||||
<a href="/system-settings/audit-logs" class="rounded-2xl bg-white border p-5 hover:shadow-sm">
|
||||
<div class="font-semibold">Audit Logs</div>
|
||||
<div class="text-sm text-slate-600 mt-1">Review recorded actions for your allowed scope.</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="mt-6 rounded-2xl border bg-white p-6 shadow-soft">
|
||||
<div class="text-lg font-semibold text-slate-900">Your workspace is ready</div>
|
||||
<p class="mt-2 text-sm text-slate-600">
|
||||
Admin modules are not assigned to your current role. Profile, task, and other user-facing features can be added here next.
|
||||
</p>
|
||||
|
||||
<div class="mt-4 grid gap-3 md:grid-cols-2">
|
||||
<div class="rounded-xl border border-slate-200 bg-slate-50 p-4">
|
||||
<div class="font-medium text-slate-800">Profile / Account</div>
|
||||
<div class="mt-1 text-sm text-slate-600">
|
||||
Use this area later for profile, password change, notifications, and personal settings.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-slate-200 bg-slate-50 p-4">
|
||||
<div class="font-medium text-slate-800">Upcoming Features</div>
|
||||
<div class="mt-1 text-sm text-slate-600">
|
||||
Staff dashboard, tasks, attendance, documents, and other user workspace modules can be plugged in here.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,59 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold">Year Backup Export</h1>
|
||||
<p class="mt-1 text-sm text-slate-600">Financial Year {{ fy.year_code }} / Assessment Year {{ fy.assessment_year }}</p>
|
||||
</div>
|
||||
<a href="/system-settings/financial-years?tenant_id={{ fy.tenant_id }}" class="rounded-xl border px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Back to Financial Years</a>
|
||||
</div>
|
||||
|
||||
{% if request.query_params.get('exported') %}
|
||||
<div class="mt-4 rounded-xl border border-green-200 bg-green-50 p-4 text-sm text-green-800">Backup export generated successfully.</div>
|
||||
{% endif %}
|
||||
{% if request.query_params.get('error') %}
|
||||
<div class="mt-4 rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-800">Backup file is not available on disk.</div>
|
||||
{% endif %}
|
||||
|
||||
<section class="mt-5 rounded-2xl border bg-white p-5 shadow-soft">
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-slate-900">Create export ZIP</h2>
|
||||
<p class="mt-1 max-w-3xl text-sm text-slate-600">
|
||||
This creates a year-wise archive containing CSV indexes for engagements, tasks, engagement documents, notice/case records, billing invoices, payments, and available stored document files.
|
||||
</p>
|
||||
</div>
|
||||
{% if can_manage_fy %}
|
||||
<form method="post" action="/system-settings/financial-years/{{ fy.id }}/backup/export">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-medium text-white" type="submit">Generate Backup ZIP</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="mt-5 overflow-hidden rounded-2xl border bg-white">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-slate-50 text-slate-600">
|
||||
<tr>
|
||||
<th class="p-3 text-left">Generated At</th>
|
||||
<th class="p-3 text-left">Status</th>
|
||||
<th class="p-3 text-left">Size</th>
|
||||
<th class="p-3 text-right">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for export in exports %}
|
||||
<tr class="border-t">
|
||||
<td class="p-3">{{ export.generated_at_utc }}</td>
|
||||
<td class="p-3"><span class="rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700">{{ export.export_status|title }}</span></td>
|
||||
<td class="p-3">{{ (export.file_size_bytes / 1024)|round(1) }} KB</td>
|
||||
<td class="p-3 text-right"><a href="/system-settings/financial-years/backups/{{ export.id }}/download" class="font-medium text-brand-700 hover:underline">Download</a></td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="4" class="p-6 text-center text-slate-500">No backup exports generated yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,50 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<h1 class="text-2xl font-semibold">Create Financial Year</h1>
|
||||
<form class="mt-5 max-w-2xl rounded-2xl border bg-white p-5" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<div class="grid gap-4">
|
||||
{% if tenants|length > 1 %}
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Audit Firm</span>
|
||||
<select name="tenant_id" class="rounded-xl border px-3 py-2" required>
|
||||
{% for tenant in tenants %}
|
||||
<option value="{{ tenant.id }}" {% if tenant.id == selected_tenant_id %}selected{% endif %}>{{ tenant.name }} ({{ tenant.code }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
{% else %}
|
||||
<input type="hidden" name="tenant_id" value="{{ selected_tenant_id }}" />
|
||||
{% endif %}
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Financial Year Code</span>
|
||||
<input class="rounded-xl border px-3 py-2" name="year_code" value="{{ form_data.year_code or '' }}" placeholder="2025-26" required />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Assessment Year</span>
|
||||
<input class="rounded-xl border px-3 py-2" name="assessment_year" value="{{ form_data.assessment_year or '' }}" placeholder="2026-27" required />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Start Date</span>
|
||||
<input type="date" class="rounded-xl border px-3 py-2" name="start_date" value="{{ form_data.start_date or '' }}" required />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">End Date</span>
|
||||
<input type="date" class="rounded-xl border px-3 py-2" name="end_date" value="{{ form_data.end_date or '' }}" required />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label class="flex items-center gap-2 rounded-xl border bg-slate-50 px-3 py-2">
|
||||
<input type="checkbox" name="is_current" />
|
||||
<span class="text-sm text-slate-700">Make this the current financial year for this audit firm</span>
|
||||
</label>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-medium text-white" type="submit">Save</button>
|
||||
<a class="rounded-xl border px-4 py-2 text-sm" href="/system-settings/financial-years?tenant_id={{ selected_tenant_id }}">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,39 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<h1 class="text-2xl font-semibold">Edit Financial Year</h1>
|
||||
<p class="mt-1 text-sm text-slate-600">{{ tenant.name if tenant else '' }} • {{ fy.year_code }}</p>
|
||||
|
||||
<form class="mt-5 max-w-2xl rounded-2xl border bg-white p-5" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<div class="grid gap-4">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Financial Year Code</span>
|
||||
<input class="rounded-xl border bg-slate-100 px-3 py-2 text-slate-500" value="{{ fy.year_code }}" disabled />
|
||||
</label>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Assessment Year</span>
|
||||
<input class="rounded-xl border px-3 py-2" name="assessment_year" value="{{ fy.assessment_year }}" required />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Status</span>
|
||||
<input class="rounded-xl border bg-slate-100 px-3 py-2 text-slate-500" value="{{ 'Locked' if fy.is_locked else 'Open' }}{% if fy.is_current %} / Current{% endif %}" disabled />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Start Date</span>
|
||||
<input type="date" class="rounded-xl border px-3 py-2" name="start_date" value="{{ fy.start_date }}" required />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">End Date</span>
|
||||
<input type="date" class="rounded-xl border px-3 py-2" name="end_date" value="{{ fy.end_date }}" required />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-medium text-white" type="submit">Save</button>
|
||||
<a class="rounded-xl border px-4 py-2 text-sm" href="/system-settings/financial-years?tenant_id={{ fy.tenant_id }}">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,83 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold">Financial Years</h1>
|
||||
<p class="mt-1 text-sm text-slate-600">Manage active financial year context, year locking and assessment year mapping.</p>
|
||||
</div>
|
||||
{% if can_manage_fy %}
|
||||
<a href="/system-settings/financial-years/new?tenant_id={{ selected_tenant_id }}" class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-medium text-white">Add Financial Year</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if tenants|length > 1 %}
|
||||
<form class="mt-5 rounded-2xl border bg-white p-4" method="get">
|
||||
<label class="grid max-w-md gap-1">
|
||||
<span class="text-sm font-medium text-slate-600">Audit Firm</span>
|
||||
<select name="tenant_id" onchange="this.form.submit()" class="rounded-xl border px-3 py-2 text-sm">
|
||||
{% for tenant in tenants %}
|
||||
<option value="{{ tenant.id }}" {% if tenant.id == selected_tenant_id %}selected{% endif %}>{{ tenant.name }} ({{ tenant.code }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-5 overflow-hidden rounded-2xl border bg-white">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-slate-50 text-slate-600">
|
||||
<tr>
|
||||
<th class="p-3 text-left">Financial Year</th>
|
||||
<th class="p-3 text-left">Assessment Year</th>
|
||||
<th class="p-3 text-left">Period</th>
|
||||
<th class="p-3 text-left">Current</th>
|
||||
<th class="p-3 text-left">Locked</th>
|
||||
<th class="p-3 text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for fy in financial_years %}
|
||||
<tr class="border-t">
|
||||
<td class="p-3 font-medium text-slate-900">{{ fy.year_code }}</td>
|
||||
<td class="p-3">{{ fy.assessment_year }}</td>
|
||||
<td class="p-3">{{ fy.start_date }} to {{ fy.end_date }}</td>
|
||||
<td class="p-3">
|
||||
{% if fy.is_current %}<span class="rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700">Current</span>{% else %}<span class="text-slate-400">No</span>{% endif %}
|
||||
</td>
|
||||
<td class="p-3">
|
||||
{% if fy.is_locked %}<span class="rounded-full bg-amber-50 px-2 py-1 text-xs font-medium text-amber-700">Locked</span>{% else %}<span class="rounded-full bg-slate-100 px-2 py-1 text-xs text-slate-600">Open</span>{% endif %}
|
||||
</td>
|
||||
<td class="p-3">
|
||||
<div class="flex flex-wrap justify-end gap-2">
|
||||
<a href="/system-settings/context/financial-year/{{ fy.year_code }}" class="rounded-lg border px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50">Use</a>
|
||||
<a href="/system-settings/financial-years/{{ fy.id }}/backup" class="rounded-lg border px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50">Backup</a>
|
||||
{% if can_manage_fy and not fy.is_locked %}
|
||||
<a href="/system-settings/financial-years/{{ fy.id }}/edit" class="rounded-lg border px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50">Edit</a>
|
||||
{% endif %}
|
||||
{% if can_manage_fy and not fy.is_current %}
|
||||
<form method="post" action="/system-settings/financial-years/{{ fy.id }}/make-current">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<button class="rounded-lg border px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50" type="submit">Make Current</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if can_manage_fy and not fy.is_locked %}
|
||||
<form method="post" action="/system-settings/financial-years/{{ fy.id }}/lock">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<button class="rounded-lg border border-amber-200 px-3 py-1.5 text-xs font-medium text-amber-700 hover:bg-amber-50" type="submit">Lock</button>
|
||||
</form>
|
||||
{% elif can_manage_fy and fy.is_locked %}
|
||||
<form method="post" action="/system-settings/financial-years/{{ fy.id }}/unlock">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<button class="rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50" type="submit">Unlock</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="6" class="p-6 text-center text-slate-500">No financial years created yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,66 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<h1 class="text-2xl font-semibold">Create Audit Firm</h1>
|
||||
|
||||
<form class="mt-4 rounded-2xl bg-white border p-5 max-w-3xl" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
|
||||
<div class="grid gap-5">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Audit Firm Code</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="code" required />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Audit Firm Name</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="name" required />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm text-slate-600">Audit Firm Type</span>
|
||||
<select class="border rounded-xl px-3 py-2" name="firm_type" required>
|
||||
<option value="partnership" selected>Partnership Firm</option>
|
||||
<option value="proprietorship">Proprietorship Firm</option>
|
||||
<option value="individual">Individual Audit Practice</option>
|
||||
</select>
|
||||
<span class="text-xs text-slate-500">Review partner is applicable only for partnership firms and assurance engagements.</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="border-t pt-4">
|
||||
<div class="font-semibold">Audit Firm Default Policies</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 mt-3">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Default Timezone</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="default_timezone" value="Asia/Kolkata" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Default Session Duration (minutes)</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="default_session_duration_minutes" value="480" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm text-slate-600">Default OTP Required Roles (CSV)</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="default_otp_required_roles_csv" value="Partner,System Admin" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Default Storage Mode</span>
|
||||
<select class="border rounded-xl px-3 py-2" name="default_storage_mode">
|
||||
<option value="local_only" selected>local_only</option>
|
||||
<option value="cloud_only">cloud_only</option>
|
||||
<option value="hybrid">hybrid</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button class="rounded-xl bg-slate-900 text-white px-4 py-2 text-sm" type="submit">Save</button>
|
||||
<a class="rounded-xl border px-4 py-2 text-sm" href="/system-settings/tenants">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,70 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<h1 class="text-2xl font-semibold">Edit Audit Firm</h1>
|
||||
|
||||
<form class="mt-4 rounded-2xl bg-white border p-5 max-w-3xl" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
|
||||
<div class="grid gap-5">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Audit Firm Code</span>
|
||||
<input class="border rounded-xl px-3 py-2 bg-slate-50" name="code" value="{{ tenant.code }}" readonly />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Audit Firm Name</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="name" value="{{ tenant.name }}" required />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm text-slate-600">Audit Firm Type</span>
|
||||
<select class="border rounded-xl px-3 py-2" name="firm_type" required>
|
||||
<option value="partnership" {% if (tenant.firm_type or 'proprietorship') == 'partnership' %}selected{% endif %}>Partnership Firm</option>
|
||||
<option value="proprietorship" {% if tenant.firm_type == 'proprietorship' %}selected{% endif %}>Proprietorship Firm</option>
|
||||
<option value="individual" {% if tenant.firm_type == 'individual' %}selected{% endif %}>Individual Audit Practice</option>
|
||||
</select>
|
||||
<span class="text-xs text-slate-500">Review partner is applicable only for partnership firms and assurance engagements.</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="border-t pt-4">
|
||||
<div class="font-semibold">Audit Firm Default Policies</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 mt-3">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Default Timezone</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="default_timezone" value="{{ tenant.default_timezone or 'Asia/Kolkata' }}" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Default Session Duration (minutes)</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="default_session_duration_minutes" value="{{ tenant.default_session_duration_minutes or 480 }}" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1 md:col-span-2">
|
||||
<span class="text-sm text-slate-600">Default OTP Required Roles (CSV)</span>
|
||||
<input class="border rounded-xl px-3 py-2" name="default_otp_required_roles_csv" value="{{ tenant.default_otp_required_roles_csv or 'Partner,System Admin' }}" />
|
||||
</label>
|
||||
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-600">Default Storage Mode</span>
|
||||
<select class="border rounded-xl px-3 py-2" name="default_storage_mode">
|
||||
{% for value in ['local_only','cloud_only','hybrid'] %}
|
||||
<option value="{{ value }}" {% if (tenant.default_storage_mode or 'local_only') == value %}selected{% endif %}>{{ value }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||||
<input type="checkbox" name="is_active" value="1" {% if tenant.is_active %}checked{% endif %} /> Active
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button class="rounded-xl bg-slate-900 text-white px-4 py-2 text-sm" type="submit">Save</button>
|
||||
<a class="rounded-xl border px-4 py-2 text-sm" href="/system-settings/tenants">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,14 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% import "ui/templates/components/macros.html" as ui %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
{{ ui.page_shell('Audit Firms', 'Audit Firm master with search, paging, and quick actions for branch setup.', '<a href="/system-settings/tenants/new" class="rounded-xl bg-slate-900 text-white px-4 py-2 text-sm">Add Audit Firm</a>') }}
|
||||
<div class="overflow-hidden rounded-3xl bg-white shadow-soft">
|
||||
{{ ui.search_bar('/system-settings/tenants', filters.q, filters.per_page) }}
|
||||
{% if tenants %}
|
||||
<table class="w-full text-sm"><thead class="bg-slate-50 text-slate-600"><tr><th class="text-left p-3">ID</th><th class="text-left p-3">Code</th><th class="text-left p-3">Name</th><th class="text-left p-3">Firm Type</th><th class="text-left p-3">Status</th><th class="text-left p-3">Actions</th></tr></thead><tbody>{% for t in tenants %}<tr class="border-t"><td class="p-3">{{ t.id }}</td><td class="p-3">{{ t.code }}</td><td class="p-3">{{ t.name }}</td><td class="p-3">{{ (t.firm_type or 'proprietorship')|replace('_',' ')|title }}</td><td class="p-3">{{ ui.badge('Active','emerald') if t.is_active else ui.badge('Inactive','rose') }}</td><td class="p-3"><div class="flex gap-3"><a class="text-slate-900 underline" href="/system-settings/tenants/{{ t.id }}/edit">Edit</a><a class="text-slate-900 underline" href="/system-settings/branches/new?tenant_id={{ t.id }}">Add Branch</a></div></td></tr>{% endfor %}</tbody></table>
|
||||
{{ ui.pagination(tenants_page, '/system-settings/tenants', request.url.query) }}
|
||||
{% else %}<div class="p-6">{{ ui.empty_state('No audit firms found.') }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user