Add Woo Manager demo:seed for store walkthroughs.
Deploy Ladill Woo Manager / deploy (push) Successful in 2m24s
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:
@@ -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],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user