EuInstallCommand   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 246
Duplicated Lines 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 124
dl 0
loc 246
rs 10
c 1
b 1
f 0
wmc 24

11 Methods

Rating   Name   Duplication   Size   Complexity  
A getOptionIncluded() 0 3 1
A getOptionThreshold() 0 6 1
A addTaxRate() 0 39 4
A getOptionCategories() 0 6 1
A configure() 0 29 1
A getArgumentCountry() 0 6 1
A __construct() 0 12 1
A addZone() 0 17 2
B execute() 0 70 8
A addCountry() 0 15 2
A addTaxCategory() 0 16 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gewebe\SyliusVATPlugin\Command;
6
7
use Gewebe\SyliusVATPlugin\Vat\Rates\RatesInterface;
8
use Sylius\Component\Addressing\Factory\ZoneFactory;
9
use Sylius\Component\Addressing\Model\CountryInterface;
10
use Sylius\Component\Addressing\Model\ZoneInterface;
11
use Sylius\Component\Core\Model\Scope;
12
use Sylius\Component\Core\Model\TaxRateInterface;
13
use Sylius\Component\Resource\Factory\FactoryInterface;
14
use Sylius\Component\Resource\Repository\RepositoryInterface;
15
use Sylius\Component\Taxation\Model\TaxCategoryInterface;
16
use Symfony\Component\Console\Command\Command;
17
use Symfony\Component\Console\Input\InputArgument;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Input\InputOption;
20
use Symfony\Component\Console\Output\OutputInterface;
21
22
/**
23
 * Install EU countries, zones and VAT rates to Sylius
24
 *
25
 *  @Todo: rewrite setup of different tax schemas
26
 */
