From 62901eaea3546eeed196cd077a49f00eefb2a607 Mon Sep 17 00:00:00 2001 From: isaacclad Date: Fri, 17 Jul 2026 10:04:28 +0000 Subject: [PATCH] Add Woo Manager demo:seed for store walkthroughs. Seed stores, products, and orders within Free caps and paid-tier volume. Co-authored-by: Cursor --- app/Console/Commands/DemoSeedCommand.php | 259 +++++++++++++++++++++++ tests/Feature/DemoSeedCommandTest.php | 77 +++++++ 2 files changed, 336 insertions(+) create mode 100644 app/Console/Commands/DemoSeedCommand.php create mode 100644 tests/Feature/DemoSeedCommandTest.php diff --git a/app/Console/Commands/DemoSeedCommand.php b/app/Console/Commands/DemoSeedCommand.php new file mode 100644 index 0000000..6fe51a2 --- /dev/null +++ b/app/Console/Commands/DemoSeedCommand.php @@ -0,0 +1,259 @@ +option('plan')); + if (! in_array($plan, ['free', 'pro', 'enterprise'], true)) { + $this->error('Invalid --plan. Use free, pro, or enterprise.'); + + return self::FAILURE; + } + + $user = $this->resolveUser((string) $this->argument('identity')); + if (! $user) { + $this->error('User not found for identity: '.$this->argument('identity')); + + return self::FAILURE; + } + + if ($this->option('reset')) { + $this->resetTenant($user); + $this->info('Reset demo data for '.$user->email); + } + + $this->assignPlan($subscriptions, $user, $plan); + + $existing = WooStore::query() + ->where('user_id', $user->id) + ->where('metadata->demo', true) + ->count(); + + if ($existing > 0 && ! $this->option('reset')) { + $this->info('Demo stores already present; plan ensured (idempotent).'); + $this->line(sprintf('user=%s plan=%s stores=%d', $user->email, $plan, $existing)); + + return self::SUCCESS; + } + + $storeSpecs = $this->storeSpecs($plan); + foreach ($storeSpecs as $spec) { + $this->seedStore($user, $spec); + } + + $this->info(sprintf( + 'Seeded %d demo store(s) for %s (%s).', + count($storeSpecs), + $user->email, + $plan, + )); + + return self::SUCCESS; + } + + private function resolveUser(string $identity): ?User + { + return User::query() + ->where(function ($query) use ($identity) { + $query->where('email', $identity)->orWhere('public_id', $identity); + }) + ->first(); + } + + private function assignPlan(SubscriptionService $subscriptions, User $user, string $plan): void + { + if ($plan === 'free') { + ProSubscription::query()->where('user_id', $user->id)->delete(); + + return; + } + + $subscriptions->activatePrepaid( + $user, + $plan === 'enterprise' ? ProSubscription::PLAN_ENTERPRISE : ProSubscription::PLAN_PRO, + 12, + ); + } + + private function resetTenant(User $user): void + { + DB::transaction(function () use ($user) { + WooStore::query()->where('user_id', $user->id)->each(function (WooStore $store) { + $store->delete(); + }); + ProSubscription::query()->where('user_id', $user->id)->delete(); + }); + } + + /** + * @return list}> + */ + private function storeSpecs(string $plan): array + { + $baseCategories = ['Apparel', 'Accessories', 'Home']; + + return match ($plan) { + 'enterprise' => [ + [ + 'site_name' => 'Demo Accra Shop', + 'site_url' => 'https://demo-accra.example.com', + 'products' => 30, + 'orders' => 12, + 'categories' => $baseCategories, + ], + [ + 'site_name' => 'Demo Kumasi Shop', + 'site_url' => 'https://demo-kumasi.example.com', + 'products' => 24, + 'orders' => 8, + 'categories' => ['Electronics', 'Gadgets'], + ], + [ + 'site_name' => 'Demo Wholesale', + 'site_url' => 'https://demo-wholesale.example.com', + 'products' => 20, + 'orders' => 6, + 'categories' => ['Bulk', 'Packaging'], + ], + ], + 'pro' => [ + [ + 'site_name' => 'Demo Pro Shop', + 'site_url' => 'https://demo-pro-shop.example.com', + 'products' => 35, + 'orders' => 10, + 'categories' => $baseCategories, + ], + [ + 'site_name' => 'Demo Outlet', + 'site_url' => 'https://demo-outlet.example.com', + 'products' => 15, + 'orders' => 5, + 'categories' => ['Clearance', 'Seasonal'], + ], + ], + default => [ + [ + 'site_name' => 'Demo Free Shop', + 'site_url' => 'https://demo-free-shop.example.com', + 'products' => 15, + 'orders' => 4, + 'categories' => $baseCategories, + ], + ], + }; + } + + /** + * @param array{site_name: string, site_url: string, products: int, orders: int, categories: list} $spec + */ + private function seedStore(User $user, array $spec): void + { + $store = WooStore::query()->create([ + 'user_id' => $user->id, + 'site_url' => $spec['site_url'], + 'site_name' => $spec['site_name'], + 'status' => WooStore::STATUS_ACTIVE, + 'connected_at' => now(), + 'metadata' => [ + 'demo' => true, + 'currency' => 'GHS', + ], + ]); + + $categoryIds = []; + foreach ($spec['categories'] as $i => $name) { + $externalId = $i + 1; + WooCategory::query()->create([ + 'woo_store_id' => $store->id, + 'external_id' => $externalId, + 'name' => $name, + 'slug' => Str::slug($name), + 'product_count' => 0, + ]); + $categoryIds[] = $externalId; + } + + $products = []; + for ($i = 1; $i <= $spec['products']; $i++) { + $cat = $categoryIds[($i - 1) % max(count($categoryIds), 1)] ?? null; + $product = WooProduct::query()->create([ + 'woo_store_id' => $store->id, + 'external_id' => $i, + 'name' => sprintf('%s Product %02d', $spec['site_name'], $i), + 'slug' => 'demo-product-'.$store->id.'-'.$i, + 'sku' => 'DEMO-'.$store->id.'-'.$i, + 'status' => WooProduct::STATUS_PUBLISH, + 'type' => 'simple', + 'regular_price_minor' => 2_500 + ($i * 100), + 'stock_quantity' => 10 + $i, + 'manage_stock' => true, + 'category_external_ids' => $cat ? [$cat] : [], + 'short_description' => 'Local demo product (no live WordPress).', + ]); + $products[] = $product; + + if ($cat) { + WooCategory::query() + ->where('woo_store_id', $store->id) + ->where('external_id', $cat) + ->increment('product_count'); + } + } + + for ($i = 1; $i <= $spec['orders']; $i++) { + $product = $products[($i - 1) % count($products)]; + $qty = 1 + ($i % 3); + $lineTotal = (int) $product->regular_price_minor * $qty; + + WooOrder::query()->create([ + 'woo_store_id' => $store->id, + 'external_order_id' => 1000 + $i, + 'order_number' => (string) (1000 + $i), + 'wc_status' => $i % 4 === 0 ? 'completed' : 'processing', + 'payment_status' => 'paid', + 'customer_name' => 'Demo Customer '.$i, + 'customer_email' => 'customer'.$i.'@example.com', + 'customer_phone' => '+233200000'.str_pad((string) $i, 3, '0', STR_PAD_LEFT), + 'line_items' => [[ + 'id' => $product->external_id, + 'name' => $product->name, + 'quantity' => $qty, + 'total' => number_format($lineTotal / 100, 2, '.', ''), + 'sku' => $product->sku, + ]], + 'currency' => 'GHS', + 'total_minor' => $lineTotal, + 'fulfillment_status' => $i % 3 === 0 + ? WooOrder::FULFILLMENT_DELIVERED + : WooOrder::FULFILLMENT_NEW, + 'wc_updated_at' => now()->subDays($spec['orders'] - $i), + 'metadata' => ['demo' => true], + ]); + } + } +} diff --git a/tests/Feature/DemoSeedCommandTest.php b/tests/Feature/DemoSeedCommandTest.php new file mode 100644 index 0000000..76c2868 --- /dev/null +++ b/tests/Feature/DemoSeedCommandTest.php @@ -0,0 +1,77 @@ +create(['email' => 'demo-free@ladill.com']); + + $this->artisan('demo:seed', ['identity' => $user->email, '--plan' => 'free']) + ->assertSuccessful(); + + $this->assertDatabaseMissing('woo_pro_subscriptions', ['user_id' => $user->id]); + + $stores = WooStore::query()->where('user_id', $user->id)->get(); + $this->assertCount(1, $stores); + $this->assertSame(WooStore::STATUS_ACTIVE, $stores->first()->status); + + $productCount = WooProduct::query()->where('woo_store_id', $stores->first()->id)->count(); + $this->assertLessThanOrEqual(20, $productCount); + $this->assertGreaterThan(0, $productCount); + $this->assertGreaterThan(0, WooOrder::query()->where('woo_store_id', $stores->first()->id)->count()); + $this->assertGreaterThan(0, $stores->first()->categories()->count()); + } + + public function test_pro_plan_assigns_subscription_and_multiple_stores(): void + { + $user = User::factory()->create(['public_id' => 'woo-demo-pro-001']); + + $this->artisan('demo:seed', ['identity' => 'woo-demo-pro-001', '--plan' => 'pro']) + ->assertSuccessful(); + + $sub = ProSubscription::query()->where('user_id', $user->id)->first(); + $this->assertNotNull($sub); + $this->assertSame(ProSubscription::PLAN_PRO, $sub->plan); + $this->assertTrue($sub->entitled()); + + $this->assertCount(2, WooStore::query()->where('user_id', $user->id)->get()); + $this->assertGreaterThan(20, WooProduct::query() + ->whereIn('woo_store_id', WooStore::query()->where('user_id', $user->id)->pluck('id')) + ->count()); + } + + public function test_idempotent_without_reset_and_reset_reseeds(): void + { + $user = User::factory()->create(['email' => 'demo-pro@ladill.com']); + + $this->artisan('demo:seed', ['identity' => $user->email, '--plan' => 'enterprise']) + ->assertSuccessful(); + $firstCount = WooStore::query()->where('user_id', $user->id)->count(); + $this->assertSame(3, $firstCount); + + $this->artisan('demo:seed', ['identity' => $user->email, '--plan' => 'enterprise']) + ->assertSuccessful(); + $this->assertSame(3, WooStore::query()->where('user_id', $user->id)->count()); + + $this->artisan('demo:seed', [ + 'identity' => $user->email, + '--plan' => 'free', + '--reset' => true, + ])->assertSuccessful(); + + $this->assertCount(1, WooStore::query()->where('user_id', $user->id)->get()); + $this->assertDatabaseMissing('woo_pro_subscriptions', ['user_id' => $user->id]); + } +}