Fix opening balance FY mirror resolution
This commit is contained in:
@@ -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 %}
|
||||||
|
|||||||
Reference in New Issue
Block a user