27
final class EuInstallCommand extends Command
28
{
29
    public function __construct(
30
        private RatesInterface $vatRates,
31
        private FactoryInterface $countryFactory,
32
        private RepositoryInterface $countryRepository,
33
        private ZoneFactory $zoneFactory,
34
        private RepositoryInterface $zoneRepository,
35
        private FactoryInterface $taxRateFactory,
36
        private RepositoryInterface $taxRateRepository,
37
        private FactoryInterface $taxCategoryFactory,
38
        private RepositoryInterface $taxCategoryRepository,
39
    ) {
40
        parent::__construct();
41
    }
42
43
    protected function configure(): void
44
    {
45
        $this
46
            ->setName('vat:install:eu')
47
            ->setDescription('Install European countries, zones and VAT rates')
48
            ->addArgument(
49
                'country',
50
                InputArgument::OPTIONAL,
51
                'Domestic Country',
52
            )
53
            ->addOption(
54
                'categories',
55
                'c',
56
                InputOption::VALUE_REQUIRED,
57
                'Tax categories, e.g.: standard,reduced',
58
                'standard',
59
            )
60
            ->addOption(
61
                'included',
62
                'i',
63
                InputOption::VALUE_NONE,
64
                'Tax rate is included in price',
65
            )
66
            ->addOption(
67
                'threshold',
68
                't',
69
                InputOption::VALUE_REQUIRED,
70
                'Threshold Countries',
71
                '',
72
            )
73
        ;
74
    }
75
76
    private function getArgumentCountry(InputInterface $input): string
77
    {
78
        /** @var string $country */
79
        $country = $input->getArgument('country') ?? '';
80
81
        return strtolower($country);
82
    }
83
84
    /**
85
     * @return string[]
86
     */
87
    private function getOptionCategories(InputInterface $input): array
88
    {
89
        /** @var string $categories */
90
        $categories = $input->getOption('categories') ?? '';
91
92
        return explode(',', $categories);
93
    }
94
95
    private function getOptionIncluded(InputInterface $input): bool
96
    {
97
        return $input->getOption('included') === true;
98
    }
99
100
    private function getOptionThreshold(InputInterface $input): array
101
    {
102
        /** @var string $threshold */
103
        $threshold = $input->getOption('threshold') ?? '';
104
105
        return explode(',', strtolower($threshold));
106
    }
107
108
    protected function execute(InputInterface $input, OutputInterface $output): int
109
    {
110
        $baseCountry = $this->getArgumentCountry($input);
111
112
        $thresholdCountries = $this->getOptionThreshold($input);
113
114
        $taxCategories = $this->getOptionCategories($input);
115
        foreach ($taxCategories as $taxCategory) {
116
            $this->addTaxCategory($taxCategory);
117
        }
118
119
        $euZones = [];
120
        foreach ($this->vatRates->getCountries() as $countryCode => $countryName) {
121
            $output->writeln('Install: ' . $countryCode);
122
123
            $country = $this->addCountry(strtoupper($countryCode));
124
125
            $zone = $this->addZone(
126
                strtoupper($countryCode) . '-vat',
127
                $countryName . ' VAT',
128
                [$country->getCode()],
129
                ZoneInterface::TYPE_COUNTRY,
130
            );
131
132
            $euZones[] = $zone->getCode();
133
134
            if (in_array(strtolower($countryCode), $thresholdCountries, true)) {
135
                $zone = $this->addZone(
136
                    $countryCode . '-tax',
137
                    $countryName . ' Tax',
138
                    [$country->getCode()],
139
                    ZoneInterface::TYPE_COUNTRY,
140
                    Scope::TAX,
141
                );
142
            } elseif (strlen($baseCountry) > 0) {
143
                continue;
144
            }
145
146
            foreach ($taxCategories as $taxCategory) {
147
                $this->addTaxRate(
148
                    $countryCode,
149
                    $countryCode,
150
                    $taxCategory,
151
                    $zone,
152
                    $this->getOptionIncluded($input),
153
                );
154
            }
155
        }
156
157
        $output->writeln('Install: EU');
158
        $zone = $this->addZone(
159
            'EU',
160
            'European Union VAT',
161
            $euZones,
162
            ZoneInterface::TYPE_ZONE,
163
        );
164
165
        if (strlen($baseCountry) > 0) {
166
            foreach ($taxCategories as $taxCategory) {
167
                $this->addTaxRate(
168
                    $baseCountry,
169
                    'eu',
170
                    $taxCategory,
171
                    $zone,
172
                    $this->getOptionIncluded($input),
173
                );
174
            }
175
        }
176
177
        return 0;
178
    }
179
180
    private function addCountry(string $code): CountryInterface
181
    {
182
        /** @var CountryInterface|null $country */
183
        $country = $this->countryRepository->findOneBy(['code' => $code]);
184
        if ($country instanceof CountryInterface) {
185
            return $country;
186
        }
187
188
        /** @var CountryInterface $country */
189
        $country = $this->countryFactory->createNew();
190
        $country->setCode($code);
191
192
        $this->countryRepository->add($country);
193
194
        return $country;
195
    }
196
197
    private function addZone(string $code, string $name, array $countries, string $type, string $scope = Scope::ALL): ZoneInterface
198
    {
199
        /** @var ZoneInterface|null $zone */
200
        $zone = $this->zoneRepository->findOneBy(['code' => $code, 'type' => $type]);
201
        if ($zone instanceof ZoneInterface) {
202
            return $zone;
203
        }
204
205
        $zone = $this->zoneFactory->createWithMembers($countries);
206
        $zone->setCode($code);
207
        $zone->setName($name);
208
        $zone->setType($type);
209
        $zone->setScope($scope);
210
211
        $this->zoneRepository->add($zone);
212
213
        return $zone;
214
    }
215
216
    private function addTaxCategory(string $name): TaxCategoryInterface
217
    {
218
        /** @var TaxCategoryInterface|null $taxCategory */
219
        $taxCategory = $this->taxCategoryRepository->findOneBy(['code' => strtolower($name)]);
220
        if ($taxCategory instanceof TaxCategoryInterface) {
221
            return $taxCategory;
222
        }
223
224
        /** @var TaxCategoryInterface $taxCategory */
225
        $taxCategory = $this->taxCategoryFactory->createNew();
226
        $taxCategory->setCode(strtolower($name));
227
        $taxCategory->setName(ucfirst($name));
228
229
        $this->taxCategoryRepository->add($taxCategory);
230
231
        return $taxCategory;
232
    }
233
234
    private function addTaxRate(
235
        string $country,
236
        string $code,
237
        string $category,
238
        ZoneInterface $zone,
239
        bool $includedInPrice = false,
240
    ): void {
241
        try {
242
            $countryRate = $this->vatRates->getCountryRate(strtoupper($country), $category) / 100;
243
        } catch (\Exception $e) {
244
            return;
245
        }
246
247
        /** @var TaxRateInterface|null $taxRate */
248
        $taxRate = $this->taxRateRepository->findOneBy(['code' => strtolower($code) . '-' . $category]);
249
        if ($taxRate instanceof TaxRateInterface) {
250
            if ($taxRate->getAmount() !== $countryRate) {
251
                $taxRate->setAmount($countryRate);
252
253
                $this->taxRateRepository->add($taxRate);
254
            }
255
256
            return;
257
        }
258
259
        /** @var TaxCategoryInterface $taxCategory */
260
        $taxCategory = $this->taxCategoryRepository->findOneBy(['code' => $category]);
261
262
        /** @var TaxRateInterface $taxRate */
263
        $taxRate = $this->taxRateFactory->createNew();
264
        $taxRate->setCode(strtolower($code . '-' . $category));
265
        $taxRate->setName('VAT');
266
        $taxRate->setAmount($countryRate);
267
        $taxRate->setCalculator('default');
268
        $taxRate->setIncludedInPrice($includedInPrice);
269
        $taxRate->setZone($zone);
270
        $taxRate->setCategory($taxCategory);
271
272
        $this->taxRateRepository->add($taxRate);
273
    }
274
}
275