fix some handling 3
This commit is contained in:
@@ -4,6 +4,7 @@ namespace App\Services;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Product;
|
||||
use Hekmatinasser\Verta\Verta;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -29,6 +30,22 @@ class PriceChannelBroadcastService
|
||||
/** وزن هر مثقال به گرم — ضریب تبدیل قیمت هر گرم به هر مثقال (مثقال = گرم × این عدد) */
|
||||
private const DEFAULT_CHANGE_MESGHAL = 4.3318;
|
||||
|
||||
/** قالب پیشفرض پیام کانال — وقتی price_channel_message_text خالی باشد */
|
||||
private const DEFAULT_MESSAGE_TEMPLATE = <<<'TXT'
|
||||
سلام 👋
|
||||
%عنوان
|
||||
📅 %تاریخ 🕐 %ساعت
|
||||
|
||||
🔴 خرید گرم: %گرم_خرید
|
||||
🟢 فروش گرم: %گرم_فروش
|
||||
🔴 خرید مثقال: %مثقال_خرید
|
||||
🟢 فروش مثقال: %مثقال_فروش
|
||||
|
||||
📞 %شماره
|
||||
%آیدی
|
||||
%لینک
|
||||
TXT;
|
||||
|
||||
public function __construct(private readonly ConfigService $config)
|
||||
{
|
||||
}
|
||||
@@ -66,19 +83,19 @@ class PriceChannelBroadcastService
|
||||
}
|
||||
|
||||
$products = $this->eligibleProducts();
|
||||
if ($products === []) {
|
||||
Log::debug('PriceChannel: محصول واجد شرایطی نیست (active/قیمت/ساعت کاری)');
|
||||
$boardProduct = $this->resolveBoardProduct($products);
|
||||
if ($boardProduct === null || (int) ($boardProduct->price ?? 0) <= 0) {
|
||||
Log::debug('PriceChannel: محصول تابلو (آبشده) واجد شرایطی نیست');
|
||||
return;
|
||||
}
|
||||
|
||||
// شرط ۲: قیمتِ نمایشی نسبت به آخرین ارسال واقعاً تغییر کرده باشد؛ وگرنه چیزی نفرست
|
||||
// و تایمر را هم دست نزن (تا سرِ اولین تغییرِ بعدی بلافاصله بفرستد).
|
||||
$signature = $this->priceSignature($products);
|
||||
// شرط ۲: قیمت تابلو نسبت به آخرین ارسال واقعاً تغییر کرده باشد
|
||||
$signature = $this->priceSignature($boardProduct);
|
||||
if ($signature === Cache::get(self::SIGNATURE_CACHE_KEY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = $this->buildMessage($products);
|
||||
$message = $this->buildMessage($products, $boardProduct);
|
||||
if ($message === '') {
|
||||
return;
|
||||
}
|
||||
@@ -111,9 +128,16 @@ class PriceChannelBroadcastService
|
||||
*
|
||||
* @param array<int, Product> $products
|
||||
*/
|
||||
private function priceSignature(array $products): string
|
||||
private function priceSignature(Product $boardProduct): string
|
||||
{
|
||||
return md5($this->buildPriceBody($products));
|
||||
$prices = $this->computeDisplayPrices($boardProduct);
|
||||
|
||||
return md5(implode('|', [
|
||||
$prices['gram_buy'],
|
||||
$prices['gram_sell'],
|
||||
$prices['mesghal_buy'],
|
||||
$prices['mesghal_sell'],
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,8 +161,9 @@ class PriceChannelBroadcastService
|
||||
if ($message !== '' && $targets !== []) {
|
||||
$this->markSent();
|
||||
$products = $this->eligibleProducts();
|
||||
if ($products !== []) {
|
||||
Cache::put(self::SIGNATURE_CACHE_KEY, $this->priceSignature($products), now()->addDay());
|
||||
$boardProduct = $this->resolveBoardProduct($products);
|
||||
if ($boardProduct !== null) {
|
||||
Cache::put(self::SIGNATURE_CACHE_KEY, $this->priceSignature($boardProduct), now()->addDay());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,27 +264,135 @@ class PriceChannelBroadcastService
|
||||
}
|
||||
|
||||
/**
|
||||
* ساخت متن پیام قیمت — مرتب و بر اساس دادهی هر محصول.
|
||||
* ساخت متن پیام قیمت — قالب از price_channel_message_text (یا پیشفرض داخلی).
|
||||
*/
|
||||
public function buildMessage(?array $products = null): string
|
||||
public function buildMessage(?array $products = null, ?Product $boardProduct = null): string
|
||||
{
|
||||
$products = $products ?? $this->eligibleProducts();
|
||||
$body = $this->buildPriceBody($products);
|
||||
if ($body === '') {
|
||||
$boardProduct = $boardProduct ?? $this->resolveBoardProduct($products);
|
||||
if ($boardProduct === null || (int) ($boardProduct->price ?? 0) <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return trim($this->header() . "\n\n" . $body);
|
||||
return $this->renderMessageTemplate($this->messageTemplate(), $products, $boardProduct);
|
||||
}
|
||||
|
||||
private function messageTemplate(): string
|
||||
{
|
||||
$fromConfig = trim((string) $this->config->getString('price_channel_message_text', ''));
|
||||
|
||||
return $fromConfig !== '' ? $fromConfig : self::DEFAULT_MESSAGE_TEMPLATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* بدنهٔ قیمت (بدون هدر/ساعت) — همان چیزی که «تغییر قیمت» را میسازد.
|
||||
* امضای تشخیص تغییر روی همین بدنهی نمایشیِ رُندشده گرفته میشود تا اختلاف چند ریالی
|
||||
* که به یک عدد نمایشیِ یکسان میرسد، پیام تکراری نفرستد.
|
||||
* جایگزینی متغیرهای قالب.
|
||||
* قیمت تابلو: %گرم_خرید %گرم_فروش %مثقال_خرید %مثقال_فروش
|
||||
* زمان: %تاریخ %ساعت — اپ: %اسم_اپ %نام %عنوان
|
||||
* تماس: %آیدی %شماره %لینک — اختیاری: %محصولات %فوتر
|
||||
*
|
||||
* @param array<int, Product> $products
|
||||
*/
|
||||
private function buildPriceBody(array $products): string
|
||||
private function renderMessageTemplate(string $template, array $products, Product $boardProduct): string
|
||||
{
|
||||
$name = trim((string) $this->config->getString('name_fa', ''));
|
||||
$title = $name !== '' ? "📊 قیمت لحظهای {$name}" : '📊 قیمت لحظهای';
|
||||
$prices = $this->computeDisplayPrices($boardProduct);
|
||||
|
||||
$message = $this->applyTemplatePlaceholders($template, [
|
||||
'مثقال_فروش' => $prices['mesghal_sell'],
|
||||
'مثقال_خرید' => $prices['mesghal_buy'],
|
||||
'گرم_فروش' => $prices['gram_sell'],
|
||||
'گرم_خرید' => $prices['gram_buy'],
|
||||
'اسم_اپ' => $name,
|
||||
'محصولات' => $this->buildProductLines($products),
|
||||
'عنوان' => $title,
|
||||
'تاریخ' => Verta::now()->format('Y/n/j'),
|
||||
'ساعت' => now()->format('H:i'),
|
||||
'آیدی' => trim((string) $this->config->getString('price_channel_footer_id', '')),
|
||||
'شماره' => trim((string) $this->config->getString('price_channel_footer_phone', '')),
|
||||
'لینک' => trim((string) $this->config->getString('price_channel_footer_link', '')),
|
||||
'فوتر' => $this->buildFooterBlock(),
|
||||
'نام' => $name,
|
||||
]);
|
||||
|
||||
return preg_replace("/\n{3,}/", "\n\n", trim($message)) ?? trim($message);
|
||||
}
|
||||
|
||||
/** @param array<string, string> $replacements */
|
||||
private function applyTemplatePlaceholders(string $template, array $replacements): string
|
||||
{
|
||||
$keys = array_keys($replacements);
|
||||
usort($keys, static fn (string $a, string $b): int => mb_strlen($b) <=> mb_strlen($a));
|
||||
|
||||
$message = $template;
|
||||
foreach ($keys as $key) {
|
||||
$message = preg_replace(
|
||||
'/[%٪]\s*' . preg_quote($key, '/') . '/u',
|
||||
$replacements[$key],
|
||||
$message
|
||||
) ?? $message;
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* محصول مرجع قیمت تابلو — اول آبشده (گرم)، بعد اولین محصول گرم، بعد اولین واجد شرایط.
|
||||
*
|
||||
* @param array<int, Product> $products
|
||||
*/
|
||||
private function resolveBoardProduct(array $products): ?Product
|
||||
{
|
||||
if ($products === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($products as $product) {
|
||||
if (trim((string) ($product->group_type ?? '')) === 'آبشده' && $this->isGramUnit($product)) {
|
||||
return $product;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($products as $product) {
|
||||
if ($this->isGramUnit($product)) {
|
||||
return $product;
|
||||
}
|
||||
}
|
||||
|
||||
return $products[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* قیمتهای نمایشی تابلو (گرم + مثقال) — همان منطق اپ + price_channel_diff.
|
||||
*
|
||||
* @return array{gram_buy: string, gram_sell: string, mesghal_buy: string, mesghal_sell: string}
|
||||
*/
|
||||
private function computeDisplayPrices(Product $product): array
|
||||
{
|
||||
$channelDiff = (float) $this->config->getInt('price_channel_diff', 0);
|
||||
$diffBuy = (float) $this->config->displayBuyPriceDiffRial();
|
||||
$diffSell = (float) $this->config->displaySellPriceDiffRial();
|
||||
$mesghal = $this->changeMesghal();
|
||||
$base = (float) ($product->price ?? 0);
|
||||
|
||||
// 🔴 خرید (مشتری میفروشد، پایینتر) — 🟢 فروش (مشتری میخرد، بالاتر)
|
||||
$gramBuy = $base - $diffSell - $channelDiff;
|
||||
$gramSell = $base + $diffBuy + $channelDiff;
|
||||
|
||||
return [
|
||||
'gram_buy' => $this->formatPrice($gramBuy),
|
||||
'gram_sell' => $this->formatPrice($gramSell),
|
||||
'mesghal_buy' => $mesghal > 0 ? $this->formatPrice($gramBuy * $mesghal) : '',
|
||||
'mesghal_sell' => $mesghal > 0 ? $this->formatPrice($gramSell * $mesghal) : '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* بدنهٔ قیمت محصولات (بدون فوتر) — مبنای تشخیص تغییر قیمت.
|
||||
*
|
||||
* @param array<int, Product> $products
|
||||
*/
|
||||
private function buildProductLines(array $products): string
|
||||
{
|
||||
if ($products === []) {
|
||||
return '';
|
||||
@@ -283,7 +416,6 @@ class PriceChannelBroadcastService
|
||||
$lines[] = '🔴 خرید ' . $this->formatPrice($low) . ' 🟢 فروش ' . $this->formatPrice($high);
|
||||
|
||||
if ($this->isGramUnit($product) && $mesghal > 0) {
|
||||
// قیمت پایه «هر گرم» است؛ مثقال = گرم × ضریب (کنوانسیون اپ: مثقال = گرم × 4.3318)
|
||||
$lines[] = '💰 هر مثقال: ' . $this->formatPrice($low * $mesghal)
|
||||
. ' / ' . $this->formatPrice($high * $mesghal);
|
||||
}
|
||||
@@ -291,25 +423,22 @@ class PriceChannelBroadcastService
|
||||
$lines[] = '';
|
||||
}
|
||||
|
||||
$footer = $this->footerLines();
|
||||
if ($footer !== []) {
|
||||
$lines[] = '➖➖➖➖➖➖';
|
||||
foreach ($footer as $line) {
|
||||
$lines[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
return trim(implode("\n", $lines));
|
||||
}
|
||||
|
||||
private function header(): string
|
||||
private function buildFooterBlock(): string
|
||||
{
|
||||
$name = trim((string) $this->config->getString('name_fa', ''));
|
||||
$time = now()->format('H:i');
|
||||
$footer = $this->footerLines();
|
||||
if ($footer === []) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$title = $name !== '' ? "📊 قیمت لحظهای {$name}" : '📊 قیمت لحظهای';
|
||||
$lines = ['➖➖➖➖➖➖'];
|
||||
foreach ($footer as $line) {
|
||||
$lines[] = $line;
|
||||
}
|
||||
|
||||
return $title . "\n🕐 ساعت " . $time;
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user