правки
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:
|
||||
|
||||
@@ -550,6 +550,7 @@ type Channel struct {
|
||||
TelegramID *int64 `json:"telegram_id"`
|
||||
Title *string `json:"title"`
|
||||
Username *string `json:"username"`
|
||||
InviteLink *string `json:"invite_link"`
|
||||
}
|
||||
|
||||
type CreateChannelInput struct {
|
||||
@@ -568,6 +569,12 @@ type CreateChannelResult struct {
|
||||
Error *string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type ProjectOutput struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
Channel Channel `json:"channel"`
|
||||
}
|
||||
|
||||
type CreateChannelsOutput struct {
|
||||
Results []CreateChannelResult `json:"results"`
|
||||
}
|
||||
@@ -586,16 +593,21 @@ type PlacementDetails struct {
|
||||
Format *string `json:"format,omitempty"`
|
||||
Comment *string `json:"comment,omitempty"`
|
||||
CreativeID *string `json:"creative_id,omitempty"`
|
||||
CreativeName *string `json:"creative_name,omitempty"`
|
||||
InviteLinkType *string `json:"invite_link_type,omitempty"`
|
||||
}
|
||||
|
||||
type PlacementOutput struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
CreativeID *string `json:"creative_id,omitempty"`
|
||||
CreativeName *string `json:"creative_name,omitempty"`
|
||||
Comment *string `json:"comment,omitempty"`
|
||||
InviteLink *string `json:"invite_link,omitempty"`
|
||||
InviteLinkType string `json:"invite_link_type"`
|
||||
Channel Channel `json:"channel"`
|
||||
Project *ProjectOutput `json:"project"`
|
||||
PlacementNumber int `json:"placement_number"`
|
||||
Details *PlacementDetails `json:"details,omitempty"`
|
||||
PlacementPost *PlacementPostOutput `json:"placement_post,omitempty"`
|
||||
}
|
||||
@@ -604,6 +616,7 @@ type PlacementPostOutput struct {
|
||||
SubscriptionsCount int `json:"subscriptions_count"`
|
||||
ViewsCount *int `json:"views_count,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
TimeOnTop *int `json:"time_on_top,omitempty"`
|
||||
Post PostOutput `json:"post"`
|
||||
}
|
||||
|
||||
|
||||
@@ -32,62 +32,164 @@ func (s *PlacementDetails) renderDetails(b *bot.Bot, mode bot.RenderMode) {
|
||||
|
||||
text := "<b>Детали размещения</b>\n\n"
|
||||
|
||||
channelName := formatChannelName(placement.Channel)
|
||||
text += fmt.Sprintf("• <b>Канал:</b> %s\n", channelName)
|
||||
text += fmt.Sprintf("• <b>Статус:</b> %s\n", formatPlacementStatus(placement.Status))
|
||||
text += fmt.Sprintf("• <b>Ссылка:</b> %s\n", formatInviteLinkType(placement.InviteLinkType))
|
||||
// Размещение в канале
|
||||
text += "📺 <b>Размещение в канале:</b>\n"
|
||||
text += formatChannelWithName(placement.Channel)
|
||||
text += "\n"
|
||||
|
||||
// Рекламируемый проект
|
||||
if placement.Project != nil {
|
||||
text += "🎯 <b>Рекламируемый проект:</b>\n"
|
||||
text += formatProject(*placement.Project)
|
||||
text += "\n"
|
||||
}
|
||||
|
||||
// Ссылка на пост
|
||||
if placementPost != nil && placementPost.Post.URL != nil && *placementPost.Post.URL != "" {
|
||||
text += fmt.Sprintf("🔗 <b>Ссылка на пост:</b> %s\n", *placementPost.Post.URL)
|
||||
}
|
||||
|
||||
// Пригласительная ссылка
|
||||
if placement.InviteLink != nil && *placement.InviteLink != "" {
|
||||
text += fmt.Sprintf("📩 <b>Пригласительная ссылка:</b> %s\n", *placement.InviteLink)
|
||||
}
|
||||
|
||||
// Тип ссылки
|
||||
text += fmt.Sprintf("🔓 <b>Тип ссылки:</b> %s\n", formatInviteLinkType(placement.InviteLinkType))
|
||||
|
||||
// Порядковый номер размещения
|
||||
if placement.PlacementNumber > 0 {
|
||||
text += fmt.Sprintf("🔢 <b>Размещение №%d</b> в этом канале\n", placement.PlacementNumber)
|
||||
}
|
||||
|
||||
// Креатив
|
||||
if placement.Details != nil && placement.Details.CreativeName != nil && *placement.Details.CreativeName != "" {
|
||||
text += fmt.Sprintf("✨ <b>Креатив:</b> %s\n", *placement.Details.CreativeName)
|
||||
} else if placement.CreativeName != nil && *placement.CreativeName != "" {
|
||||
text += fmt.Sprintf("✨ <b>Креатив:</b> %s\n", *placement.CreativeName)
|
||||
}
|
||||
|
||||
text += "\n"
|
||||
|
||||
// Финансовая информация
|
||||
if placement.Details != nil {
|
||||
if placement.Details.PlacementType != nil {
|
||||
text += fmt.Sprintf("• <b>Тип:</b> %s\n", formatPlacementType(*placement.Details.PlacementType))
|
||||
}
|
||||
if placement.Details.PlacementAt != nil && *placement.Details.PlacementAt != "" {
|
||||
text += fmt.Sprintf("• <b>Дата размещения:</b> %s\n", formatDateTime(*placement.Details.PlacementAt))
|
||||
}
|
||||
if placement.Details.PaymentAt != nil && *placement.Details.PaymentAt != "" {
|
||||
text += fmt.Sprintf("• <b>Дата оплаты:</b> %s\n", formatDateTime(*placement.Details.PaymentAt))
|
||||
}
|
||||
// Стоимость
|
||||
if placement.Details.Cost != nil {
|
||||
text += fmt.Sprintf("• <b>Стоимость:</b> %s %.0f₽\n",
|
||||
formatCostType(placement.Details.Cost.Type),
|
||||
placement.Details.Cost.Value,
|
||||
)
|
||||
costType := formatCostType(placement.Details.Cost.Type)
|
||||
text += fmt.Sprintf("💰 <b>Стоимость:</b> %s %.0f₽\n", costType, placement.Details.Cost.Value)
|
||||
}
|
||||
|
||||
// Стоимость до торга
|
||||
if placement.Details.CostBeforeBargain != nil {
|
||||
text += fmt.Sprintf("• <b>До торга:</b> %s %.0f₽\n",
|
||||
formatCostType(placement.Details.CostBeforeBargain.Type),
|
||||
placement.Details.CostBeforeBargain.Value,
|
||||
)
|
||||
costType := formatCostType(placement.Details.CostBeforeBargain.Type)
|
||||
text += fmt.Sprintf("💸 <b>До торга:</b> %s %.0f₽\n", costType, placement.Details.CostBeforeBargain.Value)
|
||||
}
|
||||
|
||||
// Дата размещения
|
||||
if placement.Details.PlacementAt != nil && *placement.Details.PlacementAt != "" {
|
||||
text += fmt.Sprintf("📅 <b>Дата размещения:</b> %s\n", formatDateTime(*placement.Details.PlacementAt))
|
||||
}
|
||||
|
||||
// Дата оплаты
|
||||
if placement.Details.PaymentAt != nil && *placement.Details.PaymentAt != "" {
|
||||
text += fmt.Sprintf("💳 <b>Дата оплаты:</b> %s\n", formatDateTime(*placement.Details.PaymentAt))
|
||||
}
|
||||
|
||||
// Формат
|
||||
if placement.Details.Format != nil && *placement.Details.Format != "" {
|
||||
text += fmt.Sprintf("• <b>Формат:</b> %s\n", *placement.Details.Format)
|
||||
text += fmt.Sprintf("📐 <b>Формат:</b> %s\n", *placement.Details.Format)
|
||||
}
|
||||
if placement.Details.Comment != nil && *placement.Details.Comment != "" {
|
||||
text += fmt.Sprintf("• <b>Комментарий:</b> %s\n", *placement.Details.Comment)
|
||||
|
||||
// Тип закупа
|
||||
if placement.Details.PlacementType != nil {
|
||||
text += fmt.Sprintf("🔄 <b>Тип закупа:</b> %s\n", formatPlacementType(*placement.Details.PlacementType))
|
||||
}
|
||||
}
|
||||
|
||||
text += "\n"
|
||||
|
||||
// Статистика
|
||||
if placementPost != nil {
|
||||
text += "\n<b>Пост</b>\n"
|
||||
if placementPost.Post.URL != nil && *placementPost.Post.URL != "" {
|
||||
text += fmt.Sprintf("• <b>Ссылка на пост:</b> %s\n", *placementPost.Post.URL)
|
||||
} else {
|
||||
text += "• <b>Ссылка на пост:</b> не найдена\n"
|
||||
// Кол-во подписчиков
|
||||
if placementPost.SubscriptionsCount > 0 {
|
||||
text += fmt.Sprintf("👥 <b>Подписчики:</b> %d\n", placementPost.SubscriptionsCount)
|
||||
}
|
||||
|
||||
// Просмотры поста
|
||||
if placementPost.ViewsCount != nil {
|
||||
text += fmt.Sprintf("👁️ <b>Просмотры:</b> %d\n", *placementPost.ViewsCount)
|
||||
}
|
||||
|
||||
// CPF (стоимость подписчика)
|
||||
if placementPost.SubscriptionsCount > 0 && placement.Details != nil && placement.Details.Cost != nil {
|
||||
cpf := calculateCPF(placement.Details.Cost.Value, placementPost.SubscriptionsCount)
|
||||
text += fmt.Sprintf("💵 <b>CPF:</b> %.2f₽\n", cpf)
|
||||
}
|
||||
|
||||
// Конверсия
|
||||
if placementPost.ViewsCount != nil && *placementPost.ViewsCount > 0 && placementPost.SubscriptionsCount > 0 {
|
||||
conversion := calculateConversion(placementPost.SubscriptionsCount, *placementPost.ViewsCount)
|
||||
text += fmt.Sprintf("📊 <b>Конверсия:</b> %.1f%%\n", conversion)
|
||||
}
|
||||
}
|
||||
|
||||
// Комментарий
|
||||
if placement.Details != nil && placement.Details.Comment != nil && *placement.Details.Comment != "" {
|
||||
text += fmt.Sprintf("\n📝 <b>Комментарий:</b> %s\n", *placement.Details.Comment)
|
||||
}
|
||||
|
||||
keyboard := Keyboard(Row(Button("← Назад", "back")))
|
||||
b.Render(text, keyboard, mode)
|
||||
}
|
||||
|
||||
func formatChannelName(channel backend.Channel) string {
|
||||
func formatChannelWithName(channel backend.Channel) string {
|
||||
var name string
|
||||
if channel.Title != nil && *channel.Title != "" {
|
||||
return *channel.Title
|
||||
name = *channel.Title
|
||||
} else if channel.Username != nil && *channel.Username != "" {
|
||||
name = "@" + *channel.Username
|
||||
} else {
|
||||
name = "Без названия"
|
||||
}
|
||||
|
||||
// Добавляем ссылку если есть username или invite_link
|
||||
if channel.Username != nil && *channel.Username != "" {
|
||||
return "@" + *channel.Username
|
||||
return fmt.Sprintf(" %s (@%s)\n", name, *channel.Username)
|
||||
} else if channel.InviteLink != nil && *channel.InviteLink != "" {
|
||||
return fmt.Sprintf(" %s\n", name)
|
||||
}
|
||||
return "Без названия"
|
||||
return fmt.Sprintf(" %s\n", name)
|
||||
}
|
||||
|
||||
func formatProject(project backend.ProjectOutput) string {
|
||||
var name string
|
||||
if project.Channel.Title != nil && *project.Channel.Title != "" {
|
||||
name = *project.Channel.Title
|
||||
} else if project.Channel.Username != nil && *project.Channel.Username != "" {
|
||||
name = "@" + *project.Channel.Username
|
||||
} else {
|
||||
name = "Без названия"
|
||||
}
|
||||
|
||||
// Добавляем ссылку если есть username
|
||||
if project.Channel.Username != nil && *project.Channel.Username != "" {
|
||||
return fmt.Sprintf(" %s (@%s)\n", name, *project.Channel.Username)
|
||||
}
|
||||
return fmt.Sprintf(" %s\n", name)
|
||||
}
|
||||
|
||||
func calculateCPF(cost float64, subscriptions int) float64 {
|
||||
if subscriptions == 0 {
|
||||
return 0
|
||||
}
|
||||
return cost / float64(subscriptions)
|
||||
}
|
||||
|
||||
func calculateConversion(subscriptions int, views int) float64 {
|
||||
if views == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(subscriptions) / float64(views) * 100
|
||||
}
|
||||
|
||||
func formatInviteLinkType(value string) string {
|
||||
|
||||
@@ -29,6 +29,7 @@ type PurchaseOptionalDetails struct {
|
||||
PurchaseType string
|
||||
Format string
|
||||
Comment string
|
||||
InviteLinkType string
|
||||
InputMode string
|
||||
CurrentParam string
|
||||
CurrentChannel string
|
||||
@@ -41,6 +42,7 @@ type PurchaseOptionalDetails struct {
|
||||
PurchaseTypeMode string
|
||||
CommentMode string
|
||||
FormatMode string
|
||||
InviteLinkTypeMode string
|
||||
PlacementByChannel map[string]*time.Time
|
||||
PaymentDateByChannel map[string]*time.Time
|
||||
CostByChannel map[string]CostEntry
|
||||
@@ -48,6 +50,7 @@ type PurchaseOptionalDetails struct {
|
||||
PurchaseTypeByChannel map[string]string
|
||||
CommentByChannel map[string]string
|
||||
FormatByChannel map[string]string
|
||||
InviteLinkTypeByChannel map[string]string
|
||||
PlacementCopy *time.Time
|
||||
PaymentDateCopy *time.Time
|
||||
CostCopy *CostEntry
|
||||
@@ -55,6 +58,7 @@ type PurchaseOptionalDetails struct {
|
||||
PurchaseTypeCopy *string
|
||||
CommentCopy *string
|
||||
FormatCopy *string
|
||||
InviteLinkTypeCopy *string
|
||||
ChannelEditMode string // "edit" или "copy"
|
||||
BackState bot.State
|
||||
}
|
||||
@@ -104,6 +108,7 @@ func (s *PurchaseOptionalDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
))
|
||||
rows = append(rows, Row(
|
||||
s.formatButton(),
|
||||
s.inviteLinkTypeButton(),
|
||||
))
|
||||
|
||||
if row := s.costBeforeDeleteRow(); row != nil {
|
||||
@@ -182,6 +187,16 @@ func (s *PurchaseOptionalDetails) HandleCallback(b *bot.Bot, u *echotron.Update)
|
||||
s.openCommonEditor(b, "format")
|
||||
}
|
||||
|
||||
case "opt_invite_link_type":
|
||||
// Открываем редактор в текущем режиме
|
||||
if s.InviteLinkTypeMode == "per_channel" && len(s.Channels) > 1 {
|
||||
s.InputMode = "invite_link_type_channels"
|
||||
s.Enter(b, bot.EditMessage)
|
||||
} else {
|
||||
s.InputMode = "invite_link_type"
|
||||
s.Enter(b, bot.EditMessage)
|
||||
}
|
||||
|
||||
case "opt_comment":
|
||||
// Открываем редактор в текущем режиме
|
||||
if s.CommentMode == "per_channel" && len(s.Channels) > 1 {
|
||||
@@ -332,6 +347,19 @@ func (s *PurchaseOptionalDetails) HandleCallback(b *bot.Bot, u *echotron.Update)
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(u.CallbackQuery.Data, "invite_link_type:") {
|
||||
value := strings.TrimPrefix(u.CallbackQuery.Data, "invite_link_type:")
|
||||
s.setInviteLinkTypeValue(value)
|
||||
if s.ReturnMode != "" {
|
||||
s.InputMode = s.ReturnMode
|
||||
s.ReturnMode = ""
|
||||
} else {
|
||||
s.InputMode = ""
|
||||
}
|
||||
s.CurrentChannel = ""
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return
|
||||
}
|
||||
s.Enter(b, bot.EditMessage)
|
||||
}
|
||||
}
|
||||
@@ -433,6 +461,9 @@ func (s *PurchaseOptionalDetails) formatOptionalSummary() string {
|
||||
if s.hasFormatValue() {
|
||||
lines = append(lines, fmt.Sprintf("<i>Формат:</i> <b>%s</b>%s", s.formatFormatSummary(), s.formatFormatDetails()))
|
||||
}
|
||||
if s.hasInviteLinkTypeValue() {
|
||||
lines = append(lines, fmt.Sprintf("<i>Тип ссылки:</i> <b>%s</b>%s", s.formatInviteLinkTypeSummary(), s.formatInviteLinkTypeDetails()))
|
||||
}
|
||||
if s.hasCommentValue() {
|
||||
lines = append(lines, fmt.Sprintf("<i>Комментарий:</i> <b>%s</b>%s", s.formatCommentSummary(), s.formatCommentDetails()))
|
||||
}
|
||||
@@ -449,8 +480,7 @@ func (s *PurchaseOptionalDetails) formatDateTime(value *time.Time) string {
|
||||
return "—"
|
||||
}
|
||||
local := value.In(ui2.MskLocation)
|
||||
text := fmt.Sprintf("%s %02d %s %s", ui2.WeekdayName(local.Weekday()), local.Day(), ui2.MonthShort(local.Month()), local.Format("15:04"))
|
||||
return fmt.Sprintf("<b>%s</b>", text)
|
||||
return fmt.Sprintf("%s %02d %s %s", ui2.WeekdayName(local.Weekday()), local.Day(), ui2.MonthShort(local.Month()), local.Format("15:04"))
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatDate(value *time.Time) string {
|
||||
@@ -458,23 +488,21 @@ func (s *PurchaseOptionalDetails) formatDate(value *time.Time) string {
|
||||
return "—"
|
||||
}
|
||||
local := value.In(ui2.MskLocation)
|
||||
text := fmt.Sprintf("%s %02d %s", ui2.WeekdayName(local.Weekday()), local.Day(), ui2.MonthShort(local.Month()))
|
||||
return fmt.Sprintf("<b>%s</b>", text)
|
||||
return fmt.Sprintf("%s %02d %s", ui2.WeekdayName(local.Weekday()), local.Day(), ui2.MonthShort(local.Month()))
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatText(value string) string {
|
||||
if value == "" {
|
||||
return "—"
|
||||
}
|
||||
return fmt.Sprintf("<b>%s</b>", value)
|
||||
return value
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatCostValue() string {
|
||||
if s.CostValue == nil {
|
||||
return "—"
|
||||
}
|
||||
text := fmt.Sprintf("%s %.0f₽", s.costTypeLabel(), *s.CostValue)
|
||||
return fmt.Sprintf("<b>%s</b>", text)
|
||||
return fmt.Sprintf("%s %.0f₽", s.costTypeLabel(), *s.CostValue)
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatCostBefore() string {
|
||||
@@ -482,8 +510,7 @@ func (s *PurchaseOptionalDetails) formatCostBefore() string {
|
||||
return "—"
|
||||
}
|
||||
label := s.costBeforeTypeLabelForEntry(*s.CostBeforeBargain)
|
||||
text := fmt.Sprintf("%s %.0f₽", label, *s.CostBeforeBargain.Value)
|
||||
return fmt.Sprintf("<b>%s</b>", text)
|
||||
return fmt.Sprintf("%s %.0f₽", label, *s.CostBeforeBargain.Value)
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) costTypeLabel() string {
|
||||
@@ -576,6 +603,22 @@ func (s *PurchaseOptionalDetails) commentButton() echotron.InlineKeyboardButton
|
||||
return Button(fmt.Sprintf("%s Комментарий%s", icon, modeIcon), "opt_comment")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) inviteLinkTypeButton() echotron.InlineKeyboardButton {
|
||||
icon := "+"
|
||||
if s.hasInviteLinkTypeValue() {
|
||||
icon = "✎"
|
||||
}
|
||||
modeIcon := ""
|
||||
if len(s.Channels) > 1 && s.InviteLinkTypeMode == "per_channel" {
|
||||
modeIcon = " 👥"
|
||||
}
|
||||
label := "Тип ссылки"
|
||||
if s.InviteLinkType != "" {
|
||||
label += ": " + s.inviteLinkTypeLabel(s.InviteLinkType)
|
||||
}
|
||||
return Button(fmt.Sprintf("%s %s%s", icon, label, modeIcon), "opt_invite_link_type")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) globalModeButton() echotron.InlineKeyboardButton {
|
||||
return Button("⚙️ Режимы параметров", "toggle_global_mode")
|
||||
}
|
||||
@@ -686,6 +729,25 @@ func (s *PurchaseOptionalDetails) renderInputPrompt(b *bot.Bot, mode bot.RenderM
|
||||
return
|
||||
case "format_custom":
|
||||
text += "<b>Введите формат</b>\n\nНапример: <code>пост</code>"
|
||||
case "invite_link_type":
|
||||
text = "<i>Страница выбора типа ссылки</i>\n\n"
|
||||
if s.CurrentChannel != "" {
|
||||
text += fmt.Sprintf("Канал: <b>%s</b>\n\n", channelLabelByKey(s.Channels, s.CurrentChannel))
|
||||
}
|
||||
text += "Выберите тип ссылки\n\n"
|
||||
keyboard := Keyboard(
|
||||
Row(
|
||||
Button("Открытая", "invite_link_type:public"),
|
||||
Button("С заявками", "invite_link_type:approval"),
|
||||
),
|
||||
Row(Button("← Назад", s.backAction())),
|
||||
)
|
||||
if mode == bot.EditMessage {
|
||||
b.Edit(text, keyboard)
|
||||
} else {
|
||||
b.SendNew(text, keyboard)
|
||||
}
|
||||
return
|
||||
case "comment":
|
||||
text += "<b>Введите комментарий</b>"
|
||||
default:
|
||||
@@ -761,6 +823,15 @@ func (s *PurchaseOptionalDetails) renderModeSelect(b *bot.Bot, mode bot.RenderMo
|
||||
}
|
||||
rows = append(rows, Row(Button(formatLabel, "toggle_param_mode:format")))
|
||||
|
||||
// Тип ссылки
|
||||
inviteLinkTypeLabel := "Тип ссылки: "
|
||||
if s.InviteLinkTypeMode == "per_channel" {
|
||||
inviteLinkTypeLabel += "👥"
|
||||
} else {
|
||||
inviteLinkTypeLabel += "общий"
|
||||
}
|
||||
rows = append(rows, Row(Button(inviteLinkTypeLabel, "toggle_param_mode:invite_link_type")))
|
||||
|
||||
rows = append(rows, Row(Button("← Назад", "back_to_optional")))
|
||||
|
||||
b.Render(text, Keyboard(rows...), mode)
|
||||
@@ -789,6 +860,9 @@ func (s *PurchaseOptionalDetails) renderParamScreens(b *bot.Bot, mode bot.Render
|
||||
case "format_channels":
|
||||
s.renderParamChannels(b, mode, "format")
|
||||
return true
|
||||
case "invite_link_type_channels":
|
||||
s.renderParamChannels(b, mode, "invite_link_type")
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -928,7 +1002,7 @@ func (s *PurchaseOptionalDetails) renderParamChannelSummary(param string) string
|
||||
// Паддинг вычисляем так, чтобы все значения начинались с одной позиции
|
||||
const (
|
||||
marker = "· " // Маркер пункта
|
||||
valueIndent = 2 // Отступ после самого длинного названия
|
||||
valueIndent = 6 // Отступ после самого длинного названия (увеличен для лучшей читаемости)
|
||||
)
|
||||
|
||||
var result []string
|
||||
@@ -936,7 +1010,7 @@ func (s *PurchaseOptionalDetails) renderParamChannelSummary(param string) string
|
||||
labelLen := len([]rune(line.label))
|
||||
// Паддинг = (макс.длина - тек.длина) + отступ после названия
|
||||
paddingLen := maxLabelLen - labelLen + valueIndent
|
||||
padding := strings.Repeat(" ", paddingLen)
|
||||
padding := strings.Repeat("\u00A0", paddingLen)
|
||||
|
||||
formattedLine := fmt.Sprintf("%s%s%s%s", marker, line.label, padding, line.value)
|
||||
result = append(result, formattedLine)
|
||||
@@ -953,6 +1027,9 @@ func (s *PurchaseOptionalDetails) renderParamChannelSummary(param string) string
|
||||
|
||||
finalResult := strings.Join(result, "\n")
|
||||
|
||||
// Оборачиваем в <pre> для моноширинного шрифта (выравнивание работает только в monospace)
|
||||
finalResult = fmt.Sprintf("<pre>%s</pre>", finalResult)
|
||||
|
||||
// Логируем итоговый результат
|
||||
log.Info().
|
||||
Str("param", param).
|
||||
@@ -1100,6 +1177,11 @@ func (s *PurchaseOptionalDetails) openCommonEditor(b *bot.Bot, param string) {
|
||||
s.ReturnMode = ""
|
||||
s.CurrentChannel = ""
|
||||
s.Enter(b, bot.EditMessage)
|
||||
case "invite_link_type":
|
||||
s.InputMode = "invite_link_type"
|
||||
s.ReturnMode = ""
|
||||
s.CurrentChannel = ""
|
||||
s.Enter(b, bot.EditMessage)
|
||||
default:
|
||||
s.InputMode = ""
|
||||
s.Enter(b, bot.EditMessage)
|
||||
@@ -1151,6 +1233,11 @@ func (s *PurchaseOptionalDetails) openChannelEditor(b *bot.Bot, param, channelKe
|
||||
s.ReturnMode = "format_channels"
|
||||
s.CurrentChannel = channelKey
|
||||
s.Enter(b, bot.EditMessage)
|
||||
case "invite_link_type":
|
||||
s.InputMode = "invite_link_type"
|
||||
s.ReturnMode = "invite_link_type_channels"
|
||||
s.CurrentChannel = channelKey
|
||||
s.Enter(b, bot.EditMessage)
|
||||
default:
|
||||
s.InputMode = ""
|
||||
s.Enter(b, bot.EditMessage)
|
||||
@@ -1204,6 +1291,13 @@ func (s *PurchaseOptionalDetails) copyParamValue(param, channelKey string) {
|
||||
} else {
|
||||
s.FormatCopy = nil
|
||||
}
|
||||
case "invite_link_type":
|
||||
if value, ok := s.InviteLinkTypeByChannel[channelKey]; ok {
|
||||
copied := value
|
||||
s.InviteLinkTypeCopy = &copied
|
||||
} else {
|
||||
s.InviteLinkTypeCopy = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1275,6 +1369,14 @@ func (s *PurchaseOptionalDetails) copyCommonValueToChannels(param string) {
|
||||
key := channelKey(ch)
|
||||
s.FormatByChannel[key] = s.Format
|
||||
}
|
||||
case "invite_link_type":
|
||||
if s.InviteLinkType == "" {
|
||||
return
|
||||
}
|
||||
for _, ch := range s.Channels {
|
||||
key := channelKey(ch)
|
||||
s.InviteLinkTypeByChannel[key] = s.InviteLinkType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1334,6 +1436,12 @@ func (s *PurchaseOptionalDetails) pasteParamValue(param, channelKey string) {
|
||||
return
|
||||
}
|
||||
s.FormatByChannel[channelKey] = *s.FormatCopy
|
||||
case "invite_link_type":
|
||||
if s.InviteLinkTypeCopy == nil {
|
||||
delete(s.InviteLinkTypeByChannel, channelKey)
|
||||
return
|
||||
}
|
||||
s.InviteLinkTypeByChannel[channelKey] = *s.InviteLinkTypeCopy
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1367,6 +1475,10 @@ func (s *PurchaseOptionalDetails) applyParamToAll(param string) {
|
||||
for _, ch := range s.Channels {
|
||||
s.pasteParamValue(param, channelKey(ch))
|
||||
}
|
||||
case "invite_link_type":
|
||||
for _, ch := range s.Channels {
|
||||
s.pasteParamValue(param, channelKey(ch))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1386,6 +1498,8 @@ func (s *PurchaseOptionalDetails) clearParamValue(param, channelKey string) {
|
||||
delete(s.CommentByChannel, channelKey)
|
||||
case "format":
|
||||
delete(s.FormatByChannel, channelKey)
|
||||
case "invite_link_type":
|
||||
delete(s.InviteLinkTypeByChannel, channelKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1405,6 +1519,8 @@ func (s *PurchaseOptionalDetails) paramTitle(param string) string {
|
||||
return "Комментарий"
|
||||
case "format":
|
||||
return "Формат"
|
||||
case "invite_link_type":
|
||||
return "Тип ссылки"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -1447,6 +1563,11 @@ func (s *PurchaseOptionalDetails) paramMode(param string) string {
|
||||
return "common"
|
||||
}
|
||||
return s.FormatMode
|
||||
case "invite_link_type":
|
||||
if s.InviteLinkTypeMode == "" {
|
||||
return "common"
|
||||
}
|
||||
return s.InviteLinkTypeMode
|
||||
default:
|
||||
return "common"
|
||||
}
|
||||
@@ -1468,6 +1589,8 @@ func (s *PurchaseOptionalDetails) setParamMode(param, mode string) {
|
||||
s.CommentMode = mode
|
||||
case "format":
|
||||
s.FormatMode = mode
|
||||
case "invite_link_type":
|
||||
s.InviteLinkTypeMode = mode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1493,6 +1616,9 @@ func (s *PurchaseOptionalDetails) ensureDefaults() {
|
||||
if s.FormatMode == "" {
|
||||
s.FormatMode = "common"
|
||||
}
|
||||
if s.InviteLinkTypeMode == "" {
|
||||
s.InviteLinkTypeMode = "common"
|
||||
}
|
||||
if s.PlacementByChannel == nil {
|
||||
s.PlacementByChannel = make(map[string]*time.Time)
|
||||
}
|
||||
@@ -1514,6 +1640,9 @@ func (s *PurchaseOptionalDetails) ensureDefaults() {
|
||||
if s.FormatByChannel == nil {
|
||||
s.FormatByChannel = make(map[string]string)
|
||||
}
|
||||
if s.InviteLinkTypeByChannel == nil {
|
||||
s.InviteLinkTypeByChannel = make(map[string]string)
|
||||
}
|
||||
s.syncChannelMaps()
|
||||
}
|
||||
|
||||
@@ -1560,6 +1689,11 @@ func (s *PurchaseOptionalDetails) syncChannelMaps() {
|
||||
delete(s.FormatByChannel, key)
|
||||
}
|
||||
}
|
||||
for key := range s.InviteLinkTypeByChannel {
|
||||
if _, ok := valid[key]; !ok {
|
||||
delete(s.InviteLinkTypeByChannel, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatPlacementSummary() string {
|
||||
@@ -1573,7 +1707,7 @@ func (s *PurchaseOptionalDetails) formatPlacementDetails() string {
|
||||
if s.PlacementMode != "per_channel" || len(s.Channels) <= 1 {
|
||||
return ""
|
||||
}
|
||||
return "\n" + s.renderParamChannelSummary("placement")
|
||||
return s.renderParamChannelSummary("placement")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatPaymentDateSummary() string {
|
||||
@@ -1587,7 +1721,7 @@ func (s *PurchaseOptionalDetails) formatPaymentDateDetails() string {
|
||||
if s.PaymentDateMode != "per_channel" || len(s.Channels) <= 1 {
|
||||
return ""
|
||||
}
|
||||
return "\n" + s.renderParamChannelSummary("payment_date")
|
||||
return s.renderParamChannelSummary("payment_date")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatCostSummary() string {
|
||||
@@ -1601,7 +1735,7 @@ func (s *PurchaseOptionalDetails) formatCostDetails() string {
|
||||
if s.CostMode != "per_channel" || len(s.Channels) <= 1 {
|
||||
return ""
|
||||
}
|
||||
return "\n" + s.renderParamChannelSummary("cost")
|
||||
return s.renderParamChannelSummary("cost")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatCostBeforeSummary() string {
|
||||
@@ -1615,7 +1749,7 @@ func (s *PurchaseOptionalDetails) formatCostBeforeDetails() string {
|
||||
if s.CostBeforeMode != "per_channel" || len(s.Channels) <= 1 {
|
||||
return ""
|
||||
}
|
||||
return "\n" + s.renderParamChannelSummary("cost_before")
|
||||
return s.renderParamChannelSummary("cost_before")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatPurchaseTypeSummary() string {
|
||||
@@ -1629,7 +1763,7 @@ func (s *PurchaseOptionalDetails) formatPurchaseTypeDetails() string {
|
||||
if s.PurchaseTypeMode != "per_channel" || len(s.Channels) <= 1 {
|
||||
return ""
|
||||
}
|
||||
return "\n" + s.renderParamChannelSummary("purchase_type")
|
||||
return s.renderParamChannelSummary("purchase_type")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatCommentSummary() string {
|
||||
@@ -1643,7 +1777,7 @@ func (s *PurchaseOptionalDetails) formatCommentDetails() string {
|
||||
if s.CommentMode != "per_channel" || len(s.Channels) <= 1 {
|
||||
return ""
|
||||
}
|
||||
return "\n" + s.renderParamChannelSummary("comment")
|
||||
return s.renderParamChannelSummary("comment")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatFormatSummary() string {
|
||||
@@ -1657,7 +1791,21 @@ func (s *PurchaseOptionalDetails) formatFormatDetails() string {
|
||||
if s.FormatMode != "per_channel" || len(s.Channels) <= 1 {
|
||||
return ""
|
||||
}
|
||||
return "\n" + s.renderParamChannelSummary("format")
|
||||
return s.renderParamChannelSummary("format")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatInviteLinkTypeSummary() string {
|
||||
if s.InviteLinkTypeMode == "per_channel" && len(s.Channels) > 1 {
|
||||
return "👥"
|
||||
}
|
||||
return s.inviteLinkTypeLabel(s.InviteLinkType)
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatInviteLinkTypeDetails() string {
|
||||
if s.InviteLinkTypeMode != "per_channel" || len(s.Channels) <= 1 {
|
||||
return ""
|
||||
}
|
||||
return s.renderParamChannelSummary("invite_link_type")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatParamValue(param, channelKey string) string {
|
||||
@@ -1674,17 +1822,22 @@ func (s *PurchaseOptionalDetails) formatParamValue(param, channelKey string) str
|
||||
return s.formatCostBeforeForChannel(channelKey)
|
||||
case "purchase_type":
|
||||
if value, ok := s.PurchaseTypeByChannel[channelKey]; ok && value != "" {
|
||||
return fmt.Sprintf("<b>%s</b>", value)
|
||||
return value
|
||||
}
|
||||
return "—"
|
||||
case "comment":
|
||||
if value, ok := s.CommentByChannel[channelKey]; ok && value != "" {
|
||||
return fmt.Sprintf("<b>%s</b>", value)
|
||||
return value
|
||||
}
|
||||
return "—"
|
||||
case "format":
|
||||
if value, ok := s.FormatByChannel[channelKey]; ok && value != "" {
|
||||
return fmt.Sprintf("<b>%s</b>", value)
|
||||
return value
|
||||
}
|
||||
return "—"
|
||||
case "invite_link_type":
|
||||
if value, ok := s.InviteLinkTypeByChannel[channelKey]; ok && value != "" {
|
||||
return s.inviteLinkTypeLabel(value)
|
||||
}
|
||||
return "—"
|
||||
default:
|
||||
@@ -1698,8 +1851,7 @@ func (s *PurchaseOptionalDetails) formatCostForChannel(channelKey string) string
|
||||
return "—"
|
||||
}
|
||||
label := s.costTypeLabelForEntry(entry)
|
||||
text := fmt.Sprintf("%s %.0f₽", label, *entry.Value)
|
||||
return fmt.Sprintf("<b>%s</b>", text)
|
||||
return fmt.Sprintf("%s %.0f₽", label, *entry.Value)
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatCostBeforeForChannel(channelKey string) string {
|
||||
@@ -1708,8 +1860,7 @@ func (s *PurchaseOptionalDetails) formatCostBeforeForChannel(channelKey string)
|
||||
return "—"
|
||||
}
|
||||
label := s.costBeforeTypeLabelForEntry(entry)
|
||||
text := fmt.Sprintf("%s %.0f₽", label, *entry.Value)
|
||||
return fmt.Sprintf("<b>%s</b>", text)
|
||||
return fmt.Sprintf("%s %.0f₽", label, *entry.Value)
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) hasCopyBuffer(param string) bool {
|
||||
@@ -1728,6 +1879,8 @@ func (s *PurchaseOptionalDetails) hasCopyBuffer(param string) bool {
|
||||
return s.CommentCopy != nil
|
||||
case "format":
|
||||
return s.FormatCopy != nil
|
||||
case "invite_link_type":
|
||||
return s.InviteLinkTypeCopy != nil
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -1756,6 +1909,9 @@ func (s *PurchaseOptionalDetails) hasChannelValue(param, channelKey string) bool
|
||||
case "format":
|
||||
value, ok := s.FormatByChannel[channelKey]
|
||||
return ok && value != ""
|
||||
case "invite_link_type":
|
||||
value, ok := s.InviteLinkTypeByChannel[channelKey]
|
||||
return ok && value != ""
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -1845,6 +2001,28 @@ func (s *PurchaseOptionalDetails) hasCommentValue() bool {
|
||||
return s.Comment != ""
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) hasInviteLinkTypeValue() bool {
|
||||
if s.InviteLinkTypeMode == "per_channel" && len(s.Channels) > 1 {
|
||||
for _, value := range s.InviteLinkTypeByChannel {
|
||||
if value != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return s.InviteLinkType != ""
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) inviteLinkTypeLabel(linkType string) string {
|
||||
if linkType == "public" {
|
||||
return "Открытая"
|
||||
}
|
||||
if linkType == "approval" {
|
||||
return "С заявками"
|
||||
}
|
||||
return "—"
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) costTypeLabelForEntry(entry CostEntry) string {
|
||||
if entry.Type == "cpm" {
|
||||
return "СРМ"
|
||||
@@ -1966,6 +2144,14 @@ func (s *PurchaseOptionalDetails) setFormatValue(value string) {
|
||||
s.FormatByChannel[s.CurrentChannel] = value
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) setInviteLinkTypeValue(value string) {
|
||||
if s.CurrentChannel == "" {
|
||||
s.InviteLinkType = value
|
||||
return
|
||||
}
|
||||
s.InviteLinkTypeByChannel[s.CurrentChannel] = value
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) backAction() string {
|
||||
if s.ReturnMode != "" {
|
||||
return "back_to_return"
|
||||
@@ -2081,6 +2267,11 @@ func (s *PurchaseOptionalDetails) buildPlacementDetails(placementType *string) *
|
||||
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
|
||||
}
|
||||
@@ -2130,6 +2321,11 @@ func (s *PurchaseOptionalDetails) buildChannelDetails(channelKey string) *backen
|
||||
details.Format = &value
|
||||
}
|
||||
}
|
||||
if s.InviteLinkTypeMode == "per_channel" {
|
||||
if value, ok := s.InviteLinkTypeByChannel[channelKey]; ok && value != "" {
|
||||
details.InviteLinkType = &value
|
||||
}
|
||||
}
|
||||
|
||||
return details
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user