Fix GST reconciliation server imports and Docker healthcheck
This commit is contained in:
@@ -27,9 +27,122 @@ from app.modules.credential_vault.crypto import decrypt_value
|
||||
from app.modules.credential_vault.models import CredentialVaultEntry
|
||||
from app.modules.credential_vault.service import can_view_entry, log_access
|
||||
from app.modules.documents.services import client_folder_parts, get_active_storage_node_for_branch, sanitize_segment
|
||||
from app.modules.documents.local_agent_runtime.erp_local_agent.gst_portal_runtime import extract_invoice_rows, extract_gstr3b_itc
|
||||
from app.modules.registrations.models import ClientRegistration, RegistrationType
|
||||
|
||||
|
||||
|
||||
def _gst_parse_date(value: str) -> str:
|
||||
text = str(value or "").strip()
|
||||
for fmt in ("%d-%m-%Y", "%d/%m/%Y", "%Y-%m-%d", "%d-%b-%Y", "%d/%m/%y"):
|
||||
try:
|
||||
return datetime.strptime(text, fmt).date().isoformat()
|
||||
except Exception:
|
||||
pass
|
||||
return text
|
||||
|
||||
|
||||
def _gst_float(value) -> float:
|
||||
try:
|
||||
return float(value or 0)
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
|
||||
def _gst_doc_key(value: str) -> str:
|
||||
return "".join(ch for ch in str(value or "").upper() if ch.isalnum())
|
||||
|
||||
|
||||
def _gst_walk(obj):
|
||||
if isinstance(obj, dict):
|
||||
yield obj
|
||||
for value in obj.values():
|
||||
yield from _gst_walk(value)
|
||||
elif isinstance(obj, list):
|
||||
for value in obj:
|
||||
yield from _gst_walk(value)
|
||||
|
||||
|
||||
def _gst_tax_from_invoice(inv: dict) -> dict[str, float]:
|
||||
out = {"taxable": 0.0, "igst": 0.0, "cgst": 0.0, "sgst": 0.0, "cess": 0.0}
|
||||
for item in inv.get("itms") or inv.get("items") or []:
|
||||
detail = item.get("itm_det") or item.get("item_det") or item
|
||||
out["taxable"] += _gst_float(detail.get("txval"))
|
||||
out["igst"] += _gst_float(detail.get("iamt"))
|
||||
out["cgst"] += _gst_float(detail.get("camt"))
|
||||
out["sgst"] += _gst_float(detail.get("samt"))
|
||||
out["cess"] += _gst_float(detail.get("csamt"))
|
||||
if not any(out.values()):
|
||||
out["taxable"] = _gst_float(inv.get("txval") or inv.get("taxable_value"))
|
||||
out["igst"] = _gst_float(inv.get("iamt") or inv.get("igst"))
|
||||
out["cgst"] = _gst_float(inv.get("camt") or inv.get("cgst"))
|
||||
out["sgst"] = _gst_float(inv.get("samt") or inv.get("sgst"))
|
||||
out["cess"] = _gst_float(inv.get("csamt") or inv.get("cess"))
|
||||
return out
|
||||
|
||||
|
||||
def extract_invoice_rows(data) -> list[dict]:
|
||||
"""Normalize invoice-like rows from GST JSON without importing workstation runtime code."""
|
||||
rows: list[dict] = []
|
||||
seen: set[tuple[str, str, str]] = set()
|
||||
for obj in _gst_walk(data):
|
||||
invno = str(
|
||||
obj.get("inum")
|
||||
or obj.get("inv_num")
|
||||
or obj.get("doc_no")
|
||||
or obj.get("invoiceNumber")
|
||||
or ""
|
||||
).strip()
|
||||
if not invno:
|
||||
continue
|
||||
gstin = str(
|
||||
obj.get("ctin")
|
||||
or obj.get("stin")
|
||||
or obj.get("supplier_gstin")
|
||||
or obj.get("recipientGstin")
|
||||
or ""
|
||||
).strip().upper()
|
||||
invoice_date = _gst_parse_date(
|
||||
str(
|
||||
obj.get("idt")
|
||||
or obj.get("inv_date")
|
||||
or obj.get("doc_date")
|
||||
or obj.get("invoiceDate")
|
||||
or ""
|
||||
)
|
||||
)
|
||||
key = (gstin, _gst_doc_key(invno), invoice_date)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
tax = _gst_tax_from_invoice(obj)
|
||||
rows.append(
|
||||
{
|
||||
"gstin": gstin,
|
||||
"invoice_no": invno,
|
||||
"invoice_key": _gst_doc_key(invno),
|
||||
"invoice_date": invoice_date,
|
||||
"invoice_value": _gst_float(
|
||||
obj.get("val") or obj.get("invoice_value") or obj.get("invoiceValue")
|
||||
),
|
||||
**tax,
|
||||
}
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
def extract_gstr3b_itc(data) -> dict[str, float]:
|
||||
"""Normalize GSTR-3B eligible-ITC totals from uploaded portal JSON."""
|
||||
totals = {"igst": 0.0, "cgst": 0.0, "sgst": 0.0, "cess": 0.0}
|
||||
if not isinstance(data, dict):
|
||||
return totals
|
||||
itc = data.get("itc_elg") or {}
|
||||
for item in itc.get("itc_avl") or []:
|
||||
totals["igst"] += _gst_float(item.get("iamt"))
|
||||
totals["cgst"] += _gst_float(item.get("camt"))
|
||||
totals["sgst"] += _gst_float(item.get("samt"))
|
||||
totals["cess"] += _gst_float(item.get("csamt"))
|
||||
return totals
|
||||
|
||||
router = APIRouter(prefix="/tools/accounting/gst-reconciliation", tags=["accounting-gst-reconciliation-ui"])
|
||||
_TOKEN_PURPOSE = "gst_operator_browser_v1"
|
||||
_TOKEN_MINUTES = 30
|
||||
|
||||
Reference in New Issue
Block a user