Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.24% covered (success)
90.24%
37 / 41
87.50% covered (warning)
87.50%
7 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
StockMovementLedgerReport
90.24% covered (success)
90.24%
37 / 41
87.50% covered (warning)
87.50%
7 / 8
12.13
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
42.86% covered (danger)
42.86%
3 / 7
0.00% covered (danger)
0.00%
0 / 1
2.75
 columns
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 rows
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
3
 summary
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace App\Reports\Inventory;
4
5use App\Models\StockMovement;
6use App\Reports\Report;
7use App\Services\Location\BranchContext;
8use Illuminate\Support\Collection;
9
10/**
11 * The stock_movements ledger — Manufacturing-only writes as of Phase 9
12 * (production_in/production_out); purchase/sale flows don't post here yet
13 * (see stock_movements migration comment), so this is empty until the
14 * Manufacturing module is used, even with plenty of stock activity elsewhere.
15 */
16class StockMovementLedgerReport extends Report
17{
18    public function key(): string
19    {
20        return 'stock-movement-ledger';
21    }
22
23    public function label(): string
24    {
25        return 'Stock Movement Ledger';
26    }
27
28    public function group(): string
29    {
30        return 'Inventory';
31    }
32
33    public function description(): ?string
34    {
35        return 'Every purchase, sale, return, adjustment, transfer and production stock movement.';
36    }
37
38    public function filters(): array
39    {
40        $filters = $this->dateRangeFilters();
41        if (feature('multi_warehouse')) {
42            $filters = ['warehouse_id' => [
43                'type' => 'select', 'label' => 'Warehouse', 'default' => '',
44                'options' => ['' => 'All warehouses'] + app(BranchContext::class)->warehouses()->active()->orderBy('name')->pluck('name', 'id')->all(),
45            ]] + $filters;
46        }
47
48        return $filters;
49    }
50
51    public function columns(): array
52    {
53        return [
54            ['key' => 'date', 'label' => 'Date'],
55            ['key' => 'product', 'label' => 'Product'],
56            ['key' => 'warehouse', 'label' => 'Warehouse'],
57            ['key' => 'type', 'label' => 'Type'],
58            ['key' => 'qty', 'label' => 'Qty', 'align' => 'right', 'render' => fn ($r) => ($r->qty > 0 ? '+' : '') . rtrim(rtrim((string) $r->qty, '0'), '.')],
59            ['key' => 'note', 'label' => 'Note'],
60        ];
61    }
62
63    public function rows(array $filters): Collection
64    {
65        $warehouseId = $filters['warehouse_id'] ?? null;
66        $branchContext = app(BranchContext::class);
67        $branchContext->ensureWarehouse($warehouseId ? (int) $warehouseId : null);
68
69        return StockMovement::query()
70            ->with(['product', 'warehouse'])
71            ->when($warehouseId, fn ($q) => $q->where('warehouse_id', $warehouseId))
72            ->when(! $warehouseId && $branchContext->id(), fn ($q, $branchId) => $q->where('branch_id', $branchId))
73            ->whereDate('created_at', '>=', $filters['from'])
74            ->whereDate('created_at', '<=', $filters['to'])
75            ->latest()
76            ->get()
77            ->map(fn (StockMovement $m) => (object) [
78                'date' => $m->created_at->format('d M, Y'),
79                'product' => $m->product->name ?? '—',
80                'warehouse' => $m->warehouse->name ?? 'Main location',
81                'type' => str_replace('_', ' ', ucfirst($m->type)),
82                'qty' => (float) $m->qty,
83                'note' => $m->note,
84            ]);
85    }
86
87    public function summary(Collection $rows, array $filters): array
88    {
89        return [
90            'Movements' => number_format($rows->count()),
91        ];
92    }
93}