From 7f372330a55bf23b9f16efe90640d0a94e696bb9 Mon Sep 17 00:00:00 2001 From: A R R R Associates Date: Wed, 9 Sep 2026 14:54:16 +0530 Subject: [PATCH] Add financial year selector to Cash Payment Review --- app/modules/accounting/cash_payment_ui.py | 68 ++++++++++++++++--- .../accounting/cash_payment_review.html | 37 ++++++++-- 2 files changed, 93 insertions(+), 12 deletions(-) diff --git a/app/modules/accounting/cash_payment_ui.py b/app/modules/accounting/cash_payment_ui.py index ce21749..361ed06 100644 --- a/app/modules/accounting/cash_payment_ui.py +++ b/app/modules/accounting/cash_payment_ui.py @@ -4,6 +4,7 @@ from datetime import date, datetime from decimal import Decimal, InvalidOperation from urllib.parse import quote import uuid +import re from fastapi import APIRouter, Request from fastapi.responses import RedirectResponse, JSONResponse @@ -148,6 +149,30 @@ def _review_financial_year(date_from: str, date_to: str, fallback_today: date) - return fy, parsed_from, parsed_to +def _financial_year_bounds(financial_year: str) -> tuple[date, date]: + text = str(financial_year or "").strip() + match = re.fullmatch(r"(\d{4})-(\d{2})", text) + if not match: + raise ValueError("Invalid financial year.") + start_year = int(match.group(1)) + expected_suffix = str((start_year + 1) % 100).zfill(2) + if match.group(2) != expected_suffix: + raise ValueError("Invalid financial year.") + return date(start_year, 4, 1), date(start_year + 1, 3, 31) + + +def _financial_year_options(selected_fy: str, today: date) -> list[str]: + current = _financial_year_for_date(today) + current_start = int(current.split("-", 1)[0]) + selected_start = int(str(selected_fy).split("-", 1)[0]) if selected_fy else current_start + newest = max(current_start, selected_start) + oldest = min(current_start - 7, selected_start) + return [ + f"{year}-{str((year + 1) % 100).zfill(2)}" + for year in range(newest, oldest - 1, -1) + ] + + def _render(request: Request, db, user, **context): base = { "request": request, @@ -170,6 +195,7 @@ def cash_payment_review( tally_guid: str = "", date_from: str = "", date_to: str = "", + financial_year: str = "", job_id: str = "", cash_ledger_name: str = "", analyze: int = 0, @@ -196,16 +222,35 @@ def cash_payment_review( today = date.today() - # Default period still follows the current date when the user first opens - # the page, but once a date range is supplied the mirror is resolved - # strictly from that review period rather than from the workspace FY. + # Financial Year is now an explicit review selector. This avoids any + # ambiguity between the ERP workspace FY and the local mirror FY. default_fy = _financial_year_for_date(today) - default_start_year = int(default_fy.split("-", 1)[0]) - start_text = date_from or date(default_start_year, 4, 1).isoformat() - end_text = date_to or today.isoformat() - review_fy, parsed_from, parsed_to = _review_financial_year(start_text, end_text, today) + requested_fy = str(financial_year or "").strip() + try: + review_fy = requested_fy if requested_fy else ( + _financial_year_for_date(_parse_review_date(date_from, today)) + if date_from else default_fy + ) + fy_start, fy_end = _financial_year_bounds(review_fy) + except Exception: + review_fy = default_fy + fy_start, fy_end = _financial_year_bounds(review_fy) + + parsed_from = _parse_review_date(date_from, fy_start) if date_from else fy_start + parsed_to = _parse_review_date(date_to, fy_end) if date_to else fy_end + + # Keep manual sub-period review possible, but never allow a reversed + # range such as 01/04/2025 -> 31/03/2025. + if parsed_from < fy_start or parsed_from > fy_end: + parsed_from = fy_start + if parsed_to < fy_start or parsed_to > fy_end: + parsed_to = fy_end + if parsed_to < parsed_from: + parsed_from, parsed_to = fy_start, fy_end + start_text = parsed_from.isoformat() end_text = parsed_to.isoformat() + financial_year_options = _financial_year_options(review_fy, today) review = None mirror_status = None @@ -294,6 +339,7 @@ def cash_payment_review( allocation_input={"total_amount": "", "party_name": "", "expense_ledger": "", "source_payments": ""}, page_mode="compliance", review_financial_year=review_fy, + financial_year_options=financial_year_options, ) finally: db.close() @@ -737,7 +783,13 @@ async def save_cash_payment_settings(request: Request): except Exception: db.rollback() client_id = str(form.get("client_id") or "").strip() - suffix = f"&client_id={client_id}" if client_id else "" + financial_year = str(form.get("financial_year") or "").strip() + parts = [] + if client_id: + parts.append("client_id=" + quote(client_id)) + if financial_year: + parts.append("financial_year=" + quote(financial_year)) + suffix = ("&" + "&".join(parts)) if parts else "" return RedirectResponse(url=f"/tools/accounting/cash-payments?saved=1{suffix}", status_code=303) finally: db.close() diff --git a/app/modules/accounting/templates/accounting/cash_payment_review.html b/app/modules/accounting/templates/accounting/cash_payment_review.html index d58d65e..1400843 100644 --- a/app/modules/accounting/templates/accounting/cash_payment_review.html +++ b/app/modules/accounting/templates/accounting/cash_payment_review.html @@ -33,7 +33,7 @@ {% endif %} -
+
-
-
+
+ + +
+
+
@@ -79,7 +87,7 @@

Rule Settings

- +
@@ -148,4 +156,25 @@ {% endif %}
+ +{% if page_mode != 'allocation' %} + +{% endif %} {% endblock %}