Files
boursecu_server/app/Services/PriceTriggerService.php
T
mohmdsdgandCursor 5b2ed2e5fc fix(price-trigger): restrict notifications to SMS only
Remove email/in-app paths and enforce sms action on create/update so
price alerts match the product requirement for SMS-only delivery.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-19 19:02:46 +03:30

179 lines
5.4 KiB
PHP

<?php
namespace App\Services;
use App\Http\Controllers\Controller;
use App\Models\Customer;
use App\Models\Price;
use App\Models\PriceTrigger;
use App\Models\Product;
use Illuminate\Support\Facades\Log;
class PriceTriggerService
{
public function evaluateActive(array $payload = []): void
{
$triggers = PriceTrigger::query()->where('is_active', true)->get();
if ($triggers->isEmpty()) {
return;
}
$config = app(ConfigService::class);
$display = app(ProductDisplayPriceService::class);
$lastPrice = Price::query()->latest('id')->first();
$boardPrice = (float) ($payload['price'] ?? $lastPrice?->price ?? 0);
$productsById = collect();
if (PriceTrigger::hasProductIdColumn()) {
$ids = $triggers->pluck('product_id')->filter()->unique()->all();
if ($ids !== []) {
$productsById = Product::query()->whereIn('id', $ids)->get()->keyBy('id');
if ($productsById->isNotEmpty()) {
$display->warmLivePriceCache();
$display->enrichForApi($productsById);
}
}
}
foreach ($triggers as $trigger) {
try {
$this->tryFire($trigger, $productsById, $display, $config, $boardPrice);
} catch (\Throwable $e) {
Log::warning('Price trigger evaluate failed', [
'id' => $trigger->id,
'message' => $e->getMessage(),
]);
}
}
}
private function tryFire(
PriceTrigger $trigger,
$productsById,
ProductDisplayPriceService $display,
ConfigService $config,
float $boardPrice
): void {
$product = $this->resolveProduct($trigger, $productsById);
$current = $this->resolveCurrentPrice($trigger, $product, $display, $config, $boardPrice);
if ($current <= 0) {
return;
}
if (! $this->conditionMet((string) $trigger->price_condition, $current, (float) $trigger->target_price)) {
return;
}
$customer = Customer::query()->find($trigger->customer_id);
if (! $customer) {
return;
}
$this->notify($trigger, $customer, $product, $current);
$trigger->is_active = false;
$trigger->triggered_at = now();
$trigger->save();
}
private function resolveProduct(PriceTrigger $trigger, $productsById): ?Product
{
if (! PriceTrigger::hasProductIdColumn()) {
return null;
}
$productId = (int) ($trigger->product_id ?? 0);
if ($productId <= 0) {
return null;
}
$product = $productsById->get($productId);
if ($product instanceof Product) {
return $product;
}
return Product::query()->find($productId);
}
private function resolveCurrentPrice(
PriceTrigger $trigger,
?Product $product,
ProductDisplayPriceService $display,
ConfigService $config,
float $boardPrice
): float {
if ($product instanceof Product) {
$enriched = (int) (($product->price ?? 0) + ($product->price_profit ?? 0));
if ($enriched > 0) {
return (float) $enriched;
}
if ($display->usesAppPrice($product)) {
$base = $boardPrice > 0 ? $boardPrice : $display->resolveOrderBasePrice($product);
if ($base <= 0) {
return 0.0;
}
$trade = $config->buildTradeDisplayPrices((float) $base, $config->priceProvider());
$direction = (int) ($trigger->direction ?? 0);
return (float) max(0, (int) ($direction === 2 ? $trade['sell'] : $trade['buy']));
}
return max(0.0, (float) $display->resolveOrderBasePrice($product));
}
if ($boardPrice <= 0) {
return 0.0;
}
$direction = (int) ($trigger->direction ?? $trigger->dirction ?? 0);
$diff = $direction === 1
? $config->displaySellPriceDiffRial()
: ($direction === 2 ? $config->displayBuyPriceDiffRial() : 0.0);
if ($direction === 1) {
return max(0.0, $boardPrice - $diff);
}
if ($direction === 2) {
return $boardPrice + $diff;
}
return $boardPrice;
}
private function conditionMet(string $op, float $current, float $target): bool
{
return match ($op) {
'>' => $current > $target,
'<' => $current < $target,
'>=', 'gte' => $current >= $target,
'<=', 'lte' => $current <= $target,
'==', '=' => abs($current - $target) < 1,
default => false,
};
}
private function notify(PriceTrigger $trigger, Customer $customer, ?Product $product, float $current): void
{
$title = trim((string) ($product?->title ?? ''));
if ($title === '') {
$title = 'طلا';
}
$shopName = (string) Controller::Config('name_fa');
$message = 'قیمت ' . $title . ' به ' . number_format((int) $current) . " رسید\n" . $shopName;
if (! Controller::isSmsNotifEnabled('sms_notif_price_trigger')) {
return;
}
if (trim((string) ($customer->mobile ?? '')) === '') {
return;
}
Controller::Send($customer->mobile, $message);
}
}