Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 102 additions & 12 deletions packages/address/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@

# Moox Address

Address is a simple Moox Entity, that can be used to create and manage addresses.
Address is a simple Moox Entity that can be used to create and manage postal addresses and assign them to owners via a morph pivot.

## Features

<!--features-->

- Title with Slug
- Active (Toggle)
- Description (Editor)
- Custom Properties (Key-Value)
- Author (User)
- UUID
- ULID
- Postal fields (street, city, country, etc.)
- Optional label and primary flag
- Custom data (JSON)
- Duplicate detection (fingerprint)
- Morph assignments with billing, postal, and delivery roles
- Soft delete
- Taxonomies
- Filament resource with relation manager

<!--/features-->

Expand All @@ -36,10 +36,6 @@ Curious what the install command does? See [Installation](https://github.com/moo

![Moox Address](https://github.com/mooxphp/moox/raw/main/art/screenshots/record.jpg)

## Get Started

See [Get Started](docs/GetStarted.md).

## Changelog

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
Expand All @@ -59,3 +55,97 @@ Thanks to so many [people for their contributions](https://github.com/mooxphp/mo
## License

The MIT License (MIT). Please see [our license and copyright information](https://github.com/mooxphp/moox/blob/main/LICENSE.md) for more information.

## The Address Model

The `Address` model (`Moox\Address\Models\Address`) stores normalized postal data. It extends `BaseItemModel`, uses soft deletes, and supports taxonomies via `HasModelTaxonomy`.

### Attributes

#### Base Fields

- `label` (string, 120) - Optional internal label (e.g. Headquarter, Warehouse)
- `name` (string, 160) - Recipient or company name on the address
- `street` (string, 160) - Street and house number
- `street2` (string, 160) - Additional address line (suite, building, etc.)
- `postal_code` (string, 20) - Postal / ZIP code
- `city` (string, 120) - City
- `state` (string, 120) - State, region, or province
- `country_code` (string, 2) - ISO 3166-1 alpha-2 country code (stored uppercase)
- `is_primary` (boolean) - Marks this address as primary (default: false)
- `data` (json) - Flexible key-value payload for custom metadata
- `deleted_at` (datetime) - Soft-delete timestamp
- `created_at` (datetime) - Creation timestamp
- `updated_at` (datetime) - Last update timestamp

Validation rules live in `Moox\Address\Support\AddressRules` and are applied in the Filament resource.

#### Duplicate detection

Duplicate addresses are blocked on save via `DuplicateAddressException`. Comparison uses `AddressFingerprint` with these columns only:

- `street`
- `street2`
- `postal_code`
- `country_code`

Not part of the fingerprint: `label`, `name`, `city`, `state`, `is_primary`, and `data`. Two records with the same street, postal code, and country but different city or name are still treated as duplicates.

Empty strings are normalized to `null` before comparison. `country_code` is trimmed and uppercased on save.

### Methods

#### Formatting

- `formattedLine()` - Single-line summary: name, street lines, postal code + city, country code

#### Duplicate detection

- `findDuplicate()` - Returns an existing `Address` with the same fingerprint, or null
- `scopeWithFingerprint()` - Query scope for fingerprint lookup

### Relationships

- `addressables()` - Pivot rows linking this address to owners

## The Addressable Pivot

Assignments between an address and an owner (company, contact, user, etc.) live on `addressables`, not on `addresses`. Roles are pivot flags, not columns on the address itself.

### Attributes

#### Pivot Fields

- `addressable_type` (uuid morph) - Owner model class
- `addressable_id` (uuid) - Owner primary key
- `address_id` (foreignId) - References `addresses.id` (cascade on delete)
- `billing_address` (boolean) - Use as billing address (default: false)
- `postal_address` (boolean) - Use as postal address (default: false)
- `delivery_address` (boolean) - Use as delivery address (default: false)
- `created_at` (datetime) - Creation timestamp
- `updated_at` (datetime) - Last update timestamp

Unique constraint: `(addressable_type, addressable_id, address_id)`.

### Methods

- `activeRoles()` - Active role keys: `billing`, `postal`, `delivery`

### Relationships

- `addressable()` - Owner (`MorphTo`)
- `address()` - Linked `Address` (`BelongsTo`)

### Owner trait

Models that can own addresses use `Moox\Address\Concerns\HasAddresses`:

- `addresses()` - `MorphToMany` via `addressables`, with pivot columns from `config('address.relations.addressables')`

Register allowed owner types under `address.relations.addressables.owner_types` in `config/address.php`.

### Translations

Field labels for the admin UI are in `resources/lang/{locale}/fields.php` (e.g. `address::fields.street`). Entity titles use `address::address.*`.

There are no translatable model attributes on `Address`; all address fields are stored on the main table.
10 changes: 3 additions & 7 deletions packages/address/config/address.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
use Moox\Address\Models\Addressable;
use Moox\Category\Resources\CategoryResource;
use Moox\Media\Resources\MediaResource;
use Moox\News\Moox\Entities\News\News\NewsResource;
use Moox\Tag\Resources\TagResource;
use Moox\User\Models\User;
use Moox\User\Resources\UserResource;
Expand Down Expand Up @@ -78,9 +77,6 @@

'scopes' => [
'allowed' => [
'news' => [
'resource' => NewsResource::class,
],
'media' => [
'resource' => MediaResource::class,
],
Expand Down Expand Up @@ -126,8 +122,8 @@
'delivery_address',
],
'owner_types' => [
// Heco\Company\Models\Company::class => 'Company',
// Heco\Contact\Models\Contact::class => 'Contact',
'Moox\Company\Models\Company' => 'Company',
// 'Heco\Contact\Models\Contact' => 'Contact',
],
],
],
Expand Down Expand Up @@ -164,6 +160,6 @@
| and if the panel is enabled.
|
*/
'navigation_group' => 'DEV',
'navigation_group' => 'Portal',

];
4 changes: 2 additions & 2 deletions packages/address/database/Factories/AddressFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ public function definition(): array
]),
'name' => fake()->company(),
'street' => fake()->streetName().' '.fake()->buildingNumber(),
'street2' => fake()->optional(0.15)->secondaryAddress(),
'street2' => fake()->optional(0.15)->streetAddress(),
'postal_code' => fake()->postcode(),
'city' => fake()->city(),
'state' => fake()->optional(0.4)->state(),
'state' => fake()->optional(0.4)->randomElement(['BE', 'BY', 'HH', 'NW', 'HE']),
'country_code' => fake()->countryCode(),
'is_primary' => false,
'data' => null,
Expand Down
96 changes: 96 additions & 0 deletions packages/address/docs/attributes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Address Attributes

