Fix GST reconciliation server imports and Docker healthcheck
This commit is contained in:
@@ -24,4 +24,6 @@ RUN mkdir -p /app/data/storage /app/data/year_backups
|
|||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=10 CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/', timeout=3)"
|
||||||
|
|
||||||
CMD ["sh", "-c", "alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8000"]
|
CMD ["sh", "-c", "alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8000"]
|
||||||
@@ -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.models import CredentialVaultEntry
|
||||||
from app.modules.credential_vault.service import can_view_entry, log_access
|
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.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
|
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"])
|
router = APIRouter(prefix="/tools/accounting/gst-reconciliation", tags=["accounting-gst-reconciliation-ui"])
|
||||||
_TOKEN_PURPOSE = "gst_operator_browser_v1"
|
_TOKEN_PURPOSE = "gst_operator_browser_v1"
|
||||||
_TOKEN_MINUTES = 30
|
_TOKEN_MINUTES = 30
|
||||||
|
|||||||
Reference in New Issue
Block a user