feat: deal status history and default created_at in db

This commit is contained in:
2025-08-26 18:12:28 +04:00
parent b776ad6758
commit 4c7a997be6
7 changed files with 54 additions and 18 deletions

View File

@ -4,6 +4,6 @@ from .base import BaseModel as BaseModel
from .board import Board as Board from .board import Board as Board
from .deal import Deal as Deal from .deal import Deal as Deal
from .project import Project as Project from .project import Project as Project
from .status import Status as Status from .status import Status as Status, CardStatusHistory as CardStatusHistory
configure_mappers() configure_mappers()

View File

@ -7,7 +7,7 @@ from models.base import BaseModel
from models.mixins import SoftDeleteMixin, CreatedAtMixin, IdMixin from models.mixins import SoftDeleteMixin, CreatedAtMixin, IdMixin
if TYPE_CHECKING: if TYPE_CHECKING:
from models import Status, Board from models import Status, Board, CardStatusHistory
class Deal(BaseModel, IdMixin, SoftDeleteMixin, CreatedAtMixin): class Deal(BaseModel, IdMixin, SoftDeleteMixin, CreatedAtMixin):
@ -27,3 +27,9 @@ class Deal(BaseModel, IdMixin, SoftDeleteMixin, CreatedAtMixin):
ForeignKey("boards.id"), nullable=True, server_default="1" ForeignKey("boards.id"), nullable=True, server_default="1"
) )
board: Mapped["Board"] = relationship(back_populates="deals") board: Mapped["Board"] = relationship(back_populates="deals")
status_history: Mapped[list["CardStatusHistory"]] = relationship(
back_populates="deal",
cascade="all, delete-orphan",
lazy="noload",
)

View File

@ -1,4 +1,4 @@
from datetime import datetime from datetime import datetime, timezone
from sqlalchemy import DateTime from sqlalchemy import DateTime
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
@ -17,5 +17,7 @@ class SoftDeleteMixin:
class CreatedAtMixin: class CreatedAtMixin:
created_at: Mapped[datetime] = mapped_column( created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
nullable=False,
) )

View File

@ -4,10 +4,10 @@ from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from models.base import BaseModel from models.base import BaseModel
from models.mixins import SoftDeleteMixin, IdMixin from models.mixins import SoftDeleteMixin, IdMixin, CreatedAtMixin
if TYPE_CHECKING: if TYPE_CHECKING:
from models import Board from models import Board, Deal
class Status(BaseModel, IdMixin, SoftDeleteMixin): class Status(BaseModel, IdMixin, SoftDeleteMixin):
@ -18,3 +18,30 @@ class Status(BaseModel, IdMixin, SoftDeleteMixin):
board_id: Mapped[int] = mapped_column(ForeignKey("boards.id"), nullable=False) board_id: Mapped[int] = mapped_column(ForeignKey("boards.id"), nullable=False)
board: Mapped["Board"] = relationship(back_populates="statuses") board: Mapped["Board"] = relationship(back_populates="statuses")
class CardStatusHistory(BaseModel, IdMixin, CreatedAtMixin):
__tablename__ = "status_history"
deal_id: Mapped[int] = mapped_column(ForeignKey("deals.id"), nullable=False)
deal: Mapped["Deal"] = relationship(back_populates="status_history")
from_status_id: Mapped[int] = mapped_column(
ForeignKey("statuses.id"),
nullable=False,
comment="Старый статус",
)
from_status: Mapped[Status] = relationship(
foreign_keys=[from_status_id],
lazy="joined",
)
to_status_id: Mapped[int] = mapped_column(
ForeignKey("statuses.id"),
nullable=False,
comment="Новый статус",
)
to_status: Mapped[Status] = relationship(
foreign_keys=[to_status_id],
lazy="joined",
)

View File

@ -29,9 +29,7 @@ class BoardRepository(BaseRepository):
return result.scalar_one_or_none() return result.scalar_one_or_none()
async def create(self, data: CreateBoardSchema) -> Board: async def create(self, data: CreateBoardSchema) -> Board:
board_data = data.model_dump() board = Board(**data.model_dump())
board_data["created_at"] = datetime.now(timezone.utc)
board = Board(**board_data)
self.session.add(board) self.session.add(board)
await self.session.commit() await self.session.commit()
await self.session.refresh(board) await self.session.refresh(board)

View File

@ -1,9 +1,8 @@
from datetime import datetime, timezone
from typing import Optional from typing import Optional
from sqlalchemy import select from sqlalchemy import select
from models import Deal from models import Deal, CardStatusHistory
from repositories.base import BaseRepository from repositories.base import BaseRepository
from schemas.deal import UpdateDealSchema, CreateDealSchema from schemas.deal import UpdateDealSchema, CreateDealSchema
@ -24,9 +23,7 @@ class DealRepository(BaseRepository):
return result.scalar_one_or_none() return result.scalar_one_or_none()
async def create(self, data: CreateDealSchema) -> Deal: async def create(self, data: CreateDealSchema) -> Deal:
deal_data = data.model_dump() deal = Deal(**data.model_dump())
deal_data["created_at"] = datetime.now(timezone.utc)
deal = Deal(**deal_data)
self.session.add(deal) self.session.add(deal)
await self.session.commit() await self.session.commit()
await self.session.refresh(deal) await self.session.refresh(deal)
@ -35,7 +32,15 @@ class DealRepository(BaseRepository):
async def update(self, deal: Deal, data: UpdateDealSchema) -> Deal: async def update(self, deal: Deal, data: UpdateDealSchema) -> Deal:
deal.lexorank = data.lexorank if data.lexorank else deal.lexorank deal.lexorank = data.lexorank if data.lexorank else deal.lexorank
deal.name = data.name if data.name else deal.name deal.name = data.name if data.name else deal.name
deal.status_id = data.status_id if data.status_id else deal.status_id
if data.status_id and deal.status_id != data.status_id:
deal.status_history.append(
CardStatusHistory(
from_status_id=deal.status_id,
to_status_id=data.status_id,
)
)
deal.status_id = data.status_id
self.session.add(deal) self.session.add(deal)
await self.session.commit() await self.session.commit()

View File

@ -25,9 +25,7 @@ class ProjectRepository(BaseRepository):
return result.scalar_one_or_none() return result.scalar_one_or_none()
async def create(self, data: CreateProjectSchema) -> Project: async def create(self, data: CreateProjectSchema) -> Project:
project_data = data.model_dump() project = Project(**data.model_dump())
project_data["created_at"] = datetime.now(timezone.utc)
project = Project(**project_data)
self.session.add(project) self.session.add(project)
await self.session.commit() await self.session.commit()
await self.session.refresh(project) await self.session.refresh(project)