закупы в бэк
This commit is contained in:
@@ -304,8 +304,12 @@ class Postgres(DatabaseBase, Database):
|
||||
invite_link_type: domain.purchase.InviteLinkType,
|
||||
*,
|
||||
status: domain.PurchaseChannelStatus | None = None,
|
||||
planned_cost: float | None = None,
|
||||
comment: str | None = None,
|
||||
placement_at: datetime.datetime | None = None,
|
||||
cost_type: domain.purchase.CostType | None = None,
|
||||
cost_value: float | None = None,
|
||||
cost_before_bargain: float | None = None,
|
||||
format: str | None = None,
|
||||
) -> domain.PurchaseChannel:
|
||||
defaults: dict[str, object | None] = {
|
||||
'invite_link': invite_link,
|
||||
@@ -313,10 +317,18 @@ class Postgres(DatabaseBase, Database):
|
||||
}
|
||||
if status is not None:
|
||||
defaults['status'] = status
|
||||
if planned_cost is not None:
|
||||
defaults['planned_cost'] = planned_cost
|
||||
if comment is not None:
|
||||
defaults['comment'] = comment
|
||||
if placement_at is not None:
|
||||
defaults['placement_at'] = placement_at
|
||||
if cost_type is not None:
|
||||
defaults['cost_type'] = cost_type
|
||||
if cost_value is not None:
|
||||
defaults['cost_value'] = cost_value
|
||||
if cost_before_bargain is not None:
|
||||
defaults['cost_before_bargain'] = cost_before_bargain
|
||||
if format is not None:
|
||||
defaults['format'] = format
|
||||
|
||||
purchase_channel, created = await domain.PurchaseChannel.get_or_create(
|
||||
purchase_id=purchase_id,
|
||||
|
||||
@@ -13,12 +13,17 @@ if TYPE_CHECKING:
|
||||
from .workspace import Workspace
|
||||
|
||||
|
||||
class PurchaseStatus(str, enum.Enum):
|
||||
class PurchaseStatus(enum.StrEnum):
|
||||
ACTIVE = 'active'
|
||||
ARCHIVED = 'archived'
|
||||
|
||||
|
||||
class PurchaseChannelStatus(str, enum.Enum):
|
||||
class CostType(enum.StrEnum):
|
||||
FIXED = 'fixed'
|
||||
CPM = 'cpm'
|
||||
|
||||
|
||||
class PurchaseChannelStatus(enum.StrEnum):
|
||||
PLANNED = 'planned'
|
||||
APPROVED = 'approved'
|
||||
REJECTED = 'rejected'
|
||||
@@ -26,8 +31,21 @@ class PurchaseChannelStatus(str, enum.Enum):
|
||||
COMPLETED = 'completed'
|
||||
|
||||
|
||||
class PurchaseType(enum.StrEnum):
|
||||
SELF_PROMO = 'self_promo'
|
||||
STANDARD = 'standard'
|
||||
|
||||
|
||||
class Purchase(TimestampedModel):
|
||||
status = fields.CharEnumField(PurchaseStatus, default=PurchaseStatus.ACTIVE)
|
||||
placement_at = fields.DatetimeField(null=True)
|
||||
payment_at = fields.DatetimeField(null=True)
|
||||
cost_type = fields.CharEnumField(CostType, null=True, max_length=8)
|
||||
cost_value = fields.FloatField(null=True)
|
||||
cost_before_bargain = fields.FloatField(null=True)
|
||||
purchase_type = fields.CharEnumField(PurchaseType, null=True, max_length=16)
|
||||
format = fields.TextField(null=True)
|
||||
comment = fields.TextField(null=True)
|
||||
|
||||
project: fields.ForeignKeyRelation['Project'] = fields.ForeignKeyField(
|
||||
'models.Project', related_name='purchases', on_delete=fields.CASCADE, index=True
|
||||
@@ -45,14 +63,18 @@ class Purchase(TimestampedModel):
|
||||
indexes = (('project', 'status'),)
|
||||
|
||||
|
||||
class InviteLinkType(str, enum.Enum):
|
||||
class InviteLinkType(enum.StrEnum):
|
||||
PUBLIC = 'public' # открытая ссылка
|
||||
APPROVAL = 'approval' # с одобрением ботом
|
||||
|
||||
|
||||
class PurchaseChannel(TimestampedModel):
|
||||
status = fields.CharEnumField(PurchaseChannelStatus, default=PurchaseChannelStatus.PLANNED)
|
||||
planned_cost = fields.FloatField(null=True)
|
||||
placement_at = fields.DatetimeField(null=True)
|
||||
cost_type = fields.CharEnumField(CostType, null=True, max_length=8)
|
||||
cost_value = fields.FloatField(null=True)
|
||||
cost_before_bargain = fields.FloatField(null=True)
|
||||
format = fields.TextField(null=True)
|
||||
comment = fields.TextField(null=True)
|
||||
|
||||
invite_link = fields.CharField(max_length=512)
|
||||
|
||||
@@ -17,6 +17,9 @@ __all__ = (
|
||||
'AttachChannelToWorkspaceInput',
|
||||
'PurchaseOutput',
|
||||
'PurchaseChannelOutput',
|
||||
'PurchaseDetails',
|
||||
'PurchaseChannelDetails',
|
||||
'CostInfo',
|
||||
'CreatePurchaseInput',
|
||||
'CreatePurchaseChannelInput',
|
||||
'GetPurchasesInput',
|
||||
@@ -117,12 +120,15 @@ from .project import (
|
||||
UpdateProjectPermissionsInput,
|
||||
)
|
||||
from .purchase import (
|
||||
CostInfo,
|
||||
CreatePurchaseChannelInput,
|
||||
CreatePurchaseInput,
|
||||
GetPurchaseInput,
|
||||
GetPurchasesInput,
|
||||
GetPurchasesOutput,
|
||||
PurchaseChannelDetails,
|
||||
PurchaseChannelOutput,
|
||||
PurchaseDetails,
|
||||
PurchaseOutput,
|
||||
)
|
||||
from .user import UserOutput
|
||||
|
||||
@@ -1,20 +1,43 @@
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
import pydantic
|
||||
|
||||
from src.domain.purchase import InviteLinkType, PurchaseChannelStatus, PurchaseStatus
|
||||
from src.domain.purchase import CostType, InviteLinkType, PurchaseChannelStatus, PurchaseStatus, PurchaseType
|
||||
|
||||
from .channel import ChannelOutput
|
||||
|
||||
|
||||
class CostInfo(pydantic.BaseModel):
|
||||
type: CostType
|
||||
value: float
|
||||
|
||||
|
||||
class PurchaseDetails(pydantic.BaseModel):
|
||||
placement_at: datetime.datetime | None = None
|
||||
payment_at: datetime.datetime | None = None
|
||||
cost: CostInfo | None = None
|
||||
cost_before_bargain: float | None = None
|
||||
purchase_type: PurchaseType | None = None
|
||||
format: str | None = None
|
||||
comment: str | None = None
|
||||
|
||||
|
||||
class PurchaseChannelDetails(pydantic.BaseModel):
|
||||
placement_at: datetime.datetime | None = None
|
||||
cost: CostInfo | None = None
|
||||
cost_before_bargain: float | None = None
|
||||
format: str | None = None
|
||||
|
||||
|
||||
class PurchaseChannelOutput(pydantic.BaseModel):
|
||||
id: uuid.UUID
|
||||
status: PurchaseChannelStatus
|
||||
planned_cost: float | None = None
|
||||
comment: str | None = None
|
||||
invite_link: str
|
||||
invite_link_type: InviteLinkType
|
||||
channel: ChannelOutput
|
||||
details: PurchaseChannelDetails | None = None
|
||||
|
||||
|
||||
class PurchaseOutput(pydantic.BaseModel):
|
||||
@@ -22,18 +45,20 @@ class PurchaseOutput(pydantic.BaseModel):
|
||||
status: PurchaseStatus
|
||||
creative_id: uuid.UUID
|
||||
channels: list[PurchaseChannelOutput]
|
||||
details: PurchaseDetails | None = None
|
||||
|
||||
|
||||
class CreatePurchaseChannelInput(pydantic.BaseModel):
|
||||
username: str = pydantic.Field(pattern=r'^[^@]')
|
||||
status: PurchaseChannelStatus | None = None
|
||||
planned_cost: float | None = None
|
||||
comment: str | None = None
|
||||
details: PurchaseChannelDetails | None = None
|
||||
|
||||
|
||||
class CreatePurchaseInput(pydantic.BaseModel):
|
||||
creative_id: uuid.UUID
|
||||
channels: list[CreatePurchaseChannelInput]
|
||||
details: PurchaseDetails | None = None
|
||||
|
||||
|
||||
class GetPurchasesInput(pydantic.BaseModel):
|
||||
|
||||
@@ -174,8 +174,12 @@ class Database(typing.Protocol):
|
||||
invite_link_type: domain.purchase.InviteLinkType,
|
||||
*,
|
||||
status: domain.PurchaseChannelStatus | None = None,
|
||||
planned_cost: float | None = None,
|
||||
comment: str | None = None,
|
||||
placement_at: datetime.datetime | None = None,
|
||||
cost_type: domain.purchase.CostType | None = None,
|
||||
cost_value: float | None = None,
|
||||
cost_before_bargain: float | None = None,
|
||||
format: str | None = None,
|
||||
) -> domain.PurchaseChannel: ...
|
||||
|
||||
async def update_purchase_channel(self, purchase_channel: domain.PurchaseChannel) -> None: ...
|
||||
|
||||
@@ -11,6 +11,39 @@ if TYPE_CHECKING:
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _build_cost_info(cost_type: domain.purchase.CostType | None, cost_value: float | None) -> dto.CostInfo | None:
|
||||
if cost_type is None or cost_value is None:
|
||||
return None
|
||||
return dto.CostInfo(type=cost_type, value=cost_value)
|
||||
|
||||
|
||||
def _build_purchase_details(purchase: domain.Purchase) -> dto.PurchaseDetails | None:
|
||||
details = dto.PurchaseDetails(
|
||||
placement_at=purchase.placement_at,
|
||||
payment_at=purchase.payment_at,
|
||||
cost=_build_cost_info(purchase.cost_type, purchase.cost_value),
|
||||
cost_before_bargain=purchase.cost_before_bargain,
|
||||
purchase_type=purchase.purchase_type,
|
||||
format=purchase.format,
|
||||
comment=purchase.comment,
|
||||
)
|
||||
if details.model_dump(exclude_none=True):
|
||||
return details
|
||||
return None
|
||||
|
||||
|
||||
def _build_purchase_channel_details(purchase_channel: domain.PurchaseChannel) -> dto.PurchaseChannelDetails | None:
|
||||
details = dto.PurchaseChannelDetails(
|
||||
placement_at=purchase_channel.placement_at,
|
||||
cost=_build_cost_info(purchase_channel.cost_type, purchase_channel.cost_value),
|
||||
cost_before_bargain=purchase_channel.cost_before_bargain,
|
||||
format=purchase_channel.format,
|
||||
)
|
||||
if details.model_dump(exclude_none=True):
|
||||
return details
|
||||
return None
|
||||
|
||||
|
||||
def _build_purchase_output(
|
||||
purchase: domain.Purchase, purchase_channels: list[domain.PurchaseChannel]
|
||||
) -> dto.PurchaseOutput:
|
||||
@@ -24,7 +57,6 @@ def _build_purchase_output(
|
||||
dto.PurchaseChannelOutput(
|
||||
id=purchase_channel.id,
|
||||
status=purchase_channel.status,
|
||||
planned_cost=purchase_channel.planned_cost,
|
||||
comment=purchase_channel.comment,
|
||||
invite_link=purchase_channel.invite_link,
|
||||
invite_link_type=purchase_channel.invite_link_type,
|
||||
@@ -34,6 +66,7 @@ def _build_purchase_output(
|
||||
title=channel.title,
|
||||
username=channel.username,
|
||||
),
|
||||
details=_build_purchase_channel_details(purchase_channel),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -42,6 +75,7 @@ def _build_purchase_output(
|
||||
status=purchase.status,
|
||||
creative_id=purchase.creative_id,
|
||||
channels=channels_output,
|
||||
details=_build_purchase_details(purchase),
|
||||
)
|
||||
|
||||
|
||||
@@ -69,7 +103,20 @@ async def create_purchase(
|
||||
channels = await self.database.get_purchase_channels(existing_purchase.id)
|
||||
return _build_purchase_output(existing_purchase, channels)
|
||||
|
||||
purchase = domain.Purchase(workspace_id=workspace_id, project_id=project.id, creative_id=creative.id)
|
||||
details = input.details
|
||||
purchase = domain.Purchase(
|
||||
workspace_id=workspace_id,
|
||||
project_id=project.id,
|
||||
creative_id=creative.id,
|
||||
placement_at=details.placement_at if details else None,
|
||||
payment_at=details.payment_at if details else None,
|
||||
cost_type=details.cost.type if details and details.cost else None,
|
||||
cost_value=details.cost.value if details and details.cost else None,
|
||||
cost_before_bargain=details.cost_before_bargain if details else None,
|
||||
purchase_type=details.purchase_type if details else None,
|
||||
format=details.format if details else None,
|
||||
comment=details.comment if details else None,
|
||||
)
|
||||
await self.database.create_purchase(purchase)
|
||||
|
||||
if project.channel.telegram_id is None:
|
||||
@@ -94,14 +141,19 @@ async def create_purchase(
|
||||
|
||||
invite_link = await self.telegram_bot.create_chat_invite_link(project.channel.telegram_id, requires_approval)
|
||||
|
||||
channel_details = channel_input.details
|
||||
purchase_channel = await self.database.add_channel_to_purchase(
|
||||
purchase.id,
|
||||
channel.id,
|
||||
invite_link=invite_link,
|
||||
invite_link_type=invite_link_type,
|
||||
status=channel_input.status,
|
||||
planned_cost=channel_input.planned_cost,
|
||||
comment=channel_input.comment,
|
||||
placement_at=channel_details.placement_at if channel_details else None,
|
||||
cost_type=channel_details.cost.type if channel_details and channel_details.cost else None,
|
||||
cost_value=channel_details.cost.value if channel_details and channel_details.cost else None,
|
||||
cost_before_bargain=channel_details.cost_before_bargain if channel_details else None,
|
||||
format=channel_details.format if channel_details else None,
|
||||
)
|
||||
purchase_channel.channel = channel
|
||||
purchase_channels.append(purchase_channel)
|
||||
|
||||
Reference in New Issue
Block a user