47afefbb10
Вопрос владельца: «а цены в нём надо актуализировать руками или он сам тянет?»
Ответ был неприятный: руками. Цифры (300/50 руб, тарифные ступени) стояли текстом
в статьях — поменяли бы цену в админке, бот продолжил бы врать про деньги.
Теперь в статьях стоят метки {{цена_подбора}}, {{цена_изучения}}, {{тарифы}},
а сервис LivePrices подставляет живые значения из system_settings и pricing_tiers
В МОМЕНТ ОТВЕТА — без переиндексации базы знаний. Нет цены в настройках — метка
не протекает клиенту, бот отсылает к экрану, где цена написана всегда.
Из текстов экскурсий цифры цен тоже убраны (фронт не знает БД): шаг подсвечивает
строку с ценой в самом окне — она всегда актуальная.
Проверено живьём: поменял цену в настройках на 450 — бот сразу назвал 450.
Pest 42/42 (3 новых теста), Vitest каталога 8/8.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
71 lines
2.8 KiB
PHP
71 lines
2.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Bot;
|
|
|
|
use App\Support\SystemSettings;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/**
|
|
* Живые цены для ответов бота (вопрос владельца 12.07.2026: «цены надо
|
|
* актуализировать руками или он сам тянет?»).
|
|
*
|
|
* В статьях инструкции цены стоят МЕТКАМИ, а не цифрами: {{цена_подбора}},
|
|
* {{цена_изучения}}, {{тарифы}}. Подстановка происходит в момент ответа —
|
|
* поменяли цену в админке, бот называет новую сразу, без переиндексации.
|
|
*
|
|
* Если цены в настройках нет — метку не показываем клиенту, а мягко отсылаем
|
|
* к экрану, где цена написана всегда.
|
|
*/
|
|
class LivePrices
|
|
{
|
|
private const FALLBACK_PRICE = 'по цене, указанной в самом окне';
|
|
|
|
public function substitute(string $text): string
|
|
{
|
|
if (! str_contains($text, '{{')) {
|
|
return $text;
|
|
}
|
|
|
|
return strtr($text, [
|
|
'{{цена_подбора}}' => $this->rub(SystemSettings::get('autopodbor_price_search_rub')),
|
|
'{{цена_изучения}}' => $this->rub(SystemSettings::get('autopodbor_price_study_rub')),
|
|
'{{тарифы}}' => $this->tiers(),
|
|
]);
|
|
}
|
|
|
|
private function rub(?string $value): string
|
|
{
|
|
if ($value === null || $value === '' || ! is_numeric($value)) {
|
|
return self::FALLBACK_PRICE;
|
|
}
|
|
|
|
return rtrim(rtrim(number_format((float) $value, 2, ',', ' '), '0'), ',').' ₽';
|
|
}
|
|
|
|
/** Сетка ступеней словами: «1-я — 500 ₽ за заявку (первые 100 заявок месяца)». */
|
|
private function tiers(): string
|
|
{
|
|
$rows = DB::table('pricing_tiers')
|
|
->where('is_active', true)
|
|
->orderBy('tier_no')
|
|
->get(['tier_no', 'leads_in_tier', 'price_per_lead_kopecks']);
|
|
|
|
if ($rows->isEmpty()) {
|
|
return 'сетка ступеней показана в разделе «Биллинг»';
|
|
}
|
|
|
|
$parts = [];
|
|
foreach ($rows as $r) {
|
|
$price = $this->rub((string) ((int) $r->price_per_lead_kopecks / 100));
|
|
$count = (int) $r->leads_in_tier;
|
|
$parts[] = $count > 0
|
|
? "ступень {$r->tier_no} — {$price} за заявку (следующие {$count} заявок месяца)"
|
|
: "ступень {$r->tier_no} — {$price} за заявку (весь объём сверх предыдущих)";
|
|
}
|
|
|
|
return implode('; ', $parts);
|
|
}
|
|
}
|