Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.21% covered (warning)
84.21%
16 / 19
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
StockResolver
84.21% covered (warning)
84.21%
16 / 19
75.00% covered (warning)
75.00%
3 / 4
19.28
0.00% covered (danger)
0.00%
0 / 1
 globallyEnabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isTracked
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 displayMode
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 canSell
75.00% covered (warning)
75.00%
9 / 12
0.00% covered (danger)
0.00%
0 / 1
12.89
1<?php
2
3namespace App\Services\Stock;
4
5use App\Models\admin\Product;
6use App\Models\ProductVariation;
7
8/**
9 * The §5.4 resolution matrix — "is this product's stock actually tracked, and
10 * how should the form / storefront treat it?".
11 *
12 *  inventory_tracking | product.manage_stock | multi_warehouse/vendor | result
13 *  ------------------ | -------------------- | ---------------------- | ------
14 *  OFF                | —                    | —                      | none      (manual availability)
15 *  ON                 | OFF                  | —                      | none      (manual availability)
16 *  ON                 | ON                   | OFF                    | single    (one quantity)
17 *  ON                 | ON                   | ON                     | per_location (per-warehouse rows)
18 */
19class StockResolver
20{
21    public const NONE = 'none';
22
23    public const SINGLE = 'single';
24
25    public const PER_LOCATION = 'per_location';
26
27    /** Master switch — is stock tracking on for the store at all? */
28    public function globallyEnabled(): bool
29    {
30        return (bool) feature('inventory_tracking');
31    }
32
33    /** Is THIS product's quantity tracked right now? */
34    public function isTracked(Product $product): bool
35    {
36        return $this->globallyEnabled() && (bool) $product->manage_stock;
37    }
38
39    /** What the product form / storefront should render for stock. */
40    public function displayMode(Product $product): string
41    {
42        if (! $this->isTracked($product)) {
43            return self::NONE;
44        }
45
46        return (feature('multi_warehouse') || feature('multi_vendor'))
47            ? self::PER_LOCATION
48            : self::SINGLE;
49    }
50
51    /**
52     * Can the shopper buy `$qty` of this product? Untracked products use
53     * manual availability. Tracked products require sufficient on-hand quantity.
54     */
55    public function canSell(Product $product, int $qty = 1, ?ProductVariation $variation = null): bool
56    {
57        if ($qty < 1 || ($variation && (int) $variation->product_id !== (int) $product->id)) {
58            return false;
59        }
60        if (! $this->isTracked($product)) {
61            return $product->stock_status === 'in_stock'
62                && (! $variation || $variation->stock_status === 'in_stock');
63        }
64        if ($product->stock_status !== 'in_stock') {
65            return false;
66        }
67        if ($variation) {
68            return $variation->stock_status === 'in_stock' && (int) $variation->stock_quantity >= $qty;
69        }
70        if ($product->variations->isNotEmpty()) {
71            return $product->variations->contains(fn ($v) => $this->canSell($product, $qty, $v));
72        }
73
74        return (int) $product->stock >= $qty;
75    }
76}