245 lines
17 KiB
HTML
245 lines
17 KiB
HTML
{% extends "ui/templates/base/layout.html" %}
|
|
{% block content %}
|
|
<div class="space-y-6">
|
|
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
|
<div>
|
|
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-brand-600">Phase 7R.1</p>
|
|
<h1 class="text-2xl font-bold text-slate-900">Firm Billing Settings</h1>
|
|
<p class="mt-1 text-sm text-slate-500">Configure firm GST, invoice numbering, payment details and invoice footer defaults.</p>
|
|
</div>
|
|
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm shadow-sm">
|
|
<div class="font-semibold text-slate-900">{{ tenant_name }}</div>
|
|
<div class="text-xs text-slate-500">{% if branch_name %}Branch: {{ branch_name }}{% else %}Firm-wide default{% endif %}</div>
|
|
<div class="mt-2 text-xs text-slate-500">Next invoice preview</div>
|
|
<div class="font-mono text-sm font-semibold text-brand-700">{{ preview_invoice_no }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid gap-6 lg:grid-cols-[minmax(0,1fr)_320px]">
|
|
<form method="post" action="/billing/settings" class="space-y-6">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
|
|
|
<section class="af-card p-5">
|
|
<div class="mb-4 flex items-center justify-between gap-3">
|
|
<div>
|
|
<h2 class="text-lg font-semibold text-slate-900">Scope</h2>
|
|
<p class="text-sm text-slate-500">Keep branch-specific settings for branch-wise invoice series, or use firm-wide default if you are working across branches.</p>
|
|
</div>
|
|
</div>
|
|
<div class="grid gap-4 md:grid-cols-2">
|
|
<label class="rounded-2xl border border-slate-200 p-4 text-sm">
|
|
<input type="radio" name="branch_scope" value="active" class="mr-2" {% if branch_scope != 'firm' %}checked{% endif %} />
|
|
Active branch settings
|
|
<div class="mt-1 text-xs text-slate-500">Recommended for branch-wise invoice numbering.</div>
|
|
</label>
|
|
<label class="rounded-2xl border border-slate-200 p-4 text-sm">
|
|
<input type="radio" name="branch_scope" value="firm" class="mr-2" {% if branch_scope == 'firm' %}checked{% endif %} />
|
|
Firm-wide default
|
|
<div class="mt-1 text-xs text-slate-500">Available when cross-branch billing permission is active.</div>
|
|
</label>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="af-card p-5">
|
|
<h2 class="text-lg font-semibold text-slate-900">Firm GST & Contact Details</h2>
|
|
<div class="mt-4 grid gap-4 md:grid-cols-2">
|
|
<label class="text-sm font-medium text-slate-700">Legal / Billing Name
|
|
<input name="legal_name" value="{{ settings.legal_name or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">GSTIN
|
|
<input name="gstin" value="{{ settings.gstin or '' }}" maxlength="15" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm uppercase" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">PAN
|
|
<input name="pan" value="{{ settings.pan or '' }}" maxlength="10" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm uppercase" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">State Code
|
|
<input name="state_code" value="{{ settings.state_code or '' }}" maxlength="2" placeholder="33" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Contact Email
|
|
<input name="contact_email" value="{{ settings.contact_email or '' }}" type="email" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Contact Mobile
|
|
<input name="contact_mobile" value="{{ settings.contact_mobile or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700 md:col-span-2">Website
|
|
<input name="website_url" value="{{ settings.website_url or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700 md:col-span-2">Billing Address
|
|
<textarea name="billing_address" rows="3" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">{{ settings.billing_address or '' }}</textarea>
|
|
</label>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="af-card p-5">
|
|
<h2 class="text-lg font-semibold text-slate-900">Invoice Numbering & Tax Defaults</h2>
|
|
<div class="mt-4 grid gap-4 md:grid-cols-3">
|
|
<label class="text-sm font-medium text-slate-700">Invoice Title
|
|
<input name="invoice_title" value="{{ settings.invoice_title or 'Tax Invoice' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Prefix
|
|
<input name="invoice_prefix" value="{{ settings.invoice_prefix or 'INV' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm uppercase" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Next Number
|
|
<input name="next_invoice_no" value="{{ settings.next_invoice_no or 1 }}" type="number" min="1" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Padding
|
|
<input name="padding" value="{{ settings.padding or 4 }}" type="number" min="1" max="10" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700 md:col-span-2">Number Format
|
|
<input name="invoice_number_format" value="{{ settings.invoice_number_format or '{prefix}/{fy}/{number}' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 font-mono text-sm" />
|
|
<span class="mt-1 block text-xs text-slate-500">Tokens: {prefix}, {fy}, {number}, {branch_id}</span>
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Default Due Days
|
|
<input name="default_due_days" value="{{ settings.default_due_days or 15 }}" type="number" min="0" max="365" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Default GST Rate %
|
|
<input name="default_gst_rate" value="{{ settings.default_gst_rate or '18.00' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Default Tax Type
|
|
<select name="default_tax_type" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
|
{% for tax in tax_types %}<option value="{{ tax }}" {% if settings.default_tax_type == tax %}selected{% endif %}>{{ tax }}</option>{% endfor %}
|
|
</select>
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Default SAC Code
|
|
<input name="default_sac_code" value="{{ settings.default_sac_code or '' }}" placeholder="9982" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="af-card p-5">
|
|
<h2 class="text-lg font-semibold text-slate-900">Bank, UPI & Payment Details</h2>
|
|
<div class="mt-4 grid gap-4 md:grid-cols-2">
|
|
<label class="text-sm font-medium text-slate-700">Bank Name
|
|
<input name="bank_name" value="{{ settings.bank_name or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Account Name
|
|
<input name="bank_account_name" value="{{ settings.bank_account_name or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Account Number
|
|
<input name="bank_account_number" value="{{ settings.bank_account_number or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">IFSC
|
|
<input name="bank_ifsc" value="{{ settings.bank_ifsc or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm uppercase" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700 md:col-span-2">UPI ID
|
|
<input name="upi_id" value="{{ settings.upi_id or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700 md:col-span-2">Additional Bank Details / Payment Instructions
|
|
<textarea name="bank_details" rows="3" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">{{ settings.bank_details or '' }}</textarea>
|
|
</label>
|
|
</div>
|
|
</section>
|
|
|
|
|
|
<section class="af-card p-5">
|
|
<h2 class="text-lg font-semibold text-slate-900">PayUMoney / PayU Online Payment Gateway</h2>
|
|
<p class="mt-1 text-sm text-slate-500">Enable this only after entering valid PayU/PayUMoney merchant credentials. Test mode posts to PayU test checkout.</p>
|
|
<div class="mt-4 grid gap-4 md:grid-cols-2">
|
|
<label class="flex items-center gap-3 rounded-2xl border border-slate-200 p-4 text-sm font-medium text-slate-700 md:col-span-2">
|
|
<input type="checkbox" name="payumoney_enabled" value="1" {% if settings.payumoney_enabled %}checked{% endif %} />
|
|
Enable PayUMoney / PayU Pay Now for client portal
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Mode
|
|
<select name="payumoney_mode" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
|
<option value="TEST" {% if settings.payumoney_mode != 'LIVE' %}selected{% endif %}>TEST / Sandbox</option>
|
|
<option value="LIVE" {% if settings.payumoney_mode == 'LIVE' %}selected{% endif %}>LIVE / Production</option>
|
|
</select>
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Merchant ID, optional
|
|
<input name="payumoney_merchant_id" value="{{ settings.payumoney_merchant_id or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Merchant Key
|
|
<input name="payumoney_merchant_key" value="{{ settings.payumoney_merchant_key or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" autocomplete="off" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Merchant Salt
|
|
<input name="payumoney_merchant_salt" value="{{ settings.payumoney_merchant_salt or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" autocomplete="off" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700 md:col-span-2">Product Info Label
|
|
<input name="payumoney_product_info" value="{{ settings.payumoney_product_info or 'Professional Services Invoice' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
</div>
|
|
<div class="mt-4 rounded-2xl border border-amber-200 bg-amber-50 p-4 text-xs leading-5 text-amber-900">
|
|
Store separate test and live credentials carefully. Do not enable LIVE until callback testing is completed from an accessible public URL.
|
|
</div>
|
|
</section>
|
|
|
|
|
|
<section class="af-card p-5">
|
|
<h2 class="text-lg font-semibold text-slate-900">Cashfree Online Payment Gateway</h2>
|
|
<p class="mt-1 text-sm text-slate-500">Enable Cashfree only after adding valid Cashfree PG credentials. Sandbox mode uses Cashfree sandbox APIs.</p>
|
|
<div class="mt-4 grid gap-4 md:grid-cols-2">
|
|
<label class="flex items-center gap-3 rounded-2xl border border-slate-200 p-4 text-sm font-medium text-slate-700 md:col-span-2">
|
|
<input type="checkbox" name="cashfree_enabled" value="1" {% if settings.cashfree_enabled %}checked{% endif %} />
|
|
Enable Cashfree Pay Now for client portal
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Mode
|
|
<select name="cashfree_mode" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
|
<option value="TEST" {% if settings.cashfree_mode != 'LIVE' %}selected{% endif %}>TEST / Sandbox</option>
|
|
<option value="LIVE" {% if settings.cashfree_mode == 'LIVE' %}selected{% endif %}>LIVE / Production</option>
|
|
</select>
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">API Version
|
|
<input name="cashfree_api_version" value="{{ settings.cashfree_api_version or '2023-08-01' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Client ID / App ID
|
|
<input name="cashfree_client_id" value="{{ settings.cashfree_client_id or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" autocomplete="off" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Client Secret
|
|
<input name="cashfree_client_secret" value="{{ settings.cashfree_client_secret or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" autocomplete="off" />
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700 md:col-span-2">Order Note
|
|
<input name="cashfree_order_note" value="{{ settings.cashfree_order_note or 'Professional Services Invoice' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
</div>
|
|
<div class="mt-4 rounded-2xl border border-sky-200 bg-sky-50 p-4 text-xs leading-5 text-sky-900">
|
|
Cashfree checkout creates an order from the server and uses payment_session_id for hosted checkout. Webhook URL: <span class="font-mono">/client/billing/cashfree/webhook</span>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="af-card p-5">
|
|
<h2 class="text-lg font-semibold text-slate-900">Invoice Notes, Terms & Signatory</h2>
|
|
<div class="mt-4 grid gap-4">
|
|
<label class="text-sm font-medium text-slate-700">Default Terms
|
|
<textarea name="terms" rows="3" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">{{ settings.terms or '' }}</textarea>
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Declaration
|
|
<textarea name="declaration" rows="3" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">{{ settings.declaration or '' }}</textarea>
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Invoice Footer Note
|
|
<textarea name="footer_note" rows="2" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">{{ settings.footer_note or '' }}</textarea>
|
|
</label>
|
|
<label class="text-sm font-medium text-slate-700">Authorised Signatory Name
|
|
<input name="authorised_signatory_name" value="{{ settings.authorised_signatory_name or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
|
</label>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="flex items-center justify-end gap-3">
|
|
<a href="/billing" class="af-btn af-btn-secondary">Back to Invoices</a>
|
|
{% if can_edit_settings %}
|
|
<button type="submit" class="af-btn af-btn-primary">Save Billing Settings</button>
|
|
{% else %}
|
|
<span class="text-sm text-slate-500">View-only access</span>
|
|
{% endif %}
|
|
</div>
|
|
</form>
|
|
|
|
<aside class="space-y-4">
|
|
<div class="af-card p-5">
|
|
<h3 class="font-semibold text-slate-900">Why this matters</h3>
|
|
<ul class="mt-3 space-y-2 text-sm text-slate-600">
|
|
<li>• GST invoice format will use these details in Phase 7R.2.</li>
|
|
<li>• Payment and receipt tracking will use bank/UPI details in Phase 7R.4.</li>
|
|
<li>• Client portal Pay Now uses UPI, PayUMoney and Cashfree settings from Phase 7R.5 / 7R.6 / 7R.6A.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="af-card p-5">
|
|
<h3 class="font-semibold text-slate-900">Recommended invoice format</h3>
|
|
<p class="mt-2 rounded-xl bg-slate-50 px-3 py-2 font-mono text-sm text-slate-700">{prefix}/{fy}/{number}</p>
|
|
<p class="mt-2 text-xs text-slate-500">Example: INV/2026-27/0001</p>
|
|
</div>
|
|
</aside>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|