from __future__ import annotations from datetime import date, datetime, timezone from sqlalchemy import Boolean, Date, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.db.common import CommonBase class NoticeCase(CommonBase): """Department notice / case / appeal master. A case is intentionally kept separate from engagements because GST/Income Tax/ROC proceedings can continue across years, have independent due dates, and may later be linked to one or more engagements/tasks. """ __tablename__ = "notice_cases" __table_args__ = ( UniqueConstraint("tenant_id", "case_code", name="uq_notice_cases_tenant_code"), ) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=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) client_id: Mapped[int] = mapped_column(ForeignKey("clients.id", ondelete="CASCADE"), nullable=False, index=True) engagement_id: Mapped[int | None] = mapped_column(ForeignKey("client_service_subscriptions.id", ondelete="SET NULL"), nullable=True, index=True) case_code: Mapped[str] = mapped_column(String(80), nullable=False, index=True) department: Mapped[str] = mapped_column(String(40), nullable=False, index=True) case_type: Mapped[str] = mapped_column(String(50), nullable=False, index=True) title: Mapped[str] = mapped_column(String(255), nullable=False) reference_no: Mapped[str | None] = mapped_column(String(150), nullable=True, index=True) din_ack_no: Mapped[str | None] = mapped_column(String(150), nullable=True, index=True) notice_date: Mapped[date | None] = mapped_column(Date, nullable=True, index=True) due_date: Mapped[date | None] = mapped_column(Date, nullable=True, index=True) financial_year: Mapped[str | None] = mapped_column(String(9), nullable=True, index=True) assessment_year: Mapped[str | None] = mapped_column(String(9), nullable=True, index=True) period_label: Mapped[str | None] = mapped_column(String(60), nullable=True, index=True) status: Mapped[str] = mapped_column(String(40), nullable=False, default="open", index=True) priority: Mapped[str] = mapped_column(String(20), nullable=False, default="normal", index=True) issue_summary: Mapped[str | None] = mapped_column(Text, nullable=True) remarks: Mapped[str | None] = mapped_column(Text, nullable=True) assigned_partner_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True) assigned_manager_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True) assigned_staff_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True) is_archived: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, index=True) archived_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) archived_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True) created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True) updated_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) client = relationship("Client") engagement = relationship("ClientServiceSubscription") assigned_partner = relationship("User", foreign_keys=[assigned_partner_user_id]) assigned_manager = relationship("User", foreign_keys=[assigned_manager_user_id]) assigned_staff = relationship("User", foreign_keys=[assigned_staff_user_id]) events = relationship("NoticeCaseEvent", back_populates="case", cascade="all, delete-orphan", passive_deletes=True, order_by="NoticeCaseEvent.event_date.desc(), NoticeCaseEvent.id.desc()") hearings = relationship("NoticeCaseHearing", back_populates="case", cascade="all, delete-orphan", passive_deletes=True, order_by="NoticeCaseHearing.hearing_date.asc(), NoticeCaseHearing.id.asc()") orders = relationship("NoticeCaseOrder", back_populates="case", cascade="all, delete-orphan", passive_deletes=True, order_by="NoticeCaseOrder.order_date.desc(), NoticeCaseOrder.id.desc()") documents = relationship("NoticeCaseDocument", back_populates="case", cascade="all, delete-orphan", passive_deletes=True, order_by="NoticeCaseDocument.uploaded_at_utc.desc()") class NoticeCaseEvent(CommonBase): __tablename__ = "notice_case_events" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=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) case_id: Mapped[int] = mapped_column(ForeignKey("notice_cases.id", ondelete="CASCADE"), nullable=False, index=True) event_type: Mapped[str] = mapped_column(String(60), nullable=False, index=True) event_date: Mapped[date] = mapped_column(Date, nullable=False, index=True) description: Mapped[str] = mapped_column(Text, nullable=False) next_due_date: Mapped[date | None] = mapped_column(Date, nullable=True, index=True) created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True) created_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False) case = relationship("NoticeCase", back_populates="events") created_by = relationship("User") class NoticeCaseHearing(CommonBase): __tablename__ = "notice_case_hearings" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=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) case_id: Mapped[int] = mapped_column(ForeignKey("notice_cases.id", ondelete="CASCADE"), nullable=False, index=True) hearing_date: Mapped[date] = mapped_column(Date, nullable=False, index=True) hearing_time: Mapped[str | None] = mapped_column(String(20), nullable=True) venue_or_mode: Mapped[str | None] = mapped_column(String(200), nullable=True) officer_name: Mapped[str | None] = mapped_column(String(150), nullable=True) agenda: Mapped[str | None] = mapped_column(Text, nullable=True) outcome: Mapped[str | None] = mapped_column(Text, nullable=True) status: Mapped[str] = mapped_column(String(30), nullable=False, default="scheduled", index=True) created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=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) case = relationship("NoticeCase", back_populates="hearings") created_by = relationship("User") class NoticeCaseOrder(CommonBase): __tablename__ = "notice_case_orders" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=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) case_id: Mapped[int] = mapped_column(ForeignKey("notice_cases.id", ondelete="CASCADE"), nullable=False, index=True) order_type: Mapped[str] = mapped_column(String(60), nullable=False, index=True) order_no: Mapped[str | None] = mapped_column(String(150), nullable=True, index=True) order_date: Mapped[date] = mapped_column(Date, nullable=False, index=True) demand_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0) tax_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0) interest_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0) penalty_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0) summary: Mapped[str | None] = mapped_column(Text, nullable=True) appeal_due_date: Mapped[date | None] = mapped_column(Date, nullable=True, index=True) appeal_filed: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, index=True) created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True) created_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False) case = relationship("NoticeCase", back_populates="orders") created_by = relationship("User") class NoticeCaseDocument(CommonBase): __tablename__ = "notice_case_documents" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=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) case_id: Mapped[int] = mapped_column(ForeignKey("notice_cases.id", ondelete="CASCADE"), nullable=False, index=True) event_id: Mapped[int | None] = mapped_column(ForeignKey("notice_case_events.id", ondelete="SET NULL"), nullable=True, index=True) document_type: Mapped[str] = mapped_column(String(80), nullable=False, default="GENERAL", index=True) title: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str | None] = mapped_column(Text, nullable=True) original_filename: Mapped[str] = mapped_column(String(255), nullable=False) stored_filename: Mapped[str] = mapped_column(String(255), nullable=False) content_type: Mapped[str | None] = mapped_column(String(150), nullable=True) file_size_bytes: Mapped[int] = mapped_column(Integer, nullable=False, default=0) local_relative_path: Mapped[str] = mapped_column(String(1000), nullable=False) version_no: Mapped[int] = mapped_column(Integer, nullable=False, default=1) status: Mapped[str] = mapped_column(String(30), nullable=False, default="active", index=True) is_deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, index=True) deleted_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) deleted_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True) uploaded_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True) uploaded_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False) case = relationship("NoticeCase", back_populates="documents") event = relationship("NoticeCaseEvent") uploaded_by = relationship("User", foreign_keys=[uploaded_by_user_id])