правки
This commit is contained in:
@@ -81,7 +81,6 @@ class CreatePlacementsInput(pydantic.BaseModel):
|
|||||||
|
|
||||||
creative_id: uuid.UUID | None = None
|
creative_id: uuid.UUID | None = None
|
||||||
channels: list[CreatePlacementChannelInput]
|
channels: list[CreatePlacementChannelInput]
|
||||||
details: PlacementDetails | None = None # Общие детали для всех placements
|
|
||||||
|
|
||||||
|
|
||||||
class GetPlacementsInput(pydantic.BaseModel):
|
class GetPlacementsInput(pydantic.BaseModel):
|
||||||
|
|||||||
@@ -178,7 +178,6 @@ async def create_placements(
|
|||||||
if project.channel.telegram_id is None:
|
if project.channel.telegram_id is None:
|
||||||
raise domain.ChannelNotFound(project.channel.id)
|
raise domain.ChannelNotFound(project.channel.id)
|
||||||
|
|
||||||
details = input.details
|
|
||||||
placements: list[domain.Placement] = []
|
placements: list[domain.Placement] = []
|
||||||
creatives_by_id: dict[uuid.UUID, domain.Creative] = {}
|
creatives_by_id: dict[uuid.UUID, domain.Creative] = {}
|
||||||
|
|
||||||
@@ -187,11 +186,9 @@ async def create_placements(
|
|||||||
if not channel:
|
if not channel:
|
||||||
raise domain.ChannelNotFound(channel_input.channel_id)
|
raise domain.ChannelNotFound(channel_input.channel_id)
|
||||||
|
|
||||||
# Объединяем общие детали с деталями конкретного канала
|
|
||||||
channel_details = channel_input.details
|
channel_details = channel_input.details
|
||||||
creative_id = (
|
creative_id = (
|
||||||
(channel_details.creative_id if channel_details and channel_details.creative_id else None)
|
(channel_details.creative_id if channel_details and channel_details.creative_id else None)
|
||||||
or (details.creative_id if details and details.creative_id else None)
|
|
||||||
or input.creative_id
|
or input.creative_id
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -211,34 +208,30 @@ async def create_placements(
|
|||||||
channel_id=channel.id,
|
channel_id=channel.id,
|
||||||
invite_link=None,
|
invite_link=None,
|
||||||
invite_link_type=(
|
invite_link_type=(
|
||||||
channel_details.invite_link_type if channel_details and channel_details.invite_link_type else None
|
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,
|
or project.purchase_invite_type_default,
|
||||||
status=channel_input.status or domain.PlacementStatus.NO_STATUS,
|
status=channel_input.status or domain.PlacementStatus.NO_STATUS,
|
||||||
comment=channel_input.comment,
|
comment=channel_input.comment
|
||||||
# Приоритет: channel details > общие details
|
or (channel_details.comment if channel_details else None),
|
||||||
placement_at=(channel_details.placement_at if channel_details else None)
|
placement_at=channel_details.placement_at if channel_details else None,
|
||||||
or (details.placement_at if details else None),
|
payment_at=channel_details.payment_at if channel_details else None,
|
||||||
payment_at=details.payment_at if details else None,
|
cost_type=channel_details.cost.type if channel_details and channel_details.cost 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,
|
||||||
or (details.cost.type if details and details.cost else None),
|
|
||||||
cost_value=(channel_details.cost.value if channel_details and channel_details.cost else None)
|
|
||||||
or (details.cost.value if details and details.cost else None),
|
|
||||||
cost_before_bargain_type=(
|
cost_before_bargain_type=(
|
||||||
channel_details.cost_before_bargain.type
|
channel_details.cost_before_bargain.type
|
||||||
if channel_details and channel_details.cost_before_bargain
|
if channel_details and channel_details.cost_before_bargain
|
||||||
else None
|
else None
|
||||||
)
|
),
|
||||||
or (details.cost_before_bargain.type if details and details.cost_before_bargain else None),
|
|
||||||
cost_before_bargain=(
|
cost_before_bargain=(
|
||||||
channel_details.cost_before_bargain.value
|
channel_details.cost_before_bargain.value
|
||||||
if channel_details and channel_details.cost_before_bargain
|
if channel_details and channel_details.cost_before_bargain
|
||||||
else None
|
else None
|
||||||
)
|
),
|
||||||
or (details.cost_before_bargain.value if details and details.cost_before_bargain else None),
|
placement_type=channel_details.placement_type if channel_details else None,
|
||||||
placement_type=details.placement_type if details else None,
|
format=channel_details.format if channel_details else None,
|
||||||
format=(channel_details.format if channel_details else None) or (details.format if details else None),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.database.create_placement(placement)
|
await self.database.create_placement(placement)
|
||||||
|
|||||||
@@ -645,7 +645,6 @@ type CreatePlacementChannelInput struct {
|
|||||||
type CreatePlacementsInput struct {
|
type CreatePlacementsInput struct {
|
||||||
CreativeID *string `json:"creative_id,omitempty"`
|
CreativeID *string `json:"creative_id,omitempty"`
|
||||||
Channels []CreatePlacementChannelInput `json:"channels"`
|
Channels []CreatePlacementChannelInput `json:"channels"`
|
||||||
Details *PlacementDetails `json:"details,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetPlacementsOutput struct {
|
type GetPlacementsOutput struct {
|
||||||
|
|||||||
@@ -2193,14 +2193,9 @@ func (s *PurchaseOptionalDetails) createPurchase(b *bot.Bot, jwt string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
placementDetails := s.buildPlacementDetails(placementType)
|
|
||||||
if placementDetails != nil && s.isPlacementDetailsEmpty(placementDetails) {
|
|
||||||
placementDetails = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
apiChannels := make([]backend.CreatePlacementChannelInput, 0, len(s.Channels))
|
apiChannels := make([]backend.CreatePlacementChannelInput, 0, len(s.Channels))
|
||||||
for _, ch := range s.Channels {
|
for _, ch := range s.Channels {
|
||||||
channelDetails := s.buildChannelDetails(channelKey(ch))
|
channelDetails := s.buildChannelDetails(channelKey(ch), placementType)
|
||||||
if channelDetails != nil && s.isChannelDetailsEmpty(channelDetails) {
|
if channelDetails != nil && s.isChannelDetailsEmpty(channelDetails) {
|
||||||
channelDetails = nil
|
channelDetails = nil
|
||||||
}
|
}
|
||||||
@@ -2214,7 +2209,6 @@ func (s *PurchaseOptionalDetails) createPurchase(b *bot.Bot, jwt string) {
|
|||||||
input := backend.CreatePlacementsInput{
|
input := backend.CreatePlacementsInput{
|
||||||
CreativeID: &s.CreativeID,
|
CreativeID: &s.CreativeID,
|
||||||
Channels: apiChannels,
|
Channels: apiChannels,
|
||||||
Details: placementDetails,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
placements, err := b.Backend.CreatePlacements(context.Background(), jwt, b.Session.WorkspaceID, s.ProjectID, input)
|
placements, err := b.Backend.CreatePlacements(context.Background(), jwt, b.Session.WorkspaceID, s.ProjectID, input)
|
||||||
@@ -2247,102 +2241,82 @@ func (s *PurchaseOptionalDetails) createPurchase(b *bot.Bot, jwt string) {
|
|||||||
}, bot.NewMessage)
|
}, bot.NewMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PurchaseOptionalDetails) buildPlacementDetails(placementType *string) *backend.PlacementDetails {
|
func (s *PurchaseOptionalDetails) buildChannelDetails(channelKey string, placementType *string) *backend.PlacementDetails {
|
||||||
details := &backend.PlacementDetails{}
|
details := &backend.PlacementDetails{}
|
||||||
|
|
||||||
if s.PlacementMode != "per_channel" || len(s.Channels) <= 1 {
|
// Placement date
|
||||||
if s.PlacementDateTime != nil {
|
if s.PlacementMode == "per_channel" && len(s.Channels) > 1 {
|
||||||
formatted := s.PlacementDateTime.Format(time.RFC3339)
|
|
||||||
details.PlacementAt = &formatted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.PaymentDateMode != "per_channel" || len(s.Channels) <= 1 {
|
|
||||||
if s.PaymentDate != nil {
|
|
||||||
formatted := s.PaymentDate.Format(time.RFC3339)
|
|
||||||
details.PaymentAt = &formatted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.CostMode != "per_channel" || len(s.Channels) <= 1 {
|
|
||||||
details.Cost = s.buildCostInfo(s.CostType, s.CostValue)
|
|
||||||
}
|
|
||||||
if s.CostBeforeMode != "per_channel" || len(s.Channels) <= 1 {
|
|
||||||
if s.CostBeforeBargain != nil && s.CostBeforeBargain.Value != nil {
|
|
||||||
details.CostBeforeBargain = s.buildCostInfo(s.CostBeforeBargain.Type, s.CostBeforeBargain.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.PurchaseTypeMode != "per_channel" || len(s.Channels) <= 1 {
|
|
||||||
if s.PurchaseType != "" {
|
|
||||||
details.PlacementType = &s.PurchaseType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.CommentMode != "per_channel" || len(s.Channels) <= 1 {
|
|
||||||
if s.Comment != "" {
|
|
||||||
details.Comment = &s.Comment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.FormatMode != "per_channel" || len(s.Channels) <= 1 {
|
|
||||||
if s.Format != "" {
|
|
||||||
details.Format = &s.Format
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.InviteLinkTypeMode != "per_channel" || len(s.Channels) <= 1 {
|
|
||||||
if s.InviteLinkType != "" {
|
|
||||||
details.InviteLinkType = &s.InviteLinkType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if placementType != nil && (s.PurchaseTypeMode == "per_channel" || len(s.Channels) <= 1) {
|
|
||||||
details.PlacementType = placementType
|
|
||||||
}
|
|
||||||
|
|
||||||
return details
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PurchaseOptionalDetails) buildChannelDetails(channelKey string) *backend.PlacementDetails {
|
|
||||||
if len(s.Channels) <= 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
details := &backend.PlacementDetails{}
|
|
||||||
if s.PlacementMode == "per_channel" {
|
|
||||||
if value, ok := s.PlacementByChannel[channelKey]; ok && value != nil {
|
if value, ok := s.PlacementByChannel[channelKey]; ok && value != nil {
|
||||||
formatted := value.Format(time.RFC3339)
|
formatted := value.Format(time.RFC3339)
|
||||||
details.PlacementAt = &formatted
|
details.PlacementAt = &formatted
|
||||||
}
|
}
|
||||||
|
} else if s.PlacementDateTime != nil {
|
||||||
|
formatted := s.PlacementDateTime.Format(time.RFC3339)
|
||||||
|
details.PlacementAt = &formatted
|
||||||
}
|
}
|
||||||
if s.PaymentDateMode == "per_channel" {
|
|
||||||
|
// Payment date
|
||||||
|
if s.PaymentDateMode == "per_channel" && len(s.Channels) > 1 {
|
||||||
if value, ok := s.PaymentDateByChannel[channelKey]; ok && value != nil {
|
if value, ok := s.PaymentDateByChannel[channelKey]; ok && value != nil {
|
||||||
formatted := value.Format(time.RFC3339)
|
formatted := value.Format(time.RFC3339)
|
||||||
details.PaymentAt = &formatted
|
details.PaymentAt = &formatted
|
||||||
}
|
}
|
||||||
|
} else if s.PaymentDate != nil {
|
||||||
|
formatted := s.PaymentDate.Format(time.RFC3339)
|
||||||
|
details.PaymentAt = &formatted
|
||||||
}
|
}
|
||||||
if s.CostMode == "per_channel" {
|
|
||||||
|
// Cost
|
||||||
|
if s.CostMode == "per_channel" && len(s.Channels) > 1 {
|
||||||
entry := s.CostByChannel[channelKey]
|
entry := s.CostByChannel[channelKey]
|
||||||
details.Cost = s.buildCostInfo(entry.Type, entry.Value)
|
details.Cost = s.buildCostInfo(entry.Type, entry.Value)
|
||||||
|
} else {
|
||||||
|
details.Cost = s.buildCostInfo(s.CostType, s.CostValue)
|
||||||
}
|
}
|
||||||
if s.CostBeforeMode == "per_channel" {
|
|
||||||
|
// Cost before bargain
|
||||||
|
if s.CostBeforeMode == "per_channel" && len(s.Channels) > 1 {
|
||||||
if entry, ok := s.CostBeforeByChannel[channelKey]; ok && entry.Value != nil {
|
if entry, ok := s.CostBeforeByChannel[channelKey]; ok && entry.Value != nil {
|
||||||
details.CostBeforeBargain = s.buildCostInfo(entry.Type, entry.Value)
|
details.CostBeforeBargain = s.buildCostInfo(entry.Type, entry.Value)
|
||||||
}
|
}
|
||||||
|
} else if s.CostBeforeBargain != nil && s.CostBeforeBargain.Value != nil {
|
||||||
|
details.CostBeforeBargain = s.buildCostInfo(s.CostBeforeBargain.Type, s.CostBeforeBargain.Value)
|
||||||
}
|
}
|
||||||
if s.PurchaseTypeMode == "per_channel" {
|
|
||||||
|
// Placement type
|
||||||
|
if s.PurchaseTypeMode == "per_channel" && len(s.Channels) > 1 {
|
||||||
if value, ok := s.PurchaseTypeByChannel[channelKey]; ok && value != "" {
|
if value, ok := s.PurchaseTypeByChannel[channelKey]; ok && value != "" {
|
||||||
details.PlacementType = &value
|
details.PlacementType = &value
|
||||||
}
|
}
|
||||||
|
} else if placementType != nil {
|
||||||
|
details.PlacementType = placementType
|
||||||
}
|
}
|
||||||
if s.CommentMode == "per_channel" {
|
|
||||||
|
// Comment
|
||||||
|
if s.CommentMode == "per_channel" && len(s.Channels) > 1 {
|
||||||
if value, ok := s.CommentByChannel[channelKey]; ok && value != "" {
|
if value, ok := s.CommentByChannel[channelKey]; ok && value != "" {
|
||||||
details.Comment = &value
|
details.Comment = &value
|
||||||
}
|
}
|
||||||
|
} else if s.Comment != "" {
|
||||||
|
details.Comment = &s.Comment
|
||||||
}
|
}
|
||||||
if s.FormatMode == "per_channel" {
|
|
||||||
|
// Format
|
||||||
|
if s.FormatMode == "per_channel" && len(s.Channels) > 1 {
|
||||||
if value, ok := s.FormatByChannel[channelKey]; ok && value != "" {
|
if value, ok := s.FormatByChannel[channelKey]; ok && value != "" {
|
||||||
details.Format = &value
|
details.Format = &value
|
||||||
}
|
}
|
||||||
|
} else if s.Format != "" {
|
||||||
|
details.Format = &s.Format
|
||||||
}
|
}
|
||||||
if s.InviteLinkTypeMode == "per_channel" {
|
|
||||||
|
// Invite link type
|
||||||
|
if s.InviteLinkTypeMode == "per_channel" && len(s.Channels) > 1 {
|
||||||
if value, ok := s.InviteLinkTypeByChannel[channelKey]; ok && value != "" {
|
if value, ok := s.InviteLinkTypeByChannel[channelKey]; ok && value != "" {
|
||||||
details.InviteLinkType = &value
|
details.InviteLinkType = &value
|
||||||
}
|
}
|
||||||
|
} else if s.InviteLinkType != "" {
|
||||||
|
details.InviteLinkType = &s.InviteLinkType
|
||||||
}
|
}
|
||||||
|
|
||||||
return details
|
return details
|
||||||
@@ -2362,21 +2336,15 @@ func (s *PurchaseOptionalDetails) buildCostInfo(costType string, value *float64)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PurchaseOptionalDetails) isPlacementDetailsEmpty(details *backend.PlacementDetails) bool {
|
func (s *PurchaseOptionalDetails) isChannelDetailsEmpty(details *backend.PlacementDetails) bool {
|
||||||
return details.PlacementAt == nil &&
|
return details.PlacementAt == nil &&
|
||||||
details.PaymentAt == nil &&
|
details.PaymentAt == nil &&
|
||||||
details.Cost == nil &&
|
details.Cost == nil &&
|
||||||
details.CostBeforeBargain == nil &&
|
details.CostBeforeBargain == nil &&
|
||||||
details.PlacementType == nil &&
|
details.PlacementType == nil &&
|
||||||
details.Format == nil &&
|
details.Format == nil &&
|
||||||
details.Comment == nil
|
details.Comment == nil &&
|
||||||
}
|
details.InviteLinkType == nil
|
||||||
|
|
||||||
func (s *PurchaseOptionalDetails) isChannelDetailsEmpty(details *backend.PlacementDetails) bool {
|
|
||||||
return details.PlacementAt == nil &&
|
|
||||||
details.Cost == nil &&
|
|
||||||
details.CostBeforeBargain == nil &&
|
|
||||||
details.Format == nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PurchaseOptionalDetails) normalizePurchaseType() (*string, bool) {
|
func (s *PurchaseOptionalDetails) normalizePurchaseType() (*string, bool) {
|
||||||
|
|||||||
@@ -95,7 +95,8 @@ func extractChannelMeta(currentChannel domain.Channel, chats []tg.ChatClass) *do
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
username := ""
|
// Preserve username if Telegram doesn't provide a new one (private channels may not have username)
|
||||||
|
username := currentChannel.Username
|
||||||
if usernameVal, ok := ch.GetUsername(); ok && usernameVal != "" {
|
if usernameVal, ok := ch.GetUsername(); ok && usernameVal != "" {
|
||||||
username = usernameVal
|
username = usernameVal
|
||||||
}
|
}
|
||||||
@@ -112,6 +113,7 @@ func extractChannelMeta(currentChannel domain.Channel, chats []tg.ChatClass) *do
|
|||||||
Username: username,
|
Username: username,
|
||||||
Title: ch.Title,
|
Title: ch.Title,
|
||||||
AccessHash: accessHash,
|
AccessHash: accessHash,
|
||||||
|
InviteLink: currentChannel.InviteLink, // Preserve invite link (never returned in channel diff)
|
||||||
Pts: currentChannel.Pts,
|
Pts: currentChannel.Pts,
|
||||||
IsAccessible: currentChannel.IsAccessible,
|
IsAccessible: currentChannel.IsAccessible,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user