Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.75% covered (success)
93.75%
15 / 16
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
BomService
93.75% covered (success)
93.75%
15 / 16
66.67% covered (warning)
66.67%
2 / 3
4.00
0.00% covered (danger)
0.00%
0 / 1
 requirements
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 canProduce
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 availableStock
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
1<?php
2
3namespace App\Services\Manufacturing;
4
5use App\Models\admin\Product;
6use App\Models\admin\Stock;
7use App\Models\Bom;
8use Illuminate\Support\Collection;
9
10/**
11 * Turns a BOM + a quantity to produce into "how much of each material is
12 * needed, and do we have enough" — the read side of Manufacturing.
13 * ProductionService is what actually moves stock once the operator commits.
14 */
15class BomService
16{
17    /**
18     * @return Collection<int, array{material: Product, required: float, available: float, sufficient: bool}>
19     */
20    public function requirements(Bom $bom, float $qty): Collection
21    {
22        return $bom->items->map(function ($item) use ($qty) {
23            $required = round($item->qtyPerUnit() * $qty, 4);
24            $available = $this->availableStock($item->material_id);
25
26            return [
27                'material' => $item->material,
28                'unit' => $item->unit,
29                'required' => $required,
30                'available' => $available,
31                'sufficient' => $available >= $required,
32            ];
33        });
34    }
35
36    public function canProduce(Bom $bom, float $qty): bool
37    {
38        return $this->requirements($bom, $qty)->every(fn ($r) => $r['sufficient']);
39    }
40
41    /** Current on-hand quantity for a simple (non-variation) product's base stock row. */
42    public function availableStock(int $productId): float
43    {
44        $stock = Stock::where('product_id', $productId)->where('sku', 'BASE-' . $productId)->first();
45
46        if (! $stock) {
47            return 0.0;
48        }
49
50        return max(0.0, (float) $stock->stock_in - (float) $stock->stock_out);
51    }
52}