Add Phase 2 Tally company mapping
This commit is contained in:
@@ -16,6 +16,7 @@ from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
|
||||
from app.modules.core.rbac.permission_guard import require_permission
|
||||
from app.modules.documents.services import build_document_scope, get_active_storage_node_for_branch
|
||||
from app.modules.accounting.agent_bridge import request_agent_command
|
||||
from app.modules.registrations.models import ClientRegistration, RegistrationType
|
||||
|
||||
|
||||
router = APIRouter(prefix="/tools/tally", tags=["accounting-tally-ui"])
|
||||
@@ -54,6 +55,33 @@ def _find_visible_client(db, request: Request, user, client_id: int):
|
||||
return client, clients, scope
|
||||
|
||||
|
||||
def _client_registrations(db, client, tenant_id: int):
|
||||
rows = db.execute(
|
||||
select(ClientRegistration, RegistrationType)
|
||||
.join(RegistrationType, RegistrationType.id == ClientRegistration.registration_type_id)
|
||||
.where(
|
||||
ClientRegistration.tenant_id == tenant_id,
|
||||
ClientRegistration.client_id == client.id,
|
||||
)
|
||||
.order_by(RegistrationType.sort_order.asc(), ClientRegistration.id.asc())
|
||||
).all()
|
||||
return rows
|
||||
|
||||
|
||||
def _registration_payload(registration, registration_type) -> dict:
|
||||
return {
|
||||
"id": int(registration.id),
|
||||
"registration_type_id": int(registration.registration_type_id),
|
||||
"registration_type_code": str(getattr(registration_type, "code", "") or "").strip(),
|
||||
"registration_type_name": str(getattr(registration_type, "name", "") or "").strip(),
|
||||
"registration_number": str(getattr(registration, "registration_number", "") or "").strip(),
|
||||
"legal_name": str(getattr(registration, "legal_name", "") or "").strip(),
|
||||
"trade_name": str(getattr(registration, "trade_name", "") or "").strip(),
|
||||
"business_unit_id": getattr(registration, "business_unit_id", None),
|
||||
"client_branch_id": getattr(registration, "client_branch_id", None),
|
||||
}
|
||||
|
||||
|
||||
def _node_online(node) -> bool:
|
||||
if not node or not node.last_seen_at_utc:
|
||||
return False
|
||||
@@ -79,7 +107,15 @@ def _render(request: Request, db, user, **context):
|
||||
|
||||
|
||||
@router.get("")
|
||||
def tally_tool(request: Request, client_id: int | None = None, refresh: int = 0, initialized: int = 0, error: str = ""):
|
||||
def tally_tool(
|
||||
request: Request,
|
||||
client_id: int | None = None,
|
||||
refresh: int = 0,
|
||||
initialized: int = 0,
|
||||
mapped: int = 0,
|
||||
unmapped: int = 0,
|
||||
error: str = "",
|
||||
):
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user, response = _require_partner(request, db, "accounting.tally.view")
|
||||
@@ -88,17 +124,21 @@ def tally_tool(request: Request, client_id: int | None = None, refresh: int = 0,
|
||||
|
||||
clients, scope = _visible_clients(db, request, user)
|
||||
selected_client = next((row for row in clients if client_id and int(row.id) == int(client_id)), None)
|
||||
registrations = _client_registrations(db, selected_client, scope.tenant_id) if selected_client else []
|
||||
node = get_active_storage_node_for_branch(db, scope.tenant_id, scope.branch_id)
|
||||
online = _node_online(node)
|
||||
|
||||
live_result = None
|
||||
command_error = error or ""
|
||||
should_query_agent = bool(refresh or selected_client)
|
||||
|
||||
if refresh:
|
||||
try:
|
||||
require_permission(db, user, "accounting.tally.connect")
|
||||
except Exception:
|
||||
return _denied()
|
||||
if refresh and node and online:
|
||||
|
||||
if should_query_agent and node and online:
|
||||
payload = {}
|
||||
if selected_client:
|
||||
payload = {
|
||||
@@ -106,7 +146,12 @@ def tally_tool(request: Request, client_id: int | None = None, refresh: int = 0,
|
||||
"client_name": selected_client.client_name,
|
||||
}
|
||||
try:
|
||||
response_data = request_agent_command(node.node_code, "phase1_status", payload, timeout_seconds=20)
|
||||
response_data = request_agent_command(
|
||||
node.node_code,
|
||||
"phase2_status",
|
||||
payload,
|
||||
timeout_seconds=20,
|
||||
)
|
||||
if response_data.get("ok"):
|
||||
live_result = response_data.get("result") or {}
|
||||
else:
|
||||
@@ -118,13 +163,16 @@ def tally_tool(request: Request, client_id: int | None = None, refresh: int = 0,
|
||||
request,
|
||||
db,
|
||||
user,
|
||||
title="Tally Connection",
|
||||
title="Tally Company Mapping",
|
||||
clients=clients,
|
||||
selected_client=selected_client,
|
||||
registrations=registrations,
|
||||
storage_node=node,
|
||||
agent_online=online,
|
||||
live_result=live_result,
|
||||
initialized=bool(initialized),
|
||||
mapped=bool(mapped),
|
||||
unmapped=bool(unmapped),
|
||||
command_error=command_error,
|
||||
)
|
||||
finally:
|
||||
@@ -156,7 +204,12 @@ def initialize_accounting_storage(
|
||||
result = request_agent_command(
|
||||
node.node_code,
|
||||
"accounting_initialize",
|
||||
{"client_id": int(client.id), "client_name": client.client_name},
|
||||
{
|
||||
"client_id": int(client.id),
|
||||
"client_name": client.client_name,
|
||||
"tenant_id": int(scope.tenant_id),
|
||||
"requested_by_user_id": int(user.id),
|
||||
},
|
||||
timeout_seconds=20,
|
||||
)
|
||||
if not result.get("ok"):
|
||||
@@ -172,3 +225,136 @@ def initialize_accounting_storage(
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/map-company")
|
||||
def map_tally_company(
|
||||
request: Request,
|
||||
client_id: int = Form(...),
|
||||
tally_guid: str = Form(...),
|
||||
registration_id: str = Form(""),
|
||||
allow_gstin_mismatch: bool = Form(False),
|
||||
csrf_token: str = Form(...),
|
||||
):
|
||||
validate_csrf(request, csrf_token)
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user, response = _require_partner(request, db, "accounting.tally.map_company")
|
||||
if response:
|
||||
return response
|
||||
|
||||
client, _clients, scope = _find_visible_client(db, request, user, client_id)
|
||||
if not client:
|
||||
return _denied()
|
||||
|
||||
node = get_active_storage_node_for_branch(db, scope.tenant_id, scope.branch_id)
|
||||
if not node or not _node_online(node):
|
||||
return RedirectResponse(
|
||||
url=f"/tools/tally?client_id={client.id}&error={quote('ERP Local Agent is offline for the active branch.')}",
|
||||
status_code=303,
|
||||
)
|
||||
|
||||
registration_payload = None
|
||||
if str(registration_id or "").strip():
|
||||
try:
|
||||
rid = int(registration_id)
|
||||
except Exception:
|
||||
return RedirectResponse(
|
||||
url=f"/tools/tally?client_id={client.id}&error={quote('Invalid registration selection.')}",
|
||||
status_code=303,
|
||||
)
|
||||
|
||||
row = db.execute(
|
||||
select(ClientRegistration, RegistrationType)
|
||||
.join(RegistrationType, RegistrationType.id == ClientRegistration.registration_type_id)
|
||||
.where(
|
||||
ClientRegistration.id == rid,
|
||||
ClientRegistration.client_id == client.id,
|
||||
ClientRegistration.tenant_id == scope.tenant_id,
|
||||
)
|
||||
).first()
|
||||
if not row:
|
||||
return _denied()
|
||||
registration_payload = _registration_payload(row[0], row[1])
|
||||
|
||||
try:
|
||||
result = request_agent_command(
|
||||
node.node_code,
|
||||
"accounting_map_company",
|
||||
{
|
||||
"client_id": int(client.id),
|
||||
"client_name": client.client_name,
|
||||
"tenant_id": int(scope.tenant_id),
|
||||
"registration": registration_payload,
|
||||
"tally_guid": str(tally_guid or "").strip(),
|
||||
"allow_gstin_mismatch": bool(allow_gstin_mismatch),
|
||||
"mapped_by_user_id": int(user.id),
|
||||
},
|
||||
timeout_seconds=20,
|
||||
)
|
||||
if not result.get("ok"):
|
||||
raise RuntimeError(str(result.get("error") or "Tally company mapping failed."))
|
||||
except Exception as exc:
|
||||
return RedirectResponse(
|
||||
url=f"/tools/tally?client_id={client.id}&error={quote(str(exc))}",
|
||||
status_code=303,
|
||||
)
|
||||
|
||||
return RedirectResponse(
|
||||
url=f"/tools/tally?client_id={client.id}&refresh=1&mapped=1",
|
||||
status_code=303,
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/unmap-company")
|
||||
def unmap_tally_company(
|
||||
request: Request,
|
||||
client_id: int = Form(...),
|
||||
mapping_id: int = Form(...),
|
||||
csrf_token: str = Form(...),
|
||||
):
|
||||
validate_csrf(request, csrf_token)
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user, response = _require_partner(request, db, "accounting.tally.map_company")
|
||||
if response:
|
||||
return response
|
||||
|
||||
client, _clients, scope = _find_visible_client(db, request, user, client_id)
|
||||
if not client:
|
||||
return _denied()
|
||||
|
||||
node = get_active_storage_node_for_branch(db, scope.tenant_id, scope.branch_id)
|
||||
if not node or not _node_online(node):
|
||||
return RedirectResponse(
|
||||
url=f"/tools/tally?client_id={client.id}&error={quote('ERP Local Agent is offline for the active branch.')}",
|
||||
status_code=303,
|
||||
)
|
||||
|
||||
try:
|
||||
result = request_agent_command(
|
||||
node.node_code,
|
||||
"accounting_unmap_company",
|
||||
{
|
||||
"client_id": int(client.id),
|
||||
"mapping_id": int(mapping_id),
|
||||
"unmapped_by_user_id": int(user.id),
|
||||
},
|
||||
timeout_seconds=20,
|
||||
)
|
||||
if not result.get("ok"):
|
||||
raise RuntimeError(str(result.get("error") or "Tally company unmapping failed."))
|
||||
except Exception as exc:
|
||||
return RedirectResponse(
|
||||
url=f"/tools/tally?client_id={client.id}&error={quote(str(exc))}",
|
||||
status_code=303,
|
||||
)
|
||||
|
||||
return RedirectResponse(
|
||||
url=f"/tools/tally?client_id={client.id}&refresh=1&unmapped=1",
|
||||
status_code=303,
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
Reference in New Issue
Block a user