Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.80% covered (success)
90.80%
79 / 87
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
SeniorDevolutionFulfillmentService
90.80% covered (success)
90.80%
79 / 87
40.00% covered (danger)
40.00%
2 / 5
14.15
0.00% covered (danger)
0.00%
0 / 1
 fulfill
92.86% covered (success)
92.86%
26 / 28
0.00% covered (danger)
0.00%
0 / 1
5.01
 makeDelivery
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 formatSeniorDecimalAmount
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 sendDeliveries
83.33% covered (warning)
83.33%
25 / 30
0.00% covered (danger)
0.00%
0 / 1
3.04
 importGeneratedRequests
95.24% covered (success)
95.24%
20 / 21
0.00% covered (danger)
0.00%
0 / 1
4
1<?php
2
3namespace Domain\Request\Services;
4
5use Domain\Shared\Integrations\Senior\Exceptions\IntegrationSoapSeniorException;
6use Domain\Shared\Integrations\Senior\SeniorRequestDeliveryExport;
7use Domain\Shared\Integrations\Senior\SeniorRequestImport;
8use Illuminate\Support\Facades\Log;
9
10readonly class SeniorDevolutionFulfillmentService
11{
12    private const IMPORT_ATTEMPTS = 3;
13
14    private const IMPORT_RETRY_DELAY_MICROSECONDS = 250000;
15
16    /**
17     * @throws IntegrationSoapSeniorException
18     */
19    public function fulfill(
20        string $requestNumber,
21        string $transactionCode,
22        ?float $amount = null,
23        ?int $sequence = null
24    ): void {
25        if (! is_null($amount)) {
26            $this->sendDeliveries([
27                $this->makeDelivery(
28                    $requestNumber,
29                    $sequence ?? 1,
30                    $amount,
31                    $transactionCode
32                ),
33            ]);
34
35            return;
36        }
37
38        $requests = $this->importGeneratedRequests($requestNumber);
39        if (empty($requests)) {
40            throw new IntegrationSoapSeniorException(__('Generated devolution request not found in Senior.'));
41        }
42
43        $deliveries = [];
44        foreach ($requests as $request) {
45            $pendingAmount = round(
46                (float) $request->qtdApr - (float) $request->qtdAtd - (float) $request->qtdCan,
47                2
48            );
49
50            if ($pendingAmount <= 0) {
51                continue;
52            }
53
54            $deliveries[] = $this->makeDelivery(
55                (string) $request->numEme,
56                (int) $request->seqEme,
57                $pendingAmount,
58                $transactionCode
59            );
60        }
61
62        $this->sendDeliveries($deliveries);
63    }
64
65    private function makeDelivery(string $requestNumber, int $sequence, float $amount, string $transactionCode): array
66    {
67        return [
68            'datMov' => now()->format('d/m/Y'),
69            'numeme' => $requestNumber,
70            'seqeme' => $sequence,
71            'qtdAtd' => $this->formatSeniorDecimalAmount($amount),
72            'codtns' => $transactionCode,
73        ];
74    }
75
76    private function formatSeniorDecimalAmount(float $amount): string
77    {
78        return number_format(round($amount, 2), 2, '.', '');
79    }
80
81    /**
82     * @throws IntegrationSoapSeniorException
83     */
84    private function sendDeliveries(array $deliveries): void
85    {
86        if (empty($deliveries)) {
87            return;
88        }
89
90        Log::channel('integration')->info('senior.devolution_fulfillment.started', [
91            'requests' => collect($deliveries)
92                ->map(fn (array $delivery) => [
93                    'numeme' => $delivery['numeme'] ?? null,
94                    'seqeme' => $delivery['seqeme'] ?? null,
95                    'qtdAtd' => $delivery['qtdAtd'] ?? null,
96                    'codtns' => $delivery['codtns'] ?? null,
97                ])
98                ->values()
99                ->all(),
100        ]);
101
102        $returns = (new SeniorRequestDeliveryExport)->execute([
103            'codEmp' => 1,
104            'codFil' => 1,
105            'requisicoes' => $deliveries,
106        ]);
107
108        $errors = collect($returns)
109            ->filter(fn (object $return) => (int) ($return->statusRegistro ?? 0) !== 1)
110            ->map(fn (object $return) => (string) ($return->mensagemRegistro ?? __('Unable to fulfill generated devolution request in Senior.')))
111            ->filter()
112            ->values()
113            ->all();
114
115        if (! empty($errors)) {
116            Log::channel('integration')->error('senior.devolution_fulfillment.failed', [
117                'errors' => $errors,
118            ]);
119
120            throw new IntegrationSoapSeniorException(implode(' | ', $errors));
121        }
122
123        Log::channel('integration')->info('senior.devolution_fulfillment.finished');
124    }
125
126    private function importGeneratedRequests(string $requestNumber): array
127    {
128        $import = new SeniorRequestImport;
129
130        for ($attempt = 1; $attempt <= self::IMPORT_ATTEMPTS; $attempt++) {
131            Log::channel('integration')->info('senior.devolution_fulfillment.import_started', [
132                'request_number' => $requestNumber,
133                'attempt' => $attempt,
134            ]);
135
136            $requests = $import->execute($requestNumber);
137            if (! empty($requests)) {
138                Log::channel('integration')->info('senior.devolution_fulfillment.import_finished', [
139                    'request_number' => $requestNumber,
140                    'attempt' => $attempt,
141                    'requests_count' => count($requests),
142                ]);
143
144                return $requests;
145            }
146
147            Log::channel('integration')->info('senior.devolution_fulfillment.import_empty', [
148                'request_number' => $requestNumber,
149                'attempt' => $attempt,
150            ]);
151
152            if ($attempt < self::IMPORT_ATTEMPTS) {
153                usleep(self::IMPORT_RETRY_DELAY_MICROSECONDS);
154            }
155        }
156
157        return [];
158    }
159}