Add platform SMTP and firm wizard invite email

This commit is contained in:
A R R R Associates
2026-07-02 23:43:24 +05:30
parent b4b8dc97fd
commit b84090d8c9
12 changed files with 821 additions and 45 deletions
+37 -6
View File
@@ -1,4 +1,4 @@
from __future__ import annotations
from __future__ import annotations
from dataclasses import dataclass
from datetime import date, datetime, timedelta
@@ -19,6 +19,7 @@ from app.modules.core.rbac.models import Role, UserRole
from app.modules.core.tenancy.models import Branch, FinancialYear, Tenant
from app.modules.core.tenancy.settings_models import BranchSettings
from app.modules.employees.models import Employee
from app.modules.email_integration.services import send_platform_user_invite_email
class FirmWizardError(ValueError):
@@ -33,6 +34,8 @@ class FirmWizardResult:
financial_year: FinancialYear | None
invite_url: str
invite_token: str
invite_email_status: str | None = None
invite_email_error: str | None = None
def normalize_code(value: str, *, upper: bool = True) -> str:
@@ -202,6 +205,19 @@ def validate_firm_wizard_payload(db: Session, payload: dict[str, Any]) -> list[s
return errors
def safe_employee_code_for_firm_admin(db: Session, tenant_id: int, tenant_code: str, user_id: int) -> str:
base = normalize_code(f"{tenant_code}_ADMIN_{user_id}")[:50]
employee_code = base
suffix = 1
while db.execute(
select(Employee).where(Employee.tenant_id == tenant_id, Employee.employee_code == employee_code)
).scalar_one_or_none():
suffix += 1
employee_code = f"{base[:44]}_{suffix}"[:50]
return employee_code
def _hash_token(token: str) -> str:
return hashlib.sha256(token.encode("utf-8")).hexdigest()
@@ -295,14 +311,11 @@ def create_firm_from_wizard(db: Session, payload: dict[str, Any]) -> FirmWizardR
db.flush()
db.add(UserRole(user_id=firm_admin.id, role_id=role.id))
# Auto-create and link Employee Master for the primary Firm Admin.
# Employee Portal requires employees.user_id to match the logged-in user.
employee_code = normalize_code(f"{payload['tenant_code']}_ADMIN")[:50]
firm_admin_employee = Employee(
tenant_id=tenant.id,
branch_id=branch.id,
user_id=firm_admin.id,
employee_code=employee_code,
employee_code=safe_employee_code_for_firm_admin(db, tenant.id, tenant.code, firm_admin.id),
full_name=payload["admin_full_name"],
email=payload["admin_email"],
mobile=payload["admin_mobile"] or None,
@@ -338,6 +351,23 @@ def create_firm_from_wizard(db: Session, payload: dict[str, Any]) -> FirmWizardR
invite_token = create_invite_token_without_commit(db, firm_admin)
invite_url = public_invite_url(invite_token)
invite_email_status = None
invite_email_error = None
try:
invite_email_log = send_platform_user_invite_email(
db,
user=firm_admin,
invite_token=invite_token,
firm_name=tenant.display_name or tenant.name,
)
if invite_email_log:
invite_email_status = invite_email_log.status
invite_email_error = invite_email_log.error_message
except Exception as exc:
# Firm creation must not fail only because SMTP is unavailable.
invite_email_status = "FAILED"
invite_email_error = str(exc)
return FirmWizardResult(
tenant=tenant,
branch=branch,
@@ -345,5 +375,6 @@ def create_firm_from_wizard(db: Session, payload: dict[str, Any]) -> FirmWizardR
financial_year=financial_year,
invite_url=invite_url,
invite_token=invite_token,
invite_email_status=invite_email_status,
invite_email_error=invite_email_error,
)