Files
arrr-erp/app/modules/registrations/ui.py
T
2026-07-23 12:05:41 +05:30

151 lines
16 KiB
Python

from __future__ import annotations
from datetime import date
from fastapi import APIRouter, Form, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from sqlalchemy import or_, select
from app.core.db.common import CommonSessionLocal
from app.core.security.csrf import get_or_create_csrf_token, validate_csrf
from app.core.security.session_auth import get_current_user
from app.core.templating import templates
from app.modules.clients.models import Client
from app.modules.consultants.models import ConsultantProfile
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
from app.modules.core.rbac.permission_guard import require_permission
from app.modules.registrations.models import *
from app.modules.registrations.service import seed_registration_types, run_registration_lifecycle_once, complete_renewal
router=APIRouter(prefix="/registrations", tags=["registrations-ui"])
def _ctx(request,user,db,**kw):
base={"request":request,"current_user":user,"current_user_roles":get_user_roles(db,user.id),"current_user_permissions":get_user_permissions(db,user.id),"csrf_token":get_or_create_csrf_token(request)};base.update(kw);return base
def _user(request,db):
user=get_current_user(request,db)
if not user: return None
require_permission(db,user,"clients.view");return user
def _tenant(request,user): return int(request.session.get("active_tenant_id") or user.tenant_id)
def _branch(request,user):
raw=request.session.get("active_branch_id");return int(raw) if raw not in (None,"",0,"0") else None
def _can_manage(db,user):
try: require_permission(db,user,"clients.edit");return True
except Exception:return False
def _d(value): return date.fromisoformat(value) if value else None
@router.get("",response_class=HTMLResponse)
def dashboard(request:Request):
with CommonSessionLocal() as db:
user=_user(request,db)
if not user:return RedirectResponse('/login',303)
seed_registration_types(db);run_registration_lifecycle_once(db)
tid=_tenant(request,user); bid=_branch(request,user)
regq=select(ClientRegistration,Client,RegistrationType).join(Client,Client.id==ClientRegistration.client_id).join(RegistrationType,RegistrationType.id==ClientRegistration.registration_type_id).where(ClientRegistration.tenant_id==tid)
taskq=select(RegistrationRenewalTask,Client).join(Client,Client.id==RegistrationRenewalTask.client_id).where(RegistrationRenewalTask.tenant_id==tid,RegistrationRenewalTask.status.notin_(["completed","cancelled"]))
if bid: regq=regq.where(ClientRegistration.branch_id==bid);taskq=taskq.where(RegistrationRenewalTask.branch_id==bid)
regs=db.execute(regq.order_by(ClientRegistration.valid_until.asc().nullslast(),Client.client_name)).all();tasks=db.execute(taskq.order_by(RegistrationRenewalTask.cycle_due_date)).all()
counts={"active":0,"due":0,"expired":0,"tasks":len(tasks)}
for reg,_,_ in regs:
if reg.status=="expired":counts["expired"]+=1
elif reg.status in {"renewal_due","update_due"}:counts["due"]+=1
else:counts["active"]+=1
return templates.TemplateResponse("modules/registrations/templates/registrations/dashboard.html",_ctx(request,user,db,registrations=regs,tasks=tasks,counts=counts,can_manage=_can_manage(db,user),today=date.today()))
@router.get("/masters",response_class=HTMLResponse)
def masters(request:Request):
with CommonSessionLocal() as db:
user=_user(request,db)
if not user:return RedirectResponse('/login',303)
seed_registration_types(db);tid=_tenant(request,user)
return templates.TemplateResponse("modules/registrations/templates/registrations/masters.html",_ctx(request,user,db,types=db.execute(select(RegistrationType).order_by(RegistrationType.sort_order)).scalars().all(),rules=db.execute(select(RegistrationLifecycleRule,RegistrationType).join(RegistrationType).where(or_(RegistrationLifecycleRule.tenant_id.is_(None),RegistrationLifecycleRule.tenant_id==tid))).all(),templates_rows=db.execute(select(RegistrationDocumentTemplate,RegistrationType).join(RegistrationType).where(or_(RegistrationDocumentTemplate.tenant_id.is_(None),RegistrationDocumentTemplate.tenant_id==tid))).all(),can_manage=_can_manage(db,user)))
@router.post("/masters/types")
async def create_type(request:Request,code:str=Form(...),name:str=Form(...),category:str=Form("registration"),issuing_authority:str=Form(""),supports_expiry:bool=Form(False),supports_related_person:bool=Form(False),csrf_token:str=Form(...)):
with CommonSessionLocal() as db:
user=_user(request,db);validate_csrf(request,csrf_token);require_permission(db,user,"clients.edit")
code=code.strip().upper();existing=db.execute(select(RegistrationType).where(RegistrationType.code==code)).scalar_one_or_none()
if existing: existing.name=name.strip();existing.category=category;existing.issuing_authority=issuing_authority or None;existing.supports_expiry=supports_expiry;existing.supports_related_person=supports_related_person;existing.is_active=True
else: db.add(RegistrationType(code=code,name=name.strip(),category=category,issuing_authority=issuing_authority or None,supports_expiry=supports_expiry,supports_related_person=supports_related_person,is_system=False))
db.commit();return RedirectResponse('/registrations/masters',303)
@router.post("/masters/rules")
async def create_rule(request:Request,registration_type_id:int=Form(...),entity_type:str=Form(""),cycle_type:str=Form(...),frequency_months:str=Form(""),renewal_window_days:int=Form(60),reminder_days_csv:str=Form("90,60,30,15,7,0"),grace_period_days:int=Form(0),requires_client_approval:bool=Form(False),requires_partner_review:bool=Form(False),auto_create_task:bool=Form(False),csrf_token:str=Form(...)):
with CommonSessionLocal() as db:
user=_user(request,db);validate_csrf(request,csrf_token);require_permission(db,user,"clients.edit")
db.add(RegistrationLifecycleRule(tenant_id=_tenant(request,user),registration_type_id=registration_type_id,entity_type=entity_type or None,cycle_type=cycle_type,frequency_months=int(frequency_months) if frequency_months else None,renewal_window_days=renewal_window_days,reminder_days_csv=reminder_days_csv,grace_period_days=grace_period_days,requires_client_approval=requires_client_approval,requires_partner_review=requires_partner_review,auto_create_task=auto_create_task,created_by_user_id=user.id));db.commit();return RedirectResponse('/registrations/masters',303)
@router.post("/masters/documents")
async def create_doc_template(request:Request,registration_type_id:int=Form(...),action_type:str=Form(...),document_name:str=Form(...),description:str=Form(""),mandatory:bool=Form(False),csrf_token:str=Form(...)):
with CommonSessionLocal() as db:
user=_user(request,db);validate_csrf(request,csrf_token);require_permission(db,user,"clients.edit")
db.add(RegistrationDocumentTemplate(tenant_id=_tenant(request,user),registration_type_id=registration_type_id,action_type=action_type,document_name=document_name.strip(),description=description or None,mandatory=mandatory));db.commit();return RedirectResponse('/registrations/masters',303)
@router.get("/clients/{client_id}",response_class=HTMLResponse)
def client_register(request:Request,client_id:int):
with CommonSessionLocal() as db:
user=_user(request,db)
if not user:return RedirectResponse('/login',303)
client=db.get(Client,client_id)
if not client or client.tenant_id!=_tenant(request,user):return RedirectResponse('/registrations',303)
seed_registration_types(db)
regs=db.execute(select(ClientRegistration,RegistrationType).join(RegistrationType).where(ClientRegistration.client_id==client_id).order_by(RegistrationType.name)).all()
people=db.execute(select(ClientRelatedPerson).where(ClientRelatedPerson.client_id==client_id).order_by(ClientRelatedPerson.full_name)).scalars().all()
dscs=db.execute(select(ClientDigitalSignature,ClientRelatedPerson).join(ClientRelatedPerson).where(ClientDigitalSignature.client_id==client_id).order_by(ClientDigitalSignature.expires_on)).all()
rules=db.execute(select(RegistrationLifecycleRule).where(or_(RegistrationLifecycleRule.tenant_id.is_(None),RegistrationLifecycleRule.tenant_id==client.tenant_id),RegistrationLifecycleRule.is_active.is_(True))).scalars().all()
consultants=db.execute(select(ConsultantProfile).where(ConsultantProfile.tenant_id==client.tenant_id,ConsultantProfile.is_active.is_(True))).scalars().all()
return templates.TemplateResponse("modules/registrations/templates/registrations/client.html",_ctx(request,user,db,client=client,registrations=regs,people=people,dscs=dscs,types=db.execute(select(RegistrationType).where(RegistrationType.is_active.is_(True)).order_by(RegistrationType.sort_order)).scalars().all(),rules=rules,consultants=consultants,can_manage=_can_manage(db,user)))
@router.post("/clients/{client_id}/people")
async def add_person(request:Request,client_id:int,person_type:str=Form(...),full_name:str=Form(...),designation:str=Form(""),pan:str=Form(""),din:str=Form(""),date_of_birth:str=Form(""),appointment_date:str=Form(""),mobile:str=Form(""),email:str=Form(""),authorised_signatory:bool=Form(False),notes:str=Form(""),csrf_token:str=Form(...)):
with CommonSessionLocal() as db:
user=_user(request,db);validate_csrf(request,csrf_token);require_permission(db,user,"clients.edit");c=db.get(Client,client_id)
db.add(ClientRelatedPerson(tenant_id=c.tenant_id,client_id=c.id,person_type=person_type,full_name=full_name.strip(),designation=designation or None,pan=pan.strip().upper() or None,din=din or None,date_of_birth=_d(date_of_birth),appointment_date=_d(appointment_date),mobile=mobile or None,email=email or None,authorised_signatory=authorised_signatory,notes=notes or None,created_by_user_id=user.id));db.commit();return RedirectResponse(f'/registrations/clients/{client_id}',303)
@router.post("/clients/{client_id}/records")
async def add_registration(request:Request,client_id:int,registration_type_id:int=Form(...),related_person_id:str=Form(""),registration_number:str=Form(...),jurisdiction:str=Form(""),state_code:str=Form(""),issue_date:str=Form(""),effective_from:str=Form(""),valid_until:str=Form(""),next_action_date:str=Form(""),lifecycle_rule_id:str=Form(""),responsible_party:str=Form("firm"),assigned_user_id:str=Form(""),assigned_consultant_id:str=Form(""),auto_create_task:bool=Form(False),notes:str=Form(""),csrf_token:str=Form(...)):
with CommonSessionLocal() as db:
user=_user(request,db);validate_csrf(request,csrf_token);require_permission(db,user,"clients.edit");c=db.get(Client,client_id)
db.add(ClientRegistration(tenant_id=c.tenant_id,branch_id=c.branch_id,client_id=c.id,registration_type_id=registration_type_id,related_person_id=int(related_person_id) if related_person_id else None,registration_number=registration_number.strip().upper(),jurisdiction=jurisdiction or None,state_code=state_code or None,issue_date=_d(issue_date),effective_from=_d(effective_from),valid_until=_d(valid_until),next_action_date=_d(next_action_date),lifecycle_rule_id=int(lifecycle_rule_id) if lifecycle_rule_id else None,responsible_party=responsible_party,assigned_user_id=int(assigned_user_id) if assigned_user_id else c.partner_id,assigned_consultant_id=int(assigned_consultant_id) if assigned_consultant_id else None,auto_create_task=auto_create_task,notes=notes or None,created_by_user_id=user.id));db.commit();run_registration_lifecycle_once(db);return RedirectResponse(f'/registrations/clients/{client_id}',303)
@router.post("/clients/{client_id}/dsc")
async def add_dsc(request:Request,client_id:int,related_person_id:int=Form(...),certificate_serial_number:str=Form(""),provider:str=Form(""),token_make:str=Form(""),class_type:str=Form(""),issued_on:str=Form(""),expires_on:str=Form(...),custody_location:str=Form(""),renewal_responsibility:str=Form("firm"),assigned_consultant_id:str=Form(""),notes:str=Form(""),csrf_token:str=Form(...)):
with CommonSessionLocal() as db:
user=_user(request,db);validate_csrf(request,csrf_token);require_permission(db,user,"clients.edit");c=db.get(Client,client_id)
expiry=_d(expires_on)
dsc=ClientDigitalSignature(tenant_id=c.tenant_id,client_id=c.id,related_person_id=related_person_id,certificate_serial_number=certificate_serial_number or None,provider=provider or None,token_make=token_make or None,class_type=class_type or None,issued_on=_d(issued_on),expires_on=expiry,custody_location=custody_location or None,renewal_responsibility=renewal_responsibility,assigned_consultant_id=int(assigned_consultant_id) if assigned_consultant_id else None,notes=notes or None,created_by_user_id=user.id)
db.add(dsc)
dsc_type=db.execute(select(RegistrationType).where(RegistrationType.code=="DSC")).scalar_one()
serial=(certificate_serial_number or f"DSC-{client_id}-{related_person_id}-{expiry.isoformat()}").strip().upper()
existing=db.execute(select(ClientRegistration).where(ClientRegistration.tenant_id==c.tenant_id,ClientRegistration.registration_type_id==dsc_type.id,ClientRegistration.registration_number==serial)).scalar_one_or_none()
if not existing:
db.add(ClientRegistration(tenant_id=c.tenant_id,branch_id=c.branch_id,client_id=c.id,registration_type_id=dsc_type.id,related_person_id=related_person_id,registration_number=serial,issue_date=_d(issued_on),effective_from=_d(issued_on),valid_until=expiry,responsible_party=renewal_responsibility,assigned_user_id=c.partner_id,assigned_consultant_id=int(assigned_consultant_id) if assigned_consultant_id else None,auto_create_task=True,notes=notes or None,created_by_user_id=user.id))
db.commit();run_registration_lifecycle_once(db);return RedirectResponse(f'/registrations/clients/{client_id}',303)
@router.get("/tasks/{task_id}",response_class=HTMLResponse)
def task_detail(request:Request,task_id:int):
with CommonSessionLocal() as db:
user=_user(request,db)
if not user:return RedirectResponse('/login',303)
task=db.get(RegistrationRenewalTask,task_id)
if not task or task.tenant_id!=_tenant(request,user):return RedirectResponse('/registrations',303)
reg=db.get(ClientRegistration,task.registration_id);client=db.get(Client,task.client_id);rt=db.get(RegistrationType,reg.registration_type_id)
docs=db.execute(select(RegistrationRenewalTaskDocument).where(RegistrationRenewalTaskDocument.renewal_task_id==task.id)).scalars().all()
actions=db.execute(select(ClientRegistrationAction).where(ClientRegistrationAction.registration_id==reg.id).order_by(ClientRegistrationAction.created_at_utc.desc())).scalars().all()
return templates.TemplateResponse("modules/registrations/templates/registrations/task.html",_ctx(request,user,db,task=task,registration=reg,client=client,registration_type=rt,documents=docs,actions=actions,can_manage=_can_manage(db,user)))
@router.post("/tasks/{task_id}/status")
async def update_task(request:Request,task_id:int,status:str=Form(...),submitted_on:str=Form(""),acknowledgement_number:str=Form(""),notes:str=Form(""),csrf_token:str=Form(...)):
with CommonSessionLocal() as db:
user=_user(request,db);validate_csrf(request,csrf_token);require_permission(db,user,"clients.edit");task=db.get(RegistrationRenewalTask,task_id);task.status=status;task.submitted_on=_d(submitted_on);task.acknowledgement_number=acknowledgement_number or None;task.notes=notes or None;db.commit();return RedirectResponse(f'/registrations/tasks/{task_id}',303)
@router.post("/tasks/{task_id}/complete")
async def complete(request:Request,task_id:int,new_valid_until:str=Form(""),acknowledgement_number:str=Form(""),notes:str=Form(""),csrf_token:str=Form(...)):
with CommonSessionLocal() as db:
user=_user(request,db);validate_csrf(request,csrf_token);require_permission(db,user,"clients.edit");task=db.get(RegistrationRenewalTask,task_id);complete_renewal(db,task,new_valid_until=_d(new_valid_until),acknowledgement_number=acknowledgement_number or None,actor_user_id=user.id,notes=notes or None);return RedirectResponse(f'/registrations/tasks/{task_id}',303)
@router.post("/run-engine")
async def run_engine(request:Request,csrf_token:str=Form(...)):
with CommonSessionLocal() as db:
user=_user(request,db);validate_csrf(request,csrf_token);require_permission(db,user,"clients.edit");run_registration_lifecycle_once(db);return RedirectResponse('/registrations',303)