Idempotent provisioning, service soft-delete, Plans page redesign, doc updates

Part A: Fix duplicate Service creation on provisioning retry
- All 4 provisioning services use Service::firstOrCreate() keyed on
  subscription_id+service_type to prevent duplicates on queue retries
- HandleSubscriptionCreated sends notification before provisioning,
  no longer re-throws on failure
- RetryProvisioningCommand simplified to reuse existing Service records

Part B: Plans/Pricing page complete redesign
- Service type tabs (VPS, Dedicated, Web Hosting, MySQL)
- Billing cycle segmented toggle (monthly/quarterly/semi-annual/annual)
- Feature icons per service type, Popular/Best Value badges
- Stock indicators, effective monthly price calculations

Part C: Admin service soft-delete/archive
- Service model uses SoftDeletes trait
- Admin can archive and restore services
- Show archived toggle on services list
- Migration adds deleted_at column

Docs: Updated TASKS.md, CLAUDE.md, PROJECT_DEVELOPMENT.md, MEMORY.md
- Phase 3 marked complete, test counts updated (252 passing)
- SupportPal references replaced with standalone ticket system
- Frontend design skill background rule added
- Closed GitHub issues #3, #6, #7, #8, #9

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Dev
2026-02-10 06:30:57 -05:00
parent bf4f5f97c0
commit 45d25d61ba
101 changed files with 13225 additions and 1888 deletions

View File

@@ -2,8 +2,55 @@
declare(strict_types=1);
use App\Models\Service;
use App\Services\Provisioning\ProvisioningFactory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::middleware('auth:api')->group(function (): void {
//
});
// Debug endpoint to check request details
Route::any('/debug/request', function (Request $request) {
return response()->json([
'method' => $request->method(),
'url' => $request->url(),
'fullUrl' => $request->fullUrl(),
'path' => $request->path(),
'is_webhook' => $request->is('webhooks/*'),
'is_api' => $request->is('api/*'),
'has_csrf_token' => $request->hasHeader('X-CSRF-TOKEN') || $request->hasHeader('X-XSRF-TOKEN'),
'headers' => $request->headers->all(),
]);
})->name('api.debug.request');
// Test endpoint for VirtFusion provisioning (no auth required for testing)
Route::post('/test/provision/{service}', function (Request $request, Service $service) {
try {
$factory = new ProvisioningFactory;
$provisioningService = $factory->make($service);
// Get or create a fake subscription for testing
$subscription = $service->subscription;
if (! $subscription) {
return response()->json([
'error' => 'Service has no subscription. Create a subscription first.',
], 400);
}
$result = $provisioningService->provision($subscription);
return response()->json([
'success' => true,
'service' => $result,
'message' => 'Service provisioned successfully',
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage(),
'trace' => config('app.debug') ? $e->getTraceAsString() : null,
], 500);
}
})->name('api.test.provision');