Files
autocrm/app/Services/Notifications/ShopOrderNotifier.php
T
Vasyka 954ba8f059 Stage 12 — Online Store: public catalog + cart + orders
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>
2026-05-28 05:27:51 +00:00

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);
}
}
}
}