diff --git a/src/Console/Commands/ShopCleanupCartsCommand.php b/src/Console/Commands/ShopCleanupCartsCommand.php index 6c00cab..af883d0 100644 --- a/src/Console/Commands/ShopCleanupCartsCommand.php +++ b/src/Console/Commands/ShopCleanupCartsCommand.php @@ -100,7 +100,7 @@ class ShopCleanupCartsCommand extends Command ['ID', 'Status', 'Customer', 'Items', 'Last Activity', 'Created'], $cartsToDelete->map(fn($cart) => [ substr($cart->id, 0, 8) . '...', - $cart->status->value, + $cart->status?->value ?? '—', $cart->customer_id ? substr($cart->customer_id, 0, 8) . '...' : 'Guest', $cart->items()->count(), $cart->last_activity_at?->diffForHumans() ?? $cart->updated_at->diffForHumans(), diff --git a/src/Console/Commands/ShopListOrdersCommand.php b/src/Console/Commands/ShopListOrdersCommand.php index c6646bf..d868622 100644 --- a/src/Console/Commands/ShopListOrdersCommand.php +++ b/src/Console/Commands/ShopListOrdersCommand.php @@ -9,19 +9,19 @@ use Illuminate\Console\Command; class ShopListOrdersCommand extends Command { protected $signature = 'shop:list:orders - {--user= : Filter by user ID} + {--customer= : Filter by customer ID (polymorphic)} {--status= : Filter by order status} {--limit=50 : Maximum number of orders to display}'; - protected $description = 'List orders, optionally filtered by user or status'; + protected $description = 'List orders, optionally filtered by customer or status'; public function handle(): int { $model = config('shop.models.order'); $query = $model::query()->latest(); - if ($user = $this->option('user')) { - $query->where('user_id', $user); + if ($customer = $this->option('customer')) { + $query->where('customer_id', $customer); } if ($status = $this->option('status')) { $query->where('status', $status); @@ -37,14 +37,17 @@ class ShopListOrdersCommand extends Command $rows = $orders->map(fn ($order) => [ substr((string) $order->id, 0, 8).'…', - $order->user_id ? substr((string) $order->user_id, 0, 8).'…' : '—', + (string) ($order->order_number ?? '—'), + $order->customer_id + ? class_basename((string) $order->customer_type).'#'.substr((string) $order->customer_id, 0, 8) + : '—', $order->status instanceof \BackedEnum ? $order->status->value : (string) ($order->status ?? '—'), - $order->total ?? '—', - $order->currency ?? '—', + number_format(((int) $order->amount_total) / 100, 2), + (string) ($order->currency ?? '—'), $order->created_at?->format('Y-m-d H:i') ?? '—', ]); - $this->table(['ID', 'User', 'Status', 'Total', 'Currency', 'Created'], $rows); + $this->table(['ID', 'Number', 'Customer', 'Status', 'Total', 'Currency', 'Created'], $rows); $this->info("Showing {$orders->count()} order(s)"); return self::SUCCESS; diff --git a/src/Console/Commands/ShopListProductsCommand.php b/src/Console/Commands/ShopListProductsCommand.php index f0a1081..0e7e466 100644 --- a/src/Console/Commands/ShopListProductsCommand.php +++ b/src/Console/Commands/ShopListProductsCommand.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Blax\Shop\Console\Commands; +use Blax\Shop\Enums\ProductStatus; use Illuminate\Console\Command; class ShopListProductsCommand extends Command @@ -11,62 +12,70 @@ class ShopListProductsCommand extends Command protected $signature = 'shop:list:products {--with-actions : Include action counts} {--with-purchases : Include purchase counts} - {--enabled : Only show enabled products} - {--disabled : Only show disabled products}'; + {--status= : Filter by status (e.g. published, draft, archived)} + {--visible : Only show is_visible=true products} + {--hidden : Only show is_visible=false products} + {--type= : Filter by product type (simple, variable, grouped, external, booking, pool, loanable)}'; protected $description = 'List all products in the shop'; - public function handle() + public function handle(): int { $productModel = config('shop.models.product'); $query = $productModel::query(); - if ($this->option('enabled')) { - $query->where('enabled', true); - } elseif ($this->option('disabled')) { - $query->where('enabled', false); + if ($status = $this->option('status')) { + $query->where('status', $status); + } + if ($this->option('visible')) { + $query->where('is_visible', true); + } elseif ($this->option('hidden')) { + $query->where('is_visible', false); + } + if ($type = $this->option('type')) { + $query->where('type', $type); } if ($this->option('with-actions')) { $query->withCount('actions'); } - if ($this->option('with-purchases')) { $query->withCount('purchases'); } - $products = $query->orderBy('id')->get(); + $products = $query->orderBy('name')->get(); if ($products->isEmpty()) { $this->info('No products found.'); - return 0; + return self::SUCCESS; } - $headers = ['ID', 'Name', 'Price', 'Type', 'Enabled']; - + $headers = ['ID', 'Name', 'SKU', 'Type', 'Status', 'Visible', 'Default Price']; if ($this->option('with-actions')) { $headers[] = 'Actions'; } - if ($this->option('with-purchases')) { $headers[] = 'Purchases'; } $rows = $products->map(function ($product) { + $defaultPrice = optional($product->defaultPrice()->first())->getCurrentPrice(); + $row = [ - $product->id, + substr((string) $product->id, 0, 8).'…', $product->name, - $product->price, - $product->type ?? 'N/A', - $product->enabled ? '✓' : '✗', + $product->sku ?: '—', + $this->enumValue($product->type), + $this->enumValue($product->status), + $product->is_visible ? '✓' : '✗', + $defaultPrice !== null ? number_format((float) $defaultPrice, 2) : '—', ]; if ($this->option('with-actions')) { - $row[] = $product->actions_count ?? 0; + $row[] = (int) ($product->actions_count ?? 0); } - if ($this->option('with-purchases')) { - $row[] = $product->purchases_count ?? 0; + $row[] = (int) ($product->purchases_count ?? 0); } return $row; @@ -75,6 +84,14 @@ class ShopListProductsCommand extends Command $this->table($headers, $rows); $this->info("Total products: {$products->count()}"); - return 0; + return self::SUCCESS; + } + + private function enumValue(mixed $value): string + { + if ($value instanceof \BackedEnum) { + return (string) $value->value; + } + return $value === null ? '—' : (string) $value; } } diff --git a/src/Console/Commands/ShopListPurchasesCommand.php b/src/Console/Commands/ShopListPurchasesCommand.php index b841bf3..e762799 100644 --- a/src/Console/Commands/ShopListPurchasesCommand.php +++ b/src/Console/Commands/ShopListPurchasesCommand.php @@ -9,54 +9,85 @@ use Illuminate\Console\Command; class ShopListPurchasesCommand extends Command { protected $signature = 'shop:list:purchases - {product? : Product ID to filter by} - {--user= : Filter by user ID} - {--status= : Filter by status} + {product? : Filter by purchasable (Product) ID} + {--purchaser= : Filter by purchaser ID (any polymorphic type)} + {--status= : Filter by purchase status} {--limit=50 : Number of purchases to show}'; - protected $description = 'List product purchases'; + protected $description = 'List product purchases (loans, bookings, sales, …)'; - public function handle() + public function handle(): int { $purchaseModel = config('shop.models.product_purchase'); - $query = $purchaseModel::with(['product', 'user']); + $query = $purchaseModel::with(['purchasable', 'purchaser', 'price']); if ($productId = $this->argument('product')) { - $query->where('product_id', $productId); + $query->where('purchasable_id', $productId); } - - if ($userId = $this->option('user')) { - $query->where('user_id', $userId); + if ($purchaserId = $this->option('purchaser')) { + $query->where('purchaser_id', $purchaserId); } - if ($status = $this->option('status')) { $query->where('status', $status); } - $limit = (int) $this->option('limit'); + $limit = max(1, (int) $this->option('limit')); $purchases = $query->latest()->limit($limit)->get(); if ($purchases->isEmpty()) { $this->info('No purchases found.'); - return 0; + return self::SUCCESS; } - $headers = ['ID', 'Product', 'User', 'Price', 'Status', 'Date']; + $rows = $purchases->map(fn ($purchase) => [ + substr((string) $purchase->id, 0, 8).'…', + $this->describePurchasable($purchase), + $this->describePurchaser($purchase), + $this->money((int) $purchase->amount), + $this->money((int) $purchase->amount_paid), + $this->enumValue($purchase->status), + $purchase->created_at?->format('Y-m-d H:i') ?? '—', + ]); - $rows = $purchases->map(function ($purchase) { - return [ - $purchase->id, - $purchase->product->name ?? "ID: {$purchase->product_id}", - $purchase->user->name ?? "ID: {$purchase->user_id}", - $purchase->price, - $purchase->status ?? 'N/A', - $purchase->created_at->format('Y-m-d H:i:s'), - ]; - }); - - $this->table($headers, $rows); + $this->table( + ['ID', 'Item', 'Purchaser', 'Amount', 'Paid', 'Status', 'Created'], + $rows, + ); $this->info("Showing {$purchases->count()} purchase(s)"); - return 0; + return self::SUCCESS; + } + + private function describePurchasable($purchase): string + { + $item = $purchase->purchasable; + if ($item === null) { + return 'ID: '.substr((string) $purchase->purchasable_id, 0, 8).'…'; + } + $name = $item->name ?? class_basename($item::class); + return $name; + } + + private function describePurchaser($purchase): string + { + $by = $purchase->purchaser; + if ($by === null) { + return 'ID: '.substr((string) $purchase->purchaser_id, 0, 8).'…'; + } + $label = $by->name ?? $by->email ?? class_basename($by::class); + return $label; + } + + private function money(int $cents): string + { + return number_format($cents / 100, 2); + } + + private function enumValue(mixed $value): string + { + if ($value instanceof \BackedEnum) { + return (string) $value->value; + } + return $value === null ? '—' : (string) $value; } } diff --git a/src/Console/Commands/ShopStatsCommand.php b/src/Console/Commands/ShopStatsCommand.php index 7cc2456..0836504 100644 --- a/src/Console/Commands/ShopStatsCommand.php +++ b/src/Console/Commands/ShopStatsCommand.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Blax\Shop\Console\Commands; +use Blax\Shop\Enums\ProductStatus; +use Blax\Shop\Enums\PurchaseStatus; use Blax\Shop\Models\ProductAction; use Illuminate\Console\Command; @@ -13,41 +15,59 @@ class ShopStatsCommand extends Command protected $description = 'Display shop statistics'; - public function handle() + public function handle(): int { $productModel = config('shop.models.product'); $purchaseModel = config('shop.models.product_purchase'); + $cartModel = config('shop.models.cart'); + $orderModel = config('shop.models.order'); + $rows = []; + + // Products $totalProducts = $productModel::count(); - $enabledProducts = $productModel::where('enabled', true)->count(); - $disabledProducts = $productModel::where('enabled', false)->count(); + $publishedProducts = $productModel::where('status', ProductStatus::PUBLISHED->value)->count(); + $visibleProducts = $productModel::where('is_visible', true)->count(); + $rows[] = ['Products: total', $totalProducts]; + $rows[] = ['Products: published', $publishedProducts]; + $rows[] = ['Products: visible', $visibleProducts]; + $rows[] = ['---', '---']; + + // Actions $totalActions = ProductAction::count(); - $enabledActions = ProductAction::where('enabled', true)->count(); - $disabledActions = ProductAction::where('enabled', false)->count(); + $activeActions = ProductAction::where('active', true)->count(); + $rows[] = ['Actions: total', $totalActions]; + $rows[] = ['Actions: active', $activeActions]; + $rows[] = ['Actions: inactive', $totalActions - $activeActions]; + $rows[] = ['---', '---']; + + // Purchases (loans, bookings, sales) $totalPurchases = $purchaseModel::count(); - $totalRevenue = $purchaseModel::sum('price'); + $completedPurchases = $purchaseModel::where('status', PurchaseStatus::COMPLETED->value)->count(); + $pendingPurchases = $purchaseModel::where('status', PurchaseStatus::PENDING->value)->count(); + $revenueCents = (int) $purchaseModel::sum('amount_paid'); + $rows[] = ['Purchases: total', $totalPurchases]; + $rows[] = ['Purchases: completed', $completedPurchases]; + $rows[] = ['Purchases: pending', $pendingPurchases]; + $rows[] = ['Revenue (paid)', number_format($revenueCents / 100, 2)]; + + // Carts (model may be absent in minimal installs — guard accordingly) + if ($cartModel) { + $rows[] = ['---', '---']; + $rows[] = ['Carts: total', $cartModel::count()]; + } + + // Orders + if ($orderModel) { + $rows[] = ['Orders: total', $orderModel::count()]; + } $this->info('=== Shop Statistics ==='); $this->newLine(); + $this->table(['Metric', 'Value'], $rows); - $this->table( - ['Metric', 'Count'], - [ - ['Total Products', $totalProducts], - ['Enabled Products', $enabledProducts], - ['Disabled Products', $disabledProducts], - ['---', '---'], - ['Total Actions', $totalActions], - ['Enabled Actions', $enabledActions], - ['Disabled Actions', $disabledActions], - ['---', '---'], - ['Total Purchases', $totalPurchases], - ['Total Revenue', number_format($totalRevenue, 2)], - ] - ); - - return 0; + return self::SUCCESS; } }