закупы в бэк
This commit is contained in:
149
migrations/models/4_20260106192017_update.py
Normal file
149
migrations/models/4_20260106192017_update.py
Normal file
@@ -0,0 +1,149 @@
|
||||
from tortoise import BaseDBAsyncClient
|
||||
|
||||
RUN_IN_TRANSACTION = True
|
||||
|
||||
|
||||
async def upgrade(db: BaseDBAsyncClient) -> str:
|
||||
return """
|
||||
ALTER TABLE "purchase"
|
||||
ADD "comment" TEXT;
|
||||
ALTER TABLE "purchase"
|
||||
ADD "payment_at" TIMESTAMPTZ;
|
||||
ALTER TABLE "purchase"
|
||||
ADD "format" TEXT;
|
||||
ALTER TABLE "purchase"
|
||||
ADD "cost_before_bargain" DOUBLE PRECISION;
|
||||
ALTER TABLE "purchase"
|
||||
ADD "placement_at" TIMESTAMPTZ;
|
||||
ALTER TABLE "purchase"
|
||||
ADD "cost_type" VARCHAR(8);
|
||||
ALTER TABLE "purchase"
|
||||
ADD "cost_value" DOUBLE PRECISION;
|
||||
ALTER TABLE "purchase"
|
||||
ADD "purchase_type" VARCHAR(16);
|
||||
ALTER TABLE "purchase_channel"
|
||||
ADD "format" TEXT;
|
||||
ALTER TABLE "purchase_channel"
|
||||
ADD "cost_before_bargain" DOUBLE PRECISION;
|
||||
ALTER TABLE "purchase_channel"
|
||||
ADD "placement_at" TIMESTAMPTZ;
|
||||
ALTER TABLE "purchase_channel"
|
||||
ADD "cost_type" VARCHAR(8);
|
||||
ALTER TABLE "purchase_channel"
|
||||
ADD "cost_value" DOUBLE PRECISION;
|
||||
ALTER TABLE "purchase_channel"
|
||||
DROP COLUMN "planned_cost";
|
||||
COMMENT ON COLUMN "purchase"."cost_type" IS 'FIXED: fixed\nCPM: cpm';
|
||||
COMMENT ON COLUMN "purchase"."purchase_type" IS 'SELF_PROMO: self_promo\nSTANDARD: standard';
|
||||
COMMENT ON COLUMN "purchase_channel"."cost_type" IS 'FIXED: fixed\nCPM: cpm';"""
|
||||
|
||||
|
||||
async def downgrade(db: BaseDBAsyncClient) -> str:
|
||||
return """
|
||||
ALTER TABLE "purchase"
|
||||
DROP COLUMN "comment";
|
||||
ALTER TABLE "purchase"
|
||||
DROP COLUMN "payment_at";
|
||||
ALTER TABLE "purchase"
|
||||
DROP COLUMN "format";
|
||||
ALTER TABLE "purchase"
|
||||
DROP COLUMN "cost_before_bargain";
|
||||
ALTER TABLE "purchase"
|
||||
DROP COLUMN "placement_at";
|
||||
ALTER TABLE "purchase"
|
||||
DROP COLUMN "cost_type";
|
||||
ALTER TABLE "purchase"
|
||||
DROP COLUMN "cost_value";
|
||||
ALTER TABLE "purchase"
|
||||
DROP COLUMN "purchase_type";
|
||||
ALTER TABLE "purchase_channel"
|
||||
ADD "planned_cost" DOUBLE PRECISION;
|
||||
ALTER TABLE "purchase_channel"
|
||||
DROP COLUMN "format";
|
||||
ALTER TABLE "purchase_channel"
|
||||
DROP COLUMN "cost_before_bargain";
|
||||
ALTER TABLE "purchase_channel"
|
||||
DROP COLUMN "placement_at";
|
||||
ALTER TABLE "purchase_channel"
|
||||
DROP COLUMN "cost_type";
|
||||
ALTER TABLE "purchase_channel"
|
||||
DROP COLUMN "cost_value";"""
|
||||
|
||||
|
||||
MODELS_STATE = (
|
||||
'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'
|
||||
'49ruDVq8qUI6aKVL2pn/5+X3/wGuY8FA'
|
||||
)
|
||||
@@ -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)
|
||||
|
||||
@@ -632,28 +632,52 @@ type Purchase struct {
|
||||
Status string `json:"status"`
|
||||
CreativeID string `json:"creative_id"`
|
||||
Channels []PurchaseChannelOut `json:"channels"`
|
||||
Details *PurchaseDetails `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
type PurchaseChannelOut struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
PlannedCost *float64 `json:"planned_cost"`
|
||||
Comment *string `json:"comment"`
|
||||
InviteLink string `json:"invite_link"`
|
||||
InviteLinkType string `json:"invite_link_type"`
|
||||
Channel Channel `json:"channel"`
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
Comment *string `json:"comment"`
|
||||
InviteLink string `json:"invite_link"`
|
||||
InviteLinkType string `json:"invite_link_type"`
|
||||
Channel Channel `json:"channel"`
|
||||
Details *PurchaseChannelDetails `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
type CostInfo struct {
|
||||
Type string `json:"type"`
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
|
||||
type PurchaseDetails struct {
|
||||
PlacementAt *string `json:"placement_at,omitempty"`
|
||||
PaymentAt *string `json:"payment_at,omitempty"`
|
||||
Cost *CostInfo `json:"cost,omitempty"`
|
||||
CostBeforeBargain *float64 `json:"cost_before_bargain,omitempty"`
|
||||
PurchaseType *string `json:"purchase_type,omitempty"`
|
||||
Format *string `json:"format,omitempty"`
|
||||
Comment *string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type PurchaseChannelDetails struct {
|
||||
PlacementAt *string `json:"placement_at,omitempty"`
|
||||
Cost *CostInfo `json:"cost,omitempty"`
|
||||
CostBeforeBargain *float64 `json:"cost_before_bargain,omitempty"`
|
||||
Format *string `json:"format,omitempty"`
|
||||
}
|
||||
|
||||
type CreatePurchaseChannelInput struct {
|
||||
Username string `json:"username"`
|
||||
Status *string `json:"status,omitempty"`
|
||||
PlannedCost *float64 `json:"planned_cost,omitempty"`
|
||||
Comment *string `json:"comment,omitempty"`
|
||||
Username string `json:"username"`
|
||||
Status *string `json:"status,omitempty"`
|
||||
Comment *string `json:"comment,omitempty"`
|
||||
Details *PurchaseChannelDetails `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
type CreatePurchaseInput struct {
|
||||
CreativeID string `json:"creative_id"`
|
||||
Channels []CreatePurchaseChannelInput `json:"channels"`
|
||||
Details *PurchaseDetails `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) CreatePurchase(
|
||||
|
||||
@@ -105,8 +105,8 @@ func (s *PurchaseDetails) renderDetails(b *bot.Bot, mode bot.RenderMode, jwt str
|
||||
text += fmt.Sprintf("%d. %s <b>%s</b>\n", i+1, statusSymbol, channelName)
|
||||
|
||||
// Стоимость
|
||||
if ch.PlannedCost != nil {
|
||||
text += fmt.Sprintf(" ▸ Стоимость: %.0f₽\n", *ch.PlannedCost)
|
||||
if ch.Details != nil && ch.Details.Cost != nil {
|
||||
text += fmt.Sprintf(" ▸ Стоимость: %.0f₽\n", ch.Details.Cost.Value)
|
||||
}
|
||||
|
||||
// Комментарий
|
||||
|
||||
@@ -221,10 +221,20 @@ func (s *SelectChannelsForPurchase) createPurchase(b *bot.Bot, jwt string) {
|
||||
// Преобразуем каналы в формат API
|
||||
var apiChannels []backend.CreatePurchaseChannelInput
|
||||
for _, ch := range s.Channels {
|
||||
var details *backend.PurchaseChannelDetails
|
||||
if ch.PlannedCost != nil {
|
||||
cost := backend.CostInfo{
|
||||
Type: "fixed",
|
||||
Value: *ch.PlannedCost,
|
||||
}
|
||||
details = &backend.PurchaseChannelDetails{
|
||||
Cost: &cost,
|
||||
}
|
||||
}
|
||||
apiChannels = append(apiChannels, backend.CreatePurchaseChannelInput{
|
||||
Username: ch.Username,
|
||||
PlannedCost: ch.PlannedCost,
|
||||
Comment: ch.Comment,
|
||||
Username: ch.Username,
|
||||
Comment: ch.Comment,
|
||||
Details: details,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user