> ## Documentation Index
> Fetch the complete documentation index at: https://docs.laravelshopper.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Locations

> Manage inventory locations, warehouses, and fulfillment origins for multi-location stock tracking.

Locations represent physical places where you store inventory and ship orders from: warehouses, retail stores, fulfillment centers, or even your apartment. Each location has its own address and can track stock independently, so a product can have 50 units in your Paris warehouse and 20 in your New York store.

When Shopper is first installed, the [setup wizard](/v2/setup-store) creates a default location using your store's address. All stock mutations target this location unless you specify another one. If your business ships from multiple places, you can add more locations and assign stock to each one.

## Model

The model used is `Shopper\Core\Models\Inventory`. It implements `Shopper\Core\Models\Contracts\Inventory` and is configurable via `config/shopper/models.php`.

### Extending the Model

To add custom behavior, extend the model and update your configuration:

```php theme={null}
namespace App\Models;

use Shopper\Core\Models\Inventory as BaseInventory;

class Inventory extends BaseInventory
{
}
```

Update `config/shopper/models.php`:

```php theme={null}
return [
    'inventory' => \App\Models\Inventory::class,
];
```

## Database Schema

| Column                | Type          | Nullable | Default | Description                     |
| --------------------- | ------------- | -------- | ------- | ------------------------------- |
| `id`                  | bigint        | no       | auto    | Primary key                     |
| `name`                | string        | no       | -       | Location name                   |
| `code`                | string        | no       | -       | Unique identifier code          |
| `description`         | text          | yes      | null    | Location description            |
| `email`               | string        | no       | -       | Location contact email (unique) |
| `street_address`      | string        | no       | -       | Street address                  |
| `street_address_plus` | string        | yes      | null    | Additional address line         |
| `postal_code`         | string        | no       | -       | Postal/ZIP code                 |
| `city`                | string        | no       | -       | City                            |
| `state`               | string        | yes      | null    | State/province/region           |
| `phone_number`        | string        | yes      | null    | Phone number                    |
| `priority`            | integer       | no       | 0       | Fulfillment priority order      |
| `latitude`            | decimal(11,5) | yes      | null    | GPS latitude                    |
| `longitude`           | decimal(10,5) | yes      | null    | GPS longitude                   |
| `is_default`          | boolean       | no       | false   | Default location for stock      |
| `country_id`          | bigint        | no       | -       | FK to countries table           |
| `created_at`          | timestamp     | yes      | null    | Creation timestamp              |
| `updated_at`          | timestamp     | yes      | null    | Last update timestamp           |

## Relationships

### Country

Each location belongs to a country:

```php theme={null}
$location->country; // Country model
```

### Inventory Histories

Every stock mutation (increase, decrease, set) on any product or variant creates an `InventoryHistory` record tied to a specific location. This is how Shopper tracks stock per location.

```php theme={null}
$location->histories; // Collection of InventoryHistory records
```

## Query Scopes

The `default` scope returns the location marked as the default inventory. This is the location used by `InitialQuantityInventory` when setting stock during product creation.

```php theme={null}
use Shopper\Core\Models\Inventory;

$defaultLocation = Inventory::query()->default()->first();
```

## How Locations Connect to Stock

When you call `mutateStock()`, `decreaseStock()`, or `setStock()` on a [product](/v2/products#modifying-stock) or [variant](/v2/product-variants#modifying-stock), you pass an `$inventoryId`. This ties the stock mutation to a specific location:

```php theme={null}
$product->mutateStock(
    inventoryId: $warehouse->id,
    quantity: 100,
    event: 'Shipment received',
);
```

To get stock for a product at a specific location:

```php theme={null}
$product->stockInventory($warehouse->id);
```

To batch-load stock for a specific location across multiple products (avoiding N+1):

```php theme={null}
use Shopper\Models\Product;

$products = Product::query()->get();

Product::loadStockForInventory($products, $warehouse->id);
```

## Creating Locations

To create a new warehouse location:

```php theme={null}
use Shopper\Core\Models\Inventory;
use Shopper\Core\Models\Country;

$france = Country::query()->where('cca2', 'FR')->first();

$warehouse = Inventory::query()->create([
    'name' => 'Paris Warehouse',
    'code' => 'WH-PARIS',
    'email' => 'paris@mystore.com',
    'street_address' => '15 Rue du Commerce',
    'city' => 'Paris',
    'postal_code' => '75015',
    'country_id' => $france->id,
    'priority' => 1,
    'latitude' => 48.8466,
    'longitude' => 2.2958,
]);
```

## Retrieving Locations

To get the default location:

```php theme={null}
$default = Inventory::query()->default()->first();
```

To get all locations ordered by priority:

```php theme={null}
$locations = Inventory::query()
    ->with('country')
    ->orderBy('priority')
    ->get();
```

To find the nearest location to a customer (if latitude/longitude are set):

```php theme={null}
$locations = Inventory::query()
    ->whereNotNull('latitude')
    ->whereNotNull('longitude')
    ->get()
    ->sortBy(function ($location) use ($customerLat, $customerLng) {
        return sqrt(
            pow($location->latitude - $customerLat, 2)
            + pow($location->longitude - $customerLng, 2)
        );
    });

$nearest = $locations->first();
```

## Default Location

The default location is used as the origin address for shipping calculations and as the fallback when stock operations do not specify a location. Only one location can be the default.

<Warning>
  Deleting a location removes all inventory history records tied to it. This affects stock levels for every product that had stock in that location. Proceed with caution.
</Warning>

## Components

To customize the admin UI for location management:

```bash theme={null}
php artisan shopper:component:publish setting
```

Location-related components in `config/shopper/components/setting.php`:

```php theme={null}
use Shopper\Livewire;

return [
    'pages' => [
        'location-index' => Livewire\Pages\Settings\Locations\Index::class,
        'location-create' => Livewire\Pages\Settings\Locations\Create::class,
        'location-edit' => Livewire\Pages\Settings\Locations\Edit::class,
    ],
    'components' => [
        'settings.locations.form' => Livewire\Components\Settings\Locations\InventoryForm::class,
    ],
];
```
