Fix opening balance scope and add async cash ledger loading

This commit is contained in:
A R R R Associates
2026-09-04 15:30:39 +05:30
parent 58752e30e1
commit 3ad20087be
9 changed files with 508 additions and 34 deletions
+100 -1
View File
@@ -1,10 +1,12 @@
from __future__ import annotations
import json
from io import BytesIO
from urllib.parse import urlencode
from datetime import datetime
from fastapi import APIRouter, Form, Request
from fastapi.responses import RedirectResponse
from fastapi.responses import RedirectResponse, StreamingResponse
from app.core.db.common import CommonSessionLocal
from app.core.security.csrf import get_or_create_csrf_token, validate_csrf
@@ -29,6 +31,8 @@ from app.modules.accounting.ui import (
)
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
from app.modules.documents.services import get_active_storage_node_for_branch
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
router = APIRouter(
prefix="/tools/accounting/opening-balances",
@@ -277,6 +281,101 @@ def page(
db.close()
@router.get("/download/{run_id}")
def download_report(request: Request, run_id: int, client_id: int | None = None):
"""Download the complete opening-balance comparison as an Excel workbook."""
db = CommonSessionLocal()
try:
user, denied = _require_partner(request, db, "accounting.tally.view")
if denied:
return denied
clients, scope = _visible_clients(db, request, user)
run = db.get(AccountingOpeningBalanceRun, int(run_id))
if not run or run.tenant_id != scope.tenant_id:
return _go(error="Opening balance comparison run was not found.")
selected = next((row for row in clients if int(row.id) == int(run.client_id)), None)
if not selected or (client_id and int(client_id) != int(run.client_id)):
return _go(error="Opening balance comparison run is not visible.")
ledger_rows = ledger_items(db, run_id=run.id)
stock_rows = stock_items(db, run_id=run.id)
wb = Workbook()
ws = wb.active
ws.title = "Balance Sheet Ledgers"
headers = [
"Previous Ledger", "Previous Group", "PY Closing", "Current Ledger",
"Current Group", "CY Opening", "Difference", "Status",
"Match Method", "Confidence %", "Correction Status", "Correction Note",
]
ws.append(headers)
for row in ledger_rows:
ws.append([
row.previous_name or "", row.previous_group or "", float(row.previous_closing_balance or 0),
row.current_name or "", row.current_group or "", float(row.current_opening_balance or 0),
float(row.difference or 0), row.match_status or "", row.match_method or "",
int(row.confidence or 0), row.correction_status or "", row.correction_note or "",
])
sws = wb.create_sheet("Stock Items")
stock_headers = [
"Previous Stock Item", "Previous Group", "HSN", "Unit", "PY Closing Qty",
"PY Closing Value", "Current Stock Item", "Current Group", "Current HSN",
"Current Unit", "CY Opening Qty", "CY Opening Value", "Qty Difference",
"Value Difference", "Status", "Match Method", "Confidence %",
"Correction Status", "Correction Note",
]
sws.append(stock_headers)
for row in stock_rows:
sws.append([
row.previous_name or "", row.previous_group or "", row.previous_hsn or "", row.previous_unit or "",
float(row.previous_closing_qty or 0), float(row.previous_closing_value or 0),
row.current_name or "", row.current_group or "", row.current_hsn or "", row.current_unit or "",
float(row.current_opening_qty or 0), float(row.current_opening_value or 0),
float(row.quantity_difference or 0), float(row.value_difference or 0),
row.match_status or "", row.match_method or "", int(row.confidence or 0),
row.correction_status or "", row.correction_note or "",
])
meta = wb.create_sheet("Summary", 0)
meta.append(["Opening Balance Review"])
meta.append(["Client", getattr(selected, "client_name", "") or getattr(selected, "name", "") or str(selected.id)])
meta.append(["Run ID", run.id])
meta.append(["Previous Tally Company", run.previous_company_name])
meta.append(["Current Tally Company", run.current_company_name])
meta.append(["Scope", "Balance Sheet ledgers and Stock Items only"])
meta.append(["Balance Sheet Ledger Rows", len(ledger_rows)])
meta.append(["Stock Item Rows", len(stock_rows)])
meta.append(["Generated", datetime.now().strftime("%Y-%m-%d %H:%M:%S")])
header_fill = PatternFill("solid", fgColor="DCE6F1")
for sheet in (ws, sws):
sheet.freeze_panes = "A2"
sheet.auto_filter.ref = sheet.dimensions
for cell in sheet[1]:
cell.font = Font(bold=True)
cell.fill = header_fill
cell.alignment = Alignment(vertical="center", wrap_text=True)
for col in sheet.columns:
letter = col[0].column_letter
width = min(38, max(10, max(len(str(c.value or "")) for c in col[:200]) + 2))
sheet.column_dimensions[letter].width = width
meta["A1"].font = Font(bold=True, size=14)
meta.column_dimensions["A"].width = 28
meta.column_dimensions["B"].width = 48
output = BytesIO()
wb.save(output)
output.seek(0)
filename = f"opening_balance_review_run_{run.id}.xlsx"
return StreamingResponse(
output,
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
)
finally:
db.close()
@router.post("/compare")
def compare(
request: Request,