EuInstallCommand::configure()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 29
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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