Passed
Push — master ( 71f7f7...fb7de3 )
by Dmitry
13:04
created

BillingContext::chargeWithTarget()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
cc 1
eloc 10
c 5
b 1
f 0
nc 1
nop 8
dl 0
loc 12
rs 9.9332

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
    /**
35
     * @Given reseller :reseller
36
     */
37
    public function reseller($reseller)
38
    {
39
        $this->builder->buildReseller($reseller);
40
    }
41
42
    /**
43
     * @Given customer :customer
44
     */
45
    public function customer($customer)
46
    {
47
        $this->builder->buildCustomer($customer);
48
    }
49
50
    /**
51
     * @Given manager :manager
52
     */
53
    public function manager($manager)
54
    {
55
        $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

55
        $this->builder->/** @scrutinizer ignore-call */ 
56
                        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...
56
    }
57
58
    /**
59
     * @Given /^(\S+ )?(\S+) tariff plan (\S+)/
60
     */
61
    public function plan($prefix, $type, $plan)
62
    {
63
        $prefix = strtr($prefix, ' ', '_');
64
        $grouping = $prefix === 'grouping_';
65
        $type = $grouping ? $type : $prefix.$type;
66
        $this->builder->buildPlan($plan, $type, $grouping);
67
    }
68
69
    protected function fullPrice(array $data)
70
    {
71
        if (!empty($data['price'])) {
72
            $data['rate'] = $data['price'];
73
        }
74
        $this->builder->buildPrice($data);
75
    }
76
77
    /**
78
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+) for target (.+)$/
79
     */
80
    public function priceWithTarget($type, $price, $currency, $unit, $target)
81
    {
82
        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...
83
    }
84
85
    /**
86
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+) prepaid (\S+)$/
87
     */
88
    public function priceWithPrepaid($type, $price, $currency, $unit, $prepaid)
89
    {
90
        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...
91
    }
92
93
    /**
94
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+) prepaid (\S+) for target (\S+)$/
95
     */
96
    public function priceWithPrepaidAndTarget($type, $price, $currency, $unit, $prepaid, $target)
97
    {
98
        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...
99
    }
100
101
    /**
102
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+)$/
103
     */
104
    public function price($type, $price, $currency, $unit)
105
    {
106
        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...
107
    }
108
109
    /**
110
     * @Given /price for (\S+) is +(\S+) (\S+) per 1 (\S+) and (\S+) (\S+) per 2 (\S+) for target (\S+)/
111
     */
112
    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

112
    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

112
    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...
113
    {
114
        $sums = [1 => $price, 2 => $price2];
115
116
        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...
117
    }
118
119
    /**
120
     * @Given /^remove and recreate tariff plan (\S+)/
121
     */
122
    public function recreatePlan($plan)
123
    {
124
        $this->builder->recreatePlan($plan);
125
    }
126
127
    /**
128
     * @Given /sale target (\S+) by plan (\S+) at (\S+)/
129
     */
130
    public function sale($target, $plan, $time): void
131
    {
132
        $this->saleTime = $this->prepareTime($time);
133
        $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

133
        $this->builder->buildSale($target, $plan, /** @scrutinizer ignore-type */ $this->saleTime);
Loading history...
134
    }
135
    /**
136
     * @When /^sale close is requested for target "([^"]*)" at "([^"]*)", assuming current time is "([^"]*)"$/
137
     */
138
    public function saleClose(string $target, string $time, ?string $wallTime)
0 ignored issues
show
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

138
    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...
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

138
    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 $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

138
    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...
139
    {
140
        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...
141
    }
142
143
    /**
144
     * @Then /^target "([^"]*)" has exactly (\d+) sale for customer$/
145
     */
146
    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

146
    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...
147
    {
148
        // TODO: implement
149
        // $sales = $this->builder->findSales(['target-name' => $target]);
150
151
        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

151
        Assert::assertCount(/** @scrutinizer ignore-type */ $count, $sales);
Loading history...
152
    }
153
154
    /**
155
     * @Given /purchase target (\S+) by plan (\S+) at (\S+)$/
156
     */
157
    public function purchaseTarget(string $target, string $plan, string $time): void
158
    {
159
        $time = $this->prepareTime($time);
160
        $this->builder->buildPurchase($target, $plan, $time);
161
    }
162
163
    /**
164
     * @Given /^purchase target "([^"]*)" by plan "([^"]*)" at "([^"]*)" with the following initial uses:$/
165
     */
