diff --git a/app/modules/accounting/opening_balance_ui.py b/app/modules/accounting/opening_balance_ui.py index 158c810..25a67ce 100644 --- a/app/modules/accounting/opening_balance_ui.py +++ b/app/modules/accounting/opening_balance_ui.py @@ -70,13 +70,48 @@ def _company_rows(tally): return rows +def _summary_counts(raw): + raw = raw or {} + ledgers = raw.get("ledgers") or {} + stock = raw.get("stock_items") or {} + return { + "ledgers": { + "matched": int(ledgers.get("matched") or 0), + "difference": int(ledgers.get("difference") or 0), + "missing_in_current_year": int(ledgers.get("missing_in_current_year") or 0), + "new_in_current_year": int(ledgers.get("new_in_current_year") or 0), + }, + "stock_items": { + "matched": int(stock.get("matched") or 0), + "quantity_difference": int(stock.get("quantity_difference") or 0), + "value_difference": int(stock.get("value_difference") or 0), + "quantity_and_value_difference": int(stock.get("quantity_and_value_difference") or 0), + "unit_difference": int(stock.get("unit_difference") or 0), + "missing_in_current_year": int(stock.get("missing_in_current_year") or 0), + "new_in_current_year": int(stock.get("new_in_current_year") or 0), + }, + } + + +def _page_rows(rows, page, per_page=50): + per_page = max(20, min(200, int(per_page or 50))) + total = len(rows) + pages = max(1, (total + per_page - 1) // per_page) + page = max(1, min(int(page or 1), pages)) + start = (page - 1) * per_page + return rows[start:start + per_page], total, page, pages + + @router.get("") def page( request: Request, client_id: int | None = None, run_id: int | None = None, - tab: str = "ledgers", + tab: str = "summary", status: str = "", + page: int = 1, + per_page: int = 50, + show_zero_new: int = 0, message: str = "", error: str = "", ): @@ -118,7 +153,11 @@ def page( selected_run = None ledgers = [] stocks = [] - summary = {} + ledger_action_rows = [] + stock_action_rows = [] + summary = _summary_counts({}) + detail_total = 0 + detail_pages = 1 if selected: runs = list_runs( @@ -134,13 +173,73 @@ def page( if selected_run: try: - summary = json.loads(selected_run.summary_json or "{}") + summary = _summary_counts(json.loads(selected_run.summary_json or "{}")) except Exception: - summary = {} - if tab == "stock": - stocks = stock_items(db, run_id=selected_run.id, status=status) + summary = _summary_counts({}) + + if tab == "summary": + ledger_action_rows = ledger_items( + db, + run_id=selected_run.id, + status="difference", + ) + stock_action_rows = [] + for action_status in ( + "quantity_difference", + "value_difference", + "quantity_and_value_difference", + "unit_difference", + ): + stock_action_rows.extend( + stock_items( + db, + run_id=selected_run.id, + status=action_status, + ) + ) + ledger_action_rows = ledger_action_rows[:100] + stock_action_rows = stock_action_rows[:100] + + elif tab == "stock": + detail_status = status or "quantity_and_value_difference" + rows = stock_items( + db, + run_id=selected_run.id, + status=detail_status, + ) + if detail_status == "new_in_current_year" and not show_zero_new: + rows = [ + row for row in rows + if abs(float(row.current_opening_qty or 0)) > 0.000001 + or abs(float(row.current_opening_value or 0)) > 0.005 + ] + stocks, detail_total, page, detail_pages = _page_rows( + rows, + page, + per_page, + ) + status = detail_status + else: - ledgers = ledger_items(db, run_id=selected_run.id, status=status) + detail_status = status or "difference" + rows = ledger_items( + db, + run_id=selected_run.id, + status=detail_status, + ) + if detail_status == "new_in_current_year" and not show_zero_new: + rows = [ + row for row in rows + if abs(float(row.current_opening_balance or 0)) > 0.005 + ] + ledgers, detail_total, page, detail_pages = _page_rows( + rows, + page, + per_page, + ) + status = detail_status + + valid_tab = tab if tab in {"summary", "ledgers", "stock"} else "summary" return templates.TemplateResponse( "modules/accounting/templates/accounting/opening_balances.html", @@ -160,9 +259,16 @@ def page( "selected_run": selected_run, "ledgers": ledgers, "stocks": stocks, + "ledger_action_rows": ledger_action_rows, + "stock_action_rows": stock_action_rows, "summary": summary, - "tab": tab if tab in {"ledgers", "stock"} else "ledgers", + "tab": valid_tab, "status_filter": status, + "page": page, + "pages": detail_pages, + "per_page": per_page, + "detail_total": detail_total, + "show_zero_new": bool(show_zero_new), "message": message, "error": command_error, }, @@ -226,7 +332,7 @@ def compare( return _go( client.id, run_id=run.id, - tab="ledgers", + tab="summary", message=( f"Opening balance comparison #{run.id} completed: " f"{run.ledger_count} ledger row(s), {run.stock_item_count} stock row(s)." @@ -368,7 +474,7 @@ async def apply_corrections( return _go( client.id, run_id=run.id, - tab="ledgers", + tab="summary", message=f"Tally correction completed: {verified} verified, {failed} failed/blocked.", ) except Exception as exc: diff --git a/app/modules/accounting/templates/accounting/opening_balances.html b/app/modules/accounting/templates/accounting/opening_balances.html index c9a9906..c55fc64 100644 --- a/app/modules/accounting/templates/accounting/opening_balances.html +++ b/app/modules/accounting/templates/accounting/opening_balances.html @@ -3,10 +3,10 @@
-

Accounting · Phase 21A

+

Accounting · Phase 21A v2

Opening Balance Review & Correction

- Compare previous-year Tally closing balances with current-year opening balances for Ledgers and Stock Items. Safe selected differences can be applied directly to current-year Tally masters and are re-read for verification. + Summary-first review of previous-year closing balances against current-year openings. Matched and new zero-balance masters stay out of the main review; actionable differences are shown immediately and retain the existing controlled Tally correction workflow.

@@ -50,7 +50,6 @@
-

Phase 21A v1 intentionally requires separate previous-year and current-year Tally companies. This avoids treating a current company's latest closing balance as the prior-year audited closing.

{% endif %} @@ -68,7 +67,7 @@ {{ run.current_company_name }} {{ run.ledger_count }} {{ run.stock_item_count }} - Open + Open Summary {% endfor %} @@ -78,29 +77,177 @@ {% endif %} {% if selected_run %} -
- Direct Tally correction control: only matched current-year masters with a genuine opening difference are eligible. Missing/new masters, ambiguous matches and stock unit differences are never changed automatically. Every applied row is re-read from Tally and marked Verified or Failed. -
+
+
+
+

Run #{{ selected_run.id }}

+

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

+
+ Summary +
+
- + {% if tab=='summary' %} +
+
+

Ledger Opening Summary

+

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

+
+ -
+
+

Stock Opening Summary

+

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

+
+ +
+ + - {% if tab=='ledgers' %} -
-
-

Ledger Opening Comparison

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

- +
+
+
+

Ledger Differences — Action Required

+

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

+
+ Open detailed report
+
+ + + + {% for item in ledger_action_rows %} + + + + + + + + + + + {% else %} + + {% endfor %} + +
ApplyPrevious LedgerPY ClosingCurrent LedgerCY OpeningDifferenceMatchCorrection
{{ item.previous_name }}
{{ item.previous_group }}
{{ '%.2f'|format(item.previous_closing_balance) }}
{{ item.current_name }}
{{ item.current_group }}
{{ '%.2f'|format(item.current_opening_balance) }}{{ '%.2f'|format(0 if item.difference|abs < 0.005 else item.difference) }}{{ item.match_method|replace('_',' ')|title }} · {{ item.confidence }}%{{ item.correction_status|replace('_',' ')|title }}{% if item.correction_note %}
{{ item.correction_note }}
{% endif %}
No Ledger opening differences require action.
+
+ {% if summary.ledgers.difference > ledger_action_rows|length %}
Showing the first {{ ledger_action_rows|length }} differences here. Open the detailed report to review the remaining rows.
{% endif %} +
+ +
+
+
+

Stock Opening Differences — Action Required

+

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

+
+ Open stock details +
+
+ + + + {% for item in stock_action_rows %} + + + + + + + + + + + {% else %} + + {% endfor %} + +
ApplyPrevious Stock ItemPY ClosingCurrent Stock ItemCY OpeningDifferenceStatusCorrection
{% if item.match_status != 'unit_difference' and item.previous_unit|lower == item.current_unit|lower %}{% else %}Blocked{% endif %}
{{ item.previous_name }}
{{ item.previous_group }} · HSN {{ item.previous_hsn or '-' }}
{{ item.previous_closing_qty }} {{ item.previous_unit }}
₹{{ '%.2f'|format(item.previous_closing_value) }}
{{ item.current_name }}
{{ item.current_group }} · HSN {{ item.current_hsn or '-' }}
{{ item.current_opening_qty }} {{ item.current_unit }}
₹{{ '%.2f'|format(item.current_opening_value) }}
Qty {{ 0 if item.quantity_difference|abs < 0.000001 else item.quantity_difference }}
Value ₹{{ '%.2f'|format(0 if item.value_difference|abs < 0.005 else item.value_difference) }}
{{ item.match_status|replace('_',' ')|title }}
{{ item.match_method|replace('_',' ')|title }} · {{ item.confidence }}%
{{ item.correction_status|replace('_',' ')|title }}{% if item.correction_note %}
{{ item.correction_note }}
{% endif %}
No Stock Item opening differences require action.
+
+
+ + {% if ledger_action_rows or stock_action_rows %} +
+

Apply selected safe corrections directly to current-year Tally

+

Existing safeguards are unchanged: current openings are rechecked immediately before write and every altered master is re-read from Tally afterward.

+
+ + +
+
+ {% endif %} + + + {% elif tab=='ledgers' %} +
+
+
+

Ledger Detailed Report

+

{{ status_filter|replace('_',' ')|title }} · {{ detail_total }} row(s). This detailed list is opened only on demand.

+
+
+ {% for value,label in [('matched','Matched'),('difference','Differences'),('missing_in_current_year','Missing'),('new_in_current_year','New')] %} + {{ label }} + {% endfor %} + Back to Summary +
+
+ + {% if status_filter=='new_in_current_year' %} +
+ {% if show_zero_new %} + Zero-balance new Ledgers are currently visible. + Hide zero-balance new Ledgers + {% else %} + Zero-balance new Ledgers are hidden by default. + Show them + {% endif %} +
+ {% endif %} + +
+ + +
@@ -112,25 +259,66 @@ - + - {% else %}{% endfor %} + {% else %}{% endfor %}
ApplyPrevious LedgerPY ClosingCurrent LedgerCY OpeningDifferenceStatusCorrection
{{ '%.2f'|format(item.previous_closing_balance) }}
{{ item.current_name or '—' }}
{{ item.current_group }}
{{ '%.2f'|format(item.current_opening_balance) }}{{ '%.2f'|format(item.difference) }}{{ '%.2f'|format(0 if item.difference|abs < 0.005 else item.difference) }} {{ item.match_status|replace('_',' ')|title }}{% if item.match_method %}
{{ item.match_method|replace('_',' ')|title }} · {{ item.confidence }}%
{% endif %}
{{ item.correction_status|replace('_',' ')|title }}{% if item.correction_note %}
{{ item.correction_note }}
{% endif %}
No ledger rows for this filter.
No Ledger rows for this category.
-
- - {% else %} -
-
-

Stock Item Opening Comparison

Quantity, unit and value are checked separately.

- + {% if status_filter=='difference' and ledgers %} +
+
+ + +
+ {% endif %} + + + {% if pages > 1 %} +
+ Page {{ page }} of {{ pages }} +
+ {% if page>1 %}Previous{% endif %} + {% if pageNext{% endif %} +
+
+ {% endif %} +
+ + {% elif tab=='stock' %} +
+
+
+

Stock Item Detailed Report

+

{{ status_filter|replace('_',' ')|title }} · {{ detail_total }} row(s).

+
+
+ {% 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 }} + {% endfor %} + Back to Summary +
+
+ + {% if status_filter=='new_in_current_year' %} +
+ {% if show_zero_new %} + Zero-balance new Stock Items are currently visible. + Hide zero-balance new Stock Items + {% else %} + Zero-balance new Stock Items are hidden by default. + Show them + {% endif %} +
+ {% endif %} + +
+ + +
@@ -142,26 +330,35 @@ - + - {% else %}{% endfor %} + {% else %}{% endfor %}
ApplyPrevious Stock ItemPY ClosingCurrent Stock ItemCY OpeningDifferenceStatusCorrection
{{ item.previous_closing_qty }} {{ item.previous_unit }}
₹{{ '%.2f'|format(item.previous_closing_value) }}
{{ item.current_name or '—' }}
{{ item.current_group }} · HSN {{ item.current_hsn or '-' }}
{{ item.current_opening_qty }} {{ item.current_unit }}
₹{{ '%.2f'|format(item.current_opening_value) }}
Qty {{ item.quantity_difference }}
Value ₹{{ '%.2f'|format(item.value_difference) }}
Qty {{ 0 if item.quantity_difference|abs < 0.000001 else item.quantity_difference }}
Value ₹{{ '%.2f'|format(0 if item.value_difference|abs < 0.005 else item.value_difference) }}
{{ item.match_status|replace('_',' ')|title }}{% if item.match_method %}
{{ item.match_method|replace('_',' ')|title }} · {{ item.confidence }}%
{% endif %}
{{ item.correction_status|replace('_',' ')|title }}{% if item.correction_note %}
{{ item.correction_note }}
{% endif %}
No stock rows for this filter.
No Stock Item rows for this category.
-
- {% endif %} - -
-

Apply selected safe corrections directly to current-year Tally

-

The Local Agent preflights the current opening against this comparison, applies only unchanged masters, and re-reads each master afterward. Type the confirmation phrase exactly.

-
- - + {% if status_filter in ['quantity_difference','value_difference','quantity_and_value_difference'] and stocks %} +
+
+ + +
-
- + {% endif %} + + + {% if pages > 1 %} +
+ Page {{ page }} of {{ pages }} +
+ {% if page>1 %}Previous{% endif %} + {% if pageNext{% endif %} +
+
+ {% endif %} +
+ {% endif %} {% endif %}
{% endblock %}