Fix GST reconciliation credential vault integration
This commit is contained in:
@@ -66,18 +66,77 @@ def _is_gstin(reg, typ) -> bool:
|
||||
return code == "GSTIN" and len(num) == 15
|
||||
|
||||
|
||||
def _vault_entries(db, user, request, tenant_id: int, client_id: int, registration_id: int | None = None):
|
||||
def _norm_gstin(value: str | None) -> str:
|
||||
return re.sub(r"[^0-9A-Z]", "", str(value or "").upper())
|
||||
|
||||
|
||||
def _credential_is_gst_portal(entry: CredentialVaultEntry) -> bool:
|
||||
category = str(entry.category or "").strip().lower()
|
||||
title = str(entry.title or "").strip().lower()
|
||||
portal_url = str(entry.portal_url or "").strip().lower()
|
||||
reference = str(entry.reference_number or "").strip().lower()
|
||||
|
||||
if category == "gst_portal":
|
||||
return True
|
||||
|
||||
# Older/client-level vault entries may have been saved under a generic category.
|
||||
# Accept them only when the entry itself clearly identifies a GST portal login.
|
||||
haystack = " ".join((title, portal_url, reference))
|
||||
gst_hint = (
|
||||
"gst portal" in haystack
|
||||
or "gst login" in haystack
|
||||
or "services.gst.gov.in" in portal_url
|
||||
or "www.gst.gov.in" in portal_url
|
||||
)
|
||||
if not gst_hint:
|
||||
return False
|
||||
|
||||
# Do not silently substitute E-Way Bill, E-Invoice or API/provider secrets for
|
||||
# a GST portal username/password unless the title explicitly says GST Portal/Login.
|
||||
if category in {"eway_bill", "einvoice", "api_key"}:
|
||||
return "gst portal" in title or "gst login" in title
|
||||
return True
|
||||
|
||||
|
||||
def _credential_matches_gstin(entry: CredentialVaultEntry, registration_id: int, gstin: str) -> bool:
|
||||
if int(entry.registration_id or 0) == int(registration_id):
|
||||
return True
|
||||
if _norm_gstin(entry.reference_number) == _norm_gstin(gstin):
|
||||
return True
|
||||
# Client-level GST Portal credentials created before registration-level vault
|
||||
# linking remain valid candidates for the selected client's GST registration.
|
||||
return entry.registration_id is None
|
||||
|
||||
|
||||
def _vault_entries(db, user, request, tenant_id: int, client_id: int, registration_id: int | None = None, gstin: str = ""):
|
||||
q = select(CredentialVaultEntry).where(
|
||||
CredentialVaultEntry.tenant_id == tenant_id,
|
||||
CredentialVaultEntry.client_id == client_id,
|
||||
CredentialVaultEntry.status != "archived",
|
||||
)
|
||||
if registration_id:
|
||||
q = q.where(CredentialVaultEntry.registration_id == registration_id)
|
||||
rows = db.execute(q.order_by(CredentialVaultEntry.title.asc())).scalars().all()
|
||||
visible = [r for r in rows if can_view_entry(db, user, r, getattr(user, "branch_id", None))]
|
||||
gst = [r for r in visible if "gst" in (str(r.title or "") + " " + str(r.category or "") + " " + str(r.portal_url or "")).lower()]
|
||||
return gst or visible
|
||||
branch_id = request.session.get("active_branch_id") or getattr(user, "branch_id", None)
|
||||
visible = [r for r in rows if can_view_entry(db, user, r, int(branch_id) if branch_id else None)]
|
||||
|
||||
if not registration_id:
|
||||
return [r for r in visible if _credential_is_gst_portal(r)]
|
||||
|
||||
eligible = [
|
||||
r for r in visible
|
||||
if _credential_is_gst_portal(r) and _credential_matches_gstin(r, int(registration_id), gstin)
|
||||
]
|
||||
|
||||
def rank(entry: CredentialVaultEntry):
|
||||
exact_registration = int(entry.registration_id or 0) == int(registration_id)
|
||||
exact_gstin = _norm_gstin(entry.reference_number) == _norm_gstin(gstin)
|
||||
client_level = entry.registration_id is None
|
||||
return (
|
||||
0 if exact_registration else 1 if exact_gstin else 2 if client_level else 3,
|
||||
str(entry.title or "").lower(),
|
||||
int(entry.id or 0),
|
||||
)
|
||||
|
||||
return sorted(eligible, key=rank)
|
||||
|
||||
|
||||
def _storage_payload(client, fy: str, gstin: str) -> tuple[str, str]:
|
||||
@@ -109,7 +168,11 @@ def page(request: Request, client_id: int | None = None, registration_id: int |
|
||||
selected_reg=next((r for r,t in registrations if registration_id and int(r.id)==int(registration_id)),None)
|
||||
if not selected_reg and registrations:
|
||||
selected_reg=registrations[0][0]
|
||||
credentials=_vault_entries(db,user,request,scope.tenant_id,selected.id,int(selected_reg.id) if selected_reg else None)
|
||||
selected_gstin = _norm_gstin(getattr(selected_reg, "registration_number", "")) if selected_reg else ""
|
||||
credentials=_vault_entries(
|
||||
db,user,request,scope.tenant_id,selected.id,
|
||||
int(selected_reg.id) if selected_reg else None, selected_gstin,
|
||||
)
|
||||
node=get_active_storage_node_for_branch(db,scope.tenant_id,scope.branch_id)
|
||||
if selected_reg and period and node and _node_online(node):
|
||||
gstin=re.sub(r"\s+","",str(selected_reg.registration_number or "").upper())
|
||||
@@ -144,8 +207,17 @@ def start_download(request: Request, client_id: int=Form(...), registration_id:
|
||||
if not pair: return _redirect(client_id,error="Select a valid GSTIN registration.")
|
||||
reg,_=pair; gstin=re.sub(r"\s+","",str(reg.registration_number or "").upper())
|
||||
cred=db.get(CredentialVaultEntry,credential_id)
|
||||
if not cred or int(cred.client_id or 0)!=int(client.id) or (cred.registration_id and int(cred.registration_id)!=int(reg.id)) or not can_view_entry(db,user,cred,scope.branch_id):
|
||||
return _redirect(client_id,registration_id=registration_id,period=period,error="Selected GST credential is not available for this client/registration.")
|
||||
credential_ok = bool(
|
||||
cred
|
||||
and int(cred.tenant_id or 0) == int(scope.tenant_id)
|
||||
and int(cred.client_id or 0) == int(client.id)
|
||||
and str(cred.status or "").lower() != "archived"
|
||||
and can_view_entry(db,user,cred,scope.branch_id)
|
||||
and _credential_is_gst_portal(cred)
|
||||
and _credential_matches_gstin(cred,int(reg.id),gstin)
|
||||
)
|
||||
if not credential_ok:
|
||||
return _redirect(client_id,registration_id=registration_id,period=period,error="Selected GST Portal credential is not available for this client/GSTIN.")
|
||||
username=decrypt_value(cred.tenant_id,cred.username_encrypted) or ""; password=decrypt_value(cred.tenant_id,cred.secret_encrypted) or ""
|
||||
if not username or not password: return _redirect(client_id,registration_id=registration_id,period=period,error="GST username/password is missing in Credential Vault.")
|
||||
fy=_fy_for_period(period); accounting_dir,gst_dir=_storage_payload(client,fy,gstin)
|
||||
|
||||
Reference in New Issue
Block a user