{{ $label }}
+{{ $value }}
+diff --git a/FIX-500-NOTES.md b/FIX-500-NOTES.md
new file mode 100644
index 00000000..37273637
--- /dev/null
+++ b/FIX-500-NOTES.md
@@ -0,0 +1,20 @@
+# 500 fix notes
+
+This build removes Flux form inputs from the login/register auth forms because the current supplied Flux/Livewire stack can compile internal Flux input views before `$attributes` exists, causing:
+
+`Undefined variable $attributes`
+
+The auth forms keep the mobile-first Tailwind styling and use normal HTML inputs. Run:
+
+```bash
+composer dump-autoload
+php artisan optimize:clear
+php artisan view:clear
+php artisan migrate --seed
+npm run build
+```
+
+Default platform admin:
+
+- Email: mark.gregory@mpba.co.uk
+- Password: 987400Kmh
diff --git a/INSTALL-WMS.md b/INSTALL-WMS.md
new file mode 100644
index 00000000..1101212e
--- /dev/null
+++ b/INSTALL-WMS.md
@@ -0,0 +1,59 @@
+# WMS modular install
+
+## Local ServBay / PostgreSQL install
+
+```bash
+cp .env.example .env
+php artisan key:generate
+php artisan migrate:fresh --seed
+npm install
+npm run build
+php artisan optimize:clear
+```
+
+Admin login:
+
+```text
+Email: mark.gregory@mpba.co.uk
+Password: 987400Kmh
+```
+
+## Main pages
+
+```text
+/dashboard Tenant WMS dashboard
+/warehouses Tenant warehouse management
+/warehouse-rules Warehouse validation rules
+/admin MPBA system administration dashboard
+/admin/companies Tenant company management
+/admin/licenses Licence keys
+/admin/payments Payments and Stripe override records
+/api/internal/companies HMAC signed internal API endpoint
+```
+
+## Production install
+
+Use PostgreSQL 18, Redis Sentinel, Laravel Octane, Horizon and HAProxy. Keep database and Redis HA native on Ubuntu. Use Docker only for stateless Laravel app/worker containers.
+
+```bash
+composer install --no-dev --prefer-dist --optimize-autoloader
+npm ci
+npm run build
+php artisan migrate --force
+php artisan db:seed --force
+php artisan config:cache
+php artisan route:cache
+php artisan view:cache
+php artisan event:cache
+php artisan queue:restart
+php artisan octane:reload
+```
+
+## Test command
+
+```bash
+composer install
+php artisan test
+```
+
+The archive does not include `vendor/` or `node_modules/`.
diff --git a/Modules/InternalApi/Providers/InternalApiServiceProvider.php b/Modules/InternalApi/Providers/InternalApiServiceProvider.php
new file mode 100644
index 00000000..88c5c793
--- /dev/null
+++ b/Modules/InternalApi/Providers/InternalApiServiceProvider.php
@@ -0,0 +1,19 @@
+prefix('api/internal')->group(__DIR__ . '/../routes/api.php');
+ app('router')->aliasMiddleware('internal.signed', VerifyInternalApiSignature::class);
+ }
+}
diff --git a/Modules/InternalApi/app/Http/Controllers/CompanySummaryController.php b/Modules/InternalApi/app/Http/Controllers/CompanySummaryController.php
new file mode 100644
index 00000000..cdb39d47
--- /dev/null
+++ b/Modules/InternalApi/app/Http/Controllers/CompanySummaryController.php
@@ -0,0 +1,19 @@
+json([
+ 'companies' => Company::withCount(['users', 'warehouses', 'licenseKeys'])->orderBy('name')->get(),
+ ]);
+ }
+}
diff --git a/Modules/InternalApi/app/Http/Middleware/VerifyInternalApiSignature.php b/Modules/InternalApi/app/Http/Middleware/VerifyInternalApiSignature.php
new file mode 100644
index 00000000..5a46a8a4
--- /dev/null
+++ b/Modules/InternalApi/app/Http/Middleware/VerifyInternalApiSignature.php
@@ -0,0 +1,28 @@
+header('X-WMS-Timestamp');
+ $signature = (string) $request->header('X-WMS-Signature');
+
+ abort_if($secret === '' || $timestamp === '' || $signature === '', 401);
+ abort_if(abs(now()->timestamp - strtotime($timestamp)) > 300, 401, 'Expired internal API signature.');
+
+ $expected = hash_hmac('sha256', $timestamp . '.' . $request->getContent(), $secret);
+ abort_unless(hash_equals($expected, $signature), 401, 'Invalid internal API signature.');
+
+ return $next($request);
+ }
+}
diff --git a/Modules/InternalApi/module.json b/Modules/InternalApi/module.json
new file mode 100644
index 00000000..c3e01f01
--- /dev/null
+++ b/Modules/InternalApi/module.json
@@ -0,0 +1 @@
+{"name":"InternalApi","alias":"internal-api","description":"Authenticated internal API between separated WMS applications.","keywords":["api","hmac","integration"],"priority":2,"providers":["Modules\\InternalApi\\Providers\\InternalApiServiceProvider"],"files":[]}
diff --git a/Modules/InternalApi/routes/api.php b/Modules/InternalApi/routes/api.php
new file mode 100644
index 00000000..b45db6b8
--- /dev/null
+++ b/Modules/InternalApi/routes/api.php
@@ -0,0 +1,8 @@
+group(function (): void {
+ Route::get('/companies', CompanySummaryController::class)->name('internal.companies.index');
+});
diff --git a/Modules/InternalApi/tests/Feature/InternalApiSignatureTest.php b/Modules/InternalApi/tests/Feature/InternalApiSignatureTest.php
new file mode 100644
index 00000000..6afa4ae9
--- /dev/null
+++ b/Modules/InternalApi/tests/Feature/InternalApiSignatureTest.php
@@ -0,0 +1,29 @@
+getJson('/api/internal/companies')->assertUnauthorized();
+ }
+
+ public function test_internal_api_accepts_valid_signature(): void
+ {
+ config(['platform.internal_api_key' => 'testing-secret']);
+ $timestamp = now()->toIso8601String();
+ $body = '';
+ $signature = hash_hmac('sha256', $timestamp . '.' . $body, 'testing-secret');
+
+ $this->withHeaders([
+ 'X-WMS-Timestamp' => $timestamp,
+ 'X-WMS-Signature' => $signature,
+ ])->getJson('/api/internal/companies')->assertOk();
+ }
+}
diff --git a/Modules/Platform/Providers/PlatformServiceProvider.php b/Modules/Platform/Providers/PlatformServiceProvider.php
new file mode 100644
index 00000000..4b6e7271
--- /dev/null
+++ b/Modules/Platform/Providers/PlatformServiceProvider.php
@@ -0,0 +1,36 @@
+mergeConfigFrom(__DIR__ . '/../config/platform.php', 'platform');
+ }
+
+ /**
+ * Bootstrap module services.
+ */
+ public function boot(): void
+ {
+ $this->loadViewsFrom(__DIR__ . '/../resources/views', 'platform');
+ $this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
+
+ Route::middleware('web')->group(__DIR__ . '/../routes/web.php');
+
+ app('router')->aliasMiddleware('tenant.assigned', EnsureTenantAssigned::class);
+ app('router')->aliasMiddleware('tenant.licensed', EnsureLicensedTenant::class);
+ app('router')->aliasMiddleware('platform.admin', EnsurePlatformAdmin::class);
+ }
+}
diff --git a/Modules/Platform/app/Http/Controllers/CompanyController.php b/Modules/Platform/app/Http/Controllers/CompanyController.php
new file mode 100644
index 00000000..63d4f326
--- /dev/null
+++ b/Modules/Platform/app/Http/Controllers/CompanyController.php
@@ -0,0 +1,41 @@
+ Company::withCount(['users', 'warehouses', 'licenseKeys'])->orderBy('name')->paginate(20),
+ ]);
+ }
+
+ /**
+ * Create a company tenant.
+ */
+ public function store(Request $request)
+ {
+ $data = $request->validate([
+ 'name' => ['required', 'string', 'max:255'],
+ 'billing_email' => ['nullable', 'email', 'max:255'],
+ 'status' => ['required', 'in:active,suspended'],
+ ]);
+
+ Company::create([
+ ...$data,
+ 'slug' => Str::slug($data['name']),
+ 'timezone' => 'Europe/London',
+ ]);
+
+ return back()->with('status', 'Company created.');
+ }
+}
diff --git a/Modules/Platform/app/Http/Controllers/LicenseKeyController.php b/Modules/Platform/app/Http/Controllers/LicenseKeyController.php
new file mode 100644
index 00000000..3e8abf0e
--- /dev/null
+++ b/Modules/Platform/app/Http/Controllers/LicenseKeyController.php
@@ -0,0 +1,41 @@
+ Company::orderBy('name')->get(),
+ 'licenses' => LicenseKey::with('company')->latest()->paginate(20),
+ 'plainKey' => session('plain_license_key'),
+ ]);
+ }
+
+ /**
+ * Issue a new licence key.
+ */
+ public function store(Request $request)
+ {
+ $data = $request->validate([
+ 'company_id' => ['required', 'exists:companies,id'],
+ 'label' => ['required', 'string', 'max:255'],
+ 'plan' => ['required', 'in:standard,professional,enterprise'],
+ 'max_users' => ['required', 'integer', 'min:1', 'max:10000'],
+ ]);
+
+ $company = Company::findOrFail($data['company_id']);
+ $plainKey = LicenseKey::issueFor($company, $data['label'], $data['plan'], (int) $data['max_users']);
+
+ return back()->with('plain_license_key', $plainKey)->with('status', 'Licence key issued. Copy it now.');
+ }
+}
diff --git a/Modules/Platform/app/Http/Controllers/PaymentController.php b/Modules/Platform/app/Http/Controllers/PaymentController.php
new file mode 100644
index 00000000..4103960c
--- /dev/null
+++ b/Modules/Platform/app/Http/Controllers/PaymentController.php
@@ -0,0 +1,44 @@
+ Company::orderBy('name')->get(),
+ 'payments' => Payment::with('company')->latest()->paginate(20),
+ ]);
+ }
+
+ /**
+ * Add a manual payment or admin override record.
+ */
+ public function store(Request $request)
+ {
+ $data = $request->validate([
+ 'company_id' => ['required', 'exists:companies,id'],
+ 'amount' => ['required', 'integer', 'min:0'],
+ 'currency' => ['required', 'string', 'size:3'],
+ 'status' => ['required', 'in:pending,paid,failed,refunded,override'],
+ 'description' => ['nullable', 'string', 'max:255'],
+ ]);
+
+ Payment::create([
+ ...$data,
+ 'paid_at' => in_array($data['status'], ['paid', 'override'], true) ? now() : null,
+ 'metadata' => ['source' => 'manual_admin'],
+ ]);
+
+ return back()->with('status', 'Payment record created.');
+ }
+}
diff --git a/Modules/Platform/app/Http/Controllers/PlatformDashboardController.php b/Modules/Platform/app/Http/Controllers/PlatformDashboardController.php
new file mode 100644
index 00000000..f04786ec
--- /dev/null
+++ b/Modules/Platform/app/Http/Controllers/PlatformDashboardController.php
@@ -0,0 +1,27 @@
+ Company::count(),
+ 'activeCompaniesCount' => Company::where('status', 'active')->count(),
+ 'licensesCount' => LicenseKey::count(),
+ 'paymentsTotal' => Payment::where('status', 'paid')->sum('amount'),
+ 'warehousesCount' => Warehouse::count(),
+ 'companies' => Company::withCount(['users', 'warehouses', 'licenseKeys'])->latest()->limit(8)->get(),
+ ]);
+ }
+}
diff --git a/Modules/Platform/app/Http/Controllers/StripeWebhookController.php b/Modules/Platform/app/Http/Controllers/StripeWebhookController.php
new file mode 100644
index 00000000..388bba5f
--- /dev/null
+++ b/Modules/Platform/app/Http/Controllers/StripeWebhookController.php
@@ -0,0 +1,42 @@
+all();
+ $eventType = data_get($payload, 'type');
+
+ if ($eventType === 'payment_intent.succeeded') {
+ $intent = data_get($payload, 'data.object', []);
+ $company = Company::where('stripe_customer_id', data_get($intent, 'customer'))->first();
+
+ if ($company) {
+ Payment::updateOrCreate(
+ ['stripe_payment_intent_id' => data_get($intent, 'id')],
+ [
+ 'company_id' => $company->id,
+ 'amount' => (int) data_get($intent, 'amount_received', 0),
+ 'currency' => (string) data_get($intent, 'currency', 'gbp'),
+ 'status' => 'paid',
+ 'description' => 'Stripe payment received',
+ 'paid_at' => now(),
+ 'metadata' => $intent,
+ ]
+ );
+ }
+ }
+
+ return response()->json(['received' => true]);
+ }
+}
diff --git a/Modules/Platform/app/Http/Middleware/EnsureLicensedTenant.php b/Modules/Platform/app/Http/Middleware/EnsureLicensedTenant.php
new file mode 100644
index 00000000..f7e6966b
--- /dev/null
+++ b/Modules/Platform/app/Http/Middleware/EnsureLicensedTenant.php
@@ -0,0 +1,29 @@
+user();
+ $company = $user?->company;
+
+ abort_if(! $company, 403, 'No company is assigned to this account.');
+
+ if ($user->isMpbaPlatformAdmin()) {
+ return $next($request);
+ }
+
+ abort_unless($company->hasActiveLicense(), 402, 'Payment Required');
+
+ return $next($request);
+ }
+}
diff --git a/Modules/Platform/app/Http/Middleware/EnsurePlatformAdmin.php b/Modules/Platform/app/Http/Middleware/EnsurePlatformAdmin.php
new file mode 100644
index 00000000..86982032
--- /dev/null
+++ b/Modules/Platform/app/Http/Middleware/EnsurePlatformAdmin.php
@@ -0,0 +1,20 @@
+user()?->isMpbaPlatformAdmin(), 403);
+
+ return $next($request);
+ }
+}
diff --git a/Modules/Platform/app/Http/Middleware/EnsureTenantAssigned.php b/Modules/Platform/app/Http/Middleware/EnsureTenantAssigned.php
new file mode 100644
index 00000000..74165ea1
--- /dev/null
+++ b/Modules/Platform/app/Http/Middleware/EnsureTenantAssigned.php
@@ -0,0 +1,22 @@
+user();
+
+ abort_if(! $user || ! $user->company_id, 403, 'No company is assigned to this account.');
+
+ return $next($request);
+ }
+}
diff --git a/Modules/Platform/app/Models/Company.php b/Modules/Platform/app/Models/Company.php
new file mode 100644
index 00000000..e263e19d
--- /dev/null
+++ b/Modules/Platform/app/Models/Company.php
@@ -0,0 +1,73 @@
+ '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 users(): HasMany
+ {
+ return $this->hasMany(\App\Models\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/Modules/Platform/app/Models/LicenseKey.php b/Modules/Platform/app/Models/LicenseKey.php
new file mode 100644
index 00000000..fc1ce73a
--- /dev/null
+++ b/Modules/Platform/app/Models/LicenseKey.php
@@ -0,0 +1,47 @@
+ 'boolean',
+ 'starts_at' => 'datetime',
+ 'expires_at' => 'datetime',
+ 'last_used_at' => 'datetime',
+ 'revoked_at' => 'datetime',
+ ];
+
+ public function company(): BelongsTo
+ {
+ return $this->belongsTo(Company::class);
+ }
+
+ public static function issueFor(Company $company, string $label, string $plan = 'standard', int $maxUsers = 10): string
+ {
+ $plainKey = 'WMS-' . Str::upper(Str::random(32));
+
+ self::create([
+ 'company_id' => $company->id,
+ 'key_hash' => hash('sha256', $plainKey),
+ 'key_prefix' => Str::substr($plainKey, 0, 12),
+ 'label' => $label,
+ 'plan' => $plan,
+ 'max_users' => $maxUsers,
+ 'is_active' => true,
+ 'starts_at' => now(),
+ 'expires_at' => now()->addYear(),
+ ]);
+
+ return $plainKey;
+ }
+}
diff --git a/Modules/Platform/app/Models/Payment.php b/Modules/Platform/app/Models/Payment.php
new file mode 100644
index 00000000..056cc7ca
--- /dev/null
+++ b/Modules/Platform/app/Models/Payment.php
@@ -0,0 +1,25 @@
+ 'integer',
+ 'paid_at' => 'datetime',
+ 'metadata' => 'array',
+ ];
+
+ public function company(): BelongsTo
+ {
+ return $this->belongsTo(Company::class);
+ }
+}
diff --git a/Modules/Platform/config/platform.php b/Modules/Platform/config/platform.php
new file mode 100644
index 00000000..ed3125b1
--- /dev/null
+++ b/Modules/Platform/config/platform.php
@@ -0,0 +1,12 @@
+ env('WMS_SYSTEM_COMPANY_SLUG', 'mpba-limited'),
+ 'system_admin_email' => env('WMS_SYSTEM_ADMIN_EMAIL', 'mark.gregory@mpba.co.uk'),
+ 'internal_api_key' => env('WMS_INTERNAL_API_KEY', 'local-dev-change-me'),
+ 'stripe' => [
+ 'key' => env('STRIPE_KEY'),
+ 'secret' => env('STRIPE_SECRET'),
+ 'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
+ ],
+];
diff --git a/Modules/Platform/database/migrations/2026_06_10_000000_create_platform_tables.php b/Modules/Platform/database/migrations/2026_06_10_000000_create_platform_tables.php
new file mode 100644
index 00000000..9e951459
--- /dev/null
+++ b/Modules/Platform/database/migrations/2026_06_10_000000_create_platform_tables.php
@@ -0,0 +1,80 @@
+id();
+ $table->uuid('uuid')->unique();
+ $table->string('name');
+ $table->string('slug')->unique();
+ $table->string('status')->default('active')->index();
+ $table->string('timezone')->default('Europe/London');
+ $table->boolean('is_system_company')->default(false)->index();
+ $table->string('stripe_customer_id')->nullable()->index();
+ $table->string('billing_email')->nullable();
+ $table->json('settings')->nullable();
+ $table->timestamps();
+ $table->softDeletes();
+ });
+
+ Schema::table('users', function (Blueprint $table): void {
+ $table->foreignId('company_id')->nullable()->after('id')->constrained('companies')->nullOnDelete();
+ $table->boolean('is_platform_admin')->default(false)->after('password');
+ $table->boolean('is_company_admin')->default(false)->after('is_platform_admin');
+ $table->string('status')->default('active')->after('is_company_admin')->index();
+ });
+
+ Schema::create('license_keys', function (Blueprint $table): void {
+ $table->id();
+ $table->foreignId('company_id')->constrained('companies')->cascadeOnDelete();
+ $table->string('key_hash', 64)->unique();
+ $table->string('key_prefix', 16)->index();
+ $table->string('label');
+ $table->string('plan')->default('standard')->index();
+ $table->unsignedInteger('max_users')->default(10);
+ $table->boolean('is_active')->default(true)->index();
+ $table->timestamp('starts_at')->nullable();
+ $table->timestamp('expires_at')->nullable()->index();
+ $table->timestamp('last_used_at')->nullable();
+ $table->timestamp('revoked_at')->nullable()->index();
+ $table->timestamps();
+ });
+
+ Schema::create('payments', function (Blueprint $table): void {
+ $table->id();
+ $table->foreignId('company_id')->constrained('companies')->cascadeOnDelete();
+ $table->string('stripe_payment_intent_id')->nullable()->unique();
+ $table->string('stripe_invoice_id')->nullable()->index();
+ $table->unsignedInteger('amount')->default(0);
+ $table->string('currency', 3)->default('gbp');
+ $table->string('status')->default('pending')->index();
+ $table->string('description')->nullable();
+ $table->timestamp('paid_at')->nullable();
+ $table->json('metadata')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('payments');
+ Schema::dropIfExists('license_keys');
+ Schema::table('users', function (Blueprint $table): void {
+ $table->dropConstrainedForeignId('company_id');
+ $table->dropColumn(['is_platform_admin', 'is_company_admin', 'status']);
+ });
+ Schema::dropIfExists('companies');
+ }
+};
diff --git a/Modules/Platform/database/seeders/PlatformSeeder.php b/Modules/Platform/database/seeders/PlatformSeeder.php
new file mode 100644
index 00000000..23cb64e5
--- /dev/null
+++ b/Modules/Platform/database/seeders/PlatformSeeder.php
@@ -0,0 +1,55 @@
+ 'mpba-limited'],
+ [
+ 'uuid' => (string) Str::uuid(),
+ 'name' => 'MPBA Limited',
+ 'status' => 'active',
+ 'timezone' => 'Europe/London',
+ 'is_system_company' => true,
+ 'billing_email' => 'mark.gregory@mpba.co.uk',
+ ]
+ );
+
+ User::updateOrCreate(
+ ['email' => 'mark.gregory@mpba.co.uk'],
+ [
+ 'company_id' => $company->id,
+ 'name' => 'Mark Gregory',
+ 'password' => Hash::make('987400Kmh'),
+ 'is_platform_admin' => true,
+ 'is_company_admin' => true,
+ 'status' => 'active',
+ 'email_verified_at' => now(),
+ ]
+ );
+
+ $warehouse = Warehouse::updateOrCreate(
+ ['company_id' => $company->id, 'code' => 'MPBA-MAIN'],
+ ['name' => 'MPBA Main Warehouse', 'status' => 'active', 'timezone' => 'Europe/London']
+ );
+
+ WarehouseValidationRule::updateOrCreate(
+ ['warehouse_id' => $warehouse->id, 'rule_key' => 'stock.location.required'],
+ ['name' => 'Stock location required', 'severity' => 'error', 'is_active' => true, 'configuration' => ['required' => true]]
+ );
+ }
+}
diff --git a/Modules/Platform/module.json b/Modules/Platform/module.json
new file mode 100644
index 00000000..54df319b
--- /dev/null
+++ b/Modules/Platform/module.json
@@ -0,0 +1 @@
+{"name":"Platform","alias":"platform","description":"System administration, tenants, licences, Stripe payments and tenant guards.","keywords":["wms","multi-tenant","licensing"],"priority":0,"providers":["Modules\\Platform\\Providers\\PlatformServiceProvider"],"files":[]}
diff --git a/Modules/Platform/resources/views/companies/index.blade.php b/Modules/Platform/resources/views/companies/index.blade.php
new file mode 100644
index 00000000..6c4c5e50
--- /dev/null
+++ b/Modules/Platform/resources/views/companies/index.blade.php
@@ -0,0 +1,21 @@
+ MPBA WMS Manage tenant companies, licences, payments, warehouses and internal API operations from one resilient control plane. System status Operational {{ $label }} {{ $value }} Tenant isolation is enforced by company ownership on every warehouse record.Companies
+
+
+
+
+ @foreach($companies as $company)Name Users Warehouses Licences Status @endforeach
+ {{ $company->name }} {{ $company->users_count }} {{ $company->warehouses_count }} {{ $company->license_keys_count }} {{ $company->is_system_company ? 'System' : $company->status }} Platform administration
+ Recent companies
+
+
+
+
+
+
+
+ @foreach ($companies as $company)
+ Company
+ Users
+ Warehouses
+ Licences
+ Status
+
+
+ @endforeach
+
+ {{ $company->name }}
+ {{ $company->users_count }}
+ {{ $company->warehouses_count }}
+ {{ $company->license_keys_count }}
+ {{ $company->is_system_company ? 'System' : ucfirst($company->status) }}
+ {{ $plainKey }}Licence keys
+
+ @foreach($licenses as $license)Company Label Plan Prefix Expires @endforeach{{ $license->company->name }} {{ $license->label }} {{ $license->plan }} {{ $license->key_prefix }}{{ optional($license->expires_at)->toDateString() }}
| Company | Amount | Status | Description |
|---|---|---|---|
| {{ $payment->company->name }} | {{ strtoupper($payment->currency) }} {{ number_format($payment->amount / 100, 2) }} | {{ $payment->status }} | {{ $payment->description }} |
| Warehouse | Rule | Key | Severity |
|---|---|---|---|
| {{ $rule->warehouse->name }} | {{ $rule->name }} | {{ $rule->rule_key }} | {{ $rule->severity }} |
Warehouse management
+High-performance tenant-isolated warehouse operations with per-warehouse validation and licence-aware access.
+Warehouses
{{ $warehousesCount }}
Rules
{{ $rulesCount }}
{{ $warehouse->code }}
{{ $warehouse->validation_rules_count }} validation rules configured.
+Warehouses are isolated to your company tenant.
Add warehouse{{ $warehouse->code }}
{{ $warehouse->validation_rules_count }} rules