Calculator::findPlans()   C
last analyzed

Complexity

Conditions 12
Paths 24

Size

Total Lines 42
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 25.8615

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 12
eloc 26
c 4
b 0
f 0
nc 24
nop 1
dl 0
loc 42
rs 6.9666
ccs 13
cts 24
cp 0.5417
crap 25.8615

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * PHP Billing Library
4
 *
5
 * @link      https://github.com/hiqdev/php-billing
6
 * @package   php-billing
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2017-2020, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\php\billing\order;
12
13
use Exception;
14
use hiqdev\php\billing\action\ActionInterface;
15
use hiqdev\php\billing\action\TemporaryActionInterface;
16
use hiqdev\php\billing\charge\Charge;
17
use hiqdev\php\billing\charge\ChargeInterface;
18
use hiqdev\php\billing\charge\ChargeModifier;
19
use hiqdev\php\billing\charge\GeneralizerInterface;
20
use hiqdev\php\billing\Exception\ActionChargingException;
21
use hiqdev\php\billing\plan\Plan;
22
use hiqdev\php\billing\plan\PlanInterface;
23
use hiqdev\php\billing\plan\PlanRepositoryInterface;
24
use hiqdev\php\billing\price\PriceInterface;
25
use hiqdev\php\billing\sale\SaleInterface;
26
use hiqdev\php\billing\sale\SaleRepositoryInterface;
27
use hiqdev\php\billing\tools\ActualDateTimeProvider;
28
use hiqdev\php\billing\tools\CurrentDateTimeProviderInterface;
29
use Throwable;
30
31
/**
32
 * Calculator calculates charges for given order or action.
33
 *
34
 * @author Andrii Vasyliev <[email protected]>
35
 */
