Completed
Push — master ( 6bfaa4...9a5e12 )
by Hannes
02:00
created

ItemBasket::reduce()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1
Metric Value
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 9.4285
cc 1
eloc 6
nc 1
nop 1
crap 1
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 33
    public function __construct(ItemEnvelope ...$items)
28
    {
29 33
        foreach ($items as $envelope) {
30 16
            $this->addItem($envelope);
31
        }
32 32
    }
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 Billable[]
145
     */
146 2
    public function getVatRates(): array
147
    {
148 2
        $rates = [];
149
150 2
        foreach ($this as $envelope) {
151 2
            if ($envelope->getVatRate()->isPositive()) {
152 2
                $key = (string)$envelope->getVatRate();
153
154 2
                if (!array_key_exists($key, $rates)) {
155 2
                    $rates[$key] = new SimpleItem('', $this->createCurrencyObject('0'), 1, $envelope->getVatRate());
156
                }
157
158 2
                $rates[$key] = new SimpleItem(
159 2
                    $rates[$key]->getBillingDescription(),
160 2
                    $rates[$key]->getCostPerUnit()->add($envelope->getTotalUnitCost()),
161 2
                    $rates[$key]->getNrOfUnits(),
162 2
                    $rates[$key]->getVatRate()
163
                );
164
            }
165
        }
166
167 2
        ksort($rates);
168
169 2
        return array_values($rates);
170
    }
171
172
    /**
173
     * Reduce loaded items to single amount using envelope method
174
     */
175 9
    private function reduce(string $method): Amount
176
    {
177 9
        return array_reduce(
178 9
            $this->getItems(),
179 9
            function (Amount $carry, ItemEnvelope $envelope) use ($method) {
180 9
                return $carry->add($envelope->$method());
181 9
            },
182 9
            $this->createCurrencyObject('0')
183
        );
184
    }
185
}
186