правки
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -15,48 +15,52 @@ import (
|
||||
)
|
||||
|
||||
type PurchaseOptionalDetails struct {
|
||||
ProjectID string
|
||||
ProjectTitle string
|
||||
CreativeID string
|
||||
CreativeTitle string
|
||||
Channels []PurchaseChannelInput
|
||||
PlacementDateTime *time.Time
|
||||
PaymentDate *time.Time
|
||||
CostType string
|
||||
CostValue *float64
|
||||
CostBeforeType string
|
||||
CostBeforeBargain *CostEntry
|
||||
PurchaseType string
|
||||
Format string
|
||||
Comment string
|
||||
InputMode string
|
||||
CurrentParam string
|
||||
CurrentChannel string
|
||||
ParamPage int
|
||||
ReturnMode string
|
||||
PlacementMode string
|
||||
PaymentDateMode string
|
||||
CostMode string
|
||||
CostBeforeMode string
|
||||
PurchaseTypeMode string
|
||||
CommentMode string
|
||||
FormatMode string
|
||||
PlacementByChannel map[string]*time.Time
|
||||
PaymentDateByChannel map[string]*time.Time
|
||||
CostByChannel map[string]CostEntry
|
||||
CostBeforeByChannel map[string]CostEntry
|
||||
PurchaseTypeByChannel map[string]string
|
||||
CommentByChannel map[string]string
|
||||
FormatByChannel map[string]string
|
||||
PlacementCopy *time.Time
|
||||
PaymentDateCopy *time.Time
|
||||
CostCopy *CostEntry
|
||||
CostBeforeCopy *CostEntry
|
||||
PurchaseTypeCopy *string
|
||||
CommentCopy *string
|
||||
FormatCopy *string
|
||||
ChannelEditMode string // "edit" или "copy"
|
||||
BackState bot.State
|
||||
ProjectID string
|
||||
ProjectTitle string
|
||||
CreativeID string
|
||||
CreativeTitle string
|
||||
Channels []PurchaseChannelInput
|
||||
PlacementDateTime *time.Time
|
||||
PaymentDate *time.Time
|
||||
CostType string
|
||||
CostValue *float64
|
||||
CostBeforeType string
|
||||
CostBeforeBargain *CostEntry
|
||||
PurchaseType string
|
||||
Format string
|
||||
Comment string
|
||||
InviteLinkType string
|
||||
InputMode string
|
||||
CurrentParam string
|
||||
CurrentChannel string
|
||||
ParamPage int
|
||||
ReturnMode string
|
||||
PlacementMode string
|
||||
PaymentDateMode string
|
||||
CostMode string
|
||||
CostBeforeMode string
|
||||
PurchaseTypeMode string
|
||||
CommentMode string
|
||||
FormatMode string
|
||||
InviteLinkTypeMode string
|
||||
PlacementByChannel map[string]*time.Time
|
||||
PaymentDateByChannel map[string]*time.Time
|
||||
CostByChannel map[string]CostEntry
|
||||
CostBeforeByChannel map[string]CostEntry
|
||||
PurchaseTypeByChannel map[string]string
|
||||
CommentByChannel map[string]string
|
||||
FormatByChannel map[string]string
|
||||
InviteLinkTypeByChannel map[string]string
|
||||
PlacementCopy *time.Time
|
||||
PaymentDateCopy *time.Time
|
||||
CostCopy *CostEntry
|
||||
CostBeforeCopy *CostEntry
|
||||
PurchaseTypeCopy *string
|
||||
CommentCopy *string
|
||||
FormatCopy *string
|
||||
InviteLinkTypeCopy *string
|
||||
ChannelEditMode string // "edit" или "copy"
|
||||
BackState bot.State
|
||||
}
|
||||
|
||||
type CostEntry struct {
|
||||
@@ -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