Integrate UDIN controls into final document workflow
This commit is contained in:
+174
-1
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user