Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
82.61% covered (warning)
82.61%
38 / 46
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
CurrentStockReport
82.61% covered (warning)
82.61%
38 / 46
75.00% covered (warning)
75.00%
6 / 8
18.52
0.00% covered (danger)
0.00%
0 / 1
 key
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 label
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 group
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 description
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 filters
33.33% covered (danger)
33.33%
2 / 6
0.00% covered (danger)
0.00%
0 / 1
3.19
 columns
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 rows
82.61% covered (warning)
82.61%
19 / 23
0.00% covered (danger)
0.00%
0 / 1
9.43
 summary
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace App\Reports\Inventory;
4
5use App\Models\admin\Product;
6use App\Models\WarehouseStock;
7use App\Reports\Report;
8use App\Services\Location\BranchContext;
9use Illuminate\Support\Collection;
10
11class CurrentStockReport extends Report
12{
13    public function key(): string
14    {
15        return 'current-stock';
16    }
17
18    public function label(): string
19    {
20        return 'Current Stock';
21    }
22
23    public function group(): string
24    {
25        return 'Inventory';
26    }
27
28    public function description(): ?string
29    {
30        return 'On-hand quantity and stock value for every product.';
31    }
32
33    public function filters(): array
34    {
35        if (! feature('multi_warehouse')) {
36            return [];
37        }
38
39        return ['warehouse_id' => [
40            'type' => 'select', 'label' => 'Warehouse', 'default' => '',
41            'options' => ['' => 'All warehouses'] + app(BranchContext::class)->warehouses()->active()->orderBy('name')->pluck('name', 'id')->all(),
42        ]];
43    }
44
45    public function columns(): array
46    {
47        return [
48            ['key' => 'name', 'label' => 'Product'],
49            ['key' => 'category', 'label' => 'Category'],
50            ['key' => 'stock', 'label' => 'On Hand', 'align' => 'right'],
51            ['key' => 'price', 'label' => 'Price', 'align' => 'right', 'render' => fn ($r) => money($r->price)],
52            ['key' => 'value', 'label' => 'Stock Value', 'align' => 'right', 'render' => fn ($r) => money($r->value)],
53            ['key' => 'status', 'label' => 'Status'],
54        ];
55    }
56
57    public function rows(array $filters): Collection
58    {
59        $warehouseId = $filters['warehouse_id'] ?? null;
60        $branchContext = app(BranchContext::class);
61        $branchContext->ensureWarehouse($warehouseId ? (int) $warehouseId : null);
62        $branchId = $branchContext->id();
63        $useLocationTotals = (bool) ($warehouseId || $branchId);
64        $locationTotals = $useLocationTotals
65            ? WarehouseStock::query()
66                ->when($warehouseId, fn ($query) => $query->where('warehouse_id', $warehouseId))
67                ->when(! $warehouseId && $branchId, fn ($query) => $query->whereHas('warehouse', fn ($warehouse) => $warehouse->where('branch_id', $branchId)))
68                ->selectRaw('product_id, SUM(quantity) quantity')->groupBy('product_id')->pluck('quantity', 'product_id')
69            : collect();
70
71        return Product::query()
72            ->with('category')
73            ->orderBy('name')
74            ->get()
75            ->map(fn (Product $p) => (object) [
76                'name' => $p->name,
77                'category' => $p->category->name ?? '—',
78                'stock' => (float) ($useLocationTotals ? ($locationTotals[$p->id] ?? 0) : $p->stock),
79                'price' => (float) $p->price,
80                'value' => (float) ($useLocationTotals ? ($locationTotals[$p->id] ?? 0) : $p->stock) * (float) $p->price,
81                'status' => (float) ($useLocationTotals ? ($locationTotals[$p->id] ?? 0) : $p->stock) > 0 ? 'In stock' : 'Out of stock',
82            ]);
83    }
84
85    public function summary(Collection $rows, array $filters): array
86    {
87        return [
88            'Products' => number_format($rows->count()),
89            'Units On Hand' => number_format($rows->sum('stock')),
90            'Stock Value' => money($rows->sum('value')),
91        ];
92    }
93}