### Install Shopper Framework and Setup Source: https://context7.com/shopperlabs/shopper/llms.txt Instructions for installing the Shopper framework package via Composer, running the interactive installation wizard, creating an admin user, and accessing the admin panel. ```bash # Install the package via Composer composer require shopper/framework # Run the interactive installation wizard php artisan shopper:install # Create your first admin user php artisan shopper:user # Access the admin panel at /cpanel (default prefix) ``` -------------------------------- ### Install Shopper Types Package Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/types/README.md Instructions for installing the @shopper/types package using common Node.js package managers like npm or yarn. ```bash npm install @shopper/types # or yarn add @shopper/types ``` -------------------------------- ### Manage Product Inventory, Pricing, and Media Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/admin/resources/boost/skills/extending-shopper/SKILL.md Provides examples for interacting with Shopper's core domain models, including stock adjustments, multi-currency pricing, and media collection management. ```php $product->setStock(100, $inventory->id); $product->decreaseStock($inventory->id, 5); $product->prices()->create([ 'currency_id' => Currency::where('code', 'USD')->first()->id, 'amount' => 2999, 'compare_amount' => 3999, ]); $product->addMedia($file)->toMediaCollection(config('shopper.media.storage.thumbnail_collection')); ``` -------------------------------- ### Authorization Checks (PHP) Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/admin/resources/boost/skills/shopper-livewire/SKILL.md This PHP code illustrates how to implement authorization checks within the ShopperLabs project. It shows examples of authorizing actions in the `mount` method using `$this->authorize()` and conditionally displaying table actions based on user permissions using `Shopper::auth()->user()->can()`. ```php // In mount $this->authorize('browse_brands'); // In table actions ->visible(Shopper::auth()->user()->can('edit_brands')) ``` -------------------------------- ### Shipping Driver Management and Configuration Source: https://context7.com/shopperlabs/shopper/llms.txt Explains how to interact with the ShippingManager to access built-in carriers like UPS and FedEx, and how to register custom drivers. It includes examples of both runtime driver retrieval and configuration file structure. ```php use Shopper\Shipping\ShippingManager; use Shopper\Shipping\Contracts\ShippingDriver; $shipping = app(ShippingManager::class); $ups = $shipping->driver('ups'); $shipping->extend('dhl', function (string $name): ShippingDriver { return new DHLDriver( apiKey: config('services.dhl.api_key'), accountNumber: config('services.dhl.account'), sandbox: config('services.dhl.sandbox', false) ); }); // config/shopper/shipping.php return [ 'drivers' => [ 'ups' => [ 'enabled' => true, 'credentials' => ['client_id' => env('UPS_CLIENT_ID'), 'client_secret' => env('UPS_CLIENT_SECRET')] ] ] ]; ``` -------------------------------- ### Category Model Management in Shopper Source: https://context7.com/shopperlabs/shopper/llms.txt Illustrates the creation and management of Category models, including hierarchical structures and SEO metadata. It shows how to create parent and child categories, query enabled categories with their products and children, retrieve the full category path, access descendant categories, and get products within a category. ```php 'Electronics', 'slug' => 'electronics', 'description' => 'Electronic devices and accessories', 'is_enabled' => true, 'position' => 1, 'seo_title' => 'Electronics - Best Deals', 'seo_description' => 'Shop the best electronic devices', ]); // Create child categories $audioCategory = Category::create([ 'name' => 'Audio', 'slug' => 'audio', 'parent_id' => $electronics->id, 'is_enabled' => true, 'position' => 1, ]); $headphones = Category::create([ 'name' => 'Headphones', 'slug' => 'headphones', 'parent_id' => $audioCategory->id, 'is_enabled' => true, ]); // Query enabled categories with products $categories = Category::query() ->enabled() ->with(['products', 'children']) ->whereNull('parent_id') // Root categories only ->get(); // Get full category path (for breadcrumbs) echo $headphones->getLabelOptionName(); // "Electronics / Audio / Headphones" // Access descendant categories $allDescendants = $electronics->descendantCategories; // Get products in category $products = $headphones->products; ``` -------------------------------- ### Livewire Page with Filament Table (PHP) Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/admin/resources/boost/skills/shopper-livewire/SKILL.md Example of a Livewire page component that utilizes Filament's Table functionality to display a list of brands. It includes authorization checks, table column definitions, and record actions for editing. This component extends `AbstractPageComponent` and implements `HasActions`, `HasForms`, and `HasTable` interfaces. ```php class Index extends AbstractPageComponent implements HasActions, HasForms, HasTable { use InteractsWithActions; use InteractsWithForms; use InteractsWithTable; public function mount(): void { $this->authorize('browse_brands'); } public function table(Table $table): Table { return $table ->query(resolve(BrandContract::class)::query()->latest()) ->columns([ TextColumn::make('name') ->label(__('shopper::forms.label.name')) ->searchable() ->sortable(), ]) ->recordActions([ Action::make('edit') ->icon(Untitledui::Edit03) ->iconButton() ->action(fn ($record) => $this->dispatch( 'openPanel', component: 'shopper-slide-overs.brand-form', arguments: ['brand' => $record] )), ]); } public function render(): View { return view('shopper::livewire.pages.brand.index') ->title(__('shopper::pages/brands.menu')); } } ``` -------------------------------- ### Configure Discount System with PHP Source: https://context7.com/shopperlabs/shopper/llms.txt Explains how to create and manage discounts using the Discount model. It covers percentage and fixed-amount types, usage limits, date ranges, and eligibility criteria. ```php use Shopper\Core\Models\Discount; use Shopper\Core\Enum\DiscountType; $discount = Discount::create([ 'code' => 'SUMMER20', 'type' => DiscountType::Percentage, 'value' => 20, 'is_active' => true, 'start_at' => now(), 'end_at' => now()->addMonths(3), 'usage_limit' => 100 ]); $fixedDiscount = Discount::create([ 'code' => 'SAVE10', 'type' => DiscountType::FixedAmount, 'value' => 1000, 'is_active' => true ]); if (! $discount->hasReachedLimit()) { $discount->increment('total_use'); } ``` -------------------------------- ### Testing Livewire Components and Business Logic with Pest Source: https://context7.com/shopperlabs/shopper/llms.txt Demonstrates how to test Livewire components, Eloquent model creation, business logic calculations, and dependency injection using Pest PHP. These tests utilize Laravel factories and the Livewire testing utility to ensure component state and model behavior are correct. ```php user = User::factory()->create(); $this->actingAs($this->user); }); it('can display the product list page', function (): void { Product::factory()->count(5)->create(); Livewire::test(ProductIndex::class) ->assertSuccessful() ->assertSee('Products'); }); it('can create a new product', function (): void { $category = Category::factory()->create(); $product = Product::create([ 'name' => 'Test Product', 'slug' => 'test-product', 'type' => ProductType::Standard, 'is_visible' => true, ]); expect($product) ->toBeInstanceOf(Product::class) ->name->toBe('Test Product') ->type->toBe(ProductType::Standard); }); it('can calculate order total', function (): void { $order = Order::factory() ->has(OrderItem::factory()->count(3)->state([ 'quantity' => 2, 'unit_price_amount' => 1000, // $10.00 ])) ->create(); expect($order->total())->toBe(60.0); // 3 items * 2 qty * $10.00 }); it('validates stock when adding to cart', function (): void { $product = Product::factory()->create(); $product->mutateStock(inventoryId: 1, quantity: 5); expect($product->inStock(5))->toBeTrue(); expect($product->inStock(10))->toBeFalse(); }); it('can resolve `Product` from the container using contract', function (): void { $resolved = resolve(ProductContract::class); expect($resolved)->toBe(Product::class); }); ``` -------------------------------- ### Development and Build Commands Source: https://github.com/shopperlabs/shopper/blob/2.x/CLAUDE.md Common CLI commands for testing, code style enforcement, and asset compilation within the Shopper project. ```bash composer test composer test:unit composer test:sqlite composer test:mysql composer test:pgsql composer test:all-databases composer test:types composer test:type-coverage composer test:refactor composer test:lint composer lint composer refactor composer cs npm run dev npm run build npm run prettier ``` -------------------------------- ### Execute development commands Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/admin/resources/boost/skills/shopper-development/SKILL.md Common CLI commands for running tests, static analysis, and code formatting. ```bash composer test:sqlite # Run tests composer test:types # PHPStan composer cs # Rector + Pint + Prettier ``` -------------------------------- ### Define Menu Items Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/sidebar/README.md Use the fluent builder pattern to define menu items, including URLs, icons, authorization logic, and nested sub-items. ```php $group->item('Label') ->url('/path') ->route('route.name') ->icon('heroicon-o-home') ->weight(1) ->badge('New') ->newTab() ->authorize(fn() => auth()->check()) ->items(function ($items) { $items->item('Sub Item')->url('/sub'); }); ``` -------------------------------- ### Write feature tests Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/admin/resources/boost/skills/shopper-development/SKILL.md Tests use Pest and Livewire testing utilities to verify component behavior and state changes. ```php beforeEach(function (): void { $this->user = User::factory()->create(); }); it('creates a product', function (): void { $this->actingAs($this->user); Livewire::test(ProductForm::class) ->set('data.name', 'Test') ->call('save') ->assertHasNoErrors(); })->group('products'); ``` -------------------------------- ### Resolving Shopper Models Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/admin/resources/boost/skills/shopper-development/MODELS.md Demonstrates how to retrieve models using either the service container via contracts or direct static class calls. Using contracts is the recommended approach to ensure compatibility with swapped model implementations. ```php use Shopper\Core\Models\Contracts\Product as ProductContract; // Via contract (recommended) resolve(ProductContract::class)::query()->get(); // Via class (static calls are proxied) Product::query()->get(); Product::find(1); ``` -------------------------------- ### Access Sidebar Helper Functions Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/sidebar/README.md A set of namespaced PHP helper functions are available to retrieve configuration values and sidebar settings programmatically. ```php \Shopper\Sidebar\sidebar_config($key, $default); \Shopper\Sidebar\sidebar_width(); \Shopper\Sidebar\sidebar_collapsed_width(); \Shopper\Sidebar\sidebar_breakpoint(); \Shopper\Sidebar\sidebar_is_collapsible(); ``` -------------------------------- ### Extending Existing Models Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/admin/resources/boost/skills/shopper-development/MODELS.md Instructions for extending a core Shopper model with custom logic. After extending the class, the new model must be registered in the configuration file to override the default implementation. ```php namespace App\Models; use Shopper\Core\Models\Product as ShopperProduct; class Product extends ShopperProduct { public function isPublished(): bool { return parent::isPublished() && $this->custom_condition; } } // In config/shopper/models.php: 'product' => App\Models\Product::class, ``` -------------------------------- ### SlideOver Configuration (PHP) Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/admin/resources/boost/skills/shopper-livewire/SKILL.md This PHP code provides configuration options for SlideOver components in ShopperLabs. It defines static methods to set the maximum width of the panel and to control whether the panel should close when clicking outside of it. The `panelMaxWidth` method accepts predefined size strings. ```php public static function panelMaxWidth(): string { return '2xl'; // sm, md, lg, xl, 2xl, 3xl, 4xl, 5xl, 6xl, 7xl } public static function closePanelOnClickAway(): bool { return false; } ``` -------------------------------- ### Build Admin Sidebars with PHP Source: https://context7.com/shopperlabs/shopper/llms.txt Demonstrates how to register and define custom admin panel navigation menus using the SidebarManager. It supports grouping, icons, authorization checks, and dynamic badges. ```php use Shopper\Sidebar\SidebarManager; use Shopper\Sidebar\Contracts\Sidebar; use Shopper\Sidebar\Contracts\Builder\Menu; class AppServiceProvider extends ServiceProvider { public function boot(): void { $sidebarManager = app(SidebarManager::class); $sidebarManager->register(AdminSidebar::class); } } class AdminSidebar implements Sidebar { public function extendWith(Menu $menu): Menu { $menu->group(__('Shop'), function ($group) { $group->item(__('Products')) ->setIcon('heroicon-o-cube', 'blade', 'size-5') ->route('shopper.products.index') ->authorize(fn () => auth()->user()->can('browse_products')) ->badge(fn ($badge) => $badge->setValue(Product::count())->setClass('bg-primary-100')); }); return $menu; } } ``` -------------------------------- ### Import and Use Shopper Models Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/types/README.md Demonstrates how to import specific TypeScript models from the package and apply them to local variables. This ensures that objects conform to the expected structure defined in the OpenAPI specification. ```typescript import type { Product, Order, Customer } from '@shopper/types' const product: Product = { id: 1, name: 'My Product', slug: 'my-product' } ``` -------------------------------- ### Build Livewire components Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/admin/resources/boost/skills/shopper-development/SKILL.md Livewire components use specific traits for form and action handling, and leverage computed properties for data retrieval. ```php class ProductForm extends Component implements HasActions, HasForms { use InteractsWithActions; use InteractsWithForms; public ?array $data = []; #[Computed] public function categories(): Collection { return resolve(CategoryContract::class)::query()->get(); } public function render(): View { return view('shopper::livewire.product-form'); } } ``` -------------------------------- ### Manage Sidebar State with JavaScript Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/sidebar/README.md The $store.sidebar object provides reactive state properties and methods to control sidebar visibility and collapse behavior across mobile and desktop devices. ```javascript // State $store.sidebar.isOpen // Mobile: sidebar visibility $store.sidebar.isCollapsed // Desktop: collapsed state $store.sidebar.collapsible // Whether collapse is enabled // Methods $store.sidebar.toggle() // Smart toggle $store.sidebar.open() // Open sidebar (mobile) $store.sidebar.close() // Close sidebar (mobile) $store.sidebar.collapse() // Collapse sidebar (desktop) $store.sidebar.expand() // Expand sidebar (desktop) $store.sidebar.toggleGroup(label) ``` -------------------------------- ### Configure Custom Routes Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/admin/resources/boost/skills/extending-shopper/SKILL.md Demonstrates how to register custom route files in the Shopper configuration and define routes within those files using Laravel's routing syntax. ```php // config/shopper/routes.php return [ 'custom_file' => base_path('routes/shopper.php'), ]; // routes/shopper.php use App\Livewire\Shopper\Shipping; Route::get('shipping', Shipping::class)->name('shopper.shipping.index'); ``` -------------------------------- ### PHP Coding Standards and PHPDoc Source: https://github.com/shopperlabs/shopper/blob/2.x/CLAUDE.md Required syntax for strict type declarations and best practices for PHPDoc annotations to ensure code quality and IDE support. ```php */ public function brand(): BelongsTo ``` -------------------------------- ### Defining and Implementing Model Contracts Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/admin/resources/boost/skills/shopper-development/MODELS.md Shows the process of defining a model interface and implementing it within a concrete Eloquent model. This pattern allows the application to swap underlying model classes via configuration. ```php interface Warehouse { public function isDefault(): bool; public function inventories(): HasMany; } class Warehouse extends Model implements WarehouseContract { use HasFactory; use HasModelContract; public function isDefault(): bool { return $this->is_default; } public function inventories(): HasMany { return $this->hasMany(config('shopper.models.inventory'), 'warehouse_id'); } } ``` -------------------------------- ### Product Model Management in Shopper Source: https://context7.com/shopperlabs/shopper/llms.txt Demonstrates how to create, update, and query Product models in Shopper. It covers setting prices, assigning categories and brands, retrieving prices for specific currencies, and querying published products. It also shows how to check product types and access variants. ```php 'Premium Wireless Headphones', 'slug' => 'premium-wireless-headphones', 'sku' => 'WH-001', 'description' => 'High-quality wireless headphones with noise cancellation', 'type' => ProductType::Standard, 'is_visible' => true, 'featured' => true, 'published_at' => now(), ]); // Set product price for a currency $product->prices()->create([ 'currency_id' => 1, // USD 'amount' => 19999, // $199.99 in cents ]); // Add product to categories $product->categories()->attach([1, 2, 3]); // Add to a brand $product->update(['brand_id' => 1]); // Get product price for current currency $price = $product->getPrice('USD'); echo $price->amount; // 19999 // Query published products $publishedProducts = Product::query() ->publish() ->forChannel(['web', 'mobile']) ->with(['brand', 'categories', 'prices']) ->get(); // Check product type if ($product->isStandard()) { // Standard product logic } if ($product->canUseVariants()) { // Product with variants (type = Variant) $variants = $product->variants; } ``` -------------------------------- ### PHP Model Definition with Shopper Conventions Source: https://github.com/shopperlabs/shopper/blob/2.x/CLAUDE.md Defines a Product model adhering to ShopperLabs conventions, including protected guarded properties, casts method, table name resolution via a helper, and trait composition for media, slugs, and prices. ```php class Product extends Model implements ProductContract, Priceable, SpatieHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMediaHasMedia ``` -------------------------------- ### Defining Model Relationships Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/admin/resources/boost/skills/shopper-development/MODELS.md Best practices for defining relationships in Shopper models. Always use the config() helper to resolve related model classes, ensuring that swapped models are correctly referenced. ```php public function brand(): BelongsTo { return $this->belongsTo(config('shopper.models.brand'), 'brand_id'); } ``` -------------------------------- ### Implement swappable models Source: https://github.com/shopperlabs/shopper/blob/2.x/packages/admin/resources/boost/skills/shopper-development/SKILL.md Models in Shopper implement specific contracts and use traits to support the swappable model pattern, allowing for flexible configuration and table naming. ```php class Product extends Model implements ProductContract { use HasFactory; use HasModelContract; use HasSlug; protected $guarded = []; public static function configKey(): string { return 'product'; } public function getTable(): string { return shopper_table('products'); } protected function casts(): array { return ['featured' => 'boolean']; } } ``` -------------------------------- ### Create and Manage Orders in PHP Source: https://context7.com/shopperlabs/shopper/llms.txt Demonstrates how to create a new order, add items, set addresses, update statuses, and query orders using the Shopper Order Model. It utilizes enums for status tracking and provides methods for calculating totals. ```php shopper_order_number(), // Auto-generated 'price_amount' => 259.99, 'tax_amount' => 20.80, 'currency_code' => 'USD', 'customer_id' => $user->id, 'channel_id' => 1, 'payment_method_id' => 1, 'notes' => 'Gift wrap requested', ]); // Add order items $order->items()->create([ 'name' => 'Premium Wireless Headphones', 'sku' => 'WH-001', 'quantity' => 1, 'unit_price_amount' => 19999, 'total_amount' => 19999, 'product_id' => $product->id, ]); // Add shipping and billing addresses $order->update([ 'shipping_address_id' => $shippingAddress->id, 'billing_address_id' => $billingAddress->id, ]); // Check order status if ($order->isNew()) { // Process new order } if ($order->isPaid()) { // Payment received } if ($order->canBeCancelled()) { $order->update([ 'status' => OrderStatus::Cancelled, 'cancelled_at' => now(), ]); } // Update order statuses $order->update([ 'status' => OrderStatus::Processing, 'payment_status' => PaymentStatus::Paid, 'shipping_status' => ShippingStatus::Shipped, ]); // Query orders $pendingOrders = Order::query() ->notArchived() ->where('status', OrderStatus::New) ->with(['customer', 'items', 'shippingAddress']) ->latest() ->get(); // Calculate order total $total = $order->total(); // Sum of all items ```