Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.53% covered (success)
97.53%
79 / 81
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
InventoryItemsApiController
97.50% covered (success)
97.50%
78 / 80
83.33% covered (warning)
83.33%
5 / 6
24
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 list
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
5
 calculate
90.91% covered (success)
90.91%
20 / 22
0.00% covered (danger)
0.00%
0 / 1
7.04
 save
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
1 / 1
8
 delete
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 handleException
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\Inventory\Presentation\Api;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Engine\Application\Service\InventoryApplicationService;
12use App\Core\Engine\Application\Service\InventoryCalculationService;
13use App\Core\Engine\Domain\Repository\MetadataRepositoryInterface;
14use App\Core\Instance\Application\Service\InstanceContextManagerInterface;
15use App\Core\Instance\Application\Service\RemoteInstanceEngineGatewayInterface;
16use App\Shared\Infrastructure\Http\ApiResponseTrait;
17use Nyholm\Psr7\Factory\Psr17Factory;
18use Psr\Http\Message\ResponseInterface;
19use Psr\Http\Message\ServerRequestInterface;
20use Throwable;
21
22/**
23 * REST API controller for inventory line items operations.
24 *
25 * Supports listing, calculating, and persisting line items both locally
26 * and via remote instance gateway when context switching is active.
27 *
28 * @package App\Modules\Inventory\Presentation\Api
29 */
30final readonly class InventoryItemsApiController
31{
32    use ApiResponseTrait;
33
34    public function __construct(
35        private InventoryApplicationService $inventoryService,
36        private MetadataRepositoryInterface $metadataRepository,
37        private InventoryCalculationService $calculationService,
38        private Psr17Factory $psr17,
39        private ?InstanceContextManagerInterface $instanceContextManager = null,
40        private ?RemoteInstanceEngineGatewayInterface $remoteGateway = null,
41    ) {
42    }
43
44    /**
45     * Lists inventory line items for a given document record.
46     *
47     * @param string $moduleName Module machine name.
48     * @param int    $recordId   Parent record identifier.
49     * @return ResponseInterface JSON API response.
50     */
51    public function list(string $moduleName, int $recordId): ResponseInterface
52    {
53        try {
54            if ($this->instanceContextManager?->isRemote() && $this->remoteGateway !== null) {
55                $instance = $this->instanceContextManager->getActiveInstance();
56                if ($instance !== null) {
57                    $remoteData = $this->remoteGateway->fetchInventoryItems($instance, $moduleName, $recordId);
58                    return $this->jsonSuccess($this->psr17, $remoteData['data'] ?? $remoteData);
59                }
60            }
61
62            $module = $this->metadataRepository->findModule($moduleName);
63            $fields = $this->inventoryService->getInventoryFields($module->id);
64            $items = $this->inventoryService->getRecordItems($module->id, $recordId);
65
66            return $this->jsonSuccess($this->psr17, [
67                'module' => $moduleName,
68                'record_id' => $recordId,
69                'fields' => $fields,
70                'items' => $items,
71            ]);
72        } catch (Throwable $e) {
73            return $this->handleException($e);
74        }
75    }
76
77    /**
78     * Recalculates line item values live without persisting to database.
79     *
80     * @param ServerRequestInterface $request    PSR-7 request.
81     * @param string                 $moduleName Module machine name.
82     * @param int                    $recordId   Parent record identifier.
83     * @return ResponseInterface JSON API response.
84     */
85    public function calculate(ServerRequestInterface $request, string $moduleName, int $recordId): ResponseInterface
86    {
87        try {
88            $data = $this->parseJsonBody($request);
89            if ($this->instanceContextManager?->isRemote() && $this->remoteGateway !== null) {
90                $instance = $this->instanceContextManager->getActiveInstance();
91                if ($instance !== null) {
92                    $remoteData = $this->remoteGateway->calculateInventoryItems(
93                        $instance,
94                        $moduleName,
95                        $recordId,
96                        $data
97                    );
98                    return $this->jsonSuccess($this->psr17, $remoteData['data'] ?? $remoteData);
99                }
100            }
101
102            $rawItems = (isset($data['items']) && is_array($data['items'])) ? array_values($data['items']) : [];
103            $currencyCode = strtoupper((string) ($data['currency'] ?? 'PLN'));
104            $exchangeRate = (float) ($data['exchange_rate'] ?? 1.0);
105
106            $calculated = $this->calculationService->calculateItemsSummary(
107                $rawItems,
108                $currencyCode,
109                $exchangeRate
110            );
111
112            return $this->jsonSuccess($this->psr17, $calculated);
113        } catch (Throwable $e) {
114            return $this->handleException($e);
115        }
116    }
117
118    /**
119     * Saves line items to database for a document record.
120     *
121     * @param ServerRequestInterface $request    PSR-7 request.
122     * @param string                 $moduleName Module machine name.
123     * @param int                    $recordId   Parent record identifier.
124     * @return ResponseInterface JSON API response.
125     */
126    public function save(ServerRequestInterface $request, string $moduleName, int $recordId): ResponseInterface
127    {
128        try {
129            $data = $this->parseJsonBody($request);
130            if ($this->instanceContextManager?->isRemote() && $this->remoteGateway !== null) {
131                $instance = $this->instanceContextManager->getActiveInstance();
132                if ($instance !== null) {
133                    $remoteData = $this->remoteGateway->saveInventoryItems(
134                        $instance,
135                        $moduleName,
136                        $recordId,
137                        $data['items'] ?? $data
138                    );
139                    return $this->jsonSuccess($this->psr17, $remoteData['data'] ?? $remoteData);
140                }
141            }
142
143            $module = $this->metadataRepository->findModule($moduleName);
144            $rawItems = (isset($data['items']) && is_array($data['items'])) ? array_values($data['items']) : [];
145            $currencyCode = strtoupper((string) ($data['currency'] ?? 'PLN'));
146            $exchangeRate = (float) ($data['exchange_rate'] ?? 1.0);
147
148            $calculated = $this->calculationService->calculateItemsSummary(
149                $rawItems,
150                $currencyCode,
151                $exchangeRate
152            );
153
154            if ($recordId > 0) {
155                $this->inventoryService->saveRecordItems($module->id, $recordId, $calculated['items']);
156            }
157
158            return $this->jsonSuccess($this->psr17, [
159                'record_id' => $recordId,
160                'module' => $moduleName,
161                'items' => $calculated['items'],
162                'summary' => $calculated['summary'],
163                'saved' => true,
164            ]);
165        } catch (Throwable $e) {
166            return $this->handleException($e);
167        }
168    }
169
170    /**
171     * Deletes a specific inventory item by ID.
172     *
173     * @param string $moduleName Module machine name.
174     * @param int    $recordId   Parent record identifier.
175     * @param int    $itemId     Line item identifier.
176     * @return ResponseInterface JSON API response.
177     */
178    public function delete(string $moduleName, int $recordId, int $itemId): ResponseInterface
179    {
180        try {
181            $module = $this->metadataRepository->findModule($moduleName);
182            $deleted = $this->inventoryService->deleteRecordItem($module->id, $itemId);
183
184            return $this->jsonSuccess($this->psr17, [
185                'record_id' => $recordId,
186                'item_id' => $itemId,
187                'deleted' => $deleted,
188            ]);
189        } catch (Throwable $e) {
190            return $this->handleException($e);
191        }
192    }
193
194    /**
195     * Handles exceptions and returns JSON error response.
196     *
197     * @param Throwable $e Handled exception.
198     * @return ResponseInterface Formatted error response.
199     */
200    private function handleException(Throwable $e): ResponseInterface
201    {
202        return $this->jsonError($this->psr17, $e->getMessage(), 400);
203    }
204}