Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.03% covered (success)
94.03%
63 / 67
80.00% covered (warning)
80.00%
12 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
RemoteInstanceEngineGateway
93.94% covered (success)
93.94%
62 / 66
80.00% covered (warning)
80.00%
12 / 15
26.15
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
 fetchSchema
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fetchFields
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 fetchList
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 fetchRecord
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createRecord
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 updateRecord
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 deleteRecord
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fetchTimeline
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fetchNavigation
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
3.00
 updateStatus
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 fetchClientUsers
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
6.07
 fetchInventoryItems
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 saveInventoryItems
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 calculateInventoryItems
100.00% covered (success)
100.00%
5 / 5
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\Core\Instance\Application\Service;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Grid\GridRequest;
12use App\Core\Instance\Domain\Model\ClientInstance;
13use App\Core\Instance\Infrastructure\Client\RemoteInstanceApiClientInterface;
14
15/**
16 * Remote Instance Engine Gateway.
17 *
18 * Dispatches module metadata, grid lists, and CRUD operations to active remote SaaS instances via REST API.
19 *
20 * @package App\Core\Instance\Application\Service
21 */
22final readonly class RemoteInstanceEngineGateway implements RemoteInstanceEngineGatewayInterface
23{
24    private const string RECORD_ENDPOINT_PATTERN = '/engine/%s/%d';
25
26    /**
27     * RemoteInstanceEngineGateway constructor.
28     *
29     * @param RemoteInstanceApiClientInterface $apiClient HTTP API client for remote communication.
30     */
31    public function __construct(
32        private RemoteInstanceApiClientInterface $apiClient
33    ) {
34    }
35
36    /**
37     * {@inheritdoc}
38     */
39    public function fetchSchema(ClientInstance $instance, string $moduleName): array
40    {
41        return $this->apiClient->get($instance, sprintf('/engine/%s/schema', $moduleName));
42    }
43
44    /**
45     * {@inheritdoc}
46     */
47    public function fetchFields(ClientInstance $instance, string|int $moduleNameOrId): array
48    {
49        return $this->apiClient->get($instance, sprintf('/engine/%s/fields', (string) $moduleNameOrId));
50    }
51
52    /**
53     * {@inheritdoc}
54     */
55    public function fetchList(
56        ClientInstance $instance,
57        string $moduleName,
58        GridRequest $request,
59        ?int $filterId = null
60    ): array {
61        $queryParams = [
62            'page'  => $request->page,
63            'limit' => $request->limit,
64            'sort'  => $request->sortColumn,
65            'order' => $request->sortDirection,
66        ];
67
68        if ($filterId !== null && $filterId > 0) {
69            $queryParams['filter_id'] = $filterId;
70        }
71
72        if ($request->filters !== []) {
73            foreach ($request->filters as $fKey => $fVal) {
74                $queryParams['filter[' . $fKey . ']'] = $fVal;
75            }
76            $queryParams['filters'] = json_encode($request->filters);
77        }
78
79        return $this->apiClient->get($instance, sprintf('/engine/%s/list', $moduleName), $queryParams);
80    }
81
82    /**
83     * {@inheritdoc}
84     */
85    public function fetchRecord(ClientInstance $instance, string $moduleName, int $id): array
86    {
87        return $this->apiClient->get($instance, sprintf(self::RECORD_ENDPOINT_PATTERN, $moduleName, $id));
88    }
89
90    /**
91     * {@inheritdoc}
92     */
93    public function createRecord(ClientInstance $instance, string $moduleName, array $payload): array
94    {
95        return $this->apiClient->post($instance, sprintf('/engine/%s', $moduleName), $payload);
96    }
97
98    /**
99     * {@inheritdoc}
100     */
101    public function updateRecord(
102        ClientInstance $instance,
103        string $moduleName,
104        int $id,
105        array $payload
106    ): array {
107        return $this->apiClient->put($instance, sprintf(self::RECORD_ENDPOINT_PATTERN, $moduleName, $id), $payload);
108    }
109
110    /**
111     * {@inheritdoc}
112     */
113    public function deleteRecord(ClientInstance $instance, string $moduleName, int $id): array
114    {
115        return $this->apiClient->delete($instance, sprintf(self::RECORD_ENDPOINT_PATTERN, $moduleName, $id));
116    }
117
118    /**
119     * {@inheritdoc}
120     */
121    public function fetchTimeline(ClientInstance $instance, string $moduleName, int $id): array
122    {
123        return $this->apiClient->get($instance, sprintf('/engine/%s/%d/timeline', $moduleName, $id));
124    }
125
126    /**
127     * {@inheritdoc}
128     */
129    public function fetchNavigation(
130        ClientInstance $instance,
131        string $moduleName,
132        int $id,
133        GridRequest $request,
134        ?int $filterId = null
135    ): array {
136        $queryParams = [
137            'page'  => $request->page,
138            'limit' => $request->limit,
139            'sort'  => $request->sortColumn,
140            'order' => $request->sortDirection,
141        ];
142
143        if ($filterId !== null && $filterId > 0) {
144            $queryParams['filter_id'] = $filterId;
145        }
146
147        return $this->apiClient->get(
148            $instance,
149            sprintf('/engine/%s/%d/navigation', $moduleName, $id),
150            $queryParams
151        );
152    }
153
154    /**
155     * {@inheritdoc}
156     */
157    public function updateStatus(ClientInstance $instance, string $moduleName, int $id, int $status): array
158    {
159        return $this->apiClient->put(
160            $instance,
161            sprintf('/engine/%s/%d/status', $moduleName, $id),
162            ['status' => $status]
163        );
164    }
165
166    /**
167     * {@inheritdoc}
168     */
169    public function fetchClientUsers(ClientInstance $instance): array
170    {
171        try {
172            $res = $this->apiClient->get($instance, '/users', ['limit' => 100]);
173            $users = $res['data']['users'] ?? $res['users'] ?? $res['records'] ?? $res['data'] ?? [];
174            $list = [];
175
176            if (is_array($users)) {
177                foreach ($users as $u) {
178                    if (is_array($u) && isset($u['id'])) {
179                        $fallback = 'User #' . $u['id'];
180                        $displayName = (string) ($u['full_name'] ?? $u['name'] ?? $u['username'] ?? $fallback);
181                        $list[] = [
182                            'id'    => (int) $u['id'],
183                            'label' => $displayName,
184                            'name'  => $displayName,
185                        ];
186                    }
187                }
188            }
189
190            return $list;
191        } catch (\Throwable) {
192            return [];
193        }
194    }
195
196    /**
197     * {@inheritdoc}
198     */
199    public function fetchInventoryItems(ClientInstance $instance, string $moduleName, int $recordId): array
200    {
201        return $this->apiClient->get($instance, sprintf('/inventory/%s/%d/items', $moduleName, $recordId));
202    }
203
204    /**
205     * {@inheritdoc}
206     */
207    public function saveInventoryItems(
208        ClientInstance $instance,
209        string $moduleName,
210        int $recordId,
211        array $items
212    ): array {
213        return $this->apiClient->post(
214            $instance,
215            sprintf('/inventory/%s/%d/save-items', $moduleName, $recordId),
216            ['items' => $items]
217        );
218    }
219
220    /**
221     * {@inheritdoc}
222     */
223    public function calculateInventoryItems(
224        ClientInstance $instance,
225        string $moduleName,
226        int $recordId,
227        array $payload
228    ): array {
229        return $this->apiClient->post(
230            $instance,
231            sprintf('/inventory/%s/%d/calculate', $moduleName, $recordId),
232            $payload
233        );
234    }
235}
236