Add digital client acceptance workflow

This commit is contained in:
A R R R Associates
2026-07-07 09:49:20 +05:30
parent 671adbd1a2
commit 1b5fec270b
6 changed files with 1103 additions and 0 deletions
+97
View File
@@ -97,3 +97,100 @@ class ClientAuditLog(CommonBase):
summary: Mapped[str] = mapped_column(String(255), nullable=False)
payload_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
class ClientAcceptanceDeclaration(CommonBase):
"""Digital independence/conflict declaration requested from partner, manager or staff.
Each row is an auditable declaration. The client level checkbox is derived
only when all applicable declaration rows are answered without issue.
"""
__tablename__ = "client_acceptance_declarations"
__table_args__ = (
UniqueConstraint("client_id", "declaration_type", "assigned_user_id", name="uq_client_acceptance_declaration_user"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
client_id: Mapped[int] = mapped_column(ForeignKey("clients.id", ondelete="CASCADE"), nullable=False, index=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int | None] = mapped_column(ForeignKey("branches.id", ondelete="SET NULL"), nullable=True, index=True)
declaration_type: Mapped[str] = mapped_column(String(30), nullable=False, index=True)
assigned_user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
assigned_role_snapshot: Mapped[str | None] = mapped_column(String(120), nullable=True)
declaration_text: Mapped[str] = mapped_column(Text, nullable=False)
status: Mapped[str] = mapped_column(String(30), nullable=False, default="pending", index=True)
response_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
issue_details: Mapped[str | None] = mapped_column(Text, nullable=True)
responded_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
response_ip: Mapped[str | None] = mapped_column(String(80), nullable=True)
response_user_agent: Mapped[str | None] = mapped_column(String(500), nullable=True)
requested_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
requested_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
updated_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
class ClientKycVerification(CommonBase):
"""KYC verification layer on top of existing permanent client documents.
This does not duplicate client uploads. It stores whether the existing
permanent documents are enough and who verified them.
"""
__tablename__ = "client_kyc_verifications"
__table_args__ = (
UniqueConstraint("client_id", name="uq_client_kyc_verification_client"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
client_id: Mapped[int] = mapped_column(ForeignKey("clients.id", ondelete="CASCADE"), nullable=False, index=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int | None] = mapped_column(ForeignKey("branches.id", ondelete="SET NULL"), nullable=True, index=True)
status: Mapped[str] = mapped_column(String(30), nullable=False, default="pending_documents", index=True)
required_document_summary: Mapped[str | None] = mapped_column(Text, nullable=True)
available_document_summary: Mapped[str | None] = mapped_column(Text, nullable=True)
verification_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
verified_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
verified_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
rejected_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
rejected_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
updated_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
class ClientEngagementLetter(CommonBase):
"""Client acceptance engagement letter with digital OTP or manual signed upload."""
__tablename__ = "client_engagement_letters"
__table_args__ = (
UniqueConstraint("client_id", "letter_code", "version_no", name="uq_client_engagement_letter_code_version"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
client_id: Mapped[int] = mapped_column(ForeignKey("clients.id", ondelete="CASCADE"), nullable=False, index=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int | None] = mapped_column(ForeignKey("branches.id", ondelete="SET NULL"), nullable=True, index=True)
letter_code: Mapped[str] = mapped_column(String(80), nullable=False, default="STANDARD", index=True)
title: Mapped[str] = mapped_column(String(255), nullable=False)
body_text: Mapped[str] = mapped_column(Text, nullable=False)
version_no: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
status: Mapped[str] = mapped_column(String(40), nullable=False, default="draft", index=True)
sent_to_client_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
approved_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
approved_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
accepted_mode: Mapped[str | None] = mapped_column(String(40), nullable=True, index=True)
accepted_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
accepted_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
accepted_ip: Mapped[str | None] = mapped_column(String(80), nullable=True)
accepted_user_agent: Mapped[str | None] = mapped_column(String(500), nullable=True)
acceptance_declaration_text: Mapped[str | None] = mapped_column(Text, nullable=True)
otp_verified: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
pdf_hash_sha256: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
manual_signed_file_path: Mapped[str | None] = mapped_column(String(1000), nullable=True)
manual_signed_file_hash_sha256: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
manual_verified_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
manual_verified_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
rejection_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
updated_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)