166
    public function purchaseTargetWithInitialUses(string $target, string $plan, string $time, TableNode $usesTable): void
167
    {
168
        $time = $this->prepareTime($time);
169
        $uses = array_map(static function (array $row) {
170
            return [
171
                'type' => $row['type'],
172
                'unit' => $row['unit'],
173
                'amount' => $row['amount'],
174
            ];
175
        }, $usesTable->getColumnsHash());
176
177
        $this->mayFail(
178
            fn() => $this->builder->buildPurchase($target, $plan, $time, $uses)
179
        );
180
    }
181
182
    /**
183
     * @Given /resource consumption for (\S+) is +(\S+) (\S+) for target (\S+) at (.+)$/
184
     */
185
    public function setConsumption(string $type, int $amount, string $unit, string $target, string $time): void
186
    {
187
        $time = $this->prepareTime($time);
188
        $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

188
        $this->builder->/** @scrutinizer ignore-call */ 
189
                        setConsumption($type, $amount, $unit, $target, $time);
Loading history...
189
    }
190
191
    /**
192
     * @Given /recalculate autotariff for target (\S+)( +at (\S+))?$/
193
     */
194
    public function recalculateAutoTariff(string $target, string $time = null): void
195
    {
196
        $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

196
        $this->builder->/** @scrutinizer ignore-call */ 
197
                        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...
197
    }
198
199
    /**
200
     * @Given /perform billing at (\S+)/
201
     */
202
    public function performBilling(string $time): void
203
    {
204
        $this->builder->performBilling($this->prepareTime($time));
205
    }
206
207
    /**
208
     * @Given /action for (\S+) is +(\S+) (\S+) +for target (.+?)( +at (\S+))?$/
209
     */
210
    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

210
    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...
211
    {
212
        $time = $this->prepareTime($time);
213
        $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

213
        $this->builder->setAction($type, $amount, $unit, $target, /** @scrutinizer ignore-type */ $time);
Loading history...
214
    }
215
216
    /**
217
     * @Given /perform calculation( at (\S+))?/
218
     */
219
    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

219
    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...
220
    {
221
        $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

221
        $this->charges = $this->builder->performCalculation(/** @scrutinizer ignore-type */ $this->prepareTime($time));
Loading history...
222
        return $this->charges;
223
    }
224
225
    /**
226
     * @Given /bill +for (\S+) is +(\S+) (\S+) per (\S+) (\S+) for target (.+?)( +at (.+))?$/
227
     */
228
    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

228
    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...
229
    {
230
        $this->builder->flushEntitiesCacheByType('bill');
231
232
        $quantity = $this->prepareQuantity($quantity);
233
        $sum = $this->prepareSum($sum, $quantity);
234
        $time = $this->prepareTime($time);
235
        $bill = $this->findBill([
236
            'type' => $type,
237
            'target' => $target,
238
            'sum' => "$sum $currency",
239
            'quantity' => "$quantity $unit",
240
            'time' => $time,
241
        ]);
242
        Assert::assertSame($type, $bill->getType()->getName(), "Bill type mismatch: expected $type, got {$bill->getType()->getName()}");
243
        Assert::assertSame($target, $bill->getTarget()->getFullName(), "Bill target mismatch: expected $target, got {$bill->getTarget()->getFullName()}");
244
        Assert::assertEquals(bcmul($sum, 100), $bill->getSum()->getAmount(), "Bill sum mismatch: expected $sum, got {$bill->getSum()->getAmount()}");
245
        Assert::assertSame($currency, $bill->getSum()->getCurrency()->getCode(), "Bill currency mismatch: expected $currency, got {$bill->getSum()->getCurrency()->getCode()}");
246
        Assert::assertEquals((float)$quantity, (float)$bill->getQuantity()->getQuantity(), "Bill quantity mismatch: expected $quantity, got {$bill->getQuantity()->getQuantity()}");
247
        Assert::assertEquals(strtolower($unit), strtolower($bill->getQuantity()->getUnit()->getName()), "Bill unit mismatch: expected $unit, got {$bill->getQuantity()->getUnit()->getName()}");
248
        if ($time) {
249
            Assert::assertEquals(new DateTimeImmutable($time), $bill->getTime(), "Bill time mismatch: expected $time, got {$bill->getTime()->format(DATE_ATOM)}");
250
        }
251
    }
252
253
    public function findBill(array $params): BillInterface
