Add Woo Manager demo:seed for store walkthroughs.
Deploy Ladill Woo Manager / deploy (push) Successful in 2m24s

Seed stores, products, and orders within Free caps and paid-tier volume.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-17 10:04:28 +00:00
co-authored by Cursor
parent d75837a440
commit 62901eaea3
2 changed files with 336 additions and 0 deletions
+259
View File
@@ -0,0 +1,259 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use App\Models\Woo\ProSubscription;
use App\Models\WooCategory;
use App\Models\WooOrder;
use App\Models\WooProduct;
use App\Models\WooStore;
use App\Services\Woo\SubscriptionService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
/**
* Seed local demo WooCommerce manager data (no live WordPress).
*/
class DemoSeedCommand extends Command
{
protected $signature = 'demo:seed
{identity : User email or public_id}
{--plan=free : free|pro|enterprise}
{--reset : Wipe this user\'s stores/subscription before seeding}';
protected $description = 'Seed idempotent demo stores, categories, products, and orders for a user';
public function handle(SubscriptionService $subscriptions): int
{
$plan = strtolower((string) $this->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<array{site_name: string, site_url: string, products: int, orders: int, categories: list<string>}>
*/
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<string>} $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],
]);
}
}
}
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace Tests\Feature;
use App\Models\User;
use App\Models\Woo\ProSubscription;
use App\Models\WooOrder;
use App\Models\WooProduct;
use App\Models\WooStore;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class DemoSeedCommandTest extends TestCase
{
use RefreshDatabase;
public function test_free_plan_seeds_one_store_within_product_limit(): void
{
$user = User::factory()->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]);
}
}