Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.82% covered (warning)
81.82%
117 / 143
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
StockOutputPurchaseSolicitationService
81.82% covered (warning)
81.82%
117 / 143
0.00% covered (danger)
0.00%
0 / 2
18.74
0.00% covered (danger)
0.00%
0 / 1
 execute
85.50% covered (warning)
85.50%
112 / 131
0.00% covered (danger)
0.00%
0 / 1
13.52
 resolvePurchaseSolicitationNumber
41.67% covered (danger)
41.67%
5 / 12
0.00% covered (danger)
0.00%
0 / 1
7.18
1<?php
2
3namespace Domain\StockIO\Services;
4
5use App\Models\Integration\Integration;
6use App\Models\StockIO\StockIO;
7use App\Models\StockIO\StockIOProduct;
8use App\Models\User;
9use Domain\Request\Support\OriginTransactionCodeResolver;
10use Domain\Shared\Integrations\Senior\SeniorPurchaseSolicitationExport;
11use Illuminate\Support\Facades\DB;
12use Illuminate\Support\Facades\Log;
13use Illuminate\Validation\ValidationException;
14
15class StockOutputPurchaseSolicitationService
16{
17    public function execute(StockIO $stockIO, array $items, ?string $observation, User $user): array
18    {
19        $stockIO->loadMissing([
20            'integration',
21            'request.requester',
22            'request.costCenter.integration',
23            'stockIOProducts.integration',
24            'stockIOProducts.requestItem',
25            'stockIOProducts.requestDeliveries',
26            'stockIOProducts.product.integration',
27            'stockIOProducts.product.unitMeasurement.integration',
28            'stockIOProducts.product.family.origin.integration',
29        ]);
30
31        $indexedProducts = $stockIO->stockIOProducts->keyBy('id');
32        $missingAmountService = new StockIOProductMissingAmountService();
33
34        $payloadItems = [];
35        $itemsToPersist = [];
36        $seqSol = 1;
37        foreach ($items as $item) {
38            /** @var StockIOProduct|null $stockIOProduct */
39            $stockIOProduct = $indexedProducts->get((int) $item['stock_io_product_id']);
40            if (!$stockIOProduct) {
41                throw ValidationException::withMessages([
42                    'items' => __('The informed stockIO product does not belong to the target stock output.'),
43                ]);
44            }
45
46            $requestedQuantity = round((float) $item['quantity'], 2);
47            $missingAmount = $missingAmountService->resolve($stockIOProduct);
48            if ($missingAmount > 0 && $requestedQuantity > $missingAmount) {
49                throw ValidationException::withMessages([
50                    'items' => __('Requested quantity exceeds quantity_missing for at least one item.'),
51                ]);
52            }
53
54            $productParameters = $stockIOProduct->product->integration?->parameters ?? [];
55            $unitParameters = $stockIOProduct->product->unitMeasurement?->integration?->parameters ?? [];
56            $stockParameters = $stockIO->integration?->parameters ?? [];
57            $requestNumber = $stockParameters['request_number'] ?? null;
58            $warehouseCode =  (new OriginTransactionCodeResolver())->forPurchaseSolicitationWarehouse(
59                $stockIOProduct->product->family->origin
60            );
61
62            $payloadItems[] = [
63                'seqSol' => $seqSol,
64                'codDep' => $warehouseCode,
65                'codPro' => (string) ($productParameters['integration_product'] ?? $productParameters['integration_code'] ?? ''),
66                'codDer' => (string) ($productParameters['integration_derivation'] ?? ''),
67                'uniMed' => (string) ($unitParameters['integration_code'] ?? ''),
68                'obsSol' => (string) ($item['observation'] ?? $observation ?? ''),
69                'qtdSol' => $requestedQuantity,
70                'datPrv' => now()->format('d/m/Y'),
71                'usuSol' => (string) ($stockParameters['request_user'] ?? ''),
72                'ccuRes' => (string) ($stockIO->request?->costCenter?->integration?->parameters['cost_center_code'] ?? $stockParameters['cost_center'] ?? ''),
73                'preSol' => 0.0,
74                'codTns' => '91400',
75            ];
76
77            $itemsToPersist[] = [
78                'stock_io_product' => $stockIOProduct,
79                'request_number' => $requestNumber,
80                'request_sequence' => (int) ($stockIOProduct->integration?->parameters['request_sequence'] ?? 0),
81                'quantity' => $requestedQuantity,
82                'seq_sol' => $seqSol,
83            ];
84            $seqSol++;
85        }
86
87        $payload = [
88            'identificadorSistema' => 'SOELX',
89            'codEmp' => (int) (($stockIO->integration?->parameters['company_code'] ?? null) ?: 1),
90            'codFil' => (int) (($stockIO->integration?->parameters['branch_code'] ?? null) ?: 1),
91            'itensProduto' => $payloadItems,
92        ];
93        $companyCode = (int) $payload['codEmp'];
94        $branchCode = (int) $payload['codFil'];
95
96        $integration = new SeniorPurchaseSolicitationExport();
97        $response = $integration->generate($payload);
98        $purchaseSolicitationNumber = $this->resolvePurchaseSolicitationNumber($response);
99
100        DB::transaction(function () use ($itemsToPersist, $integration, $purchaseSolicitationNumber, $companyCode, $branchCode): void {
101            foreach ($itemsToPersist as $itemToPersist) {
102                $requestNumber = trim((string) ($itemToPersist['request_number'] ?? ''));
103                $requestSequence = (int) ($itemToPersist['request_sequence'] ?? 0);
104                $purchaseSequence = (int) ($itemToPersist['seq_sol'] ?? 0);
105
106                if ($requestNumber === '' || $requestSequence <= 0 || $purchaseSolicitationNumber <= 0 || $purchaseSequence <= 0) {
107                    Log::channel('integration')->error('senior.purchase_solicitation.link.invalid_payload', [
108                        'stock_io_id' => $itemToPersist['stock_io_product']->stock_io_id ?? null,
109                        'stock_io_product_id' => $itemToPersist['stock_io_product']->getKey(),
110                        'numeme' => $requestNumber,
111                        'seqeme' => $requestSequence,
112                        'numSol' => $purchaseSolicitationNumber,
113                        'seqSol' => $purchaseSequence,
114                    ]);
115
116                    throw ValidationException::withMessages([
117                        'integration' => __('Invalid payload to link request and purchase solicitation in Senior.'),
118                    ]);
119                }
120
121                $linkPayload = [
122                    'codEmp' => $companyCode,
123                    'codFil' => $branchCode,
124                    'ligacoes' => [
125                        'numeme' => (int) $requestNumber,
126                        'seqeme' => $requestSequence,
127                        'numSol' => $purchaseSolicitationNumber,
128                        'seqSol' => $purchaseSequence,
129                    ]
130                ];
131
132                Log::channel('integration')->info('senior.purchase_solicitation.link.started', [
133                    'stock_io_id' => $itemToPersist['stock_io_product']->stock_io_id ?? null,
134                    'stock_io_product_id' => $itemToPersist['stock_io_product']->getKey(),
135                    'codEmp' => $companyCode,
136                    'codFil' => $branchCode,
137                    'ligacoes' => $linkPayload['ligacoes'],
138                ]);
139
140                $integration->link($linkPayload);
141
142                Log::channel('integration')->info('senior.purchase_solicitation.link.finished', [
143                    'stock_io_id' => $itemToPersist['stock_io_product']->stock_io_id ?? null,
144                    'stock_io_product_id' => $itemToPersist['stock_io_product']->getKey(),
145                    'numSol' => $purchaseSolicitationNumber,
146                    'seqSol' => $purchaseSequence,
147                ]);
148
149                $stockIOProduct = $itemToPersist['stock_io_product'];
150                $stockIOIntegration = $stockIOProduct->integration;
151                if (!$stockIOIntegration) {
152                    $stockIOIntegration = new Integration();
153                    $stockIOProduct->integration()->save($stockIOIntegration);
154                }
155
156                $parameters = $stockIOIntegration->parameters ?? [];
157                $stockIOIntegration->setAttribute('parameters', array_merge($parameters, [
158                    'purchase_solicitation_number' => $purchaseSolicitationNumber,
159                    'purchase_solicitation_sequence' => $itemToPersist['seq_sol'],
160                    'purchase_solicitation_quantity' => $itemToPersist['quantity'],
161                ]));
162                $stockIOIntegration->save();
163            }
164        });
165
166        return [
167            'stock_io_id' => $stockIO->getKey(),
168            'purchase_solicitation_number' => $purchaseSolicitationNumber,
169            'items' => collect($itemsToPersist)->map(fn (array $item) => [
170                'stock_io_product_id' => $item['stock_io_product']->getKey(),
171                'quantity' => $item['quantity'],
172                'purchase_solicitation_sequence' => $item['seq_sol'],
173            ])->values()->all(),
174        ];
175    }
176
177    private function resolvePurchaseSolicitationNumber(object $response): int
178    {
179        $fromRoot = data_get($response, 'numSol');
180        if (!is_null($fromRoot)) {
181            return (int) $fromRoot;
182        }
183
184        $fromReturn = data_get($response, 'retornoSolicitacao.numSol');
185        if (!is_null($fromReturn)) {
186            return (int) $fromReturn;
187        }
188
189        $fromItem = data_get($response, 'retornoSolicitacao.0.numSol');
190        if (!is_null($fromItem)) {
191            return (int) $fromItem;
192        }
193
194        throw ValidationException::withMessages([
195            'integration' => __('Unable to resolve purchase solicitation number from Senior response.'),
196        ]);
197    }
198}