Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
48 / 48 |
|
100.00% |
7 / 7 |
CRAP | |
100.00% |
1 / 1 |
| InventorySettingsHtmxController | |
100.00% |
47 / 47 |
|
100.00% |
7 / 7 |
13 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| table | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
| modal | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
| save | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
4 | |||
| toggle | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
| delete | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| htmlResponse | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | /** @license For full copyright and license information, please see the LICENSE.md file. */ |
| 6 | |
| 7 | namespace App\Modules\Inventory\Presentation\Htmx; |
| 8 | |
| 9 | defined('AMMONLY_APP') || exit('Direct script access is forbidden.'); |
| 10 | |
| 11 | use App\Core\Engine\Application\Service\InventoryApplicationService; |
| 12 | use App\Core\Engine\Domain\Repository\MetadataRepositoryInterface; |
| 13 | use Nyholm\Psr7\Factory\Psr17Factory; |
| 14 | use Psr\Http\Message\ResponseInterface; |
| 15 | use Psr\Http\Message\ServerRequestInterface; |
| 16 | use Throwable; |
| 17 | use Twig\Environment as TwigEnvironment; |
| 18 | |
| 19 | /** |
| 20 | * HTMX controller for interactive inventory fields management partials and modals. |
| 21 | */ |
| 22 | final readonly class InventorySettingsHtmxController |
| 23 | { |
| 24 | private const string HTML_CONTENT_TYPE = 'text/html; charset=utf-8'; |
| 25 | |
| 26 | public function __construct( |
| 27 | private InventoryApplicationService $inventoryService, |
| 28 | private MetadataRepositoryInterface $metadataRepository, |
| 29 | private TwigEnvironment $twig, |
| 30 | private Psr17Factory $psr17Factory, |
| 31 | ) { |
| 32 | } |
| 33 | |
| 34 | /** |
| 35 | * Renders inventory fields table partial (GET /htmx/inventory-fields/{moduleId:\d+}/table). |
| 36 | */ |
| 37 | public function table(ServerRequestInterface $request, int $moduleId): ResponseInterface |
| 38 | { |
| 39 | $request->getMethod(); |
| 40 | $module = $this->metadataRepository->findModuleById($moduleId); |
| 41 | $fields = $this->inventoryService->getInventoryFields($moduleId); |
| 42 | |
| 43 | $html = $this->twig->render('inventory/partials/fields_table.twig', [ |
| 44 | 'module' => $module, |
| 45 | 'fields' => $fields, |
| 46 | ]); |
| 47 | |
| 48 | return $this->htmlResponse($html); |
| 49 | } |
| 50 | |
| 51 | /** |
| 52 | * Renders add/edit inventory field modal (GET /htmx/inventory-fields/{moduleId:\d+}/modal). |
| 53 | */ |
| 54 | public function modal(ServerRequestInterface $request, int $moduleId): ResponseInterface |
| 55 | { |
| 56 | $request->getMethod(); |
| 57 | $module = $this->metadataRepository->findModuleById($moduleId); |
| 58 | |
| 59 | $html = $this->twig->render('inventory/partials/field_modal.twig', [ |
| 60 | 'module' => $module, |
| 61 | ]); |
| 62 | |
| 63 | return $this->htmlResponse($html); |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * Saves new inventory field via HTMX form submission (POST /htmx/inventory-fields/{moduleId:\d+}/save). |
| 68 | */ |
| 69 | public function save(ServerRequestInterface $request, int $moduleId): ResponseInterface |
| 70 | { |
| 71 | $data = $request->getParsedBody(); |
| 72 | $body = is_array($data) ? $data : []; |
| 73 | |
| 74 | try { |
| 75 | $fieldKey = trim((string) ($body['field_key'] ?? '')); |
| 76 | $label = trim((string) ($body['label'] ?? '')); |
| 77 | $uitypeId = (int) ($body['uitype_id'] ?? 1); |
| 78 | $isMandatory = !empty($body['is_mandatory']) ? 1 : 0; |
| 79 | $sortOrder = (int) ($body['sort_order'] ?? 50); |
| 80 | |
| 81 | $this->inventoryService->addInventoryField($moduleId, [ |
| 82 | 'field_key' => $fieldKey, |
| 83 | 'label' => $label, |
| 84 | 'uitype_id' => $uitypeId, |
| 85 | 'is_mandatory' => $isMandatory, |
| 86 | 'sort_order' => $sortOrder, |
| 87 | 'is_system' => 0, |
| 88 | 'status' => 'active', |
| 89 | ]); |
| 90 | |
| 91 | return $this->table($request, $moduleId); |
| 92 | } catch (Throwable $e) { |
| 93 | $escaped = htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8'); |
| 94 | return $this->htmlResponse("<div class='alert alert-danger mb-3'>{$escaped}</div>", 400); |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | /** |
| 99 | * Toggles field active/inactive status (POST /htmx/inventory-fields/{moduleId:\d+}/toggle/{fieldId:\d+}). |
| 100 | */ |
| 101 | public function toggle(ServerRequestInterface $request, int $moduleId, int $fieldId): ResponseInterface |
| 102 | { |
| 103 | $fields = $this->inventoryService->getInventoryFields($moduleId); |
| 104 | foreach ($fields as $field) { |
| 105 | if ((int) $field['id'] === $fieldId) { |
| 106 | $newStatus = ($field['status'] === 'active') ? 'inactive' : 'active'; |
| 107 | $this->inventoryService->updateInventoryField($fieldId, ['status' => $newStatus]); |
| 108 | break; |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | return $this->table($request, $moduleId); |
| 113 | } |
| 114 | |
| 115 | /** |
| 116 | * Deletes user-defined inventory field (DELETE /htmx/inventory-fields/{moduleId:\d+}/delete/{fieldId:\d+}). |
| 117 | */ |
| 118 | public function delete(ServerRequestInterface $request, int $moduleId, int $fieldId): ResponseInterface |
| 119 | { |
| 120 | $this->inventoryService->deleteInventoryField($fieldId); |
| 121 | return $this->table($request, $moduleId); |
| 122 | } |
| 123 | |
| 124 | private function htmlResponse(string $html, int $status = 200): ResponseInterface |
| 125 | { |
| 126 | $response = $this->psr17Factory->createResponse($status) |
| 127 | ->withHeader('Content-Type', self::HTML_CONTENT_TYPE); |
| 128 | $response->getBody()->write($html); |
| 129 | |
| 130 | return $response; |
| 131 | } |
| 132 | } |