Prepare ERP source for Gitea deployment
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="max-w-4xl space-y-6"><h1 class="text-2xl font-semibold">New Platform Billing Account</h1><form method="post" class="space-y-4 rounded-2xl border bg-white p-5 shadow-soft"><input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<div class="grid gap-4 md:grid-cols-2"><label class="text-sm">Account Type<select name="account_type" class="mt-1 w-full rounded-xl border px-3 py-2">{% for x in account_types %}<option value="{{ x }}">{{ x }}</option>{% endfor %}</select></label><label class="text-sm">Account Code<input name="account_code" required class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm md:col-span-2">Display Name<input name="display_name" required class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">Link Audit Firm<select name="tenant_id" class="mt-1 w-full rounded-xl border px-3 py-2"><option value="">Not linked</option>{% for t in audit_firms %}<option value="{{ t.id }}">{{ t.name }} ({{ t.code }})</option>{% endfor %}</select></label><label class="text-sm">Link Client<select name="client_id" class="mt-1 w-full rounded-xl border px-3 py-2"><option value="">Not linked</option>{% for c in clients %}<option value="{{ c.id }}">{{ c.client_name }} ({{ c.client_code }})</option>{% endfor %}</select></label><label class="text-sm">Link Consultant<select name="consultant_id" class="mt-1 w-full rounded-xl border px-3 py-2"><option value="">Not linked</option>{% for c in consultants %}<option value="{{ c.id }}">{{ c.contact_person }}{% if c.firm_name %} - {{ c.firm_name }}{% endif %}</option>{% endfor %}</select></label><label class="text-sm">Email<input name="email" class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">Mobile<input name="mobile" class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">GSTIN<input name="gstin" class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">PAN<input name="pan" class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">State<input name="state" class="mt-1 w-full rounded-xl border px-3 py-2" /></label></div><label class="block text-sm">Billing Address<textarea name="billing_address" class="mt-1 w-full rounded-xl border px-3 py-2"></textarea></label><label class="block text-sm">Notes<textarea name="notes" class="mt-1 w-full rounded-xl border px-3 py-2"></textarea></label><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Save Account</button></form></div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,6 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6"><div class="flex items-center justify-between"><h1 class="text-2xl font-semibold">Platform Billing Accounts</h1>{% if can_create %}<a href="/platform-billing/accounts/new" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">New Account</a>{% endif %}</div>
|
||||
<form method="get" class="grid gap-3 rounded-2xl border bg-white p-4 shadow-soft md:grid-cols-[1fr_220px_auto]"><input name="q" value="{{ q or '' }}" placeholder="Search account" class="rounded-xl border px-3 py-2 text-sm" /><select name="account_type" class="rounded-xl border px-3 py-2 text-sm"><option value="">All Types</option>{% for x in account_types %}<option value="{{ x }}" {% if selected_account_type==x %}selected{% endif %}>{{ x }}</option>{% endfor %}</select><button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white">Search</button></form>
|
||||
<div class="overflow-hidden rounded-2xl border bg-white shadow-soft"><table class="min-w-full text-sm"><thead class="bg-slate-50 text-left"><tr><th class="px-4 py-3">Code</th><th class="px-4 py-3">Name</th><th class="px-4 py-3">Type</th><th class="px-4 py-3">Email</th><th class="px-4 py-3">Status</th></tr></thead><tbody class="divide-y">{% for row in rows %}<tr><td class="px-4 py-3 font-medium">{{ row.account_code }}</td><td class="px-4 py-3">{{ row.display_name }}</td><td class="px-4 py-3">{{ row.account_type }}</td><td class="px-4 py-3">{{ row.email or '-' }}</td><td class="px-4 py-3">{{ row.status }}</td></tr>{% else %}<tr><td colspan="5" class="px-4 py-8 text-center text-slate-500">No billing accounts found.</td></tr>{% endfor %}</tbody></table></div></div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,86 @@
|
||||
{% 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-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-slate-900">Generate Audit Firm Platform Bills</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">Generate draft SaaS invoices for Audit Firm subscriptions. Duplicate invoices for the same subscription and period are skipped.</p>
|
||||
</div>
|
||||
<a href="/platform-billing/audit-firm-subscriptions" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">Back to Audit Firm Subscriptions</a>
|
||||
</div>
|
||||
|
||||
{% if generated or skipped %}
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-4 shadow-soft">
|
||||
<div class="text-sm"><span class="font-semibold text-emerald-700">Generated: {{ generated }}</span> · <span class="font-semibold text-amber-700">Skipped: {{ skipped }}</span></div>
|
||||
{% if errors %}
|
||||
<ul class="mt-3 list-disc space-y-1 pl-5 text-sm text-slate-600">
|
||||
{% for error in errors %}<li>{{ error }}</li>{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if invoices %}
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
{% for invoice in invoices %}<a href="/platform-billing/invoices/{{ invoice.id }}" class="rounded-xl bg-slate-100 px-3 py-2 text-sm text-slate-700">{{ invoice.invoice_no }}</a>{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" class="space-y-5 rounded-2xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<label class="text-sm font-medium text-slate-700">Period From<input type="date" name="period_from" required class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" /></label>
|
||||
<label class="text-sm font-medium text-slate-700">Period To<input type="date" name="period_to" required class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" /></label>
|
||||
<label class="text-sm font-medium text-slate-700">Invoice Date<input type="date" name="invoice_date" required class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" /></label>
|
||||
<label class="text-sm font-medium text-slate-700">Due Date<input type="date" name="due_date" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" /></label>
|
||||
<label class="text-sm font-medium text-slate-700">Tax Type<select name="tax_type" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2">{% for x in tax_types %}<option value="{{ x }}">{{ x }}</option>{% endfor %}</select></label>
|
||||
<label class="inline-flex items-end gap-2 text-sm text-slate-700"><input type="checkbox" name="include_zero_usage_lines" class="rounded border-slate-300" /> Include zero usage lines</label>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl bg-slate-50 p-4">
|
||||
<h2 class="font-semibold text-slate-900">Optional usage rates</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Base subscription amount comes from the subscription. Add usage rates only when you want to charge extra by count.</p>
|
||||
<div class="mt-4 grid gap-4 md:grid-cols-4">
|
||||
<label class="text-sm">Per Client<input name="client_rate" value="0" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" /></label>
|
||||
<label class="text-sm">Per Employee<input name="employee_rate" value="0" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" /></label>
|
||||
<label class="text-sm">Per Consultant<input name="consultant_rate" value="0" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" /></label>
|
||||
<label class="text-sm">Per Branch<input name="branch_rate" value="0" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" /></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-slate-200 overflow-hidden">
|
||||
<div class="border-b border-slate-200 bg-slate-50 px-4 py-3 font-semibold text-slate-900">Select Audit Firm subscriptions</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-slate-200 text-sm">
|
||||
<thead class="bg-slate-50 text-left text-xs uppercase tracking-wide text-slate-500">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Select</th>
|
||||
<th class="px-4 py-3">Audit Firm</th>
|
||||
<th class="px-4 py-3">Subscription</th>
|
||||
<th class="px-4 py-3">Plan</th>
|
||||
<th class="px-4 py-3 text-right">Base</th>
|
||||
<th class="px-4 py-3">Usage Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for row in rows %}
|
||||
{% set sub = row.subscription %}
|
||||
<tr>
|
||||
<td class="px-4 py-3"><input type="checkbox" name="subscription_ids" value="{{ sub.id }}" class="rounded border-slate-300" {% if sub.status != 'ACTIVE' or not sub.auto_generate_invoice %}disabled{% endif %} /></td>
|
||||
<td class="px-4 py-3 font-medium text-slate-900">{{ sub.account.display_name if sub.account else '-' }}</td>
|
||||
<td class="px-4 py-3">{{ sub.subscription_code }}</td>
|
||||
<td class="px-4 py-3">{{ sub.plan.name if sub.plan else '-' }}</td>
|
||||
<td class="px-4 py-3 text-right">{{ '%.2f'|format(sub.amount or 0) }}</td>
|
||||
<td class="px-4 py-3 text-slate-600">Clients: {{ row.usage.clients }} · Employees: {{ row.usage.employees }} · Consultants: {{ row.usage.consultants }} · Branches: {{ row.usage.branches }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="6" class="px-4 py-6 text-center text-slate-500">No Audit Firm subscriptions available.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white shadow-soft">Generate Draft Platform Invoices</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,83 @@
|
||||
{% 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-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-slate-900">Audit Firm Subscription Billing</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">PB2: manage SaaS subscriptions and generate platform bills for Audit Firms.</p>
|
||||
{% if synced %}<p class="mt-2 rounded-xl bg-emerald-50 px-3 py-2 text-sm text-emerald-700">Sync completed: {{ synced }}</p>{% endif %}
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% if can_manage %}
|
||||
<form method="post" action="/platform-billing/audit-firm-subscriptions/sync-accounts">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<button class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">Sync Audit Firm Accounts</button>
|
||||
</form>
|
||||
<a href="/platform-billing/subscriptions/new" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">New Subscription</a>
|
||||
{% endif %}
|
||||
{% if can_generate %}
|
||||
<a href="/platform-billing/audit-firm-subscriptions/generate" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white shadow-soft">Generate Audit Firm Bills</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="get" class="flex gap-2 rounded-2xl border border-slate-200 bg-white p-4 shadow-soft">
|
||||
<input name="q" value="{{ q }}" placeholder="Search Audit Firm / subscription" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
<button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white">Search</button>
|
||||
</form>
|
||||
|
||||
<div class="rounded-2xl border border-slate-200 bg-white shadow-soft overflow-hidden">
|
||||
<div class="border-b border-slate-200 px-5 py-4">
|
||||
<h2 class="font-semibold text-slate-900">Active Audit Firm subscriptions</h2>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-slate-200 text-sm">
|
||||
<thead class="bg-slate-50 text-left text-xs uppercase tracking-wide text-slate-500">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Audit Firm</th>
|
||||
<th class="px-4 py-3">Subscription</th>
|
||||
<th class="px-4 py-3">Plan</th>
|
||||
<th class="px-4 py-3">Cycle</th>
|
||||
<th class="px-4 py-3 text-right">Base Amount</th>
|
||||
<th class="px-4 py-3">Usage Count</th>
|
||||
<th class="px-4 py-3">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for row in rows %}
|
||||
{% set sub = row.subscription %}
|
||||
<tr>
|
||||
<td class="px-4 py-3 font-medium text-slate-900">{{ sub.account.display_name if sub.account else '-' }}</td>
|
||||
<td class="px-4 py-3">{{ sub.subscription_code }}</td>
|
||||
<td class="px-4 py-3">{{ sub.plan.name if sub.plan else '-' }}</td>
|
||||
<td class="px-4 py-3">{{ sub.billing_cycle }}</td>
|
||||
<td class="px-4 py-3 text-right">{{ '%.2f'|format(sub.amount or 0) }}</td>
|
||||
<td class="px-4 py-3 text-slate-600">
|
||||
Clients: {{ row.usage.clients }} · Employees: {{ row.usage.employees }} · Consultants: {{ row.usage.consultants }} · Branches: {{ row.usage.branches }}
|
||||
</td>
|
||||
<td class="px-4 py-3">{{ sub.status }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7" class="px-4 py-6 text-center text-slate-500">No Audit Firm subscriptions found. Sync Audit Firm accounts, create a plan, then create subscriptions.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<h2 class="font-semibold text-slate-900">Audit Firm billing accounts</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">These are platform billing accounts linked to your Audit Firm master. They are separate from firm-level client billing.</p>
|
||||
<div class="mt-4 grid gap-3 md:grid-cols-2 lg:grid-cols-3">
|
||||
{% for account in audit_firm_accounts %}
|
||||
<div class="rounded-xl border border-slate-200 p-4 text-sm">
|
||||
<div class="font-semibold text-slate-900">{{ account.display_name }}</div>
|
||||
<div class="mt-1 text-slate-500">Code: {{ account.account_code }} · Status: {{ account.status }}</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="rounded-xl bg-slate-50 p-4 text-sm text-slate-500">No Audit Firm billing accounts synced yet.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,98 @@
|
||||
{% 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-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-slate-900">Generate Client Dashboard Platform Bills</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">Generate draft platform invoices for client compliance dashboard subscriptions. Existing invoice for the same subscription and period will be skipped.</p>
|
||||
</div>
|
||||
<a href="/platform-billing/client-dashboard-subscriptions" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">Back to Client Dashboard Billing</a>
|
||||
</div>
|
||||
|
||||
{% if generated or skipped or errors %}
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<div class="text-sm text-slate-700">Generated: <b>{{ generated }}</b> · Skipped: <b>{{ skipped }}</b></div>
|
||||
{% if errors %}
|
||||
<ul class="mt-3 list-disc space-y-1 pl-5 text-sm text-amber-700">
|
||||
{% for error in errors %}<li>{{ error }}</li>{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if invoices %}
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
{% for invoice in invoices %}<a class="rounded-xl bg-slate-900 px-3 py-2 text-sm font-semibold text-white" href="/platform-billing/invoices/{{ invoice.id }}">{{ invoice.invoice_no }}</a>{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Period From</label>
|
||||
<input type="date" name="period_from" required class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Period To</label>
|
||||
<input type="date" name="period_to" required class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Invoice Date</label>
|
||||
<input type="date" name="invoice_date" required class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Due Date</label>
|
||||
<input type="date" name="due_date" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Tax Type</label>
|
||||
<select name="tax_type" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
{% for tax_type in tax_types %}<option value="{{ tax_type }}">{{ tax_type }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<label class="mt-6 inline-flex items-center gap-2 text-sm text-slate-700">
|
||||
<input type="checkbox" name="include_zero_usage_lines" class="rounded border-slate-300" /> Include zero usage lines
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 grid gap-4 md:grid-cols-3">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Extra PAN Rate</label>
|
||||
<input name="pan_rate" value="0" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Extra GSTIN Rate</label>
|
||||
<input name="gstin_rate" value="0" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Per Compliance Module Rate</label>
|
||||
<input name="module_rate" value="0" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 rounded-xl border border-slate-200">
|
||||
<div class="border-b border-slate-200 bg-slate-50 px-4 py-3 text-sm font-semibold text-slate-700">Select client dashboard subscriptions</div>
|
||||
<div class="max-h-[520px] overflow-auto divide-y divide-slate-100">
|
||||
{% for row in rows %}
|
||||
{% set sub = row.subscription %}
|
||||
<label class="flex cursor-pointer items-start gap-3 px-4 py-3 text-sm hover:bg-slate-50">
|
||||
<input type="checkbox" name="subscription_ids" value="{{ sub.id }}" class="mt-1 rounded border-slate-300" />
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-slate-900">{{ sub.account.display_name if sub.account else '-' }}</div>
|
||||
<div class="text-slate-500">{{ sub.subscription_code }} · {{ sub.plan.name if sub.plan else '-' }} · Base: {{ '%.2f'|format(sub.amount or 0) }} · {{ sub.billing_cycle }}</div>
|
||||
<div class="text-xs text-slate-400">PAN {{ row.usage.pan_units }} · GSTIN {{ row.usage.gstin_units }} · Modules {{ row.usage.module_count }}{% if row.usage.modules %} — {{ row.usage.modules|join(', ') }}{% endif %}</div>
|
||||
</div>
|
||||
<span class="rounded-full bg-slate-100 px-2 py-1 text-xs text-slate-600">{{ sub.status }}</span>
|
||||
</label>
|
||||
{% else %}
|
||||
<div class="px-4 py-6 text-center text-sm text-slate-500">No client dashboard subscriptions available.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 flex justify-end">
|
||||
<button class="rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-semibold text-white shadow-soft">Generate Draft Platform Invoices</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,145 @@
|
||||
{% 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-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-slate-900">Client Compliance Dashboard Billing</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">PB3: manage platform subscriptions and generate bills for client compliance dashboard access.</p>
|
||||
{% if synced %}<p class="mt-2 rounded-xl bg-emerald-50 px-3 py-2 text-sm text-emerald-700">Sync completed: {{ synced }}</p>{% endif %}
|
||||
{% if created %}<p class="mt-2 rounded-xl bg-emerald-50 px-3 py-2 text-sm text-emerald-700">Client dashboard subscription created.</p>{% endif %}
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% if can_manage %}
|
||||
<form method="post" action="/platform-billing/client-dashboard-subscriptions/sync-accounts">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<button class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">Sync Client Accounts</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if can_generate %}
|
||||
<a href="/platform-billing/client-dashboard-subscriptions/generate" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white shadow-soft">Generate Client Dashboard Bills</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if can_manage %}
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<h2 class="font-semibold text-slate-900">Create Client Dashboard Subscription</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Use CLIENT-target platform plans. If no plan appears, create a plan with Target Account Type = CLIENT.</p>
|
||||
<form method="post" action="/platform-billing/client-dashboard-subscriptions/new" class="mt-4 grid gap-4 md:grid-cols-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Client Account</label>
|
||||
<select name="account_id" required class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
<option value="">Select client</option>
|
||||
{% for account in client_accounts %}<option value="{{ account.id }}">{{ account.display_name }} ({{ account.account_code }})</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Plan</label>
|
||||
<select name="plan_id" required class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
<option value="">Select plan</option>
|
||||
{% for plan in client_plans %}<option value="{{ plan.id }}">{{ plan.name }} - {{ '%.2f'|format(plan.base_amount or 0) }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Subscription Code</label>
|
||||
<input name="subscription_code" required placeholder="CLD-ABC-2026" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Start Date</label>
|
||||
<input type="date" name="start_date" required class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">End Date</label>
|
||||
<input type="date" name="end_date" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Billing Cycle</label>
|
||||
<select name="billing_cycle" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
{% for cycle in billing_cycles %}<option value="{{ cycle }}">{{ cycle }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Base Amount</label>
|
||||
<input name="amount" value="0" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">GST Rate</label>
|
||||
<input name="gst_rate" value="18" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<label class="mt-6 inline-flex items-center gap-2 text-sm text-slate-700">
|
||||
<input type="checkbox" name="auto_generate_invoice" checked class="rounded border-slate-300" /> Auto generate invoice
|
||||
</label>
|
||||
<div class="md:col-span-3">
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Notes</label>
|
||||
<input name="notes" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div class="md:col-span-3">
|
||||
<button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white">Create Subscription</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="get" class="flex gap-2 rounded-2xl border border-slate-200 bg-white p-4 shadow-soft">
|
||||
<input name="q" value="{{ q }}" placeholder="Search client / subscription" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
<button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white">Search</button>
|
||||
</form>
|
||||
|
||||
<div class="rounded-2xl border border-slate-200 bg-white shadow-soft overflow-hidden">
|
||||
<div class="border-b border-slate-200 px-5 py-4">
|
||||
<h2 class="font-semibold text-slate-900">Active client dashboard subscriptions</h2>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-slate-200 text-sm">
|
||||
<thead class="bg-slate-50 text-left text-xs uppercase tracking-wide text-slate-500">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Client</th>
|
||||
<th class="px-4 py-3">Subscription</th>
|
||||
<th class="px-4 py-3">Plan</th>
|
||||
<th class="px-4 py-3">Cycle</th>
|
||||
<th class="px-4 py-3 text-right">Base Amount</th>
|
||||
<th class="px-4 py-3">Dashboard Units</th>
|
||||
<th class="px-4 py-3">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for row in rows %}
|
||||
{% set sub = row.subscription %}
|
||||
<tr>
|
||||
<td class="px-4 py-3 font-medium text-slate-900">{{ sub.account.display_name if sub.account else '-' }}</td>
|
||||
<td class="px-4 py-3">{{ sub.subscription_code }}</td>
|
||||
<td class="px-4 py-3">{{ sub.plan.name if sub.plan else '-' }}</td>
|
||||
<td class="px-4 py-3">{{ sub.billing_cycle }}</td>
|
||||
<td class="px-4 py-3 text-right">{{ '%.2f'|format(sub.amount or 0) }}</td>
|
||||
<td class="px-4 py-3 text-slate-600">
|
||||
PAN: {{ row.usage.pan_units }} · GSTIN: {{ row.usage.gstin_units }} · Modules: {{ row.usage.module_count }}
|
||||
{% if row.usage.modules %}<div class="text-xs text-slate-400">{{ row.usage.modules|join(', ') }}</div>{% endif %}
|
||||
</td>
|
||||
<td class="px-4 py-3">{{ sub.status }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7" class="px-4 py-6 text-center text-slate-500">No client dashboard subscriptions found. Sync client accounts, create a CLIENT plan, then create subscriptions.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<h2 class="font-semibold text-slate-900">Client billing accounts</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">These accounts are for platform billing of compliance dashboard access. They are separate from the Audit Firm's client invoices.</p>
|
||||
<div class="mt-4 grid gap-3 md:grid-cols-2 lg:grid-cols-3">
|
||||
{% for account in client_accounts[:12] %}
|
||||
<div class="rounded-xl border border-slate-200 p-4 text-sm">
|
||||
<div class="font-semibold text-slate-900">{{ account.display_name }}</div>
|
||||
<div class="mt-1 text-slate-500">Code: {{ account.account_code }} · Status: {{ account.status }}</div>
|
||||
<div class="mt-1 text-slate-500">PAN: {{ account.pan or '-' }} · GSTIN: {{ account.gstin or '-' }}</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="rounded-xl bg-slate-50 p-4 text-sm text-slate-500">No client billing accounts synced yet.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,94 @@
|
||||
{% 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-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-slate-900">Generate Consultant Platform Bills</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">Generate draft platform invoices for consultant SaaS/tool access subscriptions. Existing invoice for the same subscription and period will be skipped.</p>
|
||||
</div>
|
||||
<a href="/platform-billing/consultant-subscriptions" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">Back to Consultant Billing</a>
|
||||
</div>
|
||||
|
||||
{% if generated or skipped or errors %}
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<div class="text-sm text-slate-700">Generated: <b>{{ generated }}</b> · Skipped: <b>{{ skipped }}</b></div>
|
||||
{% if errors %}
|
||||
<ul class="mt-3 list-disc space-y-1 pl-5 text-sm text-amber-700">
|
||||
{% for error in errors %}<li>{{ error }}</li>{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if invoices %}
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
{% for invoice in invoices %}<a class="rounded-xl bg-slate-900 px-3 py-2 text-sm font-semibold text-white" href="/platform-billing/invoices/{{ invoice.id }}">{{ invoice.invoice_no }}</a>{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Period From</label>
|
||||
<input type="date" name="period_from" required class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Period To</label>
|
||||
<input type="date" name="period_to" required class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Invoice Date</label>
|
||||
<input type="date" name="invoice_date" required class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Due Date</label>
|
||||
<input type="date" name="due_date" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Tax Type</label>
|
||||
<select name="tax_type" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
{% for tax_type in tax_types %}<option value="{{ tax_type }}">{{ tax_type }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<label class="mt-6 inline-flex items-center gap-2 text-sm text-slate-700">
|
||||
<input type="checkbox" name="include_zero_usage_lines" class="rounded border-slate-300" /> Include zero usage lines
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 grid gap-4 md:grid-cols-3">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Per Managed Client Rate</label>
|
||||
<input name="managed_client_rate" value="0" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Per Portal User Rate</label>
|
||||
<input name="user_account_rate" value="0" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 rounded-xl border border-slate-200">
|
||||
<div class="border-b border-slate-200 bg-slate-50 px-4 py-3 text-sm font-semibold text-slate-700">Select consultant subscriptions</div>
|
||||
<div class="max-h-[520px] overflow-auto divide-y divide-slate-100">
|
||||
{% for row in rows %}
|
||||
{% set sub = row.subscription %}
|
||||
<label class="flex cursor-pointer items-start gap-3 px-4 py-3 text-sm hover:bg-slate-50">
|
||||
<input type="checkbox" name="subscription_ids" value="{{ sub.id }}" class="mt-1 rounded border-slate-300" />
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-slate-900">{{ sub.account.display_name if sub.account else '-' }}</div>
|
||||
<div class="text-slate-500">{{ sub.subscription_code }} · {{ sub.plan.name if sub.plan else '-' }} · Base: {{ '%.2f'|format(sub.amount or 0) }} · {{ sub.billing_cycle }}</div>
|
||||
<div class="text-xs text-slate-400">Managed clients {{ row.usage.managed_client_count }} · Portal users {{ row.usage.user_account_count }} · Workspace {{ row.usage.workspace_type }} · Plan {{ row.usage.workspace_plan }}</div>
|
||||
</div>
|
||||
<span class="rounded-full bg-slate-100 px-2 py-1 text-xs text-slate-600">{{ sub.status }}</span>
|
||||
</label>
|
||||
{% else %}
|
||||
<div class="px-4 py-6 text-center text-sm text-slate-500">No consultant subscriptions available.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 flex justify-end">
|
||||
<button class="rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-semibold text-white shadow-soft">Generate Draft Platform Invoices</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,145 @@
|
||||
{% 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-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-slate-900">Consultant SaaS/Tool Billing</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">PB4: manage platform subscriptions and generate bills for consultant SaaS/tool access.</p>
|
||||
{% if synced %}<p class="mt-2 rounded-xl bg-emerald-50 px-3 py-2 text-sm text-emerald-700">Sync completed: {{ synced }}</p>{% endif %}
|
||||
{% if created %}<p class="mt-2 rounded-xl bg-emerald-50 px-3 py-2 text-sm text-emerald-700">Consultant subscription created.</p>{% endif %}
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% if can_manage %}
|
||||
<form method="post" action="/platform-billing/consultant-subscriptions/sync-accounts">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<button class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">Sync Consultant Accounts</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if can_generate %}
|
||||
<a href="/platform-billing/consultant-subscriptions/generate" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white shadow-soft">Generate Consultant Bills</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if can_manage %}
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<h2 class="font-semibold text-slate-900">Create Consultant Subscription</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Use CONSULTANT-target platform plans. If no plan appears, create a plan with Target Account Type = CONSULTANT.</p>
|
||||
<form method="post" action="/platform-billing/consultant-subscriptions/new" class="mt-4 grid gap-4 md:grid-cols-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Consultant Account</label>
|
||||
<select name="account_id" required class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
<option value="">Select consultant</option>
|
||||
{% for account in consultant_accounts %}<option value="{{ account.id }}">{{ account.display_name }} ({{ account.account_code }})</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Plan</label>
|
||||
<select name="plan_id" required class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
<option value="">Select plan</option>
|
||||
{% for plan in consultant_plans %}<option value="{{ plan.id }}">{{ plan.name }} - {{ '%.2f'|format(plan.base_amount or 0) }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Subscription Code</label>
|
||||
<input name="subscription_code" required placeholder="CON-ABC-2026" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Start Date</label>
|
||||
<input type="date" name="start_date" required class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">End Date</label>
|
||||
<input type="date" name="end_date" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Billing Cycle</label>
|
||||
<select name="billing_cycle" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
{% for cycle in billing_cycles %}<option value="{{ cycle }}">{{ cycle }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Base Amount</label>
|
||||
<input name="amount" value="0" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">GST Rate</label>
|
||||
<input name="gst_rate" value="18" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<label class="mt-6 inline-flex items-center gap-2 text-sm text-slate-700">
|
||||
<input type="checkbox" name="auto_generate_invoice" checked class="rounded border-slate-300" /> Auto generate invoice
|
||||
</label>
|
||||
<div class="md:col-span-3">
|
||||
<label class="mb-1 block text-xs font-semibold uppercase text-slate-500">Notes</label>
|
||||
<input name="notes" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
</div>
|
||||
<div class="md:col-span-3">
|
||||
<button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white">Create Subscription</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="get" class="flex gap-2 rounded-2xl border border-slate-200 bg-white p-4 shadow-soft">
|
||||
<input name="q" value="{{ q }}" placeholder="Search consultant / subscription" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
<button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white">Search</button>
|
||||
</form>
|
||||
|
||||
<div class="rounded-2xl border border-slate-200 bg-white shadow-soft overflow-hidden">
|
||||
<div class="border-b border-slate-200 px-5 py-4">
|
||||
<h2 class="font-semibold text-slate-900">Active consultant subscriptions</h2>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-slate-200 text-sm">
|
||||
<thead class="bg-slate-50 text-left text-xs uppercase tracking-wide text-slate-500">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Consultant</th>
|
||||
<th class="px-4 py-3">Subscription</th>
|
||||
<th class="px-4 py-3">Plan</th>
|
||||
<th class="px-4 py-3">Cycle</th>
|
||||
<th class="px-4 py-3 text-right">Base Amount</th>
|
||||
<th class="px-4 py-3">Tool Usage</th>
|
||||
<th class="px-4 py-3">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for row in rows %}
|
||||
{% set sub = row.subscription %}
|
||||
<tr>
|
||||
<td class="px-4 py-3 font-medium text-slate-900">{{ sub.account.display_name if sub.account else '-' }}</td>
|
||||
<td class="px-4 py-3">{{ sub.subscription_code }}</td>
|
||||
<td class="px-4 py-3">{{ sub.plan.name if sub.plan else '-' }}</td>
|
||||
<td class="px-4 py-3">{{ sub.billing_cycle }}</td>
|
||||
<td class="px-4 py-3 text-right">{{ '%.2f'|format(sub.amount or 0) }}</td>
|
||||
<td class="px-4 py-3 text-slate-600">
|
||||
Managed Clients: {{ row.usage.managed_client_count }} · Portal Users: {{ row.usage.user_account_count }}
|
||||
<div class="text-xs text-slate-400">Workspace: {{ row.usage.workspace_type }} · Plan: {{ row.usage.workspace_plan }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-3">{{ sub.status }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7" class="px-4 py-6 text-center text-slate-500">No consultant subscriptions found. Sync consultant accounts, create a CONSULTANT plan, then create subscriptions.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<h2 class="font-semibold text-slate-900">Consultant billing accounts</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">These accounts are for platform billing of consultant SaaS/tool access. They are separate from firm-level billing and consultant master records.</p>
|
||||
<div class="mt-4 grid gap-3 md:grid-cols-2 lg:grid-cols-3">
|
||||
{% for account in consultant_accounts[:12] %}
|
||||
<div class="rounded-xl border border-slate-200 p-4 text-sm">
|
||||
<div class="font-semibold text-slate-900">{{ account.display_name }}</div>
|
||||
<div class="mt-1 text-slate-500">Code: {{ account.account_code }} · Status: {{ account.status }}</div>
|
||||
<div class="mt-1 text-slate-500">Email: {{ account.email or '-' }} · Mobile: {{ account.mobile or '-' }}</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="rounded-xl bg-slate-50 p-4 text-sm text-slate-500">No consultant billing accounts synced yet.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,48 @@
|
||||
{% 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-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-slate-900">Platform Billing</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">SaaS billing for Audit Firms, compliance-dashboard clients, consultants and future marketplace customers.</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% if can_manage_plans %}<a href="/platform-billing/plans/new" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">New Plan</a>{% endif %}
|
||||
{% if can_generate_platform_billing_flag %}<a href="/platform-billing/audit-firm-subscriptions/generate" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white shadow-soft">Generate Audit Firm Bills</a>{% endif %}
|
||||
{% if can_generate_platform_billing_flag %}<a href="/platform-billing/client-dashboard-subscriptions/generate" class="rounded-xl border border-brand-200 bg-white px-4 py-2 text-sm font-semibold text-brand-700 shadow-sm">Generate Client Dashboard Bills</a>{% endif %}
|
||||
{% if can_generate_platform_billing_flag %}<a href="/platform-billing/consultant-subscriptions/generate" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 shadow-sm">Generate Consultant Bills</a>{% endif %}
|
||||
{% if can_create_invoice %}<a href="/platform-billing/invoices/new" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm">New Platform Invoice</a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-4 md:grid-cols-5">
|
||||
<a href="/platform-billing/accounts" class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft"><div class="text-sm text-slate-500">Accounts</div><div class="mt-2 text-3xl font-semibold">{{ accounts|length }}</div></a>
|
||||
<a href="/platform-billing/plans" class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft"><div class="text-sm text-slate-500">Plans</div><div class="mt-2 text-3xl font-semibold">{{ plans|length }}</div></a>
|
||||
<a href="/platform-billing/audit-firm-subscriptions" class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft"><div class="text-sm text-slate-500">Audit Firm Billing</div><div class="mt-2 text-3xl font-semibold">{{ audit_firm_accounts|length }}</div></a>
|
||||
<a href="/platform-billing/client-dashboard-subscriptions" class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft"><div class="text-sm text-slate-500">Client Dashboard Billing</div><div class="mt-2 text-3xl font-semibold">{{ client_dashboard_accounts|length }}</div></a>
|
||||
<a href="/platform-billing/consultant-subscriptions" class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft"><div class="text-sm text-slate-500">Consultant Billing</div><div class="mt-2 text-3xl font-semibold">{{ consultant_billing_accounts|length }}</div></a>
|
||||
</div>
|
||||
<div class="grid gap-4 lg:grid-cols-2">
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<h2 class="font-semibold">Revenue Channels</h2>
|
||||
<div class="mt-4 grid gap-3 text-sm text-slate-700">
|
||||
<a href="/platform-billing/audit-firm-subscriptions" class="rounded-xl bg-slate-50 p-3 hover:bg-slate-100">Audit Firm subscription billing</a>
|
||||
<a href="/platform-billing/client-dashboard-subscriptions" class="rounded-xl bg-slate-50 p-3 hover:bg-slate-100">Client compliance dashboard access billing</a>
|
||||
<a href="/platform-billing/consultant-subscriptions" class="rounded-xl bg-slate-50 p-3 hover:bg-slate-100">Consultant SaaS/tool access billing</a>
|
||||
<div class="rounded-xl bg-slate-50 p-3">Marketplace lead / commission billing foundation</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||
<h2 class="font-semibold">Quick Links</h2>
|
||||
<div class="mt-4 flex flex-wrap gap-2 text-sm">
|
||||
<a href="/platform-billing/accounts" class="rounded-xl border px-3 py-2">Billing Accounts</a>
|
||||
<a href="/platform-billing/plans" class="rounded-xl border px-3 py-2">Plans</a>
|
||||
<a href="/platform-billing/audit-firm-subscriptions" class="rounded-xl border px-3 py-2">Audit Firm Subscriptions</a>
|
||||
<a href="/platform-billing/client-dashboard-subscriptions" class="rounded-xl border px-3 py-2">Client Dashboard Subscriptions</a>
|
||||
<a href="/platform-billing/consultant-subscriptions" class="rounded-xl border px-3 py-2">Consultant Subscriptions</a>
|
||||
<a href="/platform-billing/subscriptions" class="rounded-xl border px-3 py-2">All Subscriptions</a>
|
||||
<a href="/platform-billing/invoices" class="rounded-xl border px-3 py-2">Platform Invoices</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,2 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}<div class="max-w-4xl space-y-6"><h1 class="text-2xl font-semibold">New Platform Invoice</h1><form method="post" class="space-y-4 rounded-2xl border bg-white p-5 shadow-soft"><input type="hidden" name="csrf_token" value="{{ csrf_token }}" /><div class="grid gap-4 md:grid-cols-2"><label class="text-sm">Account<select name="account_id" required class="mt-1 w-full rounded-xl border px-3 py-2">{% for a in accounts %}<option value="{{ a.id }}">{{ a.display_name }} ({{ a.account_type }})</option>{% endfor %}</select></label><label class="text-sm">Subscription optional<select name="subscription_id" class="mt-1 w-full rounded-xl border px-3 py-2"><option value="">Manual invoice</option>{% for s in subscriptions %}<option value="{{ s.id }}">{{ s.subscription_code }} - {{ s.account.display_name if s.account else s.account_id }}</option>{% endfor %}</select></label><label class="text-sm">Invoice No<input name="invoice_no" required class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">Tax Type<select name="tax_type" class="mt-1 w-full rounded-xl border px-3 py-2">{% for x in tax_types %}<option value="{{ x }}">{{ x }}</option>{% endfor %}</select></label><label class="text-sm">Invoice Date<input type="date" name="invoice_date" required class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">Due Date<input type="date" name="due_date" class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">Period From<input type="date" name="billing_period_from" class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">Period To<input type="date" name="billing_period_to" class="mt-1 w-full rounded-xl border px-3 py-2" /></label></div><div class="rounded-2xl border bg-slate-50 p-4"><h2 class="font-semibold">Invoice Line</h2><div class="mt-3 grid gap-4 md:grid-cols-2"><label class="text-sm">Charge Type<select name="charge_type" class="mt-1 w-full rounded-xl border px-3 py-2">{% for x in charge_types %}<option value="{{ x }}">{{ x }}</option>{% endfor %}</select></label><label class="text-sm">GST Rate<input name="gst_rate" value="18" class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm md:col-span-2">Description<input name="description" required class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">Quantity<input name="quantity" value="1" class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">Rate<input name="rate" value="0" class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">Discount<input name="discount_amount" value="0" class="mt-1 w-full rounded-xl border px-3 py-2" /></label></div></div><label class="block text-sm">Notes<textarea name="notes" class="mt-1 w-full rounded-xl border px-3 py-2"></textarea></label><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Create Draft Invoice</button></form></div>{% endblock %}
|
||||
@@ -0,0 +1,2 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}<div class="space-y-6"><div class="flex items-center justify-between"><div><h1 class="text-2xl font-semibold">Platform Invoice {{ invoice.invoice_no }}</h1><p class="text-sm text-slate-500">{{ invoice.account.display_name if invoice.account else invoice.account_id }} • {{ invoice.status }}</p></div><div class="flex gap-2">{% if can_post and invoice.status == 'DRAFT' %}<form method="post" action="/platform-billing/invoices/{{ invoice.id }}/post"><input type="hidden" name="csrf_token" value="{{ csrf_token }}" /><button class="rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white">Post Invoice</button></form>{% endif %}</div></div><div class="rounded-2xl border bg-white p-5 shadow-soft"><div class="grid gap-3 text-sm md:grid-cols-4"><div><span class="text-slate-500">Invoice Date</span><div class="font-medium">{{ invoice.invoice_date }}</div></div><div><span class="text-slate-500">Due Date</span><div class="font-medium">{{ invoice.due_date or '-' }}</div></div><div><span class="text-slate-500">Tax Type</span><div class="font-medium">{{ invoice.tax_type }}</div></div><div><span class="text-slate-500">Total</span><div class="font-semibold">₹ {{ '%.2f'|format(invoice.total_amount or 0) }}</div></div></div></div><div class="overflow-hidden rounded-2xl border bg-white shadow-soft"><table class="min-w-full text-sm"><thead class="bg-slate-50 text-left"><tr><th class="px-4 py-3">Description</th><th class="px-4 py-3">Charge</th><th class="px-4 py-3 text-right">Taxable</th><th class="px-4 py-3 text-right">Tax</th><th class="px-4 py-3 text-right">Total</th></tr></thead><tbody class="divide-y">{% for line in invoice.lines %}<tr><td class="px-4 py-3">{{ line.description }}</td><td class="px-4 py-3">{{ line.charge_type }}</td><td class="px-4 py-3 text-right">₹ {{ '%.2f'|format(line.taxable_amount or 0) }}</td><td class="px-4 py-3 text-right">₹ {{ '%.2f'|format((line.cgst_amount or 0) + (line.sgst_amount or 0) + (line.igst_amount or 0)) }}</td><td class="px-4 py-3 text-right">₹ {{ '%.2f'|format(line.line_total or 0) }}</td></tr>{% endfor %}</tbody></table></div>{% if can_record_payment %}<form method="post" action="/platform-billing/invoices/{{ invoice.id }}/payments" class="grid gap-3 rounded-2xl border bg-white p-5 shadow-soft md:grid-cols-4"><input type="hidden" name="csrf_token" value="{{ csrf_token }}" /><input name="amount" placeholder="Amount" required class="rounded-xl border px-3 py-2 text-sm" /><input name="mode" value="Bank" class="rounded-xl border px-3 py-2 text-sm" /><input name="reference_no" placeholder="Reference No" class="rounded-xl border px-3 py-2 text-sm" /><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Record Payment</button></form>{% endif %}</div>{% endblock %}
|
||||
@@ -0,0 +1,2 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}<div class="space-y-6"><div class="flex items-center justify-between"><h1 class="text-2xl font-semibold">Platform Invoices</h1>{% if can_create %}<a href="/platform-billing/invoices/new" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">New Platform Invoice</a>{% endif %}</div><form method="get" class="rounded-2xl border bg-white p-4 shadow-soft"><input name="q" value="{{ q or '' }}" placeholder="Search invoice/account" class="w-full rounded-xl border px-3 py-2 text-sm" /></form><div class="overflow-hidden rounded-2xl border bg-white shadow-soft"><table class="min-w-full text-sm"><thead class="bg-slate-50 text-left"><tr><th class="px-4 py-3">Invoice No</th><th class="px-4 py-3">Date</th><th class="px-4 py-3">Account</th><th class="px-4 py-3 text-right">Total</th><th class="px-4 py-3">Status</th><th class="px-4 py-3"></th></tr></thead><tbody class="divide-y">{% for row in rows %}<tr><td class="px-4 py-3 font-medium">{{ row.invoice_no }}</td><td class="px-4 py-3">{{ row.invoice_date }}</td><td class="px-4 py-3">{{ row.account.display_name if row.account else row.account_id }}</td><td class="px-4 py-3 text-right">₹ {{ '%.2f'|format(row.total_amount or 0) }}</td><td class="px-4 py-3">{{ row.status }}</td><td class="px-4 py-3 text-right"><a href="/platform-billing/invoices/{{ row.id }}" class="text-brand-600 hover:underline">View</a></td></tr>{% else %}<tr><td colspan="6" class="px-4 py-8 text-center text-slate-500">No platform invoices found.</td></tr>{% endfor %}</tbody></table></div></div>{% endblock %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="max-w-3xl space-y-6"><h1 class="text-2xl font-semibold">New Platform Plan</h1><form method="post" class="space-y-4 rounded-2xl border bg-white p-5 shadow-soft"><input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<div class="grid gap-4 md:grid-cols-2"><label class="text-sm">Plan Code<input name="code" required class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">Plan Name<input name="name" required class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">Target Account<select name="target_account_type" class="mt-1 w-full rounded-xl border px-3 py-2">{% for x in account_types %}<option value="{{ x }}">{{ x }}</option>{% endfor %}</select></label><label class="text-sm">Billing Cycle<select name="billing_cycle" class="mt-1 w-full rounded-xl border px-3 py-2">{% for x in billing_cycles %}<option value="{{ x }}">{{ x }}</option>{% endfor %}</select></label><label class="text-sm">Base Amount<input name="base_amount" value="0" class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">GST Rate<input name="gst_rate" value="18" class="mt-1 w-full rounded-xl border px-3 py-2" /></label></div><label class="block text-sm">Description<textarea name="description" class="mt-1 w-full rounded-xl border px-3 py-2"></textarea></label><label class="block text-sm">Features / Limits (one per line)<textarea name="feature_text" rows="5" class="mt-1 w-full rounded-xl border px-3 py-2"></textarea></label><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Save Plan</button></form></div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,8 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between"><h1 class="text-2xl font-semibold">Platform Plans</h1>{% if can_manage %}<a href="/platform-billing/plans/new" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">New Plan</a>{% endif %}</div>
|
||||
<form method="get" class="rounded-2xl border bg-white p-4 shadow-soft"><input name="q" value="{{ q or '' }}" placeholder="Search plan" class="w-full rounded-xl border px-3 py-2 text-sm" /></form>
|
||||
<div class="overflow-hidden rounded-2xl border bg-white shadow-soft"><table class="min-w-full text-sm"><thead class="bg-slate-50 text-left"><tr><th class="px-4 py-3">Code</th><th class="px-4 py-3">Name</th><th class="px-4 py-3">For</th><th class="px-4 py-3">Cycle</th><th class="px-4 py-3 text-right">Base</th><th class="px-4 py-3">Status</th></tr></thead><tbody class="divide-y">{% for row in rows %}<tr><td class="px-4 py-3 font-medium">{{ row.code }}</td><td class="px-4 py-3">{{ row.name }}</td><td class="px-4 py-3">{{ row.target_account_type }}</td><td class="px-4 py-3">{{ row.billing_cycle }}</td><td class="px-4 py-3 text-right">₹ {{ '%.2f'|format(row.base_amount or 0) }}</td><td class="px-4 py-3">{{ 'Active' if row.is_active else 'Inactive' }}</td></tr>{% else %}<tr><td colspan="6" class="px-4 py-8 text-center text-slate-500">No plans found.</td></tr>{% endfor %}</tbody></table></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,2 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}<div class="max-w-3xl space-y-6"><h1 class="text-2xl font-semibold">New Platform Subscription</h1><form method="post" class="space-y-4 rounded-2xl border bg-white p-5 shadow-soft"><input type="hidden" name="csrf_token" value="{{ csrf_token }}" /><div class="grid gap-4 md:grid-cols-2"><label class="text-sm">Account<select name="account_id" required class="mt-1 w-full rounded-xl border px-3 py-2">{% for a in accounts %}<option value="{{ a.id }}">{{ a.display_name }} ({{ a.account_type }})</option>{% endfor %}</select></label><label class="text-sm">Plan<select name="plan_id" required class="mt-1 w-full rounded-xl border px-3 py-2">{% for p in plans %}<option value="{{ p.id }}">{{ p.name }}</option>{% endfor %}</select></label><label class="text-sm">Subscription Code<input name="subscription_code" required class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">Billing Cycle<select name="billing_cycle" class="mt-1 w-full rounded-xl border px-3 py-2">{% for x in billing_cycles %}<option value="{{ x }}">{{ x }}</option>{% endfor %}</select></label><label class="text-sm">Start Date<input type="date" name="start_date" required class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">End Date<input type="date" name="end_date" class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">Amount<input name="amount" value="0" class="mt-1 w-full rounded-xl border px-3 py-2" /></label><label class="text-sm">GST Rate<input name="gst_rate" value="18" class="mt-1 w-full rounded-xl border px-3 py-2" /></label></div><label class="inline-flex items-center gap-2 text-sm"><input type="checkbox" name="auto_generate_invoice" checked /> Auto generate invoice later</label><label class="block text-sm">Notes<textarea name="notes" class="mt-1 w-full rounded-xl border px-3 py-2"></textarea></label><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Save Subscription</button></form></div>{% endblock %}
|
||||
@@ -0,0 +1,2 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}<div class="space-y-6"><div class="flex items-center justify-between"><h1 class="text-2xl font-semibold">Platform Subscriptions</h1>{% if can_manage %}<a href="/platform-billing/subscriptions/new" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">New Subscription</a>{% endif %}</div><form method="get" class="rounded-2xl border bg-white p-4 shadow-soft"><input name="q" value="{{ q or '' }}" placeholder="Search subscription/account" class="w-full rounded-xl border px-3 py-2 text-sm" /></form><div class="overflow-hidden rounded-2xl border bg-white shadow-soft"><table class="min-w-full text-sm"><thead class="bg-slate-50 text-left"><tr><th class="px-4 py-3">Code</th><th class="px-4 py-3">Account</th><th class="px-4 py-3">Plan</th><th class="px-4 py-3">Cycle</th><th class="px-4 py-3 text-right">Amount</th><th class="px-4 py-3">Status</th></tr></thead><tbody class="divide-y">{% for row in rows %}<tr><td class="px-4 py-3 font-medium">{{ row.subscription_code }}</td><td class="px-4 py-3">{{ row.account.display_name if row.account else row.account_id }}</td><td class="px-4 py-3">{{ row.plan.name if row.plan else row.plan_id }}</td><td class="px-4 py-3">{{ row.billing_cycle }}</td><td class="px-4 py-3 text-right">₹ {{ '%.2f'|format(row.amount or 0) }}</td><td class="px-4 py-3">{{ row.status }}</td></tr>{% else %}<tr><td colspan="6" class="px-4 py-8 text-center text-slate-500">No subscriptions found.</td></tr>{% endfor %}</tbody></table></div></div>{% endblock %}
|
||||
Reference in New Issue
Block a user