BF commands

This commit is contained in:
Fabian @ Blax Software 2026-05-17 11:53:19 +02:00
parent f55c7e11df
commit ee64e86345
5 changed files with 151 additions and 80 deletions

View File

@ -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(),

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}