Deploy Ladill Events / deploy (push) Successful in 2m20s
Seed live events, registrations, and Pro subscriptions without billing APIs. Co-authored-by: Cursor <cursoragent@cursor.com>
362 lines
14 KiB
PHP
362 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\Events\ProSubscription;
|
|
use App\Models\PaymentGatewaySetting;
|
|
use App\Models\QrCode;
|
|
use App\Models\QrEventRegistration;
|
|
use App\Models\QrTeamMember;
|
|
use App\Models\User;
|
|
use App\Services\Events\SubscriptionService;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
|
|
/**
|
|
* Seed plan-aware Events demo data for a suite demo identity.
|
|
*/
|
|
class DemoSeedCommand extends Command
|
|
{
|
|
protected $signature = 'demo:seed
|
|
{identity : Email or public_id of the local mirrored user}
|
|
{--plan=free : free|pro|enterprise}
|
|
{--reset : Remove prior demo data for this user before seeding}
|
|
{--public-id= : Platform public_id when creating a local mirror}
|
|
{--email= : Email when creating a local mirror}
|
|
{--name= : Display name when creating a local mirror}';
|
|
|
|
protected $description = 'Seed Events demo data for a suite demo account';
|
|
|
|
public function handle(SubscriptionService $subscriptions): int
|
|
{
|
|
$plan = strtolower((string) $this->option('plan'));
|
|
if (! in_array($plan, ['free', 'pro', 'enterprise'], true)) {
|
|
$this->error('Plan must be free, pro, or enterprise.');
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$user = $this->resolveUser();
|
|
if (! $user) {
|
|
return self::FAILURE;
|
|
}
|
|
|
|
if ($this->option('reset')) {
|
|
$this->resetDemoData($user);
|
|
$this->info('Reset demo data for '.$user->email);
|
|
}
|
|
|
|
$this->assignPlan($user, $plan, $subscriptions);
|
|
|
|
if ($this->hasDemoMarker($user) && ! $this->option('reset')) {
|
|
$this->info('Demo data already present; plan updated. Use --reset to reseed.');
|
|
$this->printSummary($user, $plan, $subscriptions);
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$this->seedEvents($user, $plan);
|
|
$this->printSummary($user, $plan, $subscriptions);
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function resolveUser(): ?User
|
|
{
|
|
$identity = trim((string) $this->argument('identity'));
|
|
$publicIdOpt = trim((string) ($this->option('public-id') ?: ''));
|
|
$emailOpt = trim((string) ($this->option('email') ?: ''));
|
|
$nameOpt = trim((string) ($this->option('name') ?: ''));
|
|
|
|
$user = User::query()
|
|
->where('email', $identity)
|
|
->orWhere('public_id', $identity)
|
|
->first();
|
|
|
|
if ($user) {
|
|
return $user;
|
|
}
|
|
|
|
$publicId = $publicIdOpt !== '' ? $publicIdOpt : (Str::isUuid($identity) ? $identity : '');
|
|
$email = $emailOpt !== '' ? $emailOpt : (filter_var($identity, FILTER_VALIDATE_EMAIL) ? $identity : '');
|
|
|
|
if ($publicId === '' || $email === '') {
|
|
$this->error('Local user not found for "'.$identity.'". Pass --public-id and --email to create a mirror.');
|
|
|
|
return null;
|
|
}
|
|
|
|
return User::query()->updateOrCreate(
|
|
['public_id' => $publicId],
|
|
[
|
|
'email' => $email,
|
|
'name' => $nameOpt !== '' ? $nameOpt : Str::before($email, '@'),
|
|
'email_verified_at' => now(),
|
|
],
|
|
);
|
|
}
|
|
|
|
private function assignPlan(User $user, string $plan, SubscriptionService $subscriptions): void
|
|
{
|
|
if ($plan === 'free') {
|
|
ProSubscription::query()->where('user_id', $user->id)->delete();
|
|
|
|
return;
|
|
}
|
|
|
|
$planKey = $plan === 'enterprise'
|
|
? ProSubscription::PLAN_ENTERPRISE
|
|
: ProSubscription::PLAN_PRO;
|
|
|
|
ProSubscription::query()->updateOrCreate(
|
|
['user_id' => $user->id],
|
|
[
|
|
'plan' => $planKey,
|
|
'status' => ProSubscription::STATUS_ACTIVE,
|
|
'price_minor' => $planKey === ProSubscription::PLAN_ENTERPRISE
|
|
? $subscriptions->enterprisePriceMinor()
|
|
: $subscriptions->priceMinor(),
|
|
'currency' => (string) config('events.pro.currency', 'GHS'),
|
|
'auto_renew' => false,
|
|
'started_at' => now(),
|
|
'current_period_end' => now()->addYear(),
|
|
'last_charged_at' => null,
|
|
'canceled_at' => null,
|
|
'last_reference' => 'demo-seed-'.$planKey,
|
|
'last_error' => null,
|
|
],
|
|
);
|
|
}
|
|
|
|
private function hasDemoMarker(User $user): bool
|
|
{
|
|
return QrCode::query()
|
|
->where('user_id', $user->id)
|
|
->where('type', QrCode::TYPE_EVENT)
|
|
->where('short_code', 'like', $this->codePrefix($user).'%')
|
|
->exists();
|
|
}
|
|
|
|
private function codePrefix(User $user): string
|
|
{
|
|
return 'dm'.substr(md5((string) $user->public_id), 0, 8);
|
|
}
|
|
|
|
private function resetDemoData(User $user): void
|
|
{
|
|
DB::transaction(function () use ($user) {
|
|
PaymentGatewaySetting::query()->where('owner_ref', $user->public_id)->delete();
|
|
|
|
QrTeamMember::query()
|
|
->where('account_id', $user->id)
|
|
->orWhere('user_id', $user->id)
|
|
->delete();
|
|
|
|
$codes = QrCode::query()->where('user_id', $user->id)->pluck('id');
|
|
if ($codes->isNotEmpty()) {
|
|
QrEventRegistration::query()->whereIn('qr_code_id', $codes)->delete();
|
|
QrCode::query()->whereIn('id', $codes)->delete();
|
|
}
|
|
|
|
QrEventRegistration::query()->where('user_id', $user->id)->delete();
|
|
ProSubscription::query()->where('user_id', $user->id)->delete();
|
|
});
|
|
}
|
|
|
|
private function seedEvents(User $user, string $plan): void
|
|
{
|
|
$prefix = $this->codePrefix($user);
|
|
$paid = $plan !== 'free';
|
|
$eventCount = $paid ? ($plan === 'enterprise' ? 5 : 4) : 2;
|
|
$regsPerEvent = $paid ? ($plan === 'enterprise' ? 40 : 25) : 35;
|
|
|
|
$programmeId = null;
|
|
if ($paid) {
|
|
$programme = QrCode::query()->updateOrCreate(
|
|
['short_code' => $prefix.'-itn'],
|
|
[
|
|
'user_id' => $user->id,
|
|
'type' => QrCode::TYPE_ITINERARY,
|
|
'label' => 'Demo Summit Programme',
|
|
'payload' => [
|
|
'content' => [
|
|
'title' => 'Accra Tech Summit Programme',
|
|
'subtitle' => 'Two-day agenda',
|
|
'description' => 'Demo programme for sales walkthroughs.',
|
|
'event_date' => now()->addDays(14)->toDateString(),
|
|
'location' => 'Kempinski Accra',
|
|
'brand_color' => '#0F766E',
|
|
'days' => [[
|
|
'label' => 'Day 1',
|
|
'date' => now()->addDays(14)->toDateString(),
|
|
'items' => [
|
|
[
|
|
'ref' => 'day0-item0',
|
|
'time' => '09:00',
|
|
'title' => 'Opening keynote',
|
|
'description' => 'Welcome and product roadmap',
|
|
'location' => 'Main hall',
|
|
'host' => 'Ama Mensah',
|
|
'host_speaker_email' => 'ama.demo@ladill.com',
|
|
],
|
|
[
|
|
'ref' => 'day0-item1',
|
|
'time' => '11:00',
|
|
'title' => 'Breakout: ticketing',
|
|
'description' => 'Paid tiers and badges',
|
|
'location' => 'Room B',
|
|
'host' => 'Kojo Asante',
|
|
'host_speaker_email' => 'kojo.demo@ladill.com',
|
|
],
|
|
],
|
|
]],
|
|
],
|
|
'style' => [],
|
|
],
|
|
'is_active' => true,
|
|
],
|
|
);
|
|
$programmeId = $programme->id;
|
|
}
|
|
|
|
for ($i = 1; $i <= $eventCount; $i++) {
|
|
$mode = $paid ? 'ticketing' : 'free';
|
|
$tiers = $paid
|
|
? [
|
|
['name' => 'General', 'price' => 150, 'capacity' => 200],
|
|
['name' => 'VIP', 'price' => 450, 'capacity' => 40],
|
|
]
|
|
: [['name' => 'Registration', 'price' => 0, 'capacity' => 100]];
|
|
|
|
$event = QrCode::query()->updateOrCreate(
|
|
['short_code' => $prefix.'-e'.$i],
|
|
[
|
|
'user_id' => $user->id,
|
|
'type' => QrCode::TYPE_EVENT,
|
|
'label' => $paid ? 'Demo Paid Event '.$i : 'Demo Free Event '.$i,
|
|
'payload' => [
|
|
'content' => [
|
|
'name' => $paid ? 'Accra Business Forum '.$i : 'Community Meetup '.$i,
|
|
'tagline' => $paid ? 'Ticketed demo event' : 'Free RSVP demo event',
|
|
'description' => 'Seeded by demo:seed for sales walkthroughs.',
|
|
'location' => 'Accra, Ghana',
|
|
'starts_at' => now()->addDays(7 + $i)->toIso8601String(),
|
|
'ends_at' => now()->addDays(7 + $i)->addHours(6)->toIso8601String(),
|
|
'organizer' => $user->name ?: 'Ladill Demo',
|
|
'currency' => 'GHS',
|
|
'mode' => $mode,
|
|
'format' => $i === 1 ? 'in_person' : 'hybrid',
|
|
'tiers' => $tiers,
|
|
'badge_fields' => ['company', 'role'],
|
|
'badge_size' => 'standard',
|
|
'accepts_payment' => $paid,
|
|
'registration_open' => true,
|
|
'programme_qr_id' => $programmeId,
|
|
'speakers' => $paid ? [
|
|
[
|
|
'name' => 'Ama Mensah',
|
|
'email' => 'ama.demo@ladill.com',
|
|
'role' => 'Keynote',
|
|
],
|
|
] : [],
|
|
],
|
|
'style' => [],
|
|
],
|
|
'is_active' => true,
|
|
],
|
|
);
|
|
|
|
$this->seedRegistrations($user, $event, $regsPerEvent, $paid, $prefix, $i);
|
|
}
|
|
|
|
if ($paid) {
|
|
PaymentGatewaySetting::query()->updateOrCreate(
|
|
['owner_ref' => $user->public_id],
|
|
[
|
|
'provider' => PaymentGatewaySetting::PROVIDER_PAYSTACK,
|
|
'public_key' => 'pk_test_demo_events',
|
|
'secret_key' => 'sk_test_demo_events',
|
|
'is_active' => true,
|
|
'metadata' => ['demo' => true],
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
private function seedRegistrations(
|
|
User $user,
|
|
QrCode $event,
|
|
int $count,
|
|
bool $paid,
|
|
string $prefix,
|
|
int $eventIndex,
|
|
): void {
|
|
$existing = QrEventRegistration::query()
|
|
->where('qr_code_id', $event->id)
|
|
->where('reference', 'like', $prefix.'%')
|
|
->count();
|
|
|
|
if ($existing >= $count) {
|
|
return;
|
|
}
|
|
|
|
for ($n = $existing + 1; $n <= $count; $n++) {
|
|
$tier = $paid ? ($n % 5 === 0 ? 'VIP' : 'General') : 'Registration';
|
|
$amount = $paid ? ($tier === 'VIP' ? 45000 : 15000) : 0;
|
|
$ref = sprintf('%s-r%02d-%03d', $prefix, $eventIndex, $n);
|
|
$badge = strtoupper(substr($prefix, 2, 4).sprintf('%02d%03d', $eventIndex, $n));
|
|
|
|
QrEventRegistration::query()->updateOrCreate(
|
|
['reference' => $ref],
|
|
[
|
|
'qr_code_id' => $event->id,
|
|
'user_id' => $user->id,
|
|
'badge_code' => substr($badge, 0, 16),
|
|
'tier_name' => $tier,
|
|
'amount_minor' => $amount,
|
|
'currency' => 'GHS',
|
|
'attendee_name' => 'Demo Attendee '.$n,
|
|
'attendee_email' => 'attendee'.$n.'.e'.$eventIndex.'@demo.ladill.com',
|
|
'attendee_phone' => '+23320'.str_pad((string) (($eventIndex * 1000) + $n), 7, '0', STR_PAD_LEFT),
|
|
'badge_fields' => [
|
|
'company' => 'Demo Co '.$n,
|
|
'role' => $tier === 'VIP' ? 'Executive' : 'Guest',
|
|
],
|
|
'status' => QrEventRegistration::STATUS_CONFIRMED,
|
|
'payment_reference' => $paid ? 'demo-pay-'.$ref : null,
|
|
'paid_at' => $paid ? now()->subDays(max(1, 30 - $n)) : null,
|
|
'checked_in_at' => $n % 4 === 0 ? now()->subHours($n) : null,
|
|
'metadata' => ['demo' => true],
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
private function printSummary(User $user, string $plan, SubscriptionService $subscriptions): void
|
|
{
|
|
$events = QrCode::query()
|
|
->where('user_id', $user->id)
|
|
->where('type', QrCode::TYPE_EVENT)
|
|
->count();
|
|
$programmes = QrCode::query()
|
|
->where('user_id', $user->id)
|
|
->where('type', QrCode::TYPE_ITINERARY)
|
|
->count();
|
|
$regs = QrEventRegistration::query()->where('user_id', $user->id)->count();
|
|
|
|
$this->table(
|
|
['Field', 'Value'],
|
|
[
|
|
['email', $user->email],
|
|
['public_id', $user->public_id],
|
|
['requested_plan', $plan],
|
|
['effective_plan', $subscriptions->planKey($user)],
|
|
['live_events', (string) $events],
|
|
['programmes', (string) $programmes],
|
|
['registrations', (string) $regs],
|
|
],
|
|
);
|
|
}
|
|
}
|