Unify accounting analysis on local SQLite mirror

This commit is contained in:
A R R R Associates
2026-09-06 20:13:51 +05:30
parent 6c2a2081ed
commit 7adbbde220
11 changed files with 848 additions and 129 deletions
+90 -22
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
import json
from io import BytesIO
from urllib.parse import urlencode
from datetime import datetime
from datetime import date, datetime
from fastapi import APIRouter, Form, Request
from fastapi.responses import RedirectResponse, StreamingResponse
@@ -24,6 +24,7 @@ from app.modules.accounting.opening_balance_service import (
)
from app.modules.accounting.ui import (
_accounting_storage_payload,
_financial_year_for_date,
_find_visible_client,
_node_online,
_require_partner,
@@ -118,6 +119,7 @@ def page(
show_zero_new: int = 0,
message: str = "",
error: str = "",
analysis_run_id: int = 0,
):
db = CommonSessionLocal()
try:
@@ -136,20 +138,35 @@ def page(
tally = None
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:]}"
previous_mirror_status = None
current_mirror_status = None
if selected and node and online:
try:
response = request_agent_command(
prev_response = request_agent_command(
node.node_code,
"phase6_status",
_accounting_storage_payload(selected),
"accounting_mirror_status",
_accounting_storage_payload(selected, previous_fy),
timeout_seconds=20,
)
if response.get("ok"):
tally = (response.get("result") or {}).get("tally")
companies = _company_rows(tally)
else:
command_error = str(response.get("error") or "Local Agent status failed.")
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.")
except Exception as exc:
command_error = str(exc)
@@ -467,8 +484,8 @@ def download_report(request: Request, run_id: int, client_id: int | None = None)
def compare(
request: Request,
client_id: int = Form(...),
previous_company_name: str = Form(...),
current_company_name: str = Form(...),
previous_fy: str = Form(...),
current_fy: str = Form(...),
csrf_token: str = Form(...),
):
validate_csrf(request, csrf_token)
@@ -477,32 +494,30 @@ def compare(
user, denied = _require_partner(request, db, "accounting.tally.sync_masters")
if denied:
return denied
client, _clients, scope = _find_visible_client(db, request, user, client_id)
if not client:
return _go(error="Client is not visible.")
if previous_company_name.strip().casefold() == current_company_name.strip().casefold():
raise ValueError(
"Previous-year and current-year companies must be different in Phase 21A. "
"This protects against using the current company's latest closing balance as the prior-year closing."
)
if previous_fy.strip() == current_fy.strip():
raise ValueError("Previous-year and current-year mirrors must be different.")
node = get_active_storage_node_for_branch(db, scope.tenant_id, scope.branch_id)
if not node or not _node_online(node):
raise ValueError("ERP Local Agent is offline for the active branch.")
previous_payload = _accounting_storage_payload(client, previous_fy)
current_payload = _accounting_storage_payload(client, current_fy)
response = request_agent_command(
node.node_code,
"accounting_opening_balance_snapshot",
"accounting_opening_balance_mirror_snapshot",
{
**_accounting_storage_payload(client),
"previous_company_name": previous_company_name,
"current_company_name": current_company_name,
**current_payload,
"previous_accounting_relative_dir": previous_payload["accounting_relative_dir"],
"current_accounting_relative_dir": current_payload["accounting_relative_dir"],
},
timeout_seconds=180,
)
if not response.get("ok"):
raise RuntimeError(str(response.get("error") or "Opening balance snapshot failed."))
raise RuntimeError(str(response.get("error") or "Opening balance mirror comparison failed."))
result = response.get("result") or {}
run = create_comparison_run(
@@ -515,10 +530,63 @@ def compare(
current_masters=result.get("current_masters") or {},
user_id=user.id,
)
ledger_rows = ledger_items(db, run_id=run.id)
stock_rows = stock_items(db, run_id=run.id)
try:
saved = request_agent_command(
node.node_code,
"accounting_analysis_save",
{
**current_payload,
"analysis_type": "OPENING_BALANCE_REVIEW",
"financial_year": current_fy,
"period_from": previous_fy,
"period_to": current_fy,
"company_guid": run.current_company_guid,
"company_name": run.current_company_name,
"requested_by_user_id": int(user.id),
"source_mirror_path": str(result.get("current_mirror_path") or ""),
"parameters": {"previous_fy": previous_fy, "current_fy": current_fy, "erp_run_id": run.id},
"summary": json.loads(run.summary_json or "{}"),
"result": {
"erp_run_id": run.id,
"previous_company": result.get("previous_company") or {},
"current_company": result.get("current_company") or {},
"ledgers": [
{
"previous_name": x.previous_name,
"current_name": x.current_name,
"previous_closing_balance": x.previous_closing_balance,
"current_opening_balance": x.current_opening_balance,
"difference": x.difference,
"status": x.status,
} for x in ledger_rows
],
"stock_items": [
{
"previous_name": x.previous_name,
"current_name": x.current_name,
"previous_closing_qty": x.previous_closing_qty,
"current_opening_qty": x.current_opening_qty,
"previous_closing_value": x.previous_closing_value,
"current_opening_value": x.current_opening_value,
"status": x.status,
} for x in stock_rows
],
},
},
timeout_seconds=30,
)
analysis_run_id = int(((saved.get("result") or {}).get("analysis_run_id") or 0)) if saved.get("ok") else 0
except Exception:
analysis_run_id = 0
return _go(
client.id,
run_id=run.id,
tab="summary",
analysis_run_id=analysis_run_id,
message=(
f"Opening balance comparison #{run.id} completed: "
f"{run.ledger_count} ledger row(s), {run.stock_item_count} stock row(s)."