36
class Calculator implements CalculatorInterface
37
{
38
    protected GeneralizerInterface $generalizer;
39
    private SaleRepositoryInterface $saleRepository;
40
    private PlanRepositoryInterface $planRepository;
41
42
    private CurrentDateTimeProviderInterface $dateTimeProvider;
43
44
    public function __construct(
45
        GeneralizerInterface $generalizer,
46
        SaleRepositoryInterface $saleRepository,
47
        PlanRepositoryInterface $planRepository,
48
        CurrentDateTimeProviderInterface $dateTimeProvider = null
49
    ) {
50
        $this->generalizer    = $generalizer;
51 37
        $this->saleRepository = $saleRepository;
52
        $this->planRepository = $planRepository;
53
        $this->dateTimeProvider = $dateTimeProvider ?? new ActualDateTimeProvider();
54
    }
55
56 37
    /**
57 37
     * {@inheritdoc}
58 37
     */
59 37
    public function calculateOrder(OrderInterface $order): array
60
    {
61
        $plans = $this->findPlans($order);
62
        $charges = [];
63
        foreach ($order->getActions() as $actionKey => $action) {
64 2
            if (!empty($plans[$actionKey])) {
65
                try {
66 2
                    $charges = array_merge($charges, $this->calculatePlan($plans[$actionKey], $action));
67 2
                } catch (Throwable $e) {
68 2
                    throw ActionChargingException::forAction($action, $e);
69 2
                }
70
            }
71
        }
72
73 2
        return $charges;
74
    }
75
76 2
    public function calculatePlan(PlanInterface $plan, ActionInterface $action): array
77
    {
78
        $result = [];
79 4
        foreach ($plan->getPrices() as $price) {
80
            $charges = $this->calculatePrice($price, $action);
81 4
            if (!empty($charges)) {
82 4
                $result = array_merge($result, $charges);
83 4
            }
84 4
        }
85 4
86
        return $result;
87
    }
88
89 4
    /**
90
     * {@inheritdoc}
91
     */
92
    public function calculatePrice(PriceInterface $price, ActionInterface $action): array
93
    {
94
        $charge = $this->calculateCharge($price, $action);
95 4
        if ($charge === null) {
96
            return [];
97 4
        }
98 4
99 4
        if ($price instanceof ChargeModifier) {
100
            $charges = $price->modifyCharge($charge, $action);
101
        } else {
102 4
            $charges = [$charge];
103 4
        }
104
105
        if ($action->isFinished()) {
106
            foreach ($charges as $charge) {
107
                $charge->setFinished();
108 4
            }
109
        }
110
111
        return $charges;
112
    }
113
114 4
    /**
115
     * Calculates charge for given action and price.
116
     * Returns `null`, if $price is not applicable to $action.
117
     *
118
     * @return ChargeInterface|Charge|null
119
     */
120
    public function calculateCharge(PriceInterface $price, ActionInterface $action): ?ChargeInterface
121
    {
122
        if (!$action->isApplicable($price)) {
123 25
            return null;
124
        }
125 25
126 8
        if ($action->getSale() !== null && $action->getSale()->getTime() > $this->dateTimeProvider->dateTimeImmutable()) {
127
            return null;
128
        }
129 21
130 21
        $usage = $price->calculateUsage($action->getQuantity());
131 4
        if ($usage === null) {
132
            return null;
133
        }
134 17
135 17
        $sum = $price->calculateSum($action->getQuantity());
136
        if ($sum === null) {
137
            return null;
138
        }
139 17
140 17
        $type = $this->generalizer->specializeType($price->getType(), $action->getType());
141
        $target = $this->generalizer->specializeTarget($price->getTarget(), $action->getTarget());
142
143
        /* sorry, debugging facility
144
         * var_dump([
145
            'unit'      => $usage->getUnit()->getName(),
146
            'quantity'  => $usage->getQuantity(),
147
            'price'     => $price->calculatePrice($usage)->getAmount(),
148
            'sum'       => $sum->getAmount(),
149
        ]);*/
150 17
151
        return new Charge(null, $type, $target, $action, $price, $usage, $sum);
152
    }
153
154
    /**
155
     * @throws Exception
156
     * @return PlanInterface[]
157 2
     */
158
    private function findPlans(OrderInterface $order): array
159 2
    {
160 2
        $sales = $this->findSales($order);
161 2
        $plans = [];
162 2
        $lookPlanIds = [];
163
        foreach ($order->getActions() as $actionKey => $action) {
164 2
            if (!empty($sales[$actionKey])) {
165
                $sale = $sales[$actionKey];
166
                /** @var Plan|PlanInterface[] $plan */
167
                $plan = $sale->getPlan();
168 2
169
                if ($action instanceof TemporaryActionInterface && $plan->getId() && !$action->hasSale()) {
170 2
                    $action->setSale($sale);
171
                }
172 2
173
                if ($plan->hasPrices()) {
174
                    $plans[$actionKey] = $plan;
175
                } elseif ($plan->getId() !== null) {
176 2
                    $lookPlanIds[$actionKey] = $plan->getId();
177 2
                } else {
178
                    $plans[$actionKey] = null;
179
                }
180
            } else {
181 2
                // It is ok when no sale found for upper resellers
182
                $plans[$actionKey] = null;
183
            }
184
        }
185
186 2
        if ($lookPlanIds) {
187
            $foundPlans = $this->planRepository->findByIds($lookPlanIds);
188
            foreach ($foundPlans as $plan) {
189
                $foundPlans[$plan->getId()] = $plan;
190
            }
191
            foreach ($lookPlanIds as $actionKey => $planId) {
192
                if (empty($foundPlans[$planId])) {
193
                    throw new Exception('not found plan');
194
                }
195
                $plans[$actionKey] = $foundPlans[$planId];
196
            }
197
        }
198
199 2
        return $plans;
200
    }
201
202
    /**
203
     * @param OrderInterface $order
204
     * @return SaleInterface[]
205 2
     */
206
    private function findSales(OrderInterface $order): array
207 2
    {
208 2
        $sales = [];
209 2
        $lookActions = [];
210 2
        foreach ($order->getActions() as $actionKey => $action) {
211 2
            $sale = $action->getSale();
212
            if ($sale) {
213
                $sales[$actionKey] = $sale;
214 2
            } else {
215
                $lookActions[$actionKey] = $action;
216
            }
217
        }
218 2
219 2
        if ($lookActions) {
220 2
            $lookOrder = new Order(null, $order->getCustomer(), $lookActions);
221 2
            $foundSales = $this->saleRepository->findByOrder($lookOrder);
222 2
            foreach ($foundSales as $actionKey => $sale) {
223
                $sales[$actionKey] = $sale;
224
                if ($sale !== false) {
225
                    $lookActions[$actionKey]->setSale($sale);
226 2
                }
227
            }
228
        }
229
230
        return $sales;
231
    }
232
}
233