954ba8f059
Schema: - online_orders (token-tracked, status workflow, delivery method/fee) - online_order_items (price snapshot, fulfilled flag) - part_cross_refs (OEM/equivalent codes for search) - parts.is_published (shop visibility) Storefront (ShopController, tenant subdomain, /shop): - Catalog with search across name/article/brand/cross-refs, category + in-stock filters, live stock, white-label themed layout - Part detail page with cross-ref codes - VIN search → VinDecoder → guided catalog search - Session cart (per-tenant key), guest checkout, order confirmation page - Respects settings.shop.enabled (404 when off); tenant-guarded Part::searchPublished matches cross-ref articles via whereHas. Order notifications (ShopOrderNotifier, best-effort): - Staff: Web Push to active users - Customer: Telegram if phone matches a linked client Filament (tenant): - OnlineOrderResource under "Magazin" nav group, status workflow, items relation, "Onorează" action issues stock via WarehouseService (FIFO) - PartResource: is_published toggle + column + bulk publish/unpublish + CrossRefsRelationManager - Settings: shop section (enable, delivery methods, fee, free-over) - Landing page: shop button when enabled Tests (6 new): - catalog 404 when disabled; lists published only; cross-ref search; order placement (token + items + total); fulfill issues stock; cross-tenant token isolation Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
59 lines
2.2 KiB
PHP
59 lines
2.2 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Notifications;
|
|
|
|
use App\Models\Central\Company;
|
|
use App\Models\Tenant\Client;
|
|
use App\Models\Tenant\OnlineOrder;
|
|
use App\Models\Tenant\User;
|
|
|
|
/**
|
|
* Notifies staff of a new online order (Web Push) and confirms to the customer
|
|
* via Telegram if they have a linked chat. Best-effort — never throws.
|
|
*/
|
|
class ShopOrderNotifier
|
|
{
|
|
public function __construct(
|
|
private WebPushService $push,
|
|
private TelegramService $telegram,
|
|
) {
|
|
}
|
|
|
|
public function placed(OnlineOrder $order): void
|
|
{
|
|
$company = Company::withoutGlobalScopes()->find($order->company_id);
|
|
if (! $company) return;
|
|
|
|
// ── Staff: Web Push to active users of this tenant ──
|
|
$title = 'Comandă nouă #' . $order->number;
|
|
$body = $order->customer_name . ' · ' . number_format((float) $order->total, 2) . ' '
|
|
. ($company->settings['currency'] ?? 'MDL');
|
|
$url = '/app/resources/online-orders/' . $order->id . '/edit';
|
|
|
|
$userIds = User::where('status', 'active')->pluck('id');
|
|
foreach ($userIds as $uid) {
|
|
$this->push->sendToUser((int) $uid, $title, $body, $url, 'shop-order-' . $order->id);
|
|
}
|
|
|
|
// ── Customer: Telegram if their phone is linked ──
|
|
$needle = Client::normalizePhone($order->customer_phone);
|
|
if ($needle) {
|
|
$client = Client::whereNotNull('telegram_chat_id')
|
|
->whereRaw(
|
|
"REPLACE(REPLACE(REPLACE(REPLACE(phone, ' ', ''), '-', ''), '(', ''), ')', '') LIKE ?",
|
|
['%' . substr($needle, -9) . '%']
|
|
)
|
|
->first();
|
|
|
|
if ($client && $client->telegram_chat_id) {
|
|
$brand = htmlspecialchars($company->display_name ?? $company->name);
|
|
$text = "🛒 <b>Comanda #{$order->number} primită</b>\n"
|
|
. "Total: <b>" . number_format((float) $order->total, 2) . " "
|
|
. ($company->settings['currency'] ?? 'MDL') . "</b>\n\n"
|
|
. "Urmărește statusul: " . $order->trackingUrl() . "\n\n{$brand}";
|
|
$this->telegram->sendMessage($company, (string) $client->telegram_chat_id, $text);
|
|
}
|
|
}
|
|
}
|
|
}
|