BF commands
This commit is contained in:
parent
f55c7e11df
commit
ee64e86345
|
|
@ -100,7 +100,7 @@ class ShopCleanupCartsCommand extends Command
|
||||||
['ID', 'Status', 'Customer', 'Items', 'Last Activity', 'Created'],
|
['ID', 'Status', 'Customer', 'Items', 'Last Activity', 'Created'],
|
||||||
$cartsToDelete->map(fn($cart) => [
|
$cartsToDelete->map(fn($cart) => [
|
||||||
substr($cart->id, 0, 8) . '...',
|
substr($cart->id, 0, 8) . '...',
|
||||||
$cart->status->value,
|
$cart->status?->value ?? '—',
|
||||||
$cart->customer_id ? substr($cart->customer_id, 0, 8) . '...' : 'Guest',
|
$cart->customer_id ? substr($cart->customer_id, 0, 8) . '...' : 'Guest',
|
||||||
$cart->items()->count(),
|
$cart->items()->count(),
|
||||||
$cart->last_activity_at?->diffForHumans() ?? $cart->updated_at->diffForHumans(),
|
$cart->last_activity_at?->diffForHumans() ?? $cart->updated_at->diffForHumans(),
|
||||||
|
|
|
||||||
|
|
@ -9,19 +9,19 @@ use Illuminate\Console\Command;
|
||||||
class ShopListOrdersCommand extends Command
|
class ShopListOrdersCommand extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'shop:list:orders
|
protected $signature = 'shop:list:orders
|
||||||
{--user= : Filter by user ID}
|
{--customer= : Filter by customer ID (polymorphic)}
|
||||||
{--status= : Filter by order status}
|
{--status= : Filter by order status}
|
||||||
{--limit=50 : Maximum number of orders to display}';
|
{--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
|
public function handle(): int
|
||||||
{
|
{
|
||||||
$model = config('shop.models.order');
|
$model = config('shop.models.order');
|
||||||
$query = $model::query()->latest();
|
$query = $model::query()->latest();
|
||||||
|
|
||||||
if ($user = $this->option('user')) {
|
if ($customer = $this->option('customer')) {
|
||||||
$query->where('user_id', $user);
|
$query->where('customer_id', $customer);
|
||||||
}
|
}
|
||||||
if ($status = $this->option('status')) {
|
if ($status = $this->option('status')) {
|
||||||
$query->where('status', $status);
|
$query->where('status', $status);
|
||||||
|
|
@ -37,14 +37,17 @@ class ShopListOrdersCommand extends Command
|
||||||
|
|
||||||
$rows = $orders->map(fn ($order) => [
|
$rows = $orders->map(fn ($order) => [
|
||||||
substr((string) $order->id, 0, 8).'…',
|
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->status instanceof \BackedEnum ? $order->status->value : (string) ($order->status ?? '—'),
|
||||||
$order->total ?? '—',
|
number_format(((int) $order->amount_total) / 100, 2),
|
||||||
$order->currency ?? '—',
|
(string) ($order->currency ?? '—'),
|
||||||
$order->created_at?->format('Y-m-d H:i') ?? '—',
|
$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)");
|
$this->info("Showing {$orders->count()} order(s)");
|
||||||
|
|
||||||
return self::SUCCESS;
|
return self::SUCCESS;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Blax\Shop\Console\Commands;
|
namespace Blax\Shop\Console\Commands;
|
||||||
|
|
||||||
|
use Blax\Shop\Enums\ProductStatus;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
class ShopListProductsCommand extends Command
|
class ShopListProductsCommand extends Command
|
||||||
|
|
@ -11,62 +12,70 @@ class ShopListProductsCommand extends Command
|
||||||
protected $signature = 'shop:list:products
|
protected $signature = 'shop:list:products
|
||||||
{--with-actions : Include action counts}
|
{--with-actions : Include action counts}
|
||||||
{--with-purchases : Include purchase counts}
|
{--with-purchases : Include purchase counts}
|
||||||
{--enabled : Only show enabled products}
|
{--status= : Filter by status (e.g. published, draft, archived)}
|
||||||
{--disabled : Only show disabled products}';
|
{--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';
|
protected $description = 'List all products in the shop';
|
||||||
|
|
||||||
public function handle()
|
public function handle(): int
|
||||||
{
|
{
|
||||||
$productModel = config('shop.models.product');
|
$productModel = config('shop.models.product');
|
||||||
$query = $productModel::query();
|
$query = $productModel::query();
|
||||||
|
|
||||||
if ($this->option('enabled')) {
|
if ($status = $this->option('status')) {
|
||||||
$query->where('enabled', true);
|
$query->where('status', $status);
|
||||||
} elseif ($this->option('disabled')) {
|
}
|
||||||
$query->where('enabled', false);
|
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')) {
|
if ($this->option('with-actions')) {
|
||||||
$query->withCount('actions');
|
$query->withCount('actions');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->option('with-purchases')) {
|
if ($this->option('with-purchases')) {
|
||||||
$query->withCount('purchases');
|
$query->withCount('purchases');
|
||||||
}
|
}
|
||||||
|
|
||||||
$products = $query->orderBy('id')->get();
|
$products = $query->orderBy('name')->get();
|
||||||
|
|
||||||
if ($products->isEmpty()) {
|
if ($products->isEmpty()) {
|
||||||
$this->info('No products found.');
|
$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')) {
|
if ($this->option('with-actions')) {
|
||||||
$headers[] = 'Actions';
|
$headers[] = 'Actions';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->option('with-purchases')) {
|
if ($this->option('with-purchases')) {
|
||||||
$headers[] = 'Purchases';
|
$headers[] = 'Purchases';
|
||||||
}
|
}
|
||||||
|
|
||||||
$rows = $products->map(function ($product) {
|
$rows = $products->map(function ($product) {
|
||||||
|
$defaultPrice = optional($product->defaultPrice()->first())->getCurrentPrice();
|
||||||
|
|
||||||
$row = [
|
$row = [
|
||||||
$product->id,
|
substr((string) $product->id, 0, 8).'…',
|
||||||
$product->name,
|
$product->name,
|
||||||
$product->price,
|
$product->sku ?: '—',
|
||||||
$product->type ?? 'N/A',
|
$this->enumValue($product->type),
|
||||||
$product->enabled ? '✓' : '✗',
|
$this->enumValue($product->status),
|
||||||
|
$product->is_visible ? '✓' : '✗',
|
||||||
|
$defaultPrice !== null ? number_format((float) $defaultPrice, 2) : '—',
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($this->option('with-actions')) {
|
if ($this->option('with-actions')) {
|
||||||
$row[] = $product->actions_count ?? 0;
|
$row[] = (int) ($product->actions_count ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->option('with-purchases')) {
|
if ($this->option('with-purchases')) {
|
||||||
$row[] = $product->purchases_count ?? 0;
|
$row[] = (int) ($product->purchases_count ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $row;
|
return $row;
|
||||||
|
|
@ -75,6 +84,14 @@ class ShopListProductsCommand extends Command
|
||||||
$this->table($headers, $rows);
|
$this->table($headers, $rows);
|
||||||
$this->info("Total products: {$products->count()}");
|
$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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,54 +9,85 @@ use Illuminate\Console\Command;
|
||||||
class ShopListPurchasesCommand extends Command
|
class ShopListPurchasesCommand extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'shop:list:purchases
|
protected $signature = 'shop:list:purchases
|
||||||
{product? : Product ID to filter by}
|
{product? : Filter by purchasable (Product) ID}
|
||||||
{--user= : Filter by user ID}
|
{--purchaser= : Filter by purchaser ID (any polymorphic type)}
|
||||||
{--status= : Filter by status}
|
{--status= : Filter by purchase status}
|
||||||
{--limit=50 : Number of purchases to show}';
|
{--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');
|
$purchaseModel = config('shop.models.product_purchase');
|
||||||
$query = $purchaseModel::with(['product', 'user']);
|
$query = $purchaseModel::with(['purchasable', 'purchaser', 'price']);
|
||||||
|
|
||||||
if ($productId = $this->argument('product')) {
|
if ($productId = $this->argument('product')) {
|
||||||
$query->where('product_id', $productId);
|
$query->where('purchasable_id', $productId);
|
||||||
}
|
}
|
||||||
|
if ($purchaserId = $this->option('purchaser')) {
|
||||||
if ($userId = $this->option('user')) {
|
$query->where('purchaser_id', $purchaserId);
|
||||||
$query->where('user_id', $userId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($status = $this->option('status')) {
|
if ($status = $this->option('status')) {
|
||||||
$query->where('status', $status);
|
$query->where('status', $status);
|
||||||
}
|
}
|
||||||
|
|
||||||
$limit = (int) $this->option('limit');
|
$limit = max(1, (int) $this->option('limit'));
|
||||||
$purchases = $query->latest()->limit($limit)->get();
|
$purchases = $query->latest()->limit($limit)->get();
|
||||||
|
|
||||||
if ($purchases->isEmpty()) {
|
if ($purchases->isEmpty()) {
|
||||||
$this->info('No purchases found.');
|
$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) {
|
$this->table(
|
||||||
return [
|
['ID', 'Item', 'Purchaser', 'Amount', 'Paid', 'Status', 'Created'],
|
||||||
$purchase->id,
|
$rows,
|
||||||
$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->info("Showing {$purchases->count()} purchase(s)");
|
$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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Blax\Shop\Console\Commands;
|
namespace Blax\Shop\Console\Commands;
|
||||||
|
|
||||||
|
use Blax\Shop\Enums\ProductStatus;
|
||||||
|
use Blax\Shop\Enums\PurchaseStatus;
|
||||||
use Blax\Shop\Models\ProductAction;
|
use Blax\Shop\Models\ProductAction;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
|
@ -13,41 +15,59 @@ class ShopStatsCommand extends Command
|
||||||
|
|
||||||
protected $description = 'Display shop statistics';
|
protected $description = 'Display shop statistics';
|
||||||
|
|
||||||
public function handle()
|
public function handle(): int
|
||||||
{
|
{
|
||||||
$productModel = config('shop.models.product');
|
$productModel = config('shop.models.product');
|
||||||
$purchaseModel = config('shop.models.product_purchase');
|
$purchaseModel = config('shop.models.product_purchase');
|
||||||
|
$cartModel = config('shop.models.cart');
|
||||||
|
$orderModel = config('shop.models.order');
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
|
||||||
|
// Products
|
||||||
$totalProducts = $productModel::count();
|
$totalProducts = $productModel::count();
|
||||||
$enabledProducts = $productModel::where('enabled', true)->count();
|
$publishedProducts = $productModel::where('status', ProductStatus::PUBLISHED->value)->count();
|
||||||
$disabledProducts = $productModel::where('enabled', false)->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();
|
$totalActions = ProductAction::count();
|
||||||
$enabledActions = ProductAction::where('enabled', true)->count();
|
$activeActions = ProductAction::where('active', true)->count();
|
||||||
$disabledActions = ProductAction::where('enabled', false)->count();
|
$rows[] = ['Actions: total', $totalActions];
|
||||||
|
$rows[] = ['Actions: active', $activeActions];
|
||||||
|
$rows[] = ['Actions: inactive', $totalActions - $activeActions];
|
||||||
|
|
||||||
|
$rows[] = ['---', '---'];
|
||||||
|
|
||||||
|
// Purchases (loans, bookings, sales)
|
||||||
$totalPurchases = $purchaseModel::count();
|
$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->info('=== Shop Statistics ===');
|
||||||
$this->newLine();
|
$this->newLine();
|
||||||
|
$this->table(['Metric', 'Value'], $rows);
|
||||||
|
|
||||||
$this->table(
|
return self::SUCCESS;
|
||||||
['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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue