added full module support
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Platform\app\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Modules\Platform\app\Models\Company;
|
||||
|
||||
class CompanyController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display companies.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('platform::companies.index', [
|
||||
'companies' => 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.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Platform\app\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Platform\app\Models\Company;
|
||||
use Modules\Platform\app\Models\LicenseKey;
|
||||
|
||||
class LicenseKeyController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display licence keys.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('platform::licenses.index', [
|
||||
'companies' => 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.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Platform\app\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Platform\app\Models\Company;
|
||||
use Modules\Platform\app\Models\Payment;
|
||||
|
||||
class PaymentController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display company payments.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('platform::payments.index', [
|
||||
'companies' => 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.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Platform\app\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Modules\Platform\app\Models\Company;
|
||||
use Modules\Platform\app\Models\LicenseKey;
|
||||
use Modules\Platform\app\Models\Payment;
|
||||
use Modules\Warehouse\app\Models\Warehouse;
|
||||
|
||||
class PlatformDashboardController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the platform administration dashboard.
|
||||
*/
|
||||
public function __invoke()
|
||||
{
|
||||
return view('platform::dashboard.index', [
|
||||
'companiesCount' => 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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Platform\app\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Platform\app\Models\Company;
|
||||
use Modules\Platform\app\Models\Payment;
|
||||
|
||||
class StripeWebhookController extends Controller
|
||||
{
|
||||
/**
|
||||
* Receive a Stripe webhook event.
|
||||
*/
|
||||
public function __invoke(Request $request)
|
||||
{
|
||||
$payload = $request->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]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Platform\app\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsureLicensedTenant
|
||||
{
|
||||
/**
|
||||
* Ensure the current company has an active licence unless it is the MPBA platform tenant.
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$user = $request->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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Platform\app\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsurePlatformAdmin
|
||||
{
|
||||
/**
|
||||
* Ensure the current user is an MPBA platform administrator.
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
abort_unless($request->user()?->isMpbaPlatformAdmin(), 403);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Platform\app\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsureTenantAssigned
|
||||
{
|
||||
/**
|
||||
* Ensure the authenticated user belongs to a company tenant.
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
abort_if(! $user || ! $user->company_id, 403, 'No company is assigned to this account.');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Platform\app\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Str;
|
||||
use Modules\Warehouse\app\Models\Warehouse;
|
||||
|
||||
class Company extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'uuid', 'name', 'slug', 'status', 'timezone', 'settings',
|
||||
'is_system_company', 'stripe_customer_id', 'billing_email',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'settings' => '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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Platform\app\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class LicenseKey extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'company_id', 'key_hash', 'key_prefix', 'label', 'plan', 'max_users',
|
||||
'is_active', 'starts_at', 'expires_at', 'last_used_at', 'revoked_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => '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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Platform\app\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Payment extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'company_id', 'stripe_payment_intent_id', 'stripe_invoice_id', 'amount',
|
||||
'currency', 'status', 'description', 'paid_at', 'metadata',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'amount' => 'integer',
|
||||
'paid_at' => 'datetime',
|
||||
'metadata' => 'array',
|
||||
];
|
||||
|
||||
public function company(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user