This document describes the database fields and pivot attributes of the Moox Address entity.

## The Address Model

The `Address` model (`Moox\Address\Models\Address`) stores normalized postal data. It extends `BaseItemModel`, uses soft deletes, and supports taxonomies via `HasModelTaxonomy`.

### Attributes

#### Base Fields

- `label` (string, 120) - Optional internal label (e.g. Headquarter, Warehouse)
- `name` (string, 160) - Recipient or company name on the address
- `street` (string, 160) - Street and house number
- `street2` (string, 160) - Additional address line (suite, building, etc.)
- `postal_code` (string, 20) - Postal / ZIP code
- `city` (string, 120) - City
- `state` (string, 120) - State, region, or province
- `country_code` (string, 2) - ISO 3166-1 alpha-2 country code (stored uppercase)
- `is_primary` (boolean) - Marks this address as primary (default: false)
- `data` (json) - Flexible key-value payload for custom metadata
- `deleted_at` (datetime) - Soft-delete timestamp
- `created_at` (datetime) - Creation timestamp
- `updated_at` (datetime) - Last update timestamp

Validation rules live in `Moox\Address\Support\AddressRules` and are applied in the Filament resource.

#### Duplicate detection

Duplicate addresses are blocked on save via `DuplicateAddressException`. Comparison uses `AddressFingerprint` with these columns only:

- `street`
- `street2`
- `postal_code`
- `country_code`

