From 6e0abe79091003b0cbba2e852071099d3dd8d965 Mon Sep 17 00:00:00 2001 From: A R R R Associates Date: Sun, 6 Sep 2026 20:23:10 +0530 Subject: [PATCH] Fix opening balance FY mirror resolution --- app/modules/accounting/opening_balance_ui.py | 114 ++++++++++++++---- .../accounting/opening_balances.html | 86 ++++++++----- 2 files changed, 146 insertions(+), 54 deletions(-) diff --git a/app/modules/accounting/opening_balance_ui.py b/app/modules/accounting/opening_balance_ui.py index e1f5c88..fd4ffcc 100644 --- a/app/modules/accounting/opening_balance_ui.py +++ b/app/modules/accounting/opening_balance_ui.py @@ -1,6 +1,7 @@ from __future__ import annotations import json +import re from io import BytesIO from urllib.parse import urlencode 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 +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("") def page( request: Request, @@ -120,6 +145,7 @@ def page( message: str = "", error: str = "", analysis_run_id: int = 0, + financial_year: str = "", ): db = CommonSessionLocal() try: @@ -139,36 +165,70 @@ def page( companies = [] command_error = error or "" today = date.today() - current_fy = _financial_year_for_date(today) - current_start = int(current_fy.split("-", 1)[0]) - previous_fy = f"{current_start-1}-{str(current_start)[-2:]}" + active_fy = _financial_year_for_date(today) + fy_options = _financial_year_options(active_fy, 7) + + 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 current_mirror_status = None 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: - prev_response = request_agent_command( - node.node_code, - "accounting_mirror_status", - _accounting_storage_payload(selected, previous_fy), - timeout_seconds=20, - ) - curr_response = request_agent_command( - node.node_code, - "accounting_mirror_status", - _accounting_storage_payload(selected, current_fy), - timeout_seconds=20, - ) - if prev_response.get("ok"): - previous_mirror_status = prev_response.get("result") or {} - if curr_response.get("ok"): - current_mirror_status = curr_response.get("result") or {} - if not prev_response.get("ok"): - command_error = str(prev_response.get("error") or "Previous-year Accounting Mirror status failed.") - elif not curr_response.get("ok"): - command_error = str(curr_response.get("error") or "Current-year Accounting Mirror status failed.") + # When the user has not selected a review FY explicitly, prefer + # the active ERP FY only if its mirror exists. Otherwise use + # the immediately preceding FY when that is the newest mirror + # already available for the client. This prevents an existing + # FY2025-26 mirror from being shown as "missing" merely because + # the ERP header is now FY2026-27. + if not requested_fy: + active_status, active_response = mirror_status_for(active_fy) + active_ready = bool((active_status.get("mirror") or {}).get("ready")) + if active_ready: + current_fy = active_fy + else: + prior_active_fy = _previous_financial_year(active_fy) + prior_status, prior_response = mirror_status_for(prior_active_fy) + prior_ready = bool((prior_status.get("mirror") or {}).get("ready")) + if prior_ready: + current_fy = prior_active_fy + else: + current_fy = active_fy + + 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: - command_error = str(exc) + command_error = command_error or str(exc) runs = [] selected_run = None @@ -276,6 +336,12 @@ def page( "node_online": online, "tally": tally, "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, "selected_run": selected_run, "ledgers": ledgers, diff --git a/app/modules/accounting/templates/accounting/opening_balances.html b/app/modules/accounting/templates/accounting/opening_balances.html index c22979d..3bf8abd 100644 --- a/app/modules/accounting/templates/accounting/opening_balances.html +++ b/app/modules/accounting/templates/accounting/opening_balances.html @@ -20,7 +20,7 @@ {% if error %}
{{ error }}
{% endif %}
-
+ - + + + +
{% if selected_client %} {% 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 {} %}
-
Previous FY Mirror · {{ previous_fy }}
{{ 'Ready' if prev.ready else 'Mirror required' }}
{% if prev.ready %}
{{ prev.company.company_name }} · {{ prev.counts.voucher }} vouchers
{% endif %}
-
Current FY Mirror · {{ current_fy }}
{{ 'Ready' if curr.ready else 'Mirror required' }}
{% if curr.ready %}
{{ curr.company.company_name }} · {{ curr.counts.voucher }} vouchers
{% endif %}
+
+
Previous FY Mirror · FY {{ previous_fy }}
+
{{ 'Ready' if prev.ready else 'Mirror required' }}
+ {% if prev.ready %} +
{{ prev.company.company_name }} · {{ prev.counts.voucher }} vouchers
+
{{ previous_mirror_status.mirror_db_path or prev.path or '' }}
+ {% endif %} +
+ +
+
Current FY Mirror · FY {{ current_fy }}
+
{{ 'Ready' if curr.ready else 'Mirror required' }}
+ {% if curr.ready %} +
{{ curr.company.company_name }} · {{ curr.counts.voucher }} vouchers
+
{{ current_mirror_status.mirror_db_path or curr.path or '' }}
+ {% endif %} +
@@ -63,7 +89,7 @@ {{ run.current_company_name }} {{ run.ledger_count }} {{ run.stock_item_count }} - Open Summary + Open Summary {% endfor %} @@ -84,7 +110,7 @@

Run #{{ selected_run.id }}

{{ selected_run.previous_company_name }} closing → {{ selected_run.current_company_name }} opening

- Summary + Summary
@@ -95,16 +121,16 @@

Matched records are summarized only. Click a category when you need its detailed report.

- +
Matched
{{ summary.ledgers.matched }}
View detailed report
Differences
{{ summary.ledgers.difference }}
Review & apply below
- +
Missing in Current Year
{{ summary.ledgers.missing_in_current_year }}
Review details
- +
New in Current Year
{{ summary.ledgers.new_in_current_year }}
Zero-balance rows hidden by default
@@ -114,25 +140,25 @@

Quantity, value and unit exceptions are separated so the reviewer sees only the issue that needs attention.

- +
Matched
{{ summary.stock_items.matched }}
View detailed report
- +
Quantity Difference
{{ summary.stock_items.quantity_difference }}
Review details
- +
Value Difference
{{ summary.stock_items.value_difference }}
Review details
- +
Qty + Value Difference
{{ summary.stock_items.quantity_and_value_difference }}
Review & apply
- +
Unit Difference
{{ summary.stock_items.unit_difference }}
Blocked from automatic correction
- +
Missing in Current Year
{{ summary.stock_items.missing_in_current_year }}
Review details
- +
New in Current Year
{{ summary.stock_items.new_in_current_year }}
Zero-balance rows hidden by default
@@ -149,7 +175,7 @@

Ledger Differences — Action Required

Only ledgers with an actual previous-closing/current-opening difference are shown here.

- Open detailed report + Open detailed report
@@ -181,7 +207,7 @@

Stock Opening Differences — Action Required

Quantity/value differences can be selected for correction. Unit differences are shown but remain blocked.

- Open stock details + Open stock details
@@ -227,9 +253,9 @@
{% for value,label in [('matched','Matched'),('difference','Differences'),('missing_in_current_year','Missing'),('new_in_current_year','New')] %} - {{ label }} + {{ label }} {% endfor %} - Back to Summary + Back to Summary
@@ -237,10 +263,10 @@
{% if show_zero_new %} Zero-balance new Ledgers are currently visible. - Hide zero-balance new Ledgers + Hide zero-balance new Ledgers {% else %} Zero-balance new Ledgers are hidden by default. - Show them + Show them {% endif %}
{% endif %} @@ -282,8 +308,8 @@
Page {{ page }} of {{ pages }}
- {% if page>1 %}Previous{% endif %} - {% if pageNext{% endif %} + {% if page>1 %}Previous{% endif %} + {% if pageNext{% endif %}
{% endif %} @@ -298,9 +324,9 @@
{% 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')] %} - {{ label }} + {{ label }} {% endfor %} - Back to Summary + Back to Summary
@@ -308,10 +334,10 @@
{% if show_zero_new %} Zero-balance new Stock Items are currently visible. - Hide zero-balance new Stock Items + Hide zero-balance new Stock Items {% else %} Zero-balance new Stock Items are hidden by default. - Show them + Show them {% endif %}
{% endif %} @@ -353,8 +379,8 @@
Page {{ page }} of {{ pages }}
- {% if page>1 %}Previous{% endif %} - {% if pageNext{% endif %} + {% if page>1 %}Previous{% endif %} + {% if pageNext{% endif %}
{% endif %}