feat: purchases added
This commit is contained in:
@@ -4,8 +4,11 @@ __all__ = (
|
||||
'TargetChannel',
|
||||
'ExternalChannel',
|
||||
'Creative',
|
||||
'Purchase',
|
||||
'TargetChannelStatus',
|
||||
'CreativeStatus',
|
||||
'PurchaseStatus',
|
||||
'InviteLinkType',
|
||||
'LoginToken',
|
||||
'UserNotFound',
|
||||
'LoginTokenNotFound',
|
||||
@@ -18,6 +21,7 @@ __all__ = (
|
||||
'ExternalChannelAlreadyExists',
|
||||
'CreativeNotFound',
|
||||
'CreativeInUse',
|
||||
'PurchaseNotFound',
|
||||
)
|
||||
|
||||
from .base import Base
|
||||
@@ -32,10 +36,12 @@ from .error import (
|
||||
LoginTokenAlreadyUsed,
|
||||
LoginTokenExpired,
|
||||
LoginTokenNotFound,
|
||||
PurchaseNotFound,
|
||||
TargetChannelNotFound,
|
||||
UserNotFound,
|
||||
)
|
||||
from .external_channel import ExternalChannel
|
||||
from .login_token import LoginToken
|
||||
from .purchase import InviteLinkType, Purchase, PurchaseStatus
|
||||
from .target_channel import TargetChannel, TargetChannelStatus
|
||||
from .user import User
|
||||
|
||||
@@ -58,3 +58,9 @@ def CreativeInUse(creative_id: uuid.UUID) -> HTTPException:
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
f'Creative {creative_id} is used in active purchases and cannot be deleted',
|
||||
)
|
||||
|
||||
|
||||
def PurchaseNotFound(purchase_id: uuid.UUID | None = None) -> HTTPException:
|
||||
if purchase_id is None:
|
||||
return HTTPException(status.HTTP_404_NOT_FOUND, 'Purchase not found')
|
||||
return HTTPException(status.HTTP_404_NOT_FOUND, f'Purchase {purchase_id} not found')
|
||||
|
||||
52
src/domain/purchase.py
Normal file
52
src/domain/purchase.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import datetime
|
||||
import uuid
|
||||
from enum import StrEnum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from src import domain
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .creative import Creative
|
||||
from .external_channel import ExternalChannel
|
||||
from .target_channel import TargetChannel
|
||||
|
||||
|
||||
class PurchaseStatus(StrEnum):
|
||||
ACTIVE = 'active'
|
||||
ARCHIVED = 'archived'
|
||||
|
||||
|
||||
class InviteLinkType(StrEnum):
|
||||
PUBLIC = 'public' # открытая ссылка
|
||||
APPROVAL = 'approval' # с одобрением ботом
|
||||
|
||||
|
||||
class Purchase(domain.Base):
|
||||
# Основные поля
|
||||
target_channel_id: Mapped[uuid.UUID] = mapped_column(ForeignKey('targetchannels.id'), index=True)
|
||||
external_channel_id: Mapped[uuid.UUID] = mapped_column(ForeignKey('externalchannels.id'), index=True)
|
||||
creative_id: Mapped[uuid.UUID] = mapped_column(ForeignKey('creatives.id'), index=True)
|
||||
user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey('users.id'), index=True)
|
||||
|
||||
# Данные закупа
|
||||
placement_date: Mapped[datetime.datetime] # дата размещения (планируемая или фактическая)
|
||||
cost: Mapped[float | None] # стоимость закупа
|
||||
comment: Mapped[str | None] # комментарий
|
||||
ad_post_url: Mapped[str | None] # ссылка на рекламное сообщение во внешнем канале
|
||||
invite_link_type: Mapped[InviteLinkType] # тип пригласительной ссылки
|
||||
invite_link: Mapped[str] # уникальная пригласительная ссылка
|
||||
|
||||
# Статус
|
||||
status: Mapped[PurchaseStatus] = mapped_column(default=PurchaseStatus.ACTIVE)
|
||||
|
||||
# Метрики (будут обновляться из Telegram API)
|
||||
subscriptions_count: Mapped[int] = mapped_column(default=0, server_default='0')
|
||||
views_count: Mapped[int | None] # просмотры рекламного поста (если доступны)
|
||||
|
||||
# Relationships
|
||||
target_channel: Mapped['TargetChannel'] = relationship()
|
||||
external_channel: Mapped['ExternalChannel'] = relationship()
|
||||
creative: Mapped['Creative'] = relationship()
|
||||
Reference in New Issue
Block a user