254
    {
255
        $bills = $this->builder->findBills($params);
256
        $this->bill = reset($bills);
257
        $this->charges = $this->bill->getCharges();
258
259
        return $this->bill;
260
    }
261
262
    /**
263
     * @Given /bills number is (\d+) for (\S+) for target (.+?)( +at (\S+))?$/
264
     */
265
    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

265
    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...
266
    {
267
        $count = count($this->builder->findBills(array_filter([
268
            'type' => $type,
269
            'target' => $target,
270
            'time' => $this->prepareTime($time),
271
        ])));
272
273
        Assert::assertEquals($number, $count);
274
    }
275
276
    /**
277
     * @Given /charges number is (\d+)/
278
     */
279
    public function chargesNumber($number)
280
    {
281
        Assert::assertEquals($number, count($this->charges));
282
    }
283
284
    /**
285
     * @Given /charge for (\S+) is +(\S+) (\S+) per (\S+) (\S+)$/
286
     */
287
    public function charge($type, $amount, $currency, $quantity, $unit)
288
    {
289
        $this->chargeWithTarget($type, $amount, $currency, $quantity, $unit, null);
290
    }
291
292
    /**
293
     * @Given /charge for (\S+) is +(\S+) (\S+) per +(\S+) (\S+) +for target (.+?)( +at (\S+))?$/
294
     */
295
    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

295
    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

295
    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...
296
    {
297
        $quantity = $this->prepareQuantity($quantity);
298
        $amount = $this->prepareSum($amount, $quantity);
299
        $charge = $this->findCharge($type, $target);
300
        Assert::assertNotNull($charge);
301
        Assert::assertSame($type, $charge->getType()->getName());
302
        Assert::assertSame($target, $charge->getTarget()->getFullName());
303
        Assert::assertEquals(bcmul($amount, 100), (int)$charge->getSum()->getAmount());
304
        Assert::assertSame($currency, $charge->getSum()->getCurrency()->getCode());
305
        Assert::assertEquals((float)$quantity, (float)$charge->getUsage()->getQuantity());
306
        Assert::assertEquals(strtolower($unit), strtolower($charge->getUsage()->getUnit()->getName()));
307
    }
308
309
    public function findCharge($type, $target): ?ChargeInterface
310
    {
311
        foreach ($this->charges as $charge) {
312
            if ($charge->getType()->getName() !== $type) {
313
                continue;
314
            }
315
            if ($charge->getTarget()->getFullName() !== $target) {
316
                continue;
317
            }
318
319
            return $charge;
320
        }
321
322
        return null;
323
    }
324
325
    public function getNextCharge(): ChargeInterface
326
    {
327
        $charge = current($this->charges);
328
        next($this->charges);
329
330
        return $charge;
331
    }
332
333
    /**
334
     * @return string|false|null
335
     */
336
    protected function prepareTime(string $time = null)
337
    {
338
        if ($time === null) {
339
            return null;
340
        }
341
342
        if ($time === 'midnight second day of this month') {
343
            return date('Y-m-02');
344
        }
345
        if (strncmp($time, 'pY', 1) === 0) {
346
            return date(substr($time, 1), strtotime('-1 year'));
347
        }
348
        if (str_contains($time, 'nm')) {
349
            $format = str_replace('nm', 'm', $time);
350
            return date($format, strtotime('next month'));
351
        }
352
        if (str_contains($time, 'pm')) {
353
            $time = str_replace('pm', 'm', $time);
354
            $time = date($time, strtotime('-1 month'));
355
        }
356
        if (strncmp($time, 'Y', 1) === 0) {
357
            return date($time);
358
        }
359
360
        return $time;
361
    }
362
363
    private function prepareQuantity($quantity)
364
    {
365
        if ($quantity[0] === 's') {
366
            return $this->getSaleQuantity();
367
        }
368
369
        return $quantity;
370
    }
371
372
    private function prepareSum($sum, $quantity)
373
    {
374
        if ($sum[0] === 's') {
375
            $sum = round(substr($sum, 1) * $quantity*100)/100;
376
        }
377
378
        return $sum;
379
    }
380
381
    public function getSaleQuantity()
382
    {
383
        return $this->days2quantity(new DateTimeImmutable($this->saleTime));
384
    }
385
386
    private function days2quantity(DateTimeImmutable $from)
