wizard firm creation

This commit is contained in:
A R R R Associates
2026-07-01 15:17:38 +05:30
parent a834dc0e9d
commit 779565ac6c
7 changed files with 923 additions and 0 deletions
@@ -0,0 +1,50 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
<div class="mx-auto max-w-4xl">
<div class="rounded-3xl border border-emerald-200 bg-white p-6 shadow-soft">
<div class="rounded-2xl bg-emerald-50 p-4 text-emerald-800">
<h1 class="text-2xl font-semibold">Firm created successfully</h1>
<p class="mt-1 text-sm">The tenant, primary branch, Firm Admin and invite token have been created.</p>
</div>
<div class="mt-6 grid gap-4 md:grid-cols-2">
<div class="rounded-2xl border p-4">
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Firm</div>
<div class="mt-2 text-lg font-semibold">{{ tenant.name }}</div>
<div class="text-sm text-slate-500">{{ tenant.code }} · {{ tenant.firm_type }}</div>
</div>
<div class="rounded-2xl border p-4">
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Primary Branch</div>
<div class="mt-2 text-lg font-semibold">{{ branch.name }}</div>
<div class="text-sm text-slate-500">{{ branch.code }} · Head Office</div>
</div>
<div class="rounded-2xl border p-4">
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Firm Admin</div>
<div class="mt-2 text-lg font-semibold">{{ firm_admin.full_name }}</div>
<div class="text-sm text-slate-500">{{ firm_admin.email }}</div>
</div>
<div class="rounded-2xl border p-4">
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Financial Year</div>
{% if financial_year %}
<div class="mt-2 text-lg font-semibold">{{ financial_year.year_code }}</div>
<div class="text-sm text-slate-500">AY {{ financial_year.assessment_year }}{% if financial_year.is_current %} · Current{% endif %}</div>
{% else %}
<div class="mt-2 text-sm text-slate-500">Not created from wizard.</div>
{% endif %}
</div>
</div>
<div class="mt-6 rounded-2xl border border-brand-200 bg-brand-50 p-4">
<div class="text-sm font-semibold text-brand-900">Firm Admin invite link</div>
<p class="mt-1 text-xs text-brand-800">Copy and share this link with the Firm Admin. The user will set their password through the invite acceptance page.</p>
<div class="mt-3 break-all rounded-xl border border-brand-200 bg-white p-3 text-sm text-brand-900">{{ invite_url }}</div>
</div>
<div class="mt-6 flex flex-wrap gap-3">
<a href="/wizards/system/firm/new" class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-medium text-white hover:bg-slate-800">Create another firm</a>
<a href="/system-settings/tenants" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">View firms</a>
<a href="/system-settings/users" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">View users</a>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,184 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
<div class="max-w-5xl">
<div class="flex items-start justify-between gap-4">
<div>
<h1 class="text-2xl font-semibold">System Admin · Firm Creation Wizard</h1>
<p class="mt-1 text-sm text-slate-500">Create a new firm, primary branch, primary Firm Admin and optional default financial year in one controlled flow.</p>
</div>
<a href="/system-settings/tenants" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Tenants</a>
</div>
{% if errors %}
<div class="mt-4 rounded-2xl border border-red-200 bg-red-50 p-4 text-sm text-red-800">
<div class="font-semibold">Please correct the following:</div>
<ul class="mt-2 list-disc pl-5">
{% for error in errors %}<li>{{ error }}</li>{% endfor %}
</ul>
</div>
{% endif %}
<form class="mt-5 space-y-5" method="post" action="/wizards/system/firm/preview">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<section class="rounded-2xl border bg-white p-5 shadow-soft">
<div class="mb-4">
<h2 class="text-lg font-semibold">1. Firm details</h2>
<p class="text-sm text-slate-500">These are platform-level firm/tenant details controlled by System Admin.</p>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<label class="grid gap-1">
<span class="text-sm text-slate-600">Firm code</span>
<input class="rounded-xl border px-3 py-2 uppercase" name="tenant_code" value="{{ form.tenant_code }}" required />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">Firm name</span>
<input class="rounded-xl border px-3 py-2" name="tenant_name" value="{{ form.tenant_name }}" required />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">Firm type</span>
<select class="rounded-xl border px-3 py-2" name="firm_type">
<option value="partnership" {% if form.firm_type == 'partnership' %}selected{% endif %}>Partnership Firm</option>
<option value="proprietorship" {% if form.firm_type == 'proprietorship' %}selected{% endif %}>Proprietorship Firm</option>
<option value="individual" {% if form.firm_type == 'individual' %}selected{% endif %}>Individual Audit Practice</option>
</select>
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">Default timezone</span>
<input class="rounded-xl border px-3 py-2" name="default_timezone" value="{{ form.default_timezone or 'Asia/Kolkata' }}" />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">Session duration minutes</span>
<input class="rounded-xl border px-3 py-2" type="number" min="15" name="default_session_duration_minutes" value="{{ form.default_session_duration_minutes or 480 }}" />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">Storage mode</span>
<select class="rounded-xl border px-3 py-2" name="default_storage_mode">
<option value="local_only" {% if form.default_storage_mode == 'local_only' %}selected{% endif %}>local_only</option>
<option value="cloud_only" {% if form.default_storage_mode == 'cloud_only' %}selected{% endif %}>cloud_only</option>
<option value="hybrid" {% if form.default_storage_mode == 'hybrid' %}selected{% endif %}>hybrid</option>
</select>
</label>
<label class="grid gap-1 md:col-span-2">
<span class="text-sm text-slate-600">OTP required roles CSV</span>
<input class="rounded-xl border px-3 py-2" name="default_otp_required_roles_csv" value="{{ form.default_otp_required_roles_csv or 'Partner,System Admin' }}" />
</label>
</div>
</section>
<section class="rounded-2xl border bg-white p-5 shadow-soft">
<div class="mb-4">
<h2 class="text-lg font-semibold">2. Primary branch</h2>
<p class="text-sm text-slate-500">The wizard creates the head office branch and basic branch settings.</p>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<label class="grid gap-1">
<span class="text-sm text-slate-600">Branch code</span>
<input class="rounded-xl border px-3 py-2 uppercase" name="branch_code" value="{{ form.branch_code or 'HO' }}" required />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">Branch name</span>
<input class="rounded-xl border px-3 py-2" name="branch_name" value="{{ form.branch_name or 'Head Office' }}" required />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">Branch timezone</span>
<input class="rounded-xl border px-3 py-2" name="branch_timezone" value="{{ form.branch_timezone or form.default_timezone or 'Asia/Kolkata' }}" />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">State</span>
<input class="rounded-xl border px-3 py-2" name="branch_state" value="{{ form.branch_state }}" />
</label>
<label class="grid gap-1 md:col-span-2">
<span class="text-sm text-slate-600">Address line 1</span>
<input class="rounded-xl border px-3 py-2" name="branch_address_line1" value="{{ form.branch_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="rounded-xl border px-3 py-2" name="branch_address_line2" value="{{ form.branch_address_line2 }}" />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">City</span>
<input class="rounded-xl border px-3 py-2" name="branch_city" value="{{ form.branch_city }}" />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">PIN code</span>
<input class="rounded-xl border px-3 py-2" name="branch_pin_code" value="{{ form.branch_pin_code }}" />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">Branch PAN</span>
<input class="rounded-xl border px-3 py-2 uppercase" name="branch_pan" value="{{ form.branch_pan }}" />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">Branch GSTIN</span>
<input class="rounded-xl border px-3 py-2 uppercase" name="branch_gstin" value="{{ form.branch_gstin }}" />
</label>
</div>
</section>
<section class="rounded-2xl border bg-white p-5 shadow-soft">
<div class="mb-4">
<h2 class="text-lg font-semibold">3. Primary Firm Admin</h2>
<p class="text-sm text-slate-500">This user gets the Firm Admin role and an invite link to set password.</p>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<label class="grid gap-1">
<span class="text-sm text-slate-600">Full name</span>
<input class="rounded-xl border px-3 py-2" name="admin_full_name" value="{{ form.admin_full_name }}" required />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">Email</span>
<input class="rounded-xl border px-3 py-2" type="email" name="admin_email" value="{{ form.admin_email }}" required />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">Mobile</span>
<input class="rounded-xl border px-3 py-2" name="admin_mobile" value="{{ form.admin_mobile }}" />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">Designation</span>
<input class="rounded-xl border px-3 py-2" name="admin_designation" value="{{ form.admin_designation or 'Firm Admin' }}" />
</label>
</div>
</section>
<section class="rounded-2xl border bg-white p-5 shadow-soft">
<div class="mb-4 flex items-start justify-between gap-4">
<div>
<h2 class="text-lg font-semibold">4. Default financial year</h2>
<p class="text-sm text-slate-500">Optional. Firm Admin can also manage financial years later from System Settings.</p>
</div>
<label class="flex items-center gap-2 text-sm font-medium text-slate-700">
<input type="checkbox" name="create_financial_year" {% if form.create_financial_year %}checked{% endif %} />
Create FY
</label>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<label class="grid gap-1">
<span class="text-sm text-slate-600">Financial year code</span>
<input class="rounded-xl border px-3 py-2" name="fy_year_code" value="{{ form.fy_year_code }}" placeholder="2026-27" />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">Assessment year</span>
<input class="rounded-xl border px-3 py-2" name="fy_assessment_year" value="{{ form.fy_assessment_year }}" placeholder="2027-28" />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">Start date</span>
<input class="rounded-xl border px-3 py-2" type="date" name="fy_start_date" value="{{ form.fy_start_date }}" />
</label>
<label class="grid gap-1">
<span class="text-sm text-slate-600">End date</span>
<input class="rounded-xl border px-3 py-2" type="date" name="fy_end_date" value="{{ form.fy_end_date }}" />
</label>
<label class="flex items-center gap-2 text-sm font-medium text-slate-700">
<input type="checkbox" name="fy_is_current" {% if form.fy_is_current %}checked{% endif %} />
Mark as current financial year
</label>
</div>
</section>
<div class="flex gap-3">
<button class="rounded-xl bg-slate-900 px-5 py-2.5 text-sm font-medium text-white hover:bg-slate-800" type="submit">Preview Firm Creation</button>
<a class="rounded-xl border border-slate-300 px-5 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50" href="/system-settings">Cancel</a>
</div>
</form>
</div>
{% endblock %}
@@ -0,0 +1,76 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
<div class="max-w-5xl">
<h1 class="text-2xl font-semibold">Preview Firm Creation</h1>
<p class="mt-1 text-sm text-slate-500">Review the details. Nothing is saved until you click Confirm.</p>
<form class="mt-5" method="post" action="/wizards/system/firm/confirm">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
{% for key, value in form.items() %}
{% if value is sameas true %}
<input type="hidden" name="{{ key }}" value="on" />
{% elif value is not sameas false and value is not none %}
<input type="hidden" name="{{ key }}" value="{{ value }}" />
{% endif %}
{% endfor %}
<div class="grid gap-5 lg:grid-cols-2">
<section class="rounded-2xl border bg-white p-5 shadow-soft">
<h2 class="text-lg font-semibold">Firm</h2>
<dl class="mt-4 space-y-2 text-sm">
<div class="flex justify-between gap-4"><dt class="text-slate-500">Code</dt><dd class="font-medium">{{ form.tenant_code }}</dd></div>
<div class="flex justify-between gap-4"><dt class="text-slate-500">Name</dt><dd class="font-medium">{{ form.tenant_name }}</dd></div>
<div class="flex justify-between gap-4"><dt class="text-slate-500">Type</dt><dd class="font-medium">{{ form.firm_type }}</dd></div>
<div class="flex justify-between gap-4"><dt class="text-slate-500">Timezone</dt><dd class="font-medium">{{ form.default_timezone }}</dd></div>
<div class="flex justify-between gap-4"><dt class="text-slate-500">Storage</dt><dd class="font-medium">{{ form.default_storage_mode }}</dd></div>
</dl>
</section>
<section class="rounded-2xl border bg-white p-5 shadow-soft">
<h2 class="text-lg font-semibold">Primary Branch</h2>
<dl class="mt-4 space-y-2 text-sm">
<div class="flex justify-between gap-4"><dt class="text-slate-500">Code</dt><dd class="font-medium">{{ form.branch_code }}</dd></div>
<div class="flex justify-between gap-4"><dt class="text-slate-500">Name</dt><dd class="font-medium">{{ form.branch_name }}</dd></div>
<div class="flex justify-between gap-4"><dt class="text-slate-500">City / State</dt><dd class="font-medium">{{ form.branch_city or '-' }}{% if form.branch_state %}, {{ form.branch_state }}{% endif %}</dd></div>
<div class="flex justify-between gap-4"><dt class="text-slate-500">PAN</dt><dd class="font-medium">{{ form.branch_pan or '-' }}</dd></div>
<div class="flex justify-between gap-4"><dt class="text-slate-500">GSTIN</dt><dd class="font-medium">{{ form.branch_gstin or '-' }}</dd></div>
</dl>
</section>
<section class="rounded-2xl border bg-white p-5 shadow-soft">
<h2 class="text-lg font-semibold">Primary Firm Admin</h2>
<dl class="mt-4 space-y-2 text-sm">
<div class="flex justify-between gap-4"><dt class="text-slate-500">Name</dt><dd class="font-medium">{{ form.admin_full_name }}</dd></div>
<div class="flex justify-between gap-4"><dt class="text-slate-500">Email</dt><dd class="font-medium">{{ form.admin_email }}</dd></div>
<div class="flex justify-between gap-4"><dt class="text-slate-500">Mobile</dt><dd class="font-medium">{{ form.admin_mobile or '-' }}</dd></div>
<div class="flex justify-between gap-4"><dt class="text-slate-500">Role</dt><dd class="font-medium">Firm Admin</dd></div>
</dl>
</section>
<section class="rounded-2xl border bg-white p-5 shadow-soft">
<h2 class="text-lg font-semibold">Financial Year</h2>
{% if form.create_financial_year %}
<dl class="mt-4 space-y-2 text-sm">
<div class="flex justify-between gap-4"><dt class="text-slate-500">FY</dt><dd class="font-medium">{{ form.fy_year_code }}</dd></div>
<div class="flex justify-between gap-4"><dt class="text-slate-500">AY</dt><dd class="font-medium">{{ form.fy_assessment_year }}</dd></div>
<div class="flex justify-between gap-4"><dt class="text-slate-500">Dates</dt><dd class="font-medium">{{ form.fy_start_date }} to {{ form.fy_end_date }}</dd></div>
<div class="flex justify-between gap-4"><dt class="text-slate-500">Current</dt><dd class="font-medium">{{ 'Yes' if form.fy_is_current else 'No' }}</dd></div>
</dl>
{% else %}
<p class="mt-4 text-sm text-slate-500">No financial year will be created now.</p>
{% endif %}
</section>
</div>
<div class="mt-5 rounded-2xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-900">
Confirming will create the tenant, head office branch, Firm Admin user, invite token and optional financial year.
</div>
<div class="mt-5 flex gap-3">
<button class="rounded-xl bg-slate-900 px-5 py-2.5 text-sm font-medium text-white hover:bg-slate-800" type="submit">Confirm & Create Firm</button>
<button class="rounded-xl border border-slate-300 px-5 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50" type="button" onclick="history.back()">Back to Edit</button>
<a class="rounded-xl border border-slate-300 px-5 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50" href="/system-settings">Cancel</a>
</div>
</form>
</div>
{% endblock %}