Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
46 / 46
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
DiscountHtmxController
100.00% covered (success)
100.00%
45 / 45
100.00% covered (success)
100.00%
7 / 7
13
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 tabDiscounts
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 rateModal
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 rateSave
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 rateToggle
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
2
 rateDelete
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 htmlResponse
100.00% covered (success)
100.00%
4 / 4
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\Discount\Presentation\Htmx;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Modules\Discount\Application\DTO\DiscountRateDto;
12use App\Modules\Discount\Application\Service\DiscountApplicationService;
13use Nyholm\Psr7\Factory\Psr17Factory;
14use Psr\Http\Message\ResponseInterface;
15use Psr\Http\Message\ServerRequestInterface;
16use Throwable;
17use Twig\Environment as TwigEnvironment;
18
19/**
20 * HTMX Controller for Discount management interface partials and modal interactions.
21 */
22final readonly class DiscountHtmxController
23{
24    private const string HTML_CONTENT_TYPE = 'text/html; charset=utf-8';
25
26    /**
27     * DiscountHtmxController constructor.
28     */
29    public function __construct(
30        private DiscountApplicationService $discountService,
31        private TwigEnvironment $twig,
32        private Psr17Factory $psr17Factory,
33    ) {
34    }
35
36    /**
37     * Render Discounts tab content (GET /htmx/discount/tab-discounts).
38     */
39    public function tabDiscounts(): ResponseInterface
40    {
41        $discounts = $this->discountService->getDiscounts();
42        $html = $this->twig->render('discount/partials/discounts_tab.twig', [
43            'discounts' => $discounts,
44        ]);
45
46        return $this->htmlResponse($html);
47    }
48
49    /**
50     * Render Rate Modal for Create or Edit (GET /htmx/discount/rate-modal/{id:\d+}?).
51     */
52    public function rateModal(ServerRequestInterface $request): ResponseInterface
53    {
54        $queryParams = $request->getQueryParams();
55        $rawId = $request->getAttribute('id') ?? $queryParams['id'] ?? 0;
56        $id = (int) $rawId;
57
58        $discount = $id > 0 ? $this->discountService->getDiscount($id) : null;
59
60        $html = $this->twig->render('discount/partials/rate_modal.twig', [
61            'discount' => $discount,
62        ]);
63
64        return $this->htmlResponse($html);
65    }
66
67    /**
68     * Save Rate via HTMX Form (POST /htmx/discount/rate-save).
69     */
70    public function rateSave(ServerRequestInterface $request): ResponseInterface
71    {
72        $data = $request->getParsedBody();
73        $body = is_array($data) ? $data : [];
74
75        $rawId = $request->getAttribute('id') ?? $body['id'] ?? null;
76        $id = $rawId !== null && $rawId !== '' ? (int) $rawId : null;
77
78        try {
79            $dto = DiscountRateDto::fromArray($body, $id);
80            $this->discountService->saveDiscount($dto);
81
82            return $this->tabDiscounts($request);
83        } catch (Throwable $e) {
84            $escaped = htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
85            return $this->htmlResponse("<div class='alert alert-danger mb-3'>{$escaped}</div>", 400);
86        }
87    }
88
89    /**
90     * Toggle Rate Active Status (POST /htmx/discount/rate-toggle/{id:\d+}).
91     */
92    public function rateToggle(ServerRequestInterface $request): ResponseInterface
93    {
94        $id = (int) ($request->getAttribute('id') ?? 0);
95        $rate = $this->discountService->getDiscount($id);
96
97        if ($rate !== null) {
98            $dto = new DiscountRateDto(
99                id: $rate->id,
100                discountCode: $rate->discountCode,
101                discountName: $rate->discountName,
102                discountType: $rate->discountType->value,
103                discountValue: $rate->discountValue,
104                isDefault: $rate->isDefault,
105                isActive: !$rate->isActive,
106            );
107            $this->discountService->saveDiscount($dto);
108        }
109
110        return $this->tabDiscounts($request);
111    }
112
113    /**
114     * Delete Rate (DELETE /htmx/discount/rate-delete/{id:\d+}).
115     */
116    public function rateDelete(ServerRequestInterface $request): ResponseInterface
117    {
118        $id = (int) ($request->getAttribute('id') ?? 0);
119        $this->discountService->deleteDiscount($id);
120
121        return $this->tabDiscounts($request);
122    }
123
124    /**
125     * Helper to render HTML response.
126     */
127    private function htmlResponse(string $html, int $status = 200): ResponseInterface
128    {
129        $response = $this->psr17Factory->createResponse($status)
130            ->withHeader('Content-Type', self::HTML_CONTENT_TYPE);
131        $response->getBody()->write($html);
132
133        return $response;
134    }
135}