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>
This commit is contained in:
@@ -55,6 +55,10 @@ class Settings extends Page
|
||||
'telegram_bot_token' => data_get($settings, 'telegram.bot_token'),
|
||||
'reminder_after_days' => data_get($settings, 'reminder.after_days', 365),
|
||||
'reminder_cooldown_days' => data_get($settings, 'reminder.cooldown_days', 30),
|
||||
'shop_enabled' => data_get($settings, 'shop.enabled', false),
|
||||
'shop_delivery_methods' => data_get($settings, 'shop.delivery_methods', ['pickup']),
|
||||
'shop_delivery_fee' => data_get($settings, 'shop.delivery_fee', 0),
|
||||
'shop_free_delivery_over' => data_get($settings, 'shop.free_delivery_over', 0),
|
||||
'ai_default_provider' => $settings['ai']['default_provider'] ?? 'claude',
|
||||
'ai_claude_key' => $settings['ai']['claude_key'] ?? null,
|
||||
'ai_gpt_key' => $settings['ai']['gpt_key'] ?? null,
|
||||
@@ -167,6 +171,19 @@ class Settings extends Page
|
||||
->minValue(7)
|
||||
->default(30),
|
||||
]),
|
||||
Schemas\Components\Section::make('Magazin online')
|
||||
->description('Activează magazinul public la <slug>.service.mir.md/shop. Piesele apar doar dacă sunt marcate „Publicat".')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Toggle::make('shop_enabled')->label('Magazin activ')->columnSpanFull(),
|
||||
Forms\Components\CheckboxList::make('shop_delivery_methods')
|
||||
->label('Metode de livrare')
|
||||
->options(\App\Models\Tenant\OnlineOrder::DELIVERY)
|
||||
->default(['pickup'])
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('shop_delivery_fee')->label('Taxă livrare')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('shop_free_delivery_over')->label('Livrare gratuită peste')->numeric()->default(0)->helperText('0 = dezactivat'),
|
||||
]),
|
||||
Schemas\Components\Section::make('Asistent AI')
|
||||
->description('Adaugă chei API ca să activezi asistentul. Cheile rămân la voi — nu sunt partajate.')
|
||||
->columns(2)
|
||||
@@ -218,6 +235,12 @@ class Settings extends Page
|
||||
'after_days' => (int) ($data['reminder_after_days'] ?? 365),
|
||||
'cooldown_days' => (int) ($data['reminder_cooldown_days'] ?? 30),
|
||||
],
|
||||
'shop' => [
|
||||
'enabled' => (bool) ($data['shop_enabled'] ?? false),
|
||||
'delivery_methods' => array_values((array) ($data['shop_delivery_methods'] ?? ['pickup'])),
|
||||
'delivery_fee' => (float) ($data['shop_delivery_fee'] ?? 0),
|
||||
'free_delivery_over' => (float) ($data['shop_free_delivery_over'] ?? 0),
|
||||
],
|
||||
'ai' => [
|
||||
'default_provider' => $data['ai_default_provider'] ?? 'claude',
|
||||
'claude_key' => $data['ai_claude_key'] ?? null,
|
||||
|
||||
Reference in New Issue
Block a user