Not part of the fingerprint: `label`, `name`, `city`, `state`, `is_primary`, and `data`. Two records with the same street, postal code, and country but different city or name are still treated as duplicates.

Empty strings are normalized to `null` before comparison. `country_code` is trimmed and uppercased on save.

### Methods

#### Formatting

- `formattedLine()` - Single-line summary: name, street lines, postal code + city, country code

#### Duplicate detection

- `scopeWithFingerprint()` - Query scope for fingerprint lookup

### Relationships

- `addressables()` - Pivot rows linking this address to owners

## The Addressable Pivot

Assignments between an address and an owner (company, contact, user, etc.) live on `addressables`, not on `addresses`. Roles are pivot flags, not columns on the address itself.

### Attributes

#### Pivot Fields

- `addressable_type` (uuid morph) - Owner model class
- `addressable_id` (uuid) - Owner primary key
- `address_id` (foreignId) - References `addresses.id` (cascade on delete)
- `billing_address` (boolean) - Use as billing address (default: false)
- `postal_address` (boolean) - Use as postal address (default: false)
- `delivery_address` (boolean) - Use as delivery address (default: false)
- `created_at` (datetime) - Creation timestamp
- `updated_at` (datetime) - Last update timestamp

Unique constraint: `(addressable_type, addressable_id, address_id)`.

### Methods

- `activeRoles()` - Active role keys: `billing`, `postal`, `delivery`

### Relationships

- `addressable()` - Owner (`MorphTo`)
- `address()` - Linked `Address` (`BelongsTo`)

### Owner trait

Models that can own addresses use `Moox\Address\Concerns\HasAddresses`:

- `addresses()` - `MorphToMany` via `addressables`, with pivot columns from `config('address.relations.addressables')`

Register allowed owner types under `address.relations.addressables.owner_types` in `config/address.php`.

## Translations

Field labels for the admin UI are in `resources/lang/{locale}/fields.php` (e.g. `address::fields.street`). Entity titles use `address::address.*`.

There are no translatable model attributes on `Address`; all address fields are stored on the main table.
3 changes: 3 additions & 0 deletions packages/address/resources/lang/de/fields.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@
'assignments' => 'Zuordnungen',
'owner' => 'Besitzer',
'add_assignment' => 'Zuordnung hinzufügen',
'attach_address' => 'Adresse zuordnen',
'create_address' => 'Adresse anlegen',
'duplicate_address' => 'Eine Adresse mit derselben Straße, PLZ und demselben Land existiert bereits.',
'owner_name' => 'Name',
];
3 changes: 3 additions & 0 deletions packages/address/resources/lang/en/fields.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@
'assignments' => 'Assignments',
'owner' => 'Owner',
'add_assignment' => 'Add assignment',
'attach_address' => 'Attach address',
'create_address' => 'Create address',
'duplicate_address' => 'An address with the same street, postal code and country already exists.',
'owner_name' => 'Name',
];
16 changes: 16 additions & 0 deletions packages/address/src/AddressServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,27 @@

namespace Moox\Address;

use Moox\Address\Models\Address;
use Moox\Address\Resources\AddressResource;
use Moox\Core\MooxServiceProvider;
use Moox\Core\Support\MorphPivot\MorphPivotRelationRegistry;
use Spatie\LaravelPackageTools\Package;

class AddressServiceProvider extends MooxServiceProvider
{
public function boot(): void
{
parent::boot();

MorphPivotRelationRegistry::registerRelatedModel(Address::class, [
'display_columns' => ['name', 'city', 'postal_code', 'country_code', 'is_primary'],
'translation_prefix' => 'address::fields',
'related_resource' => AddressResource::class,
'record_select_label' => 'formattedLine',
'record_select_search_columns' => ['name', 'city', 'postal_code', 'street', 'street2', 'label'],
]);
}

public function configureMoox(Package $package): void
{
$package
Expand Down
35 changes: 0 additions & 35 deletions packages/address/src/Concerns/HasAddresses.php

This file was deleted.

Empty file.
18 changes: 0 additions & 18 deletions packages/address/src/Frontend/AddressFrontend.php

This file was deleted.

Loading
Loading