Completed
Push — master ( e8cf85...123a4d )
by Hannes
02:34
created

ItemBasket::getVatRates()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4
Metric Value
dl 0
loc 20
ccs 10
cts 10
cp 1
rs 9.2
cc 4
eloc 10
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 charged vat amounts for non-zero vat rates
143
     *
144
     * @return Amount[]
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
            if (!isset($rates[$envelope->getVatRate()])) {
156 2
                $rates[$envelope->getVatRate()] = $this->createCurrencyObject('0');
157
            }
158
159 2
            $rates[$envelope->getVatRate()] = $rates[$envelope->getVatRate()]->add($envelope->getTotalVatCost());
160
        }
161
162 2
        ksort($rates);
163
164 2
        return $rates;
165
    }
166
167
    /**
168
     * Reduce loaded items to single amount using envelope method
169
     */
170 9
    private function reduce(string $method): Amount
171
    {
172 9
        return array_reduce(
173 9
            $this->getItems(),
174 9
            function (Amount $carry, ItemEnvelope $envelope) use ($method) {
175 9
                return $carry->add($envelope->$method());
176 9
            },
177 9
            $this->createCurrencyObject('0')
178
        );
179
    }
180
}
181