Passed
Pull Request — master (#73)
by
unknown
27:22 queued 12:23
created

targetIsSoldToCustomerByPlanSinceTill()   B

Complexity

Conditions 10
Paths 66

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 15
c 3
b 0
f 0
dl 0
loc 26
rs 7.6666
cc 10
nc 66
nop 4

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\tests\behat\bootstrap;
12
13
use Behat\Gherkin\Node\TableNode;
14
use BehatExpectException\ExpectException;
15
use DateTimeImmutable;
16
use hiqdev\php\billing\bill\BillInterface;
17
use hiqdev\php\billing\charge\ChargeInterface;
18
use PHPUnit\Framework\Assert;
19
20
class BillingContext extends BaseContext
21
{
22
    use ExpectException {
23
        mayFail as protected;
24
        shouldFail as protected;
25
        assertCaughtExceptionMatches as protected;
26
    }
27
28
    protected $saleTime;
29
30
    protected $saleCloseTime;
31
32
    protected $bill;
33
34
    protected $charges = [];
35
36
    protected array $progressivePrice = [];
37
38
    /**
39
     * @Given reseller :reseller
40
     */
41
    public function reseller($reseller)
42
    {
43
        $this->builder->buildReseller($reseller);
44
    }
45
46
    /**
47
     * @Given customer :customer
48
     */
49
    public function customer($customer)
50
    {
51
        $this->builder->buildCustomer($customer);
52
    }
53
54
    /**
55
     * @Given manager :manager
56
     */
57
    public function manager($manager)
58
    {
59
        $this->builder->buildManager($manager);
0 ignored issues
show
Bug introduced by
The method buildManager() does not exist on hiqdev\php\billing\tests...tstrap\BuilderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

59
        $this->builder->/** @scrutinizer ignore-call */ 
60
                        buildManager($manager);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
60
    }
61
62
    /**
63
     * @Given /^(\S+ )?(\S+) tariff plan (\S+)/
64
     */
65
    public function plan($prefix, $type, $plan)
66
    {
67
        $prefix = strtr($prefix, ' ', '_');
68
        $grouping = $prefix === 'grouping_';
69
        $type = $grouping ? $type : $prefix.$type;
70
        $this->builder->buildPlan($plan, $type, $grouping);
71
    }
72
73
    protected function fullPrice(array $data)
74
    {
75
        if (!empty($data['price'])) {
76
            $data['rate'] = $data['price'];
77
        }
78
        $this->builder->buildPrice($data);
79
    }
80
81
    /**
82
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+) for target (.+)$/
83
     */
84
    public function priceWithTarget($type, $price, $currency, $unit, $target)
85
    {
86
        return $this->fullPrice(compact('type', 'price', 'currency', 'unit', 'target'));
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->fullPrice(compact...cy', 'unit', 'target')) targeting hiqdev\php\billing\tests...ingContext::fullPrice() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
87
    }
88
89
    /**
90
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+) prepaid (\S+)$/
91
     */
92
    public function priceWithPrepaid($type, $price, $currency, $unit, $prepaid)
93
    {
94
        return $this->fullPrice(compact('type', 'price', 'currency', 'unit', 'prepaid'));
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->fullPrice(compact...y', 'unit', 'prepaid')) targeting hiqdev\php\billing\tests...ingContext::fullPrice() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
95
    }
96
97
    /**
98
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+) prepaid (\S+) for target (\S+)$/
99
     */
100
    public function priceWithPrepaidAndTarget($type, $price, $currency, $unit, $prepaid, $target)
101
    {
102
        return $this->fullPrice(compact('type', 'price', 'currency', 'unit', 'prepaid', 'target'));
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->fullPrice(compact..., 'prepaid', 'target')) targeting hiqdev\php\billing\tests...ingContext::fullPrice() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
103
    }
104
105
    /**
106
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+)$/
107
     */
108
    public function price($type, $price, $currency, $unit)
109
    {
110
        return $this->fullPrice(compact('type', 'price', 'currency', 'unit'));
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->fullPrice(compact...', 'currency', 'unit')) targeting hiqdev\php\billing\tests...ingContext::fullPrice() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
111
    }
112
113
    /**
114
     * @Given /price for (\S+) is +(\S+) (\S+) per 1 (\S+) and (\S+) (\S+) per 2 (\S+) for target (\S+)/
115
     */
116
    public function enumPrice($type, $price, $currency, $unit, $price2, $currency2, $unit2, $target)
0 ignored issues
show
Unused Code introduced by
The parameter $unit2 is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

116
    public function enumPrice($type, $price, $currency, $unit, $price2, $currency2, /** @scrutinizer ignore-unused */ $unit2, $target)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $currency2 is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

116
    public function enumPrice($type, $price, $currency, $unit, $price2, /** @scrutinizer ignore-unused */ $currency2, $unit2, $target)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
117
    {
118
        $sums = [1 => $price, 2 => $price2];
119
120
        return $this->fullPrice(compact('type', 'sums', 'currency', 'unit', 'target'));
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->fullPrice(compact...cy', 'unit', 'target')) targeting hiqdev\php\billing\tests...ingContext::fullPrice() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
121
    }
122
123
    /**
124
     * @Given /progressive price for (\S+) is +(\S+) (\S+) per (\S+) (\S+) (\S+) (\S+)$/
125
     */
126
    public function progressivePrice($type, $price, $currency, $unit, $sign, $quantity, $perUnit): void
0 ignored issues
show
Unused Code introduced by
The parameter $perUnit is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

126
    public function progressivePrice($type, $price, $currency, $unit, $sign, $quantity, /** @scrutinizer ignore-unused */ $perUnit): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $sign is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

126
    public function progressivePrice($type, $price, $currency, $unit, /** @scrutinizer ignore-unused */ $sign, $quantity, $perUnit): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
127
    {
128
        if (empty($this->progressivePrice[$type])) {
129
            $this->progressivePrice[$type] = [
130
                'price' => 0,
131
                'currency' => $currency,
132
                'unit' => $unit,
133
                'thresholds' =>[
134
                    [
135
                        'price' => $price,
136
                        'currency' => $currency,
137
                        'quantity' => $quantity,
138
                        'unit' => $unit,
139
                    ],
140
                ] ,
141
            ];
142
        } else {
143
            array_push(
144
                $this->progressivePrice[$type]['thresholds'],
145
                [
146
                    'price' => $price,
147
                    'currency' => $currency,
148
                    'quantity' => $quantity,
149
                    'unit' => $unit,
150
                ]
151
            );
152
        }
153
    }
154
155
    /**
156
     * @Given /^remove and recreate tariff plan (\S+)/
157
     */
158
    public function recreatePlan($plan)
159
    {
160
        $this->builder->recreatePlan($plan);
161
    }
162
163
    /**
164
     * @Given /sale target (\S+) by plan (\S+) at (\S+)/
165
     */
166
    public function sale($target, $plan, $time): void
167
    {
168
        $this->saleTime = $this->prepareTime($time);
169
        $this->sale = $this->builder->buildSale($target, $plan, $this->saleTime);
0 ignored issues
show
Bug introduced by
It seems like $this->saleTime can also be of type null; however, parameter $time of hiqdev\php\billing\tests...rInterface::buildSale() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

169
        $this->sale = $this->builder->buildSale($target, $plan, /** @scrutinizer ignore-type */ $this->saleTime);
Loading history...
Bug Best Practice introduced by
The property sale does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
170
    }
171
172
    /**
173
     * @Given /sale target (\S+) by plan (\S+) at (\S+) and close at (\S+)/
174
     */
175
    public function saleWithCloseTime($target, $plan, $time, $closeTime): void
176
    {
177
        $this->saleTime = $this->prepareTime($time);
178
        $this->saleCloseTime = $this->prepareTime($closeTime);
179
        $this->builder->buildSale($target, $plan, $this->saleTime, $this->saleCloseTime);
0 ignored issues
show
Bug introduced by
It seems like $this->saleTime can also be of type null; however, parameter $time of hiqdev\php\billing\tests...rInterface::buildSale() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

179
        $this->builder->buildSale($target, $plan, /** @scrutinizer ignore-type */ $this->saleTime, $this->saleCloseTime);
Loading history...
180
    }
181
182
    /**
183
     * @When /^sale close is requested for target "([^"]*)" at "([^"]*)", assuming current time is "([^"]*)"$/
184
     */
185
    public function saleClose(string $target, string $time, ?string $wallTime)
0 ignored issues
show
Unused Code introduced by
The parameter $wallTime is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

185
    public function saleClose(string $target, string $time, /** @scrutinizer ignore-unused */ ?string $wallTime)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $target is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

185
    public function saleClose(/** @scrutinizer ignore-unused */ string $target, string $time, ?string $wallTime)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $time is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

185
    public function saleClose(string $target, /** @scrutinizer ignore-unused */ string $time, ?string $wallTime)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
186
    {
187
        throw new PendingException();
0 ignored issues
show
Bug introduced by
The type hiqdev\php\billing\tests...tstrap\PendingException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
188
    }
189
190
    /**
191
     * @Then /^target "([^"]*)" has exactly (\d+) sale for customer$/
192
     */
193
    public function targetHasExactlyNSaleForCustomer(string $target, string $count)
0 ignored issues
show
Unused Code introduced by
The parameter $target is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

193
    public function targetHasExactlyNSaleForCustomer(/** @scrutinizer ignore-unused */ string $target, string $count)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
194
    {
195
        // TODO: implement
196
        // $sales = $this->builder->findSales(['target-name' => $target]);
197
198
        Assert::assertCount($count, $sales);
0 ignored issues
show
Bug introduced by
$count of type string is incompatible with the type integer expected by parameter $expectedCount of PHPUnit\Framework\Assert::assertCount(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

198
        Assert::assertCount(/** @scrutinizer ignore-type */ $count, $sales);
Loading history...
Comprehensibility Best Practice introduced by
The variable $sales seems to be never defined.
Loading history...
199
    }
200
201
    /**
202
     * @Given /purchase target (\S+) by plan (\S+) at ([-:\w\s]+)$/
203
     */
204
    public function purchaseTarget(string $target, string $plan, string $time): void
205
    {
206
        $time = $this->prepareTime($time);
207
        $this->builder->buildPurchase($target, $plan, $time);
208
    }
209
210
    /**
211
     * @Given /^purchase target "([^"]*)" by plan "([^"]*)" at "([^"]*)" with the following initial uses:$/
212
     */
213
    public function purchaseTargetWithInitialUses(string $target, string $plan, string $time, TableNode $usesTable): void
214
    {
215
        $time = $this->prepareTime($time);
216
        $uses = array_map(static function (array $row) {
217
            return [
218
                'type' => $row['type'],
219
                'unit' => $row['unit'],
220
                'amount' => $row['amount'],
221
            ];
222
        }, $usesTable->getColumnsHash());
223
224
        $this->mayFail(
225
            fn() => $this->builder->buildPurchase($target, $plan, $time, $uses)
226
        );
227
    }
228
229
    /**
230
     * @Given /resource consumption for (\S+) is +(\S+) (\S+) for target (\S+) at (.+)$/
231
     */
232
    public function setConsumption(string $type, int $amount, string $unit, string $target, string $time): void
233
    {
234
        $time = $this->prepareTime($time);
235
        $this->builder->setConsumption($type, $amount, $unit, $target, $time);
0 ignored issues
show
Bug introduced by
The method setConsumption() does not exist on hiqdev\php\billing\tests...tstrap\BuilderInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to hiqdev\php\billing\tests...tstrap\BuilderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

235
        $this->builder->/** @scrutinizer ignore-call */ 
236
                        setConsumption($type, $amount, $unit, $target, $time);
Loading history...
236
    }
237
238
    /**
239
     * @Given /recalculate autotariff for target (\S+)( +at (\S+))?$/
240
     */
241
    public function recalculateAutoTariff(string $target, string $time = null): void
242
    {
243
        $this->builder->clientSetAutoTariff($target, $time);
0 ignored issues
show
Bug introduced by
The method clientSetAutoTariff() does not exist on hiqdev\php\billing\tests...tstrap\BuilderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

243
        $this->builder->/** @scrutinizer ignore-call */ 
244
                        clientSetAutoTariff($target, $time);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
244
    }
245
246
    /**
247
     * @Given /perform billing at (\S+)/
248
     */
249
    public function performBilling(string $time): void
250
    {
251
        $this->builder->performBilling($this->prepareTime($time));
252
    }
253
254
    /**
255
     * @Given /action for (\S+) is +(\S+) (\S+) +for target (.+?)( +at (\S+))?$/
256
     */
257
    public function setAction(string $type, int $amount, string $unit, string $target, string $at = null, string $time = null): void
0 ignored issues
show
Unused Code introduced by
The parameter $at is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

257
    public function setAction(string $type, int $amount, string $unit, string $target, /** @scrutinizer ignore-unused */ string $at = null, string $time = null): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
258
    {
259
        $time = $this->prepareTime($time);
260
        $this->builder->setAction($type, $amount, $unit, $target, $time);
0 ignored issues
show
Bug introduced by
It seems like $time can also be of type null; however, parameter $time of hiqdev\php\billing\tests...rInterface::setAction() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

260
        $this->builder->setAction($type, $amount, $unit, $target, /** @scrutinizer ignore-type */ $time);
Loading history...
261
    }
262
263
    /**
264
     * @Given /perform calculation( at (\S+))?/
265
     */
266
    public function performCalculation(string $at = null, string $time = null): array
0 ignored issues
show
Unused Code introduced by
The parameter $at is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

266
    public function performCalculation(/** @scrutinizer ignore-unused */ string $at = null, string $time = null): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
267
    {
268
        $this->charges = $this->builder->performCalculation($this->prepareTime($time));
0 ignored issues
show
Bug introduced by
It seems like $this->prepareTime($time) can also be of type null; however, parameter $time of hiqdev\php\billing\tests...e::performCalculation() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

268
        $this->charges = $this->builder->performCalculation(/** @scrutinizer ignore-type */ $this->prepareTime($time));
Loading history...
269
        return $this->charges;
270
    }
271
272
    /**
273
     * @Given /bill +for (\S+) is +(\S+) (\S+) per (\S+) (\S+) for target (.+?)( +at (.+))?$/
274
     */
275
    public function billWithTime($type, $sum, $currency, $quantity, $unit, $target, $at = null, $time = null)
0 ignored issues
show
Unused Code introduced by
The parameter $at is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

275
    public function billWithTime($type, $sum, $currency, $quantity, $unit, $target, /** @scrutinizer ignore-unused */ $at = null, $time = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
276
    {
277
        $this->builder->flushEntitiesCacheByType('bill');
278
279
        $quantity = $this->prepareQuantity($quantity);
280
        $sum = $this->prepareSum($sum, $quantity);
281
        $time = $this->prepareTime($time);
282
        $bill = $this->findBill([
283
            'type' => $type,
284
            'target' => $target,
285
            'sum' => "$sum $currency",
286
            'quantity' => "$quantity $unit",
287
            'time' => $time,
288
        ]);
289
        Assert::assertSame($type, $bill->getType()->getName(), "Bill type mismatch: expected $type, got {$bill->getType()->getName()}");
290
        Assert::assertSame($target, $bill->getTarget()->getFullName(), "Bill target mismatch: expected $target, got {$bill->getTarget()->getFullName()}");
291
        Assert::assertEquals(bcmul($sum, 100), $bill->getSum()->getAmount(), "Bill sum mismatch: expected $sum, got {$bill->getSum()->getAmount()}");
292
        Assert::assertSame($currency, $bill->getSum()->getCurrency()->getCode(), "Bill currency mismatch: expected $currency, got {$bill->getSum()->getCurrency()->getCode()}");
293
        Assert::assertEquals((float)$quantity, (float)$bill->getQuantity()->getQuantity(), "Bill quantity mismatch: expected $quantity, got {$bill->getQuantity()->getQuantity()}");
294
        Assert::assertEquals(strtolower($unit), strtolower($bill->getQuantity()->getUnit()->getName()), "Bill unit mismatch: expected $unit, got {$bill->getQuantity()->getUnit()->getName()}");
295
        if ($time) {
296
            Assert::assertEquals(new DateTimeImmutable($time), $bill->getTime(), "Bill time mismatch: expected $time, got {$bill->getTime()->format(DATE_ATOM)}");
297
        }
298
    }
299
300
    public function findBill(array $params): BillInterface
301
    {
302
        $bills = $this->builder->findBills($params);
303
        $this->bill = reset($bills);
304
        $this->charges = $this->bill->getCharges();
305
306
        return $this->bill;
307
    }
308
309
    /**
310
     * @Given /bills number is (\d+) for (\S+) for target (.+?)( +at (\S+))?$/
311
     */
312
    public function billsNumberWithTime($number, $type, $target, $at = null, $time = null)
0 ignored issues
show
Unused Code introduced by
The parameter $at is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

312
    public function billsNumberWithTime($number, $type, $target, /** @scrutinizer ignore-unused */ $at = null, $time = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
313
    {
314
        $count = count($this->builder->findBills(array_filter([
315
            'type' => $type,
316
            'target' => $target,
317
            'time' => $this->prepareTime($time),
318
        ])));
319
320
        Assert::assertEquals($number, $count);
321
    }
322
323
    /**
324
     * @Given /charges number is (\d+)/
325
     */
326
    public function chargesNumber($number)
327
    {
328
        Assert::assertEquals($number, count($this->charges));
329
    }
330
331
    /**
332
     * @Given /charge for (\S+) is +(\S+) (\S+) per (\S+) (\S+)$/
333
     */
334
    public function charge($type, $amount, $currency, $quantity, $unit)
335
    {
336
        $this->chargeWithTarget($type, $amount, $currency, $quantity, $unit, null);
337
    }
338
339
    /**
340
     * @Given /charge for (\S+) is +(\S+) (\S+) per +(\S+) (\S+) +for target (.+?)( +at (\S+))?$/
341
     */
342
    public function chargeWithTarget($type, $amount, $currency, $quantity, $unit, $target, $at = null, $time = null)
0 ignored issues
show
Unused Code introduced by
The parameter $at is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

342
    public function chargeWithTarget($type, $amount, $currency, $quantity, $unit, $target, /** @scrutinizer ignore-unused */ $at = null, $time = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $time is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

342
    public function chargeWithTarget($type, $amount, $currency, $quantity, $unit, $target, $at = null, /** @scrutinizer ignore-unused */ $time = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
343
    {
344
        $quantity = $this->prepareQuantity($quantity);
345
        $amount = $this->prepareSum($amount, $quantity);
346
        $charge = $this->findCharge($type, $target);
347
        Assert::assertNotNull($charge);
348
        Assert::assertSame($type, $charge->getType()->getName());
349
        Assert::assertSame($target, $charge->getTarget()->getFullName());
350
        Assert::assertEquals(bcmul($amount, 100), (int)$charge->getSum()->getAmount());
351
        Assert::assertSame($currency, $charge->getSum()->getCurrency()->getCode());
352
        Assert::assertEquals((float)$quantity, (float)$charge->getUsage()->getQuantity());
353
        Assert::assertEquals(strtolower($unit), strtolower($charge->getUsage()->getUnit()->getName()));
354
    }
355
356
    public function findCharge($type, $target): ?ChargeInterface
357
    {
358
        foreach ($this->charges as $charge) {
359
            if ($charge->getType()->getName() !== $type) {
360
                continue;
361
            }
362
            if ($charge->getTarget()->getFullName() !== $target) {
363
                continue;
364
            }
365
366
            return $charge;
367
        }
368
369
        return null;
370
    }
371
372
    public function getNextCharge(): ChargeInterface
373
    {
374
        $charge = current($this->charges);
375
        next($this->charges);
376
377
        return $charge;
378
    }
379
380
    /**
381
     * @return string|false|null
382
     */
383
    protected function prepareTime(string $time = null)
384
    {
385
        if ($time === null) {
386
            return null;
387
        }
388
389
        if ($time === 'midnight second day of this month') {
390
            return date('Y-m-02');
391
        }
392
        if (strncmp($time, 'pY', 1) === 0) {
393
            return date(substr($time, 1), strtotime('-1 year'));
394
        }
395
        if (str_contains($time, 'nm')) {
396
            $format = str_replace('nm', 'm', $time);
397
            return date($format, strtotime('next month'));
398
        }
399
        if (str_contains($time, 'pm')) {
400
            $time = str_replace('pm', 'm', $time);
401
            $time = date($time, strtotime('-1 month'));
402
        }
403
        if (strncmp($time, 'Y', 1) === 0) {
404
            return date($time);
405
        }
406
407
        return $time;
408
    }
409
410
    private function prepareQuantity($quantity)
411
    {
412
        if ($quantity[0] === 's') {
413
            return $this->getSaleQuantity();
414
        }
415
416
        return $quantity;
417
    }
418
419
    private function prepareSum($sum, $quantity)
420
    {
421
        if ($sum[0] === 's') {
422
            $sum = round(substr($sum, 1) * $quantity*100)/100;
423
        }
424
425
        return $sum;
426
    }
427
428
    public function getSaleQuantity()
429
    {
430
        return $this->days2quantity(new DateTimeImmutable($this->saleTime));
431
    }
432
433
    private function days2quantity(DateTimeImmutable $from)
434
    {
435
        $till = new DateTimeImmutable('first day of next month midnight');
436
        $diff = $from->diff($till);
437
        if ($diff->m) {
438
            return 1;
439
        }
440
441
        return $diff->d/date('t');
442
    }
443
444
    /**
445
     * @When /^tariff plan change is requested for target "([^"]*)" to plan "([^"]*)" at "([^"]*)"$/
446
     */
447
    public function tariffPlanChangeIsRequestedForTarget(string $target, string $planName, string $date)
448
    {
449
        $this->mayFail(fn () => $this->builder->targetChangePlan($target, $planName, $this->prepareTime($date)));
450
    }
451
452
    /**
453
     * @When /^tariff plan change is requested for target "([^"]*)" to plan "([^"]*)" at "([^"]*)", assuming current time is "([^"]*)"$/
454
     */
455
    public function tariffPlanChangeIsRequestedForTargetAtSpecificTime(string $target, string $planName, string $date, ?string $wallTime = null)
456
    {
457
        $this->mayFail(fn () => $this->builder->targetChangePlan($target, $planName, $this->prepareTime($date), $this->prepareTime($wallTime)));
458
    }
459
460
    /**
461
     * @Then /^target "([^"]*)" is sold to customer by plan "([^"]*)" since "([^"]*)"(?: till "([^"]*)")?$/
462
     */
463
    public function targetIsSoldToCustomerByPlanSinceTill(string $target, string $planName, string $saleDate, ?string $saleCloseDate = null)
464
    {
465
        $sales = $this->builder->findHistoricalSales([
466
            'target' => $target,
467
        ]);
468
469
        $saleDateTime = new DateTimeImmutable('@' . strtotime($this->prepareTime($saleDate)));
470
        $saleCloseDateTime = $saleCloseDate ? new DateTimeImmutable('@' . strtotime($this->prepareTime($saleCloseDate))) : null;
471
472
        foreach ($sales as $sale) {
473
            /** @noinspection PhpBooleanCanBeSimplifiedInspection */
474
            $saleExists = true
475
                && str_contains($sale->getPlan()->getName(), $planName)
476
                && $sale->getTime()->format(DATE_ATOM) === $saleDateTime->format(DATE_ATOM)
477
                && (
478
                    ($saleCloseDate === null && $sale->getCloseTime() === null)
479
                    ||
480
                    ($saleCloseDate !== null && $sale->getCloseTime()->format(DATE_ATOM) === $saleCloseDateTime->format(DATE_ATOM))
481
                );
482
483
            if ($saleExists) {
484
                return;
485
            }
486
        }
487
488
        Assert::fail('Requested sale does not exist');
489
    }
490
491
    /**
492
     * @Then /^target "([^"]*)" has exactly (\d+) sales for customer$/
493
     */
494
    public function targetHasExactlySalesForCustomer(string $target, int $count)
495
    {
496
        $sales = $this->builder->findHistoricalSales([
497
            'target' => $target,
498
        ]);
499
500
        Assert::assertCount($count, $sales);
501
    }
502
503
    /**
504
     * @Then /^caught error is "([^"]*)"$/
505
     */
506
    public function caughtErrorIs(string $errorMessage): void
507
    {
508
        $this->assertCaughtExceptionMatches(\Throwable::class, $errorMessage);
509
    }
510
511
    /**
512
     * @Given /^target "([^"]*)"$/
513
     */
514
    public function target(string $target)
515
    {
516
        $this->builder->buildTarget($target);
517
    }
518
519
    /**
520
     * @Then /^flush entities cache$/
521
     */
522
    public function flushEntitiesCache()
523
    {
524
        $this->builder->flushEntitiesCache();
525
    }
526
527
    /**
528
     * @Given /^target "([^"]*)" has the following uses:$/
529
     */
530
    public function targetHasTheFollowingUses(string $target, TableNode $usesTable)
531
    {
532
        foreach ($usesTable->getColumnsHash() as $row) {
533
            $uses = $this->builder->findUsage($row['time'], $target, $row['type']);
534
            Assert::assertCount(1, $uses);
535
536
            $use = reset($uses);
537
            Assert::assertSame(
538
                $row['unit'], $use['unit'],
539
                sprintf('Exptected unit to be %s, got %s instead', $row['unit'], $use['unit'])
540
            );
541
            Assert::assertEquals(
542
                $row['amount'], $use['total'],
543
                sprintf('Exptected total to be %s, got %s instead', $row['amount'], $use['total'])
544
            );
545
        }
546
    }
547
}
548