Integrate UDIN controls into final document workflow

This commit is contained in:
A R R R Associates
2026-07-06 18:05:00 +05:30
parent eb1cf6cbf2
commit 561de117e4
10 changed files with 723 additions and 10 deletions
+174 -1
View File
@@ -1,6 +1,6 @@
from __future__ import annotations
from datetime import datetime, timezone
from datetime import date, datetime, timezone
import asyncio
import logging
from pathlib import Path
@@ -34,6 +34,7 @@ from app.modules.documents.services import (
get_storage_job_for_node,
list_documents_for_engagement,
list_download_requests,
list_udin_records,
list_recent_download_requests_for_engagement,
list_visible_engagements,
list_pending_download_requests,
@@ -41,6 +42,11 @@ from app.modules.documents.services import (
list_storage_jobs,
list_storage_nodes,
log_document_access,
release_document_final,
reopen_final_document,
update_document_udin,
user_can_manage_udin,
user_can_view_udin_register,
create_download_request_for_version,
download_request_cache_path,
fulfill_download_request_from_upload,
@@ -128,6 +134,18 @@ def _active_financial_year(request: Request) -> str | None:
value = (value or "").strip()
return value or None
def _parse_optional_date(value: str | None) -> date | None:
value = (value or "").strip()
if not value:
return None
return date.fromisoformat(value)
def _bool_from_form(value: str | None) -> bool:
return (value or "").strip().lower() in {"1", "true", "yes", "on", "required"}
def _load_engagement(db, engagement_id: int):
return db.execute(
select(ClientServiceSubscription)
@@ -186,6 +204,7 @@ def upload_engagement_document(
description: str | None = Form(None),
remarks: str | None = Form(None),
existing_document_id: str | None = Form(None),
udin_required: str | None = Form(None),
file: UploadFile = File(...),
csrf_token: str = Form(...),
):
@@ -217,6 +236,7 @@ def upload_engagement_document(
remarks=remarks,
user=user,
existing_document_id=int(existing_document_id) if existing_document_id else None,
udin_required=_bool_from_form(udin_required),
)
log_document_access(db, action="upload", result="success", user=user, request=request, document=doc)
db.commit()
@@ -287,6 +307,7 @@ def upload_task_document(
description: str | None = Form(None),
remarks: str | None = Form(None),
existing_document_id: str | None = Form(None),
udin_required: str | None = Form(None),
file: UploadFile = File(...),
csrf_token: str = Form(...),
):
@@ -325,6 +346,7 @@ def upload_task_document(
remarks=remarks,
user=user,
existing_document_id=int(existing_document_id) if existing_document_id else None,
udin_required=_bool_from_form(udin_required),
)
log_document_access(db, action="task_upload", result="success", user=user, request=request, document=doc)
db.commit()
@@ -341,6 +363,157 @@ def upload_task_document(
finally:
db.close()
@router.get("/udin-register")
def udin_register(request: Request, financial_year: str = "", status: str = "", q: str = ""):
db = CommonSessionLocal()
try:
user, response = _require_user(request, db, "udin.view")
if response:
return response
scope = build_document_scope(request, db, user)
if not user_can_view_udin_register(db, user, scope):
return _redirect_denied()
selected_financial_year = (financial_year or _active_financial_year(request) or "").strip()
rows = list_udin_records(
db,
user,
scope,
financial_year=selected_financial_year or None,
status=status or None,
q=q or None,
)
return _render(
request,
"modules/documents/templates/documents/udin_register.html",
db,
user,
title="UDIN Register",
rows=rows,
financial_year=selected_financial_year,
selected_status=status,
q=q,
)
finally:
db.close()
@router.post("/{document_id}/udin")
def update_document_udin_submit(
request: Request,
document_id: int,
udin_required: str | None = Form(None),
udin_number: str | None = Form(None),
udin_date: str | None = Form(None),
udin_document_date: str | None = Form(None),
udin_document_type: str | None = Form(None),
udin_financial_year: str | None = Form(None),
udin_amount: str | None = Form(None),
udin_notes: str | None = Form(None),
redirect_to: str | None = Form(None),
csrf_token: str = Form(...),
):
validate_csrf(request, csrf_token)
db = CommonSessionLocal()
try:
user, response = _require_user(request, db, "udin.manage")
if response:
return response
scope = build_document_scope(request, db, user)
document = get_document(db, document_id)
if not document or not user_can_manage_udin(db, user, document, scope):
return _redirect_denied()
if is_row_financial_year_locked(db, document.engagement):
return RedirectResponse(url=f"/documents/engagements/{document.engagement_id}?year_locked=1", status_code=303)
try:
update_document_udin(
db,
document=document,
udin_required=_bool_from_form(udin_required),
udin_number=udin_number,
udin_date=_parse_optional_date(udin_date),
udin_document_date=_parse_optional_date(udin_document_date),
udin_document_type=udin_document_type,
udin_financial_year=udin_financial_year or document.financial_year,
udin_amount=udin_amount,
udin_notes=udin_notes,
user=user,
)
log_document_access(db, action="udin_update", result="success", user=user, request=request, document=document)
db.commit()
target = (redirect_to or f"/documents/engagements/{document.engagement_id}").strip()
return RedirectResponse(url=f"{target}{'&' if '?' in target else '?'}udin_saved=1", status_code=303)
except Exception as exc:
db.rollback()
log_document_access(db, action="udin_update", result="failed", user=user, request=request, document=document, message=str(exc)[:1000])
db.commit()
return RedirectResponse(url=f"/documents/engagements/{document.engagement_id}?error=udin_failed", status_code=303)
finally:
db.close()
@router.post("/{document_id}/final-release")
def release_document_final_submit(
request: Request,
document_id: int,
release_notes: str | None = Form(None),
redirect_to: str | None = Form(None),
csrf_token: str = Form(...),
):
validate_csrf(request, csrf_token)
db = CommonSessionLocal()
try:
user, response = _require_user(request, db, "udin.manage")
if response:
return response
scope = build_document_scope(request, db, user)
document = get_document(db, document_id)
if not document or not user_can_manage_udin(db, user, document, scope):
return _redirect_denied()
if is_row_financial_year_locked(db, document.engagement):
return RedirectResponse(url=f"/documents/engagements/{document.engagement_id}?year_locked=1", status_code=303)
try:
release_document_final(db, document=document, release_notes=release_notes, user=user)
log_document_access(db, action="final_release", result="success", user=user, request=request, document=document)
db.commit()
target = (redirect_to or f"/documents/engagements/{document.engagement_id}").strip()
return RedirectResponse(url=f"{target}{'&' if '?' in target else '?'}released=1", status_code=303)
except Exception as exc:
db.rollback()
log_document_access(db, action="final_release", result="failed", user=user, request=request, document=document, message=str(exc)[:1000])
db.commit()
target = (redirect_to or f"/documents/engagements/{document.engagement_id}").strip()
return RedirectResponse(url=f"{target}{'&' if '?' in target else '?'}error=release_failed", status_code=303)
finally:
db.close()
@router.post("/{document_id}/reopen-final")
def reopen_final_document_submit(
request: Request,
document_id: int,
release_notes: str | None = Form(None),
redirect_to: str | None = Form(None),
csrf_token: str = Form(...),
):
validate_csrf(request, csrf_token)
db = CommonSessionLocal()
try:
user, response = _require_user(request, db, "udin.manage")
if response:
return response
scope = build_document_scope(request, db, user)
document = get_document(db, document_id)
if not document or not user_can_manage_udin(db, user, document, scope):
return _redirect_denied()
reopen_final_document(db, document=document, release_notes=release_notes, user=user)
log_document_access(db, action="final_reopen", result="success", user=user, request=request, document=document)
db.commit()
target = (redirect_to or f"/documents/engagements/{document.engagement_id}").strip()
return RedirectResponse(url=f"{target}{'&' if '?' in target else '?'}reopened=1", status_code=303)
finally:
db.close()
def _stream_file(path: Path, download_name: str, content_type: str | None = None):
def file_iterator():
with path.open("rb") as fh: