Passed
Pull Request — master (#67)
by
unknown
12:07
created

BillingContext::progressivePriceWithInterval()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 32
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
c 0
b 0
f 0
dl 0
loc 32
rs 9.7666
cc 2
nc 2
nop 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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 $bill;
31
32
    protected $charges = [];
33
34
    protected array $progressivePrice = [];
35
36
    /**
37
     * @Given reseller :reseller
38
     */
39
    public function reseller($reseller)
40
    {
41
        $this->builder->buildReseller($reseller);
42
    }
43
44
    /**
45
     * @Given customer :customer
46
     */
47
    public function customer($customer)
48
    {
49
        $this->builder->buildCustomer($customer);
50
    }
51
52
    /**
53
     * @Given manager :manager
54
     */
55
    public function manager($manager)
56
    {
57
        $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

57
        $this->builder->/** @scrutinizer ignore-call */ 
58
                        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...
58
    }
59
60
    /**
61
     * @Given /^(\S+ )?(\S+) tariff plan (\S+)/
62
     */
63
    public function plan($prefix, $type, $plan)
64
    {
65
        $prefix = strtr($prefix, ' ', '_');
66
        $grouping = $prefix === 'grouping_';
67
        $type = $grouping ? $type : $prefix.$type;
68
        $this->builder->buildPlan($plan, $type, $grouping);
69
    }
70
71
    protected function fullPrice(array $data)
72
    {
73
        if (!empty($data['price'])) {
74
            $data['rate'] = $data['price'];
75
        }
76
        $this->builder->buildPrice($data);
77
    }
78
79
    /**
80
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+) for target (.+)$/
81
     */
82
    public function priceWithTarget($type, $price, $currency, $unit, $target)
83
    {
84
        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...
85
    }
86
87
    /**
88
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+) prepaid (\S+)$/
89
     */
90
    public function priceWithPrepaid($type, $price, $currency, $unit, $prepaid)
91
    {
92
        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...
93
    }
94
95
    /**
96
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+) prepaid (\S+) for target (\S+)$/
97
     */
98
    public function priceWithPrepaidAndTarget($type, $price, $currency, $unit, $prepaid, $target)
99
    {
100
        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...
101
    }
102
103
    /**
104
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+)$/
105
     */
106
    public function price($type, $price, $currency, $unit)
107
    {
108
        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...
109
    }
110
111
    /**
112
     * @Given /price for (\S+) is +(\S+) (\S+) per 1 (\S+) and (\S+) (\S+) per 2 (\S+) for target (\S+)/
113
     */
114
    public function enumPrice($type, $price, $currency, $unit, $price2, $currency2, $unit2, $target)
115
    {
116
        $sums = [1 => $price, 2 => $price2];
117
118
        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...
119
    }
120
121
    /**
122
     * @Given /progressive price for (\S+) is +(\S+) (\S+) per (\S+) (\S+) (\S+) (\S+)$/
123
     */
124
    public function progressivePrice($type, $price, $currency, $unit, $sign, $quantity, $perUnit): void
125
    {
126
        if (empty($this->progressivePrice[$type])) {
127
            $this->progressivePrice[$type] = [
128
                'price' => $price,
129
                'currency' => $currency,
130
                'unit' => $unit,
131
                'condition' =>[
132
                    [
133
                        'sign_till' => $sign,
134
                        'value_till' => $quantity,
135
                    ],
136
                ] ,
137
            ];
138
        } else {
139
            array_push(
140
                $this->progressivePrice[$type]['condition'],
141
                [
142
                    'sign_till' => $sign,
143
                    'value_till' => $quantity,
144
                ]
145
            );
146
        }
147
    }
148
149
    /**
150
     * @Given /progressive price for (\S+) is +(\S+) (\S+) per (\S+) (\S+) (\S+) (\S+) and (\S+) (\S+) (\S+)$/
151
     */
152
    public function progressivePriceWithInterval(
153
        $type,
154
        $price,
155
        $currency,
156
        $unit,
157
        $signFrom,
158
        $quantityFrom,
159
        $perUnit,
160
        $signTill,
161
        $quantityTill,
162
        $perUnit1
163
    ) {
164
        if (empty($this->progressivePrice[$type])) {
165
            $this->progressivePrice[$type] = [
166
                'price' => $price,
167
                'currency' => $currency,
168
                'unit' => $unit,
169
                'condition' =>[
170
                    [
171
                        'sign_till' => $signFrom,
172
                        'value_till' => $quantityFrom,
173
                    ],
174
                ] ,
175
            ];
176
        } else {
177
            array_push(
178
                $this->progressivePrice[$type]['condition'],
179
                [
180
                    'sign_from' => $signFrom,
181
                    'value_from' => $quantityFrom,
182
                    'sign_till' => $signTill,
183
                    'value_till' => $quantityTill,
184
                ]
185
            );
186
        }
187
    }
188
189
    /**
190
     * @Given /^create progressive price/
191
     */
192
    public function createProgressivePrices()
193
    {
194
        foreach ($this->progressivePrice as $type => $price) {
195
            $this->fullPrice([
196
                'type' => $type,
197
                'price' => $price['price'],
198
                'currency' => $price['currency'],
199
                'unit' => $price['unit'],
200
                'condition' => $price['condition']
201
            ]);
202
        }
203
    }
204
205
    /**
206
     * @Given /^remove and recreate tariff plan (\S+)/
207
     */
208
    public function recreatePlan($plan)
209
    {
210
        $this->builder->recreatePlan($plan);
211
    }
212
213
    /**
214
     * @Given /sale target (\S+) by plan (\S+) at (\S+)/
215
     */
216
    public function sale($target, $plan, $time): void
217
    {
218
        $this->saleTime = $this->prepareTime($time);
219
        $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

219
        $this->builder->buildSale($target, $plan, /** @scrutinizer ignore-type */ $this->saleTime);
Loading history...
220
    }
221
    /**
222
     * @When /^sale close is requested for target "([^"]*)" at "([^"]*)", assuming current time is "([^"]*)"$/
223
     */
224
    public function saleClose(string $target, string $time, ?string $wallTime)
225
    {
226
        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...
227
    }
228
229
    /**
230
     * @Then /^target "([^"]*)" has exactly (\d+) sale for customer$/
231
     */
232
    public function targetHasExactlyNSaleForCustomer(string $target, string $count)
233
    {
234
        // TODO: implement
235
        // $sales = $this->builder->findSales(['target-name' => $target]);
236
237
        Assert::assertCount($count, $sales);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sales seems to be never defined.
Loading history...
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

237
        Assert::assertCount(/** @scrutinizer ignore-type */ $count, $sales);
Loading history...
238
    }
239
240
    /**
241
     * @Given /purchase target (\S+) by plan (\S+) at (\S+)$/
242
     */
243
    public function purchaseTarget(string $target, string $plan, string $time): void
244
    {
245
        $time = $this->prepareTime($time);
246
        $this->builder->buildPurchase($target, $plan, $time);
247
    }
248
249
    /**
250
     * @Given /^purchase target "([^"]*)" by plan "([^"]*)" at "([^"]*)" with the following initial uses:$/
251
     */
252
    public function purchaseTargetWithInitialUses(string $target, string $plan, string $time, TableNode $usesTable): void
253
    {
254
        $time = $this->prepareTime($time);
255
        $uses = array_map(static function (array $row) {
256
            return [
257
                'type' => $row['type'],
258
                'unit' => $row['unit'],
259
                'amount' => $row['amount'],
260
            ];
261
        }, $usesTable->getColumnsHash());
262
263
        $this->mayFail(
264
            fn() => $this->builder->buildPurchase($target, $plan, $time, $uses)
265
        );
266
    }
267
268
    /**
269
     * @Given /resource consumption for (\S+) is +(\S+) (\S+) for target (\S+) at (.+)$/
270
     */
271
    public function setConsumption(string $type, int $amount, string $unit, string $target, string $time): void
272
    {
273
        $time = $this->prepareTime($time);
274
        $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

274
        $this->builder->/** @scrutinizer ignore-call */ 
275
                        setConsumption($type, $amount, $unit, $target, $time);
Loading history...
275
    }
276
277
    /**
278
     * @Given /recalculate autotariff for target (\S+)( +at (\S+))?$/
279
     */
280
    public function recalculateAutoTariff(string $target, string $time = null): void
281
    {
282
        $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

282
        $this->builder->/** @scrutinizer ignore-call */ 
283
                        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...
283
    }
284
285
    /**
286
     * @Given /perform billing at (\S+)/
287
     */
288
    public function performBilling(string $time): void
289
    {
290
        $this->builder->performBilling($this->prepareTime($time));
291
    }
292
293
    /**
294
     * @Given /action for (\S+) is +(\S+) (\S+) +for target (.+?)( +at (\S+))?$/
295
     */
296
    public function setAction(string $type, int $amount, string $unit, string $target, string $at = null, string $time = null): void
297
    {
298
        $time = $this->prepareTime($time);
299
        $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

299
        $this->builder->setAction($type, $amount, $unit, $target, /** @scrutinizer ignore-type */ $time);
Loading history...
300
    }
301
302
    /**
303
     * @Given /perform calculation( at (\S+))?/
304
     */
305
    public function performCalculation(string $at = null, string $time = null): array
306
    {
307
        $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

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