From 14e78478da2f9451a9dc18c59822f0a4746a17e4 Mon Sep 17 00:00:00 2001 From: Leopoldadmin Date: Thu, 4 Jun 2026 00:06:18 +0000 Subject: [PATCH] Upload app/models/deal.py --- app/models/deal.py | 94 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 app/models/deal.py diff --git a/app/models/deal.py b/app/models/deal.py new file mode 100644 index 0000000..dd5ae4d --- /dev/null +++ b/app/models/deal.py @@ -0,0 +1,94 @@ +"""Deal model: sales opportunities tied to an account.""" + +from __future__ import annotations + +from datetime import date +from decimal import Decimal +from enum import Enum +from typing import TYPE_CHECKING, Optional + +from sqlalchemy import Date, ForeignKey, Numeric, String +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from app.models.base import Base, OrgScopedMixin, SoftDeleteMixin, TimestampMixin + +if TYPE_CHECKING: + from app.models.user import User + from app.models.account import Account + from app.models.activity import Activity + from app.models.note import Note + from app.models.tag_link import TagLink + from app.models.deal_stage_history import DealStageHistory + + +class DealStage(str, Enum): + """Sales pipeline stage for a deal.""" + + lead = "lead" + qualified = "qualified" + proposal = "proposal" + negotiation = "negotiation" + won = "won" + lost = "lost" + + +class Deal(Base, TimestampMixin, SoftDeleteMixin, OrgScopedMixin): + __tablename__ = "deals" + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + title: Mapped[str] = mapped_column(String(255), nullable=False) + value: Mapped[Decimal] = mapped_column(Numeric(12, 2), nullable=False, default=Decimal("0")) + currency: Mapped[str] = mapped_column( + String(3), nullable=False, default="EUR", server_default="EUR" + ) + stage: Mapped[DealStage] = mapped_column( + String(32), + nullable=False, + default=DealStage.lead, + server_default=DealStage.lead.value, + index=True, + ) + close_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True) + account_id: Mapped[int] = mapped_column( + ForeignKey("accounts.id"), nullable=False, index=True + ) + owner_id: Mapped[int] = mapped_column( + ForeignKey("users.id"), nullable=False, index=True + ) + won_lost_reason: Mapped[Optional[str]] = mapped_column(String(512), nullable=True) + + # Relationships + account: Mapped["Account"] = relationship( + "Account", back_populates="deals", lazy="selectin" + ) + owner: Mapped["User"] = relationship( + "User", foreign_keys=[owner_id], lazy="joined" + ) + stage_history: Mapped[list["DealStageHistory"]] = relationship( + "DealStageHistory", + back_populates="deal", + cascade="all, delete-orphan", + lazy="selectin", + order_by="DealStageHistory.created_at", + ) + activities: Mapped[list["Activity"]] = relationship( + "Activity", back_populates="deal", lazy="selectin" + ) + notes: Mapped[list["Note"]] = relationship( + "Note", + primaryjoin="and_(Deal.id==foreign(Note.parent_id), Note.parent_type=='deal')", + viewonly=True, + lazy="selectin", + ) + tags: Mapped[list["TagLink"]] = relationship( + "TagLink", + primaryjoin="and_(Deal.id==foreign(TagLink.parent_id), TagLink.parent_type=='deal')", + viewonly=True, + lazy="selectin", + ) + + def __repr__(self) -> str: + return f"" + + +__all__ = ["Deal", "DealStage"]