Fix opening balance scope and add async cash ledger loading
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user