правки
This commit is contained in:
@@ -397,6 +397,17 @@ class Postgres(DatabaseBase):
|
||||
'project', 'project__channel', 'channel', 'creative'
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def count_placements_by_project_and_channel(
|
||||
project_id: uuid.UUID,
|
||||
channel_id: uuid.UUID,
|
||||
) -> int:
|
||||
"""Count placements for a project in a specific channel."""
|
||||
return await domain.Placement.filter(
|
||||
project_id=project_id,
|
||||
channel_id=channel_id,
|
||||
).count()
|
||||
|
||||
@staticmethod
|
||||
async def update_placement(placement: domain.Placement) -> None:
|
||||
await placement.save()
|
||||
|
||||
@@ -9,6 +9,12 @@ from src.domain.placement_post import PlacementPostStatus
|
||||
from .channel import ChannelOutput
|
||||
|
||||
|
||||
class ProjectOutput(pydantic.BaseModel):
|
||||
id: uuid.UUID
|
||||
status: str
|
||||
channel: ChannelOutput
|
||||
|
||||
|
||||
class CostInfo(pydantic.BaseModel):
|
||||
type: CostType
|
||||
value: float
|
||||
@@ -30,11 +36,14 @@ class PlacementOutput(pydantic.BaseModel):
|
||||
id: uuid.UUID
|
||||
status: PlacementStatus
|
||||
creative_id: uuid.UUID | None = None
|
||||
creative_name: str | None = None
|
||||
comment: str | None = None
|
||||
invite_link: str | None
|
||||
invite_link_created_at: datetime.datetime | None = None
|
||||
invite_link_type: InviteLinkType
|
||||
channel: ChannelOutput
|
||||
project: ProjectOutput | None = None
|
||||
placement_number: int
|
||||
details: PlacementDetails | None = None
|
||||
|
||||
|
||||
|
||||
@@ -31,16 +31,41 @@ def _build_placement_details(placement: domain.Placement) -> dto.PlacementDetail
|
||||
return None
|
||||
|
||||
|
||||
def _build_placement_output(placement: domain.Placement) -> dto.PlacementOutput:
|
||||
def _build_placement_output(
|
||||
placement: domain.Placement,
|
||||
project: domain.Project | None = None,
|
||||
placement_number: int = 0,
|
||||
creative_name: str | None = None,
|
||||
) -> dto.PlacementOutput:
|
||||
channel = placement.channel
|
||||
if channel is None:
|
||||
log.error('Placement %s has no channel prefetched', placement.id)
|
||||
raise ValueError(f'Placement {placement.id} has no channel')
|
||||
|
||||
# Build project output if project is provided
|
||||
project_output: dto.ProjectOutput | None = None
|
||||
if project is not None:
|
||||
project_channel = project.channel
|
||||
if project_channel is None:
|
||||
log.error('Project %s has no channel', project.id)
|
||||
raise ValueError(f'Project {project.id} has no channel')
|
||||
|
||||
project_output = dto.ProjectOutput(
|
||||
id=project.id,
|
||||
status=project.status.value,
|
||||
channel=dto.ChannelOutput(
|
||||
id=project_channel.id,
|
||||
telegram_id=project_channel.telegram_id,
|
||||
title=project_channel.title,
|
||||
username=project_channel.username,
|
||||
),
|
||||
)
|
||||
|
||||
return dto.PlacementOutput(
|
||||
id=placement.id,
|
||||
status=placement.status,
|
||||
creative_id=placement.creative_id,
|
||||
creative_name=creative_name,
|
||||
comment=placement.comment,
|
||||
invite_link=placement.invite_link,
|
||||
invite_link_type=placement.invite_link_type,
|
||||
@@ -50,6 +75,8 @@ def _build_placement_output(placement: domain.Placement) -> dto.PlacementOutput:
|
||||
title=channel.title,
|
||||
username=channel.username,
|
||||
),
|
||||
project=project_output,
|
||||
placement_number=placement_number,
|
||||
details=_build_placement_details(placement),
|
||||
)
|
||||
|
||||
@@ -73,7 +100,6 @@ async def create_placements(
|
||||
if project.channel.telegram_id is None:
|
||||
raise domain.ChannelNotFound(project.channel.id)
|
||||
|
||||
invite_link_type = project.purchase_invite_type_default
|
||||
details = input.details
|
||||
placements: list[domain.Placement] = []
|
||||
creatives_by_id: dict[uuid.UUID, domain.Creative] = {}
|
||||
@@ -106,7 +132,9 @@ async def create_placements(
|
||||
creative_id=creative.id if creative else None,
|
||||
channel_id=channel.id,
|
||||
invite_link=None,
|
||||
invite_link_type=invite_link_type,
|
||||
invite_link_type=(channel_details.invite_link_type if channel_details and channel_details.invite_link_type else None)
|
||||
or (details.invite_link_type if details and details.invite_link_type else None)
|
||||
or project.purchase_invite_type_default,
|
||||
status=channel_input.status or domain.PlacementStatus.NO_STATUS,
|
||||
comment=channel_input.comment,
|
||||
# Приоритет: channel details > общие details
|
||||
@@ -138,7 +166,7 @@ async def create_placements(
|
||||
|
||||
placement_outputs = []
|
||||
for placement in placements:
|
||||
placement_output = _build_placement_output(placement)
|
||||
placement_output = _build_placement_output(placement, project=project)
|
||||
placement_outputs.append(
|
||||
dto.PlacementWithPostsOutput(
|
||||
**placement_output.model_dump(),
|
||||
|
||||
@@ -77,6 +77,30 @@ async def get_placement_user(self: 'Usecase', input: dto.GetPlacementInput) -> d
|
||||
channel = await self.database.get_channel(channel_id=placement.channel_id)
|
||||
placement.channel = channel
|
||||
|
||||
# Prefetch project channel for output building
|
||||
if project.channel is None:
|
||||
project_channel = await self.database.get_channel(channel_id=project.channel_id)
|
||||
project.channel = project_channel
|
||||
|
||||
# Get creative name if exists
|
||||
creative_name = None
|
||||
if placement.creative_id:
|
||||
creative = await self.database.get_creative(input.workspace_id, placement.creative_id)
|
||||
if creative:
|
||||
creative_name = creative.name
|
||||
|
||||
# Calculate placement number (ordinal number of this placement)
|
||||
placement_number = await self.database.count_placements_by_project_and_channel(
|
||||
project.id, placement.channel_id
|
||||
)
|
||||
# Count placements created before this one
|
||||
placements_before = await domain.Placement.filter(
|
||||
project_id=project.id,
|
||||
channel_id=placement.channel_id,
|
||||
created_at__lt=placement.created_at,
|
||||
).count()
|
||||
placement_number = placements_before + 1
|
||||
|
||||
placement_posts = await self.database.get_workspace_placement_posts(
|
||||
input.workspace_id,
|
||||
placement_id=placement.id,
|
||||
@@ -112,7 +136,12 @@ async def get_placement_user(self: 'Usecase', input: dto.GetPlacementInput) -> d
|
||||
if placement_post_output is not None:
|
||||
break
|
||||
|
||||
placement_output = _build_placement_output(placement)
|
||||
placement_output = _build_placement_output(
|
||||
placement,
|
||||
project=project,
|
||||
placement_number=placement_number,
|
||||
creative_name=creative_name,
|
||||
)
|
||||
return dto.PlacementWithPostsOutput(
|
||||
**placement_output.model_dump(),
|
||||
placement_post=placement_post_output,
|
||||
|
||||
@@ -82,7 +82,7 @@ async def get_placements(self: 'Usecase', input: dto.GetPlacementsInput) -> dto.
|
||||
|
||||
placement_outputs = []
|
||||
for placement in placements:
|
||||
placement_output = _build_placement_output(placement)
|
||||
placement_output = _build_placement_output(placement, project=project)
|
||||
placement_post_output = None
|
||||
placement_posts_for_placement = placement_posts_by_placement_id.get(placement.id, [])
|
||||
if placement_posts_for_placement:
|
||||
|
||||
Reference in New Issue
Block a user