Fix opening balance FY mirror resolution

This commit is contained in:
A R R R Associates
2026-09-06 20:23:10 +05:30
parent 7adbbde220
commit 6e0abe7909
2 changed files with 146 additions and 54 deletions
+90 -24
View File
@@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import json import json
import re
from io import BytesIO from io import BytesIO
from urllib.parse import urlencode from urllib.parse import urlencode
from datetime import date, datetime from datetime import date, datetime
@@ -107,6 +108,30 @@ def _page_rows(rows, page, per_page=50):
return rows[start:start + per_page], total, page, pages return rows[start:start + per_page], total, page, pages
def _previous_financial_year(financial_year: str) -> str:
"""Return the immediately preceding Indian financial year."""
value = str(financial_year or "").strip()
match = re.fullmatch(r"(\d{4})-(\d{2})", value)
if not match:
raise ValueError(f"Invalid financial year: {value}")
start_year = int(match.group(1))
previous_start = start_year - 1
return f"{previous_start}-{str(start_year)[-2:]}"
def _financial_year_options(reference_fy: str, years_back: int = 6) -> list[str]:
"""Return the active FY and earlier FYs for the review selector."""
value = str(reference_fy or "").strip()
match = re.fullmatch(r"(\d{4})-(\d{2})", value)
if not match:
return [value] if value else []
start_year = int(match.group(1))
return [
f"{year}-{str(year + 1)[-2:]}"
for year in range(start_year, start_year - max(1, int(years_back)), -1)
]
@router.get("") @router.get("")
def page( def page(
request: Request, request: Request,
@@ -120,6 +145,7 @@ def page(
message: str = "", message: str = "",
error: str = "", error: str = "",
analysis_run_id: int = 0, analysis_run_id: int = 0,
financial_year: str = "",
): ):
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
@@ -139,36 +165,70 @@ def page(
companies = [] companies = []
command_error = error or "" command_error = error or ""
today = date.today() today = date.today()
current_fy = _financial_year_for_date(today) active_fy = _financial_year_for_date(today)
current_start = int(current_fy.split("-", 1)[0]) fy_options = _financial_year_options(active_fy, 7)
previous_fy = f"{current_start-1}-{str(current_start)[-2:]}"
requested_fy = str(financial_year or "").strip()
if requested_fy and not re.fullmatch(r"\d{4}-\d{2}", requested_fy):
command_error = command_error or "Invalid financial year selection."
requested_fy = ""
current_fy = requested_fy or active_fy
previous_fy = _previous_financial_year(current_fy)
previous_mirror_status = None previous_mirror_status = None
current_mirror_status = None current_mirror_status = None
if selected and node and online: if selected and node and online:
status_cache = {}
def mirror_status_for(fy_value: str):
if fy_value in status_cache:
return status_cache[fy_value]
response = request_agent_command(
node.node_code,
"accounting_mirror_status",
_accounting_storage_payload(selected, fy_value),
timeout_seconds=20,
)
status = (response.get("result") or {}) if response.get("ok") else {}
status_cache[fy_value] = (status, response)
return status, response
try: try:
prev_response = request_agent_command( # When the user has not selected a review FY explicitly, prefer
node.node_code, # the active ERP FY only if its mirror exists. Otherwise use
"accounting_mirror_status", # the immediately preceding FY when that is the newest mirror
_accounting_storage_payload(selected, previous_fy), # already available for the client. This prevents an existing
timeout_seconds=20, # FY2025-26 mirror from being shown as "missing" merely because
) # the ERP header is now FY2026-27.
curr_response = request_agent_command( if not requested_fy:
node.node_code, active_status, active_response = mirror_status_for(active_fy)
"accounting_mirror_status", active_ready = bool((active_status.get("mirror") or {}).get("ready"))
_accounting_storage_payload(selected, current_fy), if active_ready:
timeout_seconds=20, current_fy = active_fy
) else:
if prev_response.get("ok"): prior_active_fy = _previous_financial_year(active_fy)
previous_mirror_status = prev_response.get("result") or {} prior_status, prior_response = mirror_status_for(prior_active_fy)
if curr_response.get("ok"): prior_ready = bool((prior_status.get("mirror") or {}).get("ready"))
current_mirror_status = curr_response.get("result") or {} if prior_ready:
if not prev_response.get("ok"): current_fy = prior_active_fy
command_error = str(prev_response.get("error") or "Previous-year Accounting Mirror status failed.") else:
elif not curr_response.get("ok"): current_fy = active_fy
command_error = str(curr_response.get("error") or "Current-year Accounting Mirror status failed.")
previous_fy = _previous_financial_year(current_fy)
current_mirror_status, current_response = mirror_status_for(current_fy)
previous_mirror_status, previous_response = mirror_status_for(previous_fy)
if not current_response.get("ok"):
command_error = command_error or str(
current_response.get("error") or "Current-year Accounting Mirror status failed."
)
elif not previous_response.get("ok"):
command_error = command_error or str(
previous_response.get("error") or "Previous-year Accounting Mirror status failed."
)
except Exception as exc: except Exception as exc:
command_error = str(exc) command_error = command_error or str(exc)
runs = [] runs = []
selected_run = None selected_run = None
@@ -276,6 +336,12 @@ def page(
"node_online": online, "node_online": online,
"tally": tally, "tally": tally,
"companies": companies, "companies": companies,
"active_fy": active_fy,
"fy_options": fy_options,
"current_fy": current_fy,
"previous_fy": previous_fy,
"current_mirror_status": current_mirror_status,
"previous_mirror_status": previous_mirror_status,
"runs": runs, "runs": runs,
"selected_run": selected_run, "selected_run": selected_run,
"ledgers": ledgers, "ledgers": ledgers,
@@ -20,7 +20,7 @@
{% if error %}<div class="rounded-xl border border-red-200 bg-red-50 p-3 text-sm text-red-800">{{ error }}</div>{% endif %} {% if error %}<div class="rounded-xl border border-red-200 bg-red-50 p-3 text-sm text-red-800">{{ error }}</div>{% endif %}
<section class="rounded-2xl bg-white p-5 shadow-soft"> <section class="rounded-2xl bg-white p-5 shadow-soft">
<form method="get" class="grid gap-4 md:grid-cols-[1.5fr_auto]"> <form method="get" class="grid gap-4 md:grid-cols-[1.4fr_0.7fr_auto]">
<label class="text-sm font-semibold">ERP Client <label class="text-sm font-semibold">ERP Client
<select name="client_id" required class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2" onchange="this.form.submit()"> <select name="client_id" required class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2" onchange="this.form.submit()">
<option value="">Select client</option> <option value="">Select client</option>
@@ -29,15 +29,41 @@
{% endfor %} {% endfor %}
</select> </select>
</label> </label>
<div class="flex items-end"><a href="/tools/tally{% if selected_client %}?client_id={{ selected_client.id }}{% endif %}" class="rounded-xl border border-slate-300 bg-white px-5 py-2 text-sm font-semibold">Manage Mirrors</a></div>
<label class="text-sm font-semibold">Review Financial Year
<select name="financial_year" class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2" onchange="this.form.submit()">
{% for fy in fy_options %}
<option value="{{ fy }}" {% if fy==current_fy %}selected{% endif %}>FY {{ fy }}</option>
{% endfor %}
</select>
</label>
<div class="flex items-end">
<a href="/tools/tally{% if selected_client %}?client_id={{ selected_client.id }}{% endif %}" class="rounded-xl border border-slate-300 bg-white px-5 py-2 text-sm font-semibold">Manage Mirrors</a>
</div>
</form> </form>
{% if selected_client %} {% if selected_client %}
{% set prev = previous_mirror_status.mirror if previous_mirror_status and previous_mirror_status.mirror else {} %} {% set prev = previous_mirror_status.mirror if previous_mirror_status and previous_mirror_status.mirror else {} %}
{% set curr = current_mirror_status.mirror if current_mirror_status and current_mirror_status.mirror else {} %} {% set curr = current_mirror_status.mirror if current_mirror_status and current_mirror_status.mirror else {} %}
<div class="mt-5 grid gap-3 md:grid-cols-2"> <div class="mt-5 grid gap-3 md:grid-cols-2">
<div class="rounded-xl border p-4 {{ 'border-emerald-200 bg-emerald-50' if prev.ready else 'border-amber-200 bg-amber-50' }}"><div class="text-xs font-semibold uppercase text-slate-500">Previous FY Mirror · {{ previous_fy }}</div><div class="mt-1 font-semibold">{{ 'Ready' if prev.ready else 'Mirror required' }}</div>{% if prev.ready %}<div class="mt-1 text-xs text-slate-500">{{ prev.company.company_name }} · {{ prev.counts.voucher }} vouchers</div>{% endif %}</div> <div class="rounded-xl border p-4 {{ 'border-emerald-200 bg-emerald-50' if prev.ready else 'border-amber-200 bg-amber-50' }}">
<div class="rounded-xl border p-4 {{ 'border-emerald-200 bg-emerald-50' if curr.ready else 'border-amber-200 bg-amber-50' }}"><div class="text-xs font-semibold uppercase text-slate-500">Current FY Mirror · {{ current_fy }}</div><div class="mt-1 font-semibold">{{ 'Ready' if curr.ready else 'Mirror required' }}</div>{% if curr.ready %}<div class="mt-1 text-xs text-slate-500">{{ curr.company.company_name }} · {{ curr.counts.voucher }} vouchers</div>{% endif %}</div> <div class="text-xs font-semibold uppercase text-slate-500">Previous FY Mirror · FY {{ previous_fy }}</div>
<div class="mt-1 font-semibold">{{ 'Ready' if prev.ready else 'Mirror required' }}</div>
{% if prev.ready %}
<div class="mt-1 text-xs text-slate-600">{{ prev.company.company_name }} · {{ prev.counts.voucher }} vouchers</div>
<div class="mt-1 break-all text-[11px] text-slate-500">{{ previous_mirror_status.mirror_db_path or prev.path or '' }}</div>
{% endif %}
</div>
<div class="rounded-xl border p-4 {{ 'border-emerald-200 bg-emerald-50' if curr.ready else 'border-amber-200 bg-amber-50' }}">
<div class="text-xs font-semibold uppercase text-slate-500">Current FY Mirror · FY {{ current_fy }}</div>
<div class="mt-1 font-semibold">{{ 'Ready' if curr.ready else 'Mirror required' }}</div>
{% if curr.ready %}
<div class="mt-1 text-xs text-slate-600">{{ curr.company.company_name }} · {{ curr.counts.voucher }} vouchers</div>
<div class="mt-1 break-all text-[11px] text-slate-500">{{ current_mirror_status.mirror_db_path or curr.path or '' }}</div>
{% endif %}
</div>
</div> </div>
<form method="post" action="/tools/accounting/opening-balances/compare" class="mt-4 flex flex-wrap items-end gap-3"> <form method="post" action="/tools/accounting/opening-balances/compare" class="mt-4 flex flex-wrap items-end gap-3">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
@@ -63,7 +89,7 @@
<td class="px-4 py-3">{{ run.current_company_name }}</td> <td class="px-4 py-3">{{ run.current_company_name }}</td>
<td class="px-4 py-3">{{ run.ledger_count }}</td> <td class="px-4 py-3">{{ run.ledger_count }}</td>
<td class="px-4 py-3">{{ run.stock_item_count }}</td> <td class="px-4 py-3">{{ run.stock_item_count }}</td>
<td class="px-4 py-3 text-right"><a class="font-semibold text-brand-700 hover:underline" href="?client_id={{ selected_client.id }}&run_id={{ run.id }}&tab=summary">Open Summary</a></td> <td class="px-4 py-3 text-right"><a class="font-semibold text-brand-700 hover:underline" href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ run.id }}&tab=summary">Open Summary</a></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@@ -84,7 +110,7 @@
<p class="text-xs font-semibold uppercase tracking-[0.14em] text-slate-500">Run #{{ selected_run.id }}</p> <p class="text-xs font-semibold uppercase tracking-[0.14em] text-slate-500">Run #{{ selected_run.id }}</p>
<h2 class="mt-1 text-lg font-semibold">{{ selected_run.previous_company_name }} closing → {{ selected_run.current_company_name }} opening</h2> <h2 class="mt-1 text-lg font-semibold">{{ selected_run.previous_company_name }} closing → {{ selected_run.current_company_name }} opening</h2>
</div> </div>
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=summary" class="rounded-xl {% if tab=='summary' %}bg-brand-600 text-white{% else %}border border-slate-300 bg-white{% endif %} px-4 py-2 text-sm font-semibold">Summary</a> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=summary" class="rounded-xl {% if tab=='summary' %}bg-brand-600 text-white{% else %}border border-slate-300 bg-white{% endif %} px-4 py-2 text-sm font-semibold">Summary</a>
</div> </div>
</section> </section>
@@ -95,16 +121,16 @@
<p class="text-sm text-slate-500">Matched records are summarized only. Click a category when you need its detailed report.</p> <p class="text-sm text-slate-500">Matched records are summarized only. Click a category when you need its detailed report.</p>
</div> </div>
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-4"> <div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=ledgers&status=matched" class="rounded-2xl border border-emerald-200 bg-emerald-50 p-4"> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=ledgers&status=matched" class="rounded-2xl border border-emerald-200 bg-emerald-50 p-4">
<div class="text-xs font-semibold uppercase text-emerald-700">Matched</div><div class="mt-1 text-3xl font-bold text-emerald-900">{{ summary.ledgers.matched }}</div><div class="mt-2 text-xs text-emerald-700">View detailed report</div> <div class="text-xs font-semibold uppercase text-emerald-700">Matched</div><div class="mt-1 text-3xl font-bold text-emerald-900">{{ summary.ledgers.matched }}</div><div class="mt-2 text-xs text-emerald-700">View detailed report</div>
</a> </a>
<a href="#ledger-actions" class="rounded-2xl border border-amber-300 bg-amber-50 p-4"> <a href="#ledger-actions" class="rounded-2xl border border-amber-300 bg-amber-50 p-4">
<div class="text-xs font-semibold uppercase text-amber-700">Differences</div><div class="mt-1 text-3xl font-bold text-amber-900">{{ summary.ledgers.difference }}</div><div class="mt-2 text-xs text-amber-700">Review & apply below</div> <div class="text-xs font-semibold uppercase text-amber-700">Differences</div><div class="mt-1 text-3xl font-bold text-amber-900">{{ summary.ledgers.difference }}</div><div class="mt-2 text-xs text-amber-700">Review & apply below</div>
</a> </a>
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=ledgers&status=missing_in_current_year" class="rounded-2xl border border-red-200 bg-red-50 p-4"> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=ledgers&status=missing_in_current_year" class="rounded-2xl border border-red-200 bg-red-50 p-4">
<div class="text-xs font-semibold uppercase text-red-700">Missing in Current Year</div><div class="mt-1 text-3xl font-bold text-red-900">{{ summary.ledgers.missing_in_current_year }}</div><div class="mt-2 text-xs text-red-700">Review details</div> <div class="text-xs font-semibold uppercase text-red-700">Missing in Current Year</div><div class="mt-1 text-3xl font-bold text-red-900">{{ summary.ledgers.missing_in_current_year }}</div><div class="mt-2 text-xs text-red-700">Review details</div>
</a> </a>
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=ledgers&status=new_in_current_year" class="rounded-2xl border border-slate-200 bg-slate-50 p-4"> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=ledgers&status=new_in_current_year" class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
<div class="text-xs font-semibold uppercase text-slate-600">New in Current Year</div><div class="mt-1 text-3xl font-bold text-slate-900">{{ summary.ledgers.new_in_current_year }}</div><div class="mt-2 text-xs text-slate-600">Zero-balance rows hidden by default</div> <div class="text-xs font-semibold uppercase text-slate-600">New in Current Year</div><div class="mt-1 text-3xl font-bold text-slate-900">{{ summary.ledgers.new_in_current_year }}</div><div class="mt-2 text-xs text-slate-600">Zero-balance rows hidden by default</div>
</a> </a>
</div> </div>
@@ -114,25 +140,25 @@
<p class="text-sm text-slate-500">Quantity, value and unit exceptions are separated so the reviewer sees only the issue that needs attention.</p> <p class="text-sm text-slate-500">Quantity, value and unit exceptions are separated so the reviewer sees only the issue that needs attention.</p>
</div> </div>
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-4"> <div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=stock&status=matched" class="rounded-2xl border border-emerald-200 bg-emerald-50 p-4"> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=stock&status=matched" class="rounded-2xl border border-emerald-200 bg-emerald-50 p-4">
<div class="text-xs font-semibold uppercase text-emerald-700">Matched</div><div class="mt-1 text-3xl font-bold text-emerald-900">{{ summary.stock_items.matched }}</div><div class="mt-2 text-xs text-emerald-700">View detailed report</div> <div class="text-xs font-semibold uppercase text-emerald-700">Matched</div><div class="mt-1 text-3xl font-bold text-emerald-900">{{ summary.stock_items.matched }}</div><div class="mt-2 text-xs text-emerald-700">View detailed report</div>
</a> </a>
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=stock&status=quantity_difference" class="rounded-2xl border border-amber-200 bg-amber-50 p-4"> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=stock&status=quantity_difference" class="rounded-2xl border border-amber-200 bg-amber-50 p-4">
<div class="text-xs font-semibold uppercase text-amber-700">Quantity Difference</div><div class="mt-1 text-3xl font-bold text-amber-900">{{ summary.stock_items.quantity_difference }}</div><div class="mt-2 text-xs text-amber-700">Review details</div> <div class="text-xs font-semibold uppercase text-amber-700">Quantity Difference</div><div class="mt-1 text-3xl font-bold text-amber-900">{{ summary.stock_items.quantity_difference }}</div><div class="mt-2 text-xs text-amber-700">Review details</div>
</a> </a>
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=stock&status=value_difference" class="rounded-2xl border border-amber-200 bg-amber-50 p-4"> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=stock&status=value_difference" class="rounded-2xl border border-amber-200 bg-amber-50 p-4">
<div class="text-xs font-semibold uppercase text-amber-700">Value Difference</div><div class="mt-1 text-3xl font-bold text-amber-900">{{ summary.stock_items.value_difference }}</div><div class="mt-2 text-xs text-amber-700">Review details</div> <div class="text-xs font-semibold uppercase text-amber-700">Value Difference</div><div class="mt-1 text-3xl font-bold text-amber-900">{{ summary.stock_items.value_difference }}</div><div class="mt-2 text-xs text-amber-700">Review details</div>
</a> </a>
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=stock&status=quantity_and_value_difference" class="rounded-2xl border border-orange-300 bg-orange-50 p-4"> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=stock&status=quantity_and_value_difference" class="rounded-2xl border border-orange-300 bg-orange-50 p-4">
<div class="text-xs font-semibold uppercase text-orange-700">Qty + Value Difference</div><div class="mt-1 text-3xl font-bold text-orange-900">{{ summary.stock_items.quantity_and_value_difference }}</div><div class="mt-2 text-xs text-orange-700">Review & apply</div> <div class="text-xs font-semibold uppercase text-orange-700">Qty + Value Difference</div><div class="mt-1 text-3xl font-bold text-orange-900">{{ summary.stock_items.quantity_and_value_difference }}</div><div class="mt-2 text-xs text-orange-700">Review & apply</div>
</a> </a>
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=stock&status=unit_difference" class="rounded-2xl border border-red-200 bg-red-50 p-4"> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=stock&status=unit_difference" class="rounded-2xl border border-red-200 bg-red-50 p-4">
<div class="text-xs font-semibold uppercase text-red-700">Unit Difference</div><div class="mt-1 text-3xl font-bold text-red-900">{{ summary.stock_items.unit_difference }}</div><div class="mt-2 text-xs text-red-700">Blocked from automatic correction</div> <div class="text-xs font-semibold uppercase text-red-700">Unit Difference</div><div class="mt-1 text-3xl font-bold text-red-900">{{ summary.stock_items.unit_difference }}</div><div class="mt-2 text-xs text-red-700">Blocked from automatic correction</div>
</a> </a>
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=stock&status=missing_in_current_year" class="rounded-2xl border border-red-200 bg-red-50 p-4"> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=stock&status=missing_in_current_year" class="rounded-2xl border border-red-200 bg-red-50 p-4">
<div class="text-xs font-semibold uppercase text-red-700">Missing in Current Year</div><div class="mt-1 text-3xl font-bold text-red-900">{{ summary.stock_items.missing_in_current_year }}</div><div class="mt-2 text-xs text-red-700">Review details</div> <div class="text-xs font-semibold uppercase text-red-700">Missing in Current Year</div><div class="mt-1 text-3xl font-bold text-red-900">{{ summary.stock_items.missing_in_current_year }}</div><div class="mt-2 text-xs text-red-700">Review details</div>
</a> </a>
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=stock&status=new_in_current_year" class="rounded-2xl border border-slate-200 bg-slate-50 p-4"> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=stock&status=new_in_current_year" class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
<div class="text-xs font-semibold uppercase text-slate-600">New in Current Year</div><div class="mt-1 text-3xl font-bold text-slate-900">{{ summary.stock_items.new_in_current_year }}</div><div class="mt-2 text-xs text-slate-600">Zero-balance rows hidden by default</div> <div class="text-xs font-semibold uppercase text-slate-600">New in Current Year</div><div class="mt-1 text-3xl font-bold text-slate-900">{{ summary.stock_items.new_in_current_year }}</div><div class="mt-2 text-xs text-slate-600">Zero-balance rows hidden by default</div>
</a> </a>
</div> </div>
@@ -149,7 +175,7 @@
<h2 class="font-semibold">Ledger Differences — Action Required</h2> <h2 class="font-semibold">Ledger Differences — Action Required</h2>
<p class="text-sm text-slate-500">Only ledgers with an actual previous-closing/current-opening difference are shown here.</p> <p class="text-sm text-slate-500">Only ledgers with an actual previous-closing/current-opening difference are shown here.</p>
</div> </div>
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=ledgers&status=difference" class="text-sm font-semibold text-brand-700 hover:underline">Open detailed report</a> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=ledgers&status=difference" class="text-sm font-semibold text-brand-700 hover:underline">Open detailed report</a>
</div> </div>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="min-w-full text-sm"> <table class="min-w-full text-sm">
@@ -181,7 +207,7 @@
<h2 class="font-semibold">Stock Opening Differences — Action Required</h2> <h2 class="font-semibold">Stock Opening Differences — Action Required</h2>
<p class="text-sm text-slate-500">Quantity/value differences can be selected for correction. Unit differences are shown but remain blocked.</p> <p class="text-sm text-slate-500">Quantity/value differences can be selected for correction. Unit differences are shown but remain blocked.</p>
</div> </div>
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=stock&status=quantity_and_value_difference" class="text-sm font-semibold text-brand-700 hover:underline">Open stock details</a> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=stock&status=quantity_and_value_difference" class="text-sm font-semibold text-brand-700 hover:underline">Open stock details</a>
</div> </div>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="min-w-full text-xs"> <table class="min-w-full text-xs">
@@ -227,9 +253,9 @@
</div> </div>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
{% for value,label in [('matched','Matched'),('difference','Differences'),('missing_in_current_year','Missing'),('new_in_current_year','New')] %} {% for value,label in [('matched','Matched'),('difference','Differences'),('missing_in_current_year','Missing'),('new_in_current_year','New')] %}
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=ledgers&status={{ value }}" class="rounded-lg {% if status_filter==value %}bg-brand-600 text-white{% else %}border border-slate-300 bg-white{% endif %} px-3 py-2 text-xs font-semibold">{{ label }}</a> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=ledgers&status={{ value }}" class="rounded-lg {% if status_filter==value %}bg-brand-600 text-white{% else %}border border-slate-300 bg-white{% endif %} px-3 py-2 text-xs font-semibold">{{ label }}</a>
{% endfor %} {% endfor %}
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=summary" class="rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-semibold">Back to Summary</a> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=summary" class="rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-semibold">Back to Summary</a>
</div> </div>
</div> </div>
@@ -237,10 +263,10 @@
<div class="border-b border-slate-200 px-5 py-3 text-xs text-slate-600"> <div class="border-b border-slate-200 px-5 py-3 text-xs text-slate-600">
{% if show_zero_new %} {% if show_zero_new %}
Zero-balance new Ledgers are currently visible. Zero-balance new Ledgers are currently visible.
<a class="font-semibold text-brand-700" href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=ledgers&status=new_in_current_year">Hide zero-balance new Ledgers</a> <a class="font-semibold text-brand-700" href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=ledgers&status=new_in_current_year">Hide zero-balance new Ledgers</a>
{% else %} {% else %}
Zero-balance new Ledgers are hidden by default. Zero-balance new Ledgers are hidden by default.
<a class="font-semibold text-brand-700" href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=ledgers&status=new_in_current_year&show_zero_new=1">Show them</a> <a class="font-semibold text-brand-700" href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=ledgers&status=new_in_current_year&show_zero_new=1">Show them</a>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
@@ -282,8 +308,8 @@
<div class="flex items-center justify-between border-t border-slate-200 p-4 text-sm"> <div class="flex items-center justify-between border-t border-slate-200 p-4 text-sm">
<span>Page {{ page }} of {{ pages }}</span> <span>Page {{ page }} of {{ pages }}</span>
<div class="flex gap-2"> <div class="flex gap-2">
{% if page>1 %}<a class="rounded-lg border border-slate-300 px-3 py-2" href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=ledgers&status={{ status_filter }}&page={{ page-1 }}{% if show_zero_new %}&show_zero_new=1{% endif %}">Previous</a>{% endif %} {% if page>1 %}<a class="rounded-lg border border-slate-300 px-3 py-2" href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=ledgers&status={{ status_filter }}&page={{ page-1 }}{% if show_zero_new %}&show_zero_new=1{% endif %}">Previous</a>{% endif %}
{% if page<pages %}<a class="rounded-lg border border-slate-300 px-3 py-2" href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=ledgers&status={{ status_filter }}&page={{ page+1 }}{% if show_zero_new %}&show_zero_new=1{% endif %}">Next</a>{% endif %} {% if page<pages %}<a class="rounded-lg border border-slate-300 px-3 py-2" href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=ledgers&status={{ status_filter }}&page={{ page+1 }}{% if show_zero_new %}&show_zero_new=1{% endif %}">Next</a>{% endif %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
@@ -298,9 +324,9 @@
</div> </div>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
{% for value,label in [('matched','Matched'),('quantity_difference','Qty Difference'),('value_difference','Value Difference'),('quantity_and_value_difference','Qty + Value'),('unit_difference','Unit Difference'),('missing_in_current_year','Missing'),('new_in_current_year','New')] %} {% for value,label in [('matched','Matched'),('quantity_difference','Qty Difference'),('value_difference','Value Difference'),('quantity_and_value_difference','Qty + Value'),('unit_difference','Unit Difference'),('missing_in_current_year','Missing'),('new_in_current_year','New')] %}
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=stock&status={{ value }}" class="rounded-lg {% if status_filter==value %}bg-brand-600 text-white{% else %}border border-slate-300 bg-white{% endif %} px-3 py-2 text-xs font-semibold">{{ label }}</a> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=stock&status={{ value }}" class="rounded-lg {% if status_filter==value %}bg-brand-600 text-white{% else %}border border-slate-300 bg-white{% endif %} px-3 py-2 text-xs font-semibold">{{ label }}</a>
{% endfor %} {% endfor %}
<a href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=summary" class="rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-semibold">Back to Summary</a> <a href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=summary" class="rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-semibold">Back to Summary</a>
</div> </div>
</div> </div>
@@ -308,10 +334,10 @@
<div class="border-b border-slate-200 px-5 py-3 text-xs text-slate-600"> <div class="border-b border-slate-200 px-5 py-3 text-xs text-slate-600">
{% if show_zero_new %} {% if show_zero_new %}
Zero-balance new Stock Items are currently visible. Zero-balance new Stock Items are currently visible.
<a class="font-semibold text-brand-700" href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=stock&status=new_in_current_year">Hide zero-balance new Stock Items</a> <a class="font-semibold text-brand-700" href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=stock&status=new_in_current_year">Hide zero-balance new Stock Items</a>
{% else %} {% else %}
Zero-balance new Stock Items are hidden by default. Zero-balance new Stock Items are hidden by default.
<a class="font-semibold text-brand-700" href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=stock&status=new_in_current_year&show_zero_new=1">Show them</a> <a class="font-semibold text-brand-700" href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=stock&status=new_in_current_year&show_zero_new=1">Show them</a>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
@@ -353,8 +379,8 @@
<div class="flex items-center justify-between border-t border-slate-200 p-4 text-sm"> <div class="flex items-center justify-between border-t border-slate-200 p-4 text-sm">
<span>Page {{ page }} of {{ pages }}</span> <span>Page {{ page }} of {{ pages }}</span>
<div class="flex gap-2"> <div class="flex gap-2">
{% if page>1 %}<a class="rounded-lg border border-slate-300 px-3 py-2" href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=stock&status={{ status_filter }}&page={{ page-1 }}{% if show_zero_new %}&show_zero_new=1{% endif %}">Previous</a>{% endif %} {% if page>1 %}<a class="rounded-lg border border-slate-300 px-3 py-2" href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=stock&status={{ status_filter }}&page={{ page-1 }}{% if show_zero_new %}&show_zero_new=1{% endif %}">Previous</a>{% endif %}
{% if page<pages %}<a class="rounded-lg border border-slate-300 px-3 py-2" href="?client_id={{ selected_client.id }}&run_id={{ selected_run.id }}&tab=stock&status={{ status_filter }}&page={{ page+1 }}{% if show_zero_new %}&show_zero_new=1{% endif %}">Next</a>{% endif %} {% if page<pages %}<a class="rounded-lg border border-slate-300 px-3 py-2" href="?client_id={{ selected_client.id }}&financial_year={{ current_fy }}&run_id={{ selected_run.id }}&tab=stock&status={{ status_filter }}&page={{ page+1 }}{% if show_zero_new %}&show_zero_new=1{% endif %}">Next</a>{% endif %}
</div> </div>
</div> </div>
{% endif %} {% endif %}