diff --git a/Company/Database/Migrations/2026_07_07_093000_add_country_id_to_companies_table.php b/Company/Database/Migrations/2026_07_07_093000_add_country_id_to_companies_table.php
new file mode 100644
index 00000000..0d0a61f0
--- /dev/null
+++ b/Company/Database/Migrations/2026_07_07_093000_add_country_id_to_companies_table.php
@@ -0,0 +1,26 @@
+foreignId('country_id')
+ ->nullable()
+ ->after('billing_email')
+ ->constrained('countries')
+ ->nullOnDelete();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('companies', function (Blueprint $table): void {
+ $table->dropConstrainedForeignId('country_id');
+ });
+ }
+};
diff --git a/Company/Http/Controllers/CompanyController.php b/Company/Http/Controllers/CompanyController.php
new file mode 100644
index 00000000..0880b292
--- /dev/null
+++ b/Company/Http/Controllers/CompanyController.php
@@ -0,0 +1,141 @@
+ Company::query()
+ ->with(['address.country', 'country'])
+ ->withCount(['users', 'warehouses', 'licenseKeys', 'divisions'])
+ ->latest('id')
+ ->paginate(20),
+ 'countries' => $this->countries(),
+ ]);
+ }
+
+ /**
+ * Show the create company screen.
+ */
+ public function create()
+ {
+ return view('company::create', [
+ 'company' => new Company([
+ 'status' => 'active',
+ 'timezone' => 'Europe/London',
+ ]),
+ 'countries' => $this->countries(),
+ ]);
+ }
+
+ /**
+ * Store a company tenant.
+ */
+ public function store(Request $request)
+ {
+ $company = Company::create($this->validated($request));
+
+ return redirect()
+ ->route('platform.companies.index')
+ ->with('status', 'Company created.');
+ }
+
+ /**
+ * Display a company workbench.
+ */
+ public function show(Company $company)
+ {
+ $company->load([
+ 'address.country',
+ 'divisions.address.country',
+ 'divisions.warehouses',
+ 'divisions.users',
+ 'warehouses.address.country',
+ 'warehouses.division',
+ 'directorAssignments.user',
+ 'directorAssignments.division',
+ ])->loadCount(['users', 'warehouses', 'licenseKeys', 'payments', 'divisions']);
+
+ return view('company::show', [
+ 'company' => $company,
+ 'recentUsers' => $company->users()->latest('id')->limit(5)->get(),
+ 'recentWarehouses' => $company->warehouses()->latest('id')->limit(5)->get(),
+ 'recentLicenseKeys' => $company->licenseKeys()->latest('id')->limit(5)->get(),
+ 'countries' => Country::query()->active()->orderBy('sort_order')->orderBy('name')->get(['id', 'alpha2', 'name', 'display_name', 'common_name']),
+ 'companyUsers' => $company->users()->orderBy('name')->get(['id', 'name', 'email', 'company_id']),
+ 'unassignedWarehouses' => $company->warehouses()->whereNull('division_id')->orderBy('name')->get(),
+ ]);
+ }
+
+ /**
+ * Show the edit company screen.
+ */
+ public function edit(Company $company)
+ {
+ return view('company::edit', [
+ 'company' => $company->loadMissing(['address.country', 'country']),
+ 'countries' => $this->countries(),
+ ]);
+ }
+
+ /**
+ * Update a company tenant.
+ */
+ public function update(Request $request, Company $company)
+ {
+ $company->update($this->validated($request, $company));
+
+ return redirect()
+ ->route('platform.companies.show', $company)
+ ->with('status', 'Company updated.');
+ }
+
+ /**
+ * Delete a company tenant.
+ */
+ public function destroy(Company $company)
+ {
+ abort_if($company->is_system_company, 403, 'The system company cannot be deleted.');
+
+ $company->delete();
+
+ return redirect()
+ ->route('platform.companies.index')
+ ->with('status', 'Company deleted.');
+ }
+
+ private function validated(Request $request, ?Company $company = null): array
+ {
+ $data = $request->validate([
+ 'name' => ['required', 'string', 'max:255'],
+ 'billing_email' => ['nullable', 'email', 'max:255'],
+ 'status' => ['required', Rule::in(['active', 'suspended', 'inactive'])],
+ 'country_id' => ['nullable', Rule::exists('countries', 'id')->where('is_active', true)],
+ ]);
+
+ $data['slug'] = Str::slug($data['name']);
+ $data['timezone'] = $company?->timezone ?: 'Europe/London';
+
+ return $data;
+ }
+
+ private function countries()
+ {
+ return Country::query()
+ ->active()
+ ->ordered()
+ ->get(['id', 'alpha2', 'name', 'display_name', 'common_name', 'sort_order']);
+ }
+}
diff --git a/Company/Models/Company.php b/Company/Models/Company.php
new file mode 100644
index 00000000..e3dd2a77
--- /dev/null
+++ b/Company/Models/Company.php
@@ -0,0 +1,100 @@
+ 'array',
+ 'is_system_company' => 'boolean',
+ ];
+
+ protected static function booted(): void
+ {
+ static::creating(function (Company $company): void {
+ $company->uuid ??= (string) Str::uuid();
+ $company->slug ??= Str::slug($company->name);
+ $company->status ??= 'active';
+ $company->timezone ??= 'Europe/London';
+ });
+ }
+
+
+
+ public function country(): BelongsTo
+ {
+ return $this->belongsTo(Country::class);
+ }
+
+ public function address(): MorphOne
+ {
+ return $this->morphOne(CompanyAddress::class, 'addressable');
+ }
+
+ public function divisions(): HasMany
+ {
+ return $this->hasMany(CompanyDivision::class);
+ }
+
+ public function directorAssignments(): HasMany
+ {
+ return $this->hasMany(CompanyDirectorAssignment::class);
+ }
+
+ public function users(): HasMany
+ {
+ return $this->hasMany(User::class);
+ }
+
+ public function licenseKeys(): HasMany
+ {
+ return $this->hasMany(LicenseKey::class);
+ }
+
+ public function payments(): HasMany
+ {
+ return $this->hasMany(Payment::class);
+ }
+
+ public function warehouses(): HasMany
+ {
+ return $this->hasMany(Warehouse::class);
+ }
+
+ public function hasActiveLicense(): bool
+ {
+ if ($this->is_system_company) {
+ return true;
+ }
+
+ return $this->licenseKeys()
+ ->where('is_active', true)
+ ->where(function ($query): void {
+ $query->whereNull('starts_at')->orWhere('starts_at', '<=', now());
+ })
+ ->where(function ($query): void {
+ $query->whereNull('expires_at')->orWhere('expires_at', '>=', now());
+ })
+ ->whereNull('revoked_at')
+ ->exists();
+ }
+}
diff --git a/Company/Resources/views/_form.blade.php b/Company/Resources/views/_form.blade.php
new file mode 100644
index 00000000..e044d2d7
--- /dev/null
+++ b/Company/Resources/views/_form.blade.php
@@ -0,0 +1,83 @@
+@csrf
+
+
+
+
+
+ @error('name')
+
{{ $message }}
+ @enderror
+
+
+
+
+
+ @error('billing_email')
+
{{ $message }}
+ @enderror
+
+
+
+
+
+ @error('status')
+
{{ $message }}
+ @enderror
+
+
+
+
+
+ @error('country_id')
+
{{ $message }}
+ @enderror
+
+
+
+
diff --git a/Company/Resources/views/index.blade.php b/Company/Resources/views/index.blade.php
new file mode 100644
index 00000000..b4515449
--- /dev/null
+++ b/Company/Resources/views/index.blade.php
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+ @if (session('status'))
+
+ {{ session('status') }}
+
+ @endif
+
+
+
+
+
Create company
+
Add a new tenant without leaving the administration index.
+
+
+
+ Open full form
+
+
+
+
+
+
+
+
+
+ | Name |
+ Users |
+ Divisions |
+ Warehouses |
+ Licences |
+ Status |
+ Actions |
+
+
+
+
+ @forelse ($companies as $company)
+
+ |
+ {{ $company->name }}
+ |
+
+
+ {{ $company->users_count }}
+ |
+
+
+ {{ $company->divisions_count }}
+ |
+
+
+ {{ $company->warehouses_count }}
+ |
+
+
+ {{ $company->license_keys_count }}
+ |
+
+
+
+ {{ $company->is_system_company ? 'System' : ucfirst($company->status) }}
+
+ |
+
+
+
+
+ View
+
+
+
+ Edit
+
+
+ @unless ($company->is_system_company)
+
+ @endunless
+
+ |
+
+ @empty
+
+ |
+ No companies found.
+ |
+
+ @endforelse
+
+
+
+
+ {{ $companies->links() }}
+
+
diff --git a/Modules/Company/Database/Migrations/2026_07_07_093000_add_country_id_to_companies_table.php b/Modules/Company/Database/Migrations/2026_07_07_093000_add_country_id_to_companies_table.php
new file mode 100644
index 00000000..0d0a61f0
--- /dev/null
+++ b/Modules/Company/Database/Migrations/2026_07_07_093000_add_country_id_to_companies_table.php
@@ -0,0 +1,26 @@
+foreignId('country_id')
+ ->nullable()
+ ->after('billing_email')
+ ->constrained('countries')
+ ->nullOnDelete();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('companies', function (Blueprint $table): void {
+ $table->dropConstrainedForeignId('country_id');
+ });
+ }
+};
diff --git a/Modules/Company/Http/Controllers/CompanyController.php b/Modules/Company/Http/Controllers/CompanyController.php
index 5bd16514..0880b292 100644
--- a/Modules/Company/Http/Controllers/CompanyController.php
+++ b/Modules/Company/Http/Controllers/CompanyController.php
@@ -18,10 +18,11 @@ class CompanyController extends Controller
{
return view('company::index', [
'companies' => Company::query()
- ->with(['address.country'])
+ ->with(['address.country', 'country'])
->withCount(['users', 'warehouses', 'licenseKeys', 'divisions'])
->latest('id')
->paginate(20),
+ 'countries' => $this->countries(),
]);
}
@@ -35,6 +36,7 @@ class CompanyController extends Controller
'status' => 'active',
'timezone' => 'Europe/London',
]),
+ 'countries' => $this->countries(),
]);
}
@@ -83,7 +85,8 @@ class CompanyController extends Controller
public function edit(Company $company)
{
return view('company::edit', [
- 'company' => $company,
+ 'company' => $company->loadMissing(['address.country', 'country']),
+ 'countries' => $this->countries(),
]);
}
@@ -119,12 +122,20 @@ class CompanyController extends Controller
'name' => ['required', 'string', 'max:255'],
'billing_email' => ['nullable', 'email', 'max:255'],
'status' => ['required', Rule::in(['active', 'suspended', 'inactive'])],
- 'timezone' => ['nullable', 'string', 'max:64'],
+ 'country_id' => ['nullable', Rule::exists('countries', 'id')->where('is_active', true)],
]);
$data['slug'] = Str::slug($data['name']);
- $data['timezone'] = $data['timezone'] ?? ($company?->timezone ?: 'Europe/London');
+ $data['timezone'] = $company?->timezone ?: 'Europe/London';
return $data;
}
+
+ private function countries()
+ {
+ return Country::query()
+ ->active()
+ ->ordered()
+ ->get(['id', 'alpha2', 'name', 'display_name', 'common_name', 'sort_order']);
+ }
}
diff --git a/Modules/Company/Models/Company.php b/Modules/Company/Models/Company.php
index 0a3bb73e..e3dd2a77 100644
--- a/Modules/Company/Models/Company.php
+++ b/Modules/Company/Models/Company.php
@@ -4,12 +4,14 @@ namespace Modules\Company\Models;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
use Modules\Platform\app\Models\LicenseKey;
use Modules\Platform\app\Models\Payment;
+use Modules\Countries\Models\Country;
use Modules\Warehouse\app\Models\Warehouse;
class Company extends Model
@@ -18,7 +20,7 @@ class Company extends Model
protected $fillable = [
'uuid', 'name', 'slug', 'status', 'timezone', 'settings',
- 'is_system_company', 'stripe_customer_id', 'billing_email',
+ 'is_system_company', 'stripe_customer_id', 'billing_email', 'country_id',
];
protected $casts = [
@@ -37,6 +39,12 @@ class Company extends Model
}
+
+ public function country(): BelongsTo
+ {
+ return $this->belongsTo(Country::class);
+ }
+
public function address(): MorphOne
{
return $this->morphOne(CompanyAddress::class, 'addressable');
diff --git a/Modules/Company/Resources/views/_form.blade.php b/Modules/Company/Resources/views/_form.blade.php
index 5ddfb4b0..e044d2d7 100644
--- a/Modules/Company/Resources/views/_form.blade.php
+++ b/Modules/Company/Resources/views/_form.blade.php
@@ -47,15 +47,23 @@
@enderror
-
-
-
+
+
+ @error('country_id')
{{ $message }}
@enderror
diff --git a/Modules/Company/Resources/views/index.blade.php b/Modules/Company/Resources/views/index.blade.php
index 8433fee2..b4515449 100644
--- a/Modules/Company/Resources/views/index.blade.php
+++ b/Modules/Company/Resources/views/index.blade.php
@@ -55,12 +55,16 @@
-
+
+
+