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

114 lines
9.1 KiB
Python

from __future__ import annotations
from datetime import date, datetime, timedelta, timezone
from calendar import monthrange
from sqlalchemy import and_, or_, select
from sqlalchemy.orm import Session
from app.modules.alerts.service import create_alert
from app.modules.clients.models import Client
from app.modules.consultants.models import ConsultantProfile
from app.modules.registrations.models import (
RegistrationType, ClientRegistration, ClientRelatedPerson, ClientDigitalSignature,
RegistrationLifecycleRule, RegistrationDocumentTemplate, RegistrationRenewalTask,
RegistrationRenewalTaskDocument, ClientRegistrationAction,
)
DEFAULT_TYPES = [
("PAN","PAN","Income Tax Department",False,False),
("TAN","TAN","Income Tax Department",False,False),
("GSTIN","GST Registration","GST Department",False,False),
("CIN","Company Registration","Ministry of Corporate Affairs",False,False),
("LLPIN","LLP Registration","Ministry of Corporate Affairs",False,False),
("DSC","Digital Signature Certificate","Certifying Authority",True,True),
("UDYAM","Udyam / MSME Registration","Ministry of MSME",False,False),
("FSSAI","FSSAI Licence","FSSAI",True,False),
("IEC","Import Export Code","DGFT",False,False),
("SHOP_ACT","Shops and Establishment","State Authority",True,False),
("TRADE_LICENCE","Trade Licence","Local Authority",True,False),
("PT","Professional Tax","State Authority",True,False),
("PF","EPFO Registration","EPFO",False,False),
("ESI","ESIC Registration","ESIC",False,False),
("12AB","Section 12AB Registration","Income Tax Department",True,False),
("80G","Section 80G Approval","Income Tax Department",True,False),
("FCRA","FCRA Registration","Ministry of Home Affairs",True,False),
("RERA","RERA Registration","RERA Authority",True,False),
]
def seed_registration_types(db: Session) -> None:
existing={x for x in db.execute(select(RegistrationType.code)).scalars().all()}
for i,(code,name,authority,expiry,person) in enumerate(DEFAULT_TYPES,1):
if code not in existing:
db.add(RegistrationType(code=code,name=name,issuing_authority=authority,supports_expiry=expiry,supports_related_person=person,sort_order=i*10))
db.commit()
def add_months(value: date, months: int) -> date:
m=value.month-1+months; y=value.year+m//12; m=m%12+1
return date(y,m,min(value.day,monthrange(y,m)[1]))
def resolve_next_action(reg: ClientRegistration, rule: RegistrationLifecycleRule|None) -> date|None:
if reg.valid_until and rule and rule.cycle_type=="expiry_renewal":
return reg.valid_until-timedelta(days=max(0,rule.renewal_window_days))
base=reg.effective_from or reg.issue_date
if base and rule and rule.frequency_months:
candidate=add_months(base,rule.frequency_months)
while candidate<=date.today(): candidate=add_months(candidate,rule.frequency_months)
return candidate
return reg.next_action_date
def create_task_documents(db: Session, task: RegistrationRenewalTask, registration_type_id: int) -> None:
rows=db.execute(select(RegistrationDocumentTemplate).where(RegistrationDocumentTemplate.registration_type_id==registration_type_id,RegistrationDocumentTemplate.action_type==task.action_type,RegistrationDocumentTemplate.is_active.is_(True)).order_by(RegistrationDocumentTemplate.sort_order)).scalars().all()
for row in rows:
db.add(RegistrationRenewalTaskDocument(renewal_task_id=task.id,template_id=row.id,document_name=row.document_name,mandatory=row.mandatory))
def _alert_once(db: Session,user_id:int|None,title:str,message:str,target_url:str,tenant_id:int,branch_id:int|None):
if not user_id:return
today=datetime.now(timezone.utc).date()
from app.modules.alerts.models import UserAlert
exists=db.execute(select(UserAlert.id).where(UserAlert.user_id==user_id,UserAlert.title==title,UserAlert.target_url==target_url,UserAlert.created_at_utc>=datetime.combine(today,datetime.min.time(),tzinfo=timezone.utc))).scalar_one_or_none()
if not exists:create_alert(db,user_id=user_id,title=title,message=message,tenant_id=tenant_id,branch_id=branch_id,role_context="registrations",alert_type="registration_expiry",priority="high",target_url=target_url,commit=False)
def run_registration_lifecycle_once(db: Session, today: date|None=None) -> dict:
today=today or date.today(); created=alerts=0
regs=db.execute(select(ClientRegistration).where(ClientRegistration.status.in_(["active","renewal_due","update_due"]))).scalars().all()
for reg in regs:
rule=db.get(RegistrationLifecycleRule,reg.lifecycle_rule_id) if reg.lifecycle_rule_id else None
if not rule:
rule=db.execute(select(RegistrationLifecycleRule).join(Client, Client.id==reg.client_id).where(RegistrationLifecycleRule.registration_type_id==reg.registration_type_id,RegistrationLifecycleRule.is_active.is_(True),or_(RegistrationLifecycleRule.tenant_id.is_(None),RegistrationLifecycleRule.tenant_id==reg.tenant_id),or_(RegistrationLifecycleRule.entity_type.is_(None),RegistrationLifecycleRule.entity_type==Client.client_type)).order_by(RegistrationLifecycleRule.tenant_id.desc(),RegistrationLifecycleRule.id.desc())).scalars().first()
due=reg.valid_until or reg.next_action_date
if not due: continue
next_action=resolve_next_action(reg,rule)
if next_action and reg.next_action_date!=next_action: reg.next_action_date=next_action
window=(rule.renewal_window_days if rule else 60)
if due-timedelta(days=window)<=today and reg.auto_create_task:
task=db.execute(select(RegistrationRenewalTask).where(RegistrationRenewalTask.registration_id==reg.id,RegistrationRenewalTask.cycle_due_date==due)).scalar_one_or_none()
if not task:
rt=db.get(RegistrationType,reg.registration_type_id); client=db.get(Client,reg.client_id)
action="renewal" if reg.valid_until else "update"
task=RegistrationRenewalTask(tenant_id=reg.tenant_id,branch_id=reg.branch_id,client_id=reg.client_id,registration_id=reg.id,lifecycle_rule_id=rule.id if rule else None,cycle_due_date=due,action_type=action,title=f"{action.title()} - {rt.name} - {client.client_name}",priority="critical" if due<=today else "high",assigned_user_id=reg.assigned_user_id,assigned_consultant_id=reg.assigned_consultant_id,client_approval_required=bool(rule and rule.requires_client_approval),partner_review_required=bool(rule and rule.requires_partner_review))
db.add(task);db.flush();create_task_documents(db,task,reg.registration_type_id);created+=1
client=db.get(Client,reg.client_id)
days=(due-today).days
title=f"{db.get(RegistrationType,reg.registration_type_id).name} {'expired' if days<0 else 'due'}: {client.client_name}"
msg=f"Registration {reg.registration_number} is {'overdue by '+str(abs(days))+' day(s)' if days<0 else 'due in '+str(days)+' day(s)'} on {due:%d-%m-%Y}."
reminder_days={int(x.strip()) for x in ((rule.reminder_days_csv if rule else "60,30,15,7,0") or "").split(",") if x.strip().lstrip("-").isdigit()}
should_alert = days in reminder_days or days < 0
if should_alert:
_alert_once(db,reg.assigned_user_id or client.partner_id,title,msg,f"/registrations/tasks/{task.id}",reg.tenant_id,reg.branch_id);alerts+=1
if should_alert and reg.assigned_consultant_id:
cp=db.get(ConsultantProfile,reg.assigned_consultant_id); _alert_once(db,cp.user_id if cp else None,title,msg,f"/registrations/tasks/{task.id}",reg.tenant_id,reg.branch_id)
reg.status="expired" if due<today else ("renewal_due" if reg.valid_until else "update_due")
db.commit();return {"tasks_created":created,"alerts_processed":alerts}
def complete_renewal(db:Session, task:RegistrationRenewalTask, *, new_valid_until:date|None, acknowledgement_number:str|None, actor_user_id:int, notes:str|None):
reg=db.get(ClientRegistration,task.registration_id); previous=reg.valid_until
task.status="completed"; task.completed_on=date.today(); task.acknowledgement_number=acknowledgement_number; task.notes=notes
if new_valid_until: reg.valid_until=new_valid_until
rt=db.get(RegistrationType,reg.registration_type_id)
if rt and rt.code=="DSC" and new_valid_until:
dsc=db.execute(select(ClientDigitalSignature).where(ClientDigitalSignature.client_id==reg.client_id,ClientDigitalSignature.related_person_id==reg.related_person_id).order_by(ClientDigitalSignature.id.desc())).scalars().first()
if dsc:
dsc.expires_on=new_valid_until; dsc.status="active"
rule=db.get(RegistrationLifecycleRule,task.lifecycle_rule_id) if task.lifecycle_rule_id else None
reg.status="active"; reg.next_action_date=resolve_next_action(reg,rule)
db.add(ClientRegistrationAction(tenant_id=reg.tenant_id,client_id=reg.client_id,registration_id=reg.id,renewal_task_id=task.id,action_type=task.action_type,previous_valid_until=previous,new_valid_until=new_valid_until,submission_date=task.submitted_on,acknowledgement_number=acknowledgement_number,completion_date=date.today(),status="completed",performed_by_user_id=actor_user_id,notes=notes))
db.commit()