387
    {
388
        $till = new DateTimeImmutable('first day of next month midnight');
389
        $diff = $from->diff($till);
390
        if ($diff->m) {
391
            return 1;
392
        }
393
394
        return $diff->d/date('t');
395
    }
396
397
    /**
398
     * @When /^tariff plan change is requested for target "([^"]*)" to plan "([^"]*)" at "([^"]*)"$/
399
     */
400
    public function tariffPlanChangeIsRequestedForTarget(string $target, string $planName, string $date)
401
    {
402
        $this->mayFail(fn () => $this->builder->targetChangePlan($target, $planName, $this->prepareTime($date)));
403
    }
404
405
    /**
406
     * @When /^tariff plan change is requested for target "([^"]*)" to plan "([^"]*)" at "([^"]*)", assuming current time is "([^"]*)"$/
407
     */
408
    public function tariffPlanChangeIsRequestedForTargetAtSpecificTime(string $target, string $planName, string $date, ?string $wallTime = null)
409
    {
410
        $this->mayFail(fn () => $this->builder->targetChangePlan($target, $planName, $this->prepareTime($date), $this->prepareTime($wallTime)));
411
    }
412
413
    /**
414
     * @Then /^target "([^"]*)" is sold to customer by plan "([^"]*)" since "([^"]*)"(?: till "([^"]*)")?$/
415
     */
416
    public function targetIsSoldToCustomerByPlanSinceTill(string $target, string $planName, string $saleDate, ?string $saleCloseDate = null)
417
    {
418
        $sales = $this->builder->findHistoricalSales([
419
            'target' => $target,
420
        ]);
421
422
        $saleDateTime = new DateTimeImmutable('@' . strtotime($this->prepareTime($saleDate)));
423
        $saleCloseDateTime = $saleCloseDate ? new DateTimeImmutable('@' . strtotime($this->prepareTime($saleCloseDate))) : null;
424
425
        foreach ($sales as $sale) {
426
            /** @noinspection PhpBooleanCanBeSimplifiedInspection */
427
            $saleExists = true
428
                && str_contains($sale->getPlan()->getName(), $planName)
429
                && $sale->getTime()->format(DATE_ATOM) === $saleDateTime->format(DATE_ATOM)
430
                && (
431
                    ($saleCloseDate === null && $sale->getCloseTime() === null)
432
                    ||
433
                    ($saleCloseDate !== null && $sale->getCloseTime()->format(DATE_ATOM) === $saleCloseDateTime->format(DATE_ATOM))
434
                );
435
436
            if ($saleExists) {
437
                return;
438
            }
439
        }
440
441
        Assert::fail('Requested sale does not exist');
442
    }
443
444
    /**
445
     * @Then /^target "([^"]*)" has exactly (\d+) sales for customer$/
446
     */
447
    public function targetHasExactlySalesForCustomer(string $target, int $count)
448
    {
449
        $sales = $this->builder->findHistoricalSales([
450
            'target' => $target,
451
        ]);
452
453
        Assert::assertCount($count, $sales);
454
    }
455
456
    /**
457
     * @Then /^caught error is "([^"]*)"$/
458
     */
459
    public function caughtErrorIs(string $errorMessage): void
460
    {
461
        $this->assertCaughtExceptionMatches(\Throwable::class, $errorMessage);
462
    }
463
464
    /**
465
     * @Given /^target "([^"]*)"$/
466
     */
467
    public function target(string $target)
468
    {
469
        $this->builder->buildTarget($target);
470
    }
471
472
    /**
473
     * @Then /^flush entities cache$/
474
     */
475
    public function flushEntitiesCache()
476
    {
477
        $this->builder->flushEntitiesCache();
478
    }
479
480
    /**
481
     * @Given /^target "([^"]*)" has the following uses:$/
482
     */
483
    public function targetHasTheFollowingUses(string $target, TableNode $usesTable)
484
    {
485
        foreach ($usesTable->getColumnsHash() as $row) {
486
            $uses = $this->builder->findUsage($row['time'], $target, $row['type']);
487
            Assert::assertCount(1, $uses);
488
489
            $use = reset($uses);
490
            Assert::assertSame(
491
                $row['unit'], $use['unit'],
492
                sprintf('Exptected unit to be %s, got %s instead', $row['unit'], $use['unit'])
493
            );
494
            Assert::assertEquals(
495
                $row['amount'], $use['total'],
496
                sprintf('Exptected total to be %s, got %s instead', $row['amount'], $use['total'])
497
            );
498
        }
499
    }
500
}
501