Passed
Push — trunk ( 0e4c2d...148418 )
by Christian
12:04 queued 12s
created

ScriptPriceStubs::getRules()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 0
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 2
rs 10
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core\Checkout\Cart\Facade;
4
5
use Doctrine\DBAL\Connection;
6
use Shopware\Core\Checkout\Cart\CartException;
7
use Shopware\Core\Checkout\Cart\Price\PercentagePriceCalculator;
8
use Shopware\Core\Checkout\Cart\Price\QuantityPriceCalculator;
9
use Shopware\Core\Checkout\Cart\Price\Struct\CalculatedPrice;
10
use Shopware\Core\Checkout\Cart\Price\Struct\PriceCollection as CalculatedPriceCollection;
11
use Shopware\Core\Checkout\Cart\Price\Struct\QuantityPriceDefinition;
12
use Shopware\Core\Defaults;
13
use Shopware\Core\Framework\DataAbstractionLayer\Pricing\Price;
14
use Shopware\Core\Framework\DataAbstractionLayer\Pricing\PriceCollection;
15
use Shopware\Core\Framework\Log\Package;
16
use Shopware\Core\Framework\Uuid\Uuid;
17
use Shopware\Core\System\SalesChannel\SalesChannelContext;
18
use Symfony\Contracts\Service\ResetInterface;
19
20
/**
21
 * @internal PriceFacade is public api, this class is only a service layer for better testing and re-usability for internal logic
22
 */
23
#[Package('checkout')]
24
class ScriptPriceStubs implements ResetInterface
25
{
26
    /**
27
     * @var array<string, string>
28
     */
29
    private array $currencies = [];
30
31
    public function __construct(
32
        private readonly Connection $connection,
33
        private readonly QuantityPriceCalculator $quantityCalculator,
34
        private readonly PercentagePriceCalculator $percentageCalculator
35
    ) {
36
    }
37
38
    public function calculateQuantity(QuantityPriceDefinition $definition, SalesChannelContext $context): CalculatedPrice
39
    {
40
        return $this->quantityCalculator->calculate($definition, $context);
41
    }
42
43
    public function calculatePercentage(float $percentage, CalculatedPriceCollection $prices, SalesChannelContext $context): CalculatedPrice
44
    {
45
        return $this->percentageCalculator->calculate($percentage, $prices, $context);
46
    }
47
48
    /**
49
     * // script value (only use case: shop owner defines a script)
50
     * set price = services.cart.price.create({
51
     *      'default': { gross: 100, net: 84.03},
52
     *      'USD': { gross: 59.5 net: 50 }
53
     * });
54
     *      => default will be validate on function call (shop owner has to define it)
55
     *      => we cannot calculate the net/gross equivalent value because we do not know how the price will be taxed
56
     *
57
     * // storage value (custom fields, product.price, etc)
58
     * set price = {
59
     *      { gross: 100, net: 50, currencyId: {currency-id} },
60
     *      { gross: 90, net: 40, currencyId: {currency-id} },
61
     * }; => default is validate when persisting as storage
62
     *
63
     * @param array<string, array{gross:float, net:float, linked?:bool}> $price
64
     */
65
    public function build(array $price): PriceCollection
66
    {
67
        $collection = new PriceCollection();
68
69
        $price = $this->validatePrice($price);
70
71
        foreach ($price as $id => $value) {
72
            $collection->add(
73
                new Price($id, $value['net'], $value['gross'], $value['linked'] ?? false)
74
            );
75
        }
76
77
        return $collection;
78
    }
79
80
    public function reset(): void
81
    {
82
        $this->currencies = [];
83
    }
84
85
    /**
86
     * @param array<string, array{gross:float, net:float, linked?:bool}> $price
87
     *
88
     * @return array<string, array{gross:float, net:float, linked?:bool}>
89
     */
90
    private function validatePrice(array $price): array
91
    {
92
        $price = $this->resolveIsoCodes($price);
93
94
        if (!\array_key_exists(Defaults::CURRENCY, $price)) {
95
            throw CartException::invalidPriceDefinition();
96
        }
97
98
        foreach ($price as $id => $value) {
99
            if (!Uuid::isValid($id)) {
100
                throw CartException::invalidPriceDefinition();
101
            }
102
103
            if (!\array_key_exists('gross', $value)) {
104
                throw CartException::invalidPriceDefinition();
105
            }
106
107
            if (!\array_key_exists('net', $value)) {
108
                throw CartException::invalidPriceDefinition();
109
            }
110
        }
111
112
        return $price;
113
    }
114
115
    /**
116
     * @param array<string, array{gross:float, net:float, linked?:bool, currencyId?:string}> $prices
117
     *
118
     * @return array<string, array{gross:float, net:float, linked?:bool, currencyId?:string}>
119
     */
120
    private function resolveIsoCodes(array $prices): array
121
    {
122
        if (empty($this->currencies)) {
123
            /** @var array<string, string> $currencies */
124
            $currencies = $this->connection->fetchAllKeyValue('SELECT iso_code, LOWER(HEX(id)) FROM currency');
125
            $this->currencies = $currencies;
126
        }
127
128
        $mapped = [];
129
        foreach ($prices as $iso => $value) {
130
            if ($iso === 'default') {
131
                $mapped[Defaults::CURRENCY] = $value;
132
133
                continue;
134
            }
135
136
            if (\array_key_exists('currencyId', $value)) {
137
                $mapped[$value['currencyId']] = $value;
138
139
                continue;
140
            }
141
142
            if (\array_key_exists($iso, $this->currencies)) {
143
                $mapped[$this->currencies[$iso]] = $value;
144
            }
145
        }
146
147
        return $mapped;
148
    }
149
}
150