Completed
Push — master ( c9bbb6...141696 )
by Hannes
03:35 queued 01:56
created

ItemBasket::getVatRates()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4
Metric Value
dl 0
loc 27
ccs 15
cts 15
cp 1
rs 8.5806
cc 4
eloc 15
nc 4
nop 0
crap 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace byrokrat\billing;
6
7
use byrokrat\amount\Amount;
8
9
/**
10
 * Container for billable items
11
 */
12
class ItemBasket implements \IteratorAggregate
13
{
14
    /**
15
     * @var ItemEnvelope[] Contained items
16
     */
17
    private $items = [];
18
19
    /**
20
     * @var string Classname of currency used in basket
21
     */
22
    private $currencyClassname = '';
23
24
    /**
25
     * Optionally load items at construct
26
     */
27 32
    public function __construct(ItemEnvelope ...$items)
28
    {
29 32
        foreach ($items as $envelope) {
30 16
            $this->addItem($envelope);
31
        }
32 31
    }
33
34
    /**
35
     * Add item to basket
36
     *
37
     * @throws Exception If two items with different currencies are added
38
     */
39 17
    public function addItem(ItemEnvelope $envelope): self
40
    {
41 17
        if (!$this->currencyClassname) {
42 17
            $this->currencyClassname = $envelope->getCurrencyClassname();
43
        }
44
45 17
        if ($envelope->getCurrencyClassname() != $this->getCurrencyClassname()) {
46 1
            throw new Exception('Unable to load items with different currencies');
47
        }
48
49 17
        $this->items[] = $envelope;
50 17
        return $this;
51
    }
52
53
    /**
54
     * Get classname of currency used in basket
55
     */
56 18
    public function getCurrencyClassname(): string
57
    {
58 18
        return $this->currencyClassname;
59
    }
60
61
    /**
62
     * Create amount object using the current basket currency
63
     *
64
     * @throws Exception If no item is loaded and currency is unknown
65
     */
66 12
    public function createCurrencyObject(string $value): Amount
67
    {
68 12
        if (!$currency = $this->getCurrencyClassname()) {
69 1
            throw new Exception('Unable to create currency object, currency unknown.');
70
        }
71
72 11
        return new $currency($value);
73
    }
74
75
    /**
76
     * Get contained items
77
     *
78
     * @return ItemEnvelope[]
79
     */
80 15
    public function getItems(): array
81
    {
82 15
        return $this->items;
83
    }
84
85
    /**
86
     * Implements the IteratorAggregate interface
87
     */
88 2
    public function getIterator(): \Traversable
89
    {
90 2
        foreach ($this->getItems() as $envelope) {
91 2
            yield $envelope;
92
        }
93 2
    }
94
95
    /**
96
     * Get number of items in basket
97
     */
98 2
    public function getNrOfItems(): int
99
    {
100 2
        return count($this->getItems());
101
    }
102
103
    /**
104
     * Get number of units in basket (each item may contain multiple units)
105
     */
106 2
    public function getNrOfUnits(): int
107
    {
108 2
        return array_reduce(
109 2
            $this->getItems(),
110
            function (int $carry, ItemEnvelope $envelope) {
111 2
                return $carry + $envelope->getNrOfUnits();
112 2
            },
113
            0
114
        );
115
    }
116
117
    /**
118
     * Get total cost of all items (VAT excluded)
119
     */
120 7
    public function getTotalUnitCost(): Amount
121
    {
122 7
        return $this->reduce('getTotalUnitCost');
123
    }
124
125
    /**
126
     * Get total VAT cost for all items
127
     */
128 7
    public function getTotalVatCost(): Amount
129
    {
130 7
        return $this->reduce('getTotalVatCost');
131
    }
132
133
    /**
134
     * Get total cost of all items (VAT included)
135
     */
136 5
    public function getTotalCost(): Amount
137
    {
138 5
        return $this->getTotalVatCost()->add($this->getTotalUnitCost());
139
    }
140
141
    /**
142
     * Get unit and vat totals for non-zero vat rates
143
     *
144
     * @return array
145
     */
146 2
    public function getVatRates(): array
147
    {
148 2
        $rates = [];
149
150 2
        foreach ($this as $envelope) {
151 2
            if ($envelope->getVatRate() <= 0) {
152 2
                continue;
153
            }
154
155 2
            $key = (string)$envelope->getVatRate();
156
157 2
            if (!isset($rates[$key])) {
158 2
                $rates[$key] = [
159 2
                    'unit_total' => $this->createCurrencyObject('0'),
160 2
                    'vat_total' => $this->createCurrencyObject('0')
161
                ];
162
            }
163
164 2
            $rates[$key] = [
165 2
                'unit_total' => $rates[$key]['unit_total']->add($envelope->getTotalUnitCost()),
166 2
                'vat_total' => $rates[$key]['vat_total']->add($envelope->getTotalVatCost())
167
            ];
168
        }
169
170 2
        ksort($rates);
171 2
        return $rates;
172
    }
173
174
    /**
175
     * Reduce loaded items to single amount using envelope method
176
     */
177 9
    private function reduce(string $method): Amount
178
    {
179 9
        return array_reduce(
180 9
            $this->getItems(),
181 9
            function (Amount $carry, ItemEnvelope $envelope) use ($method) {
182 9
                return $carry->add($envelope->$method());
183 9
            },
184 9
            $this->createCurrencyObject('0')
185
        );
186
    }
187
}
188