Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
99.01% covered (success)
99.01%
100 / 101
87.50% covered (warning)
87.50%
7 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
SqlDiscountRepository
99.00% covered (success)
99.00%
99 / 100
87.50% covered (warning)
87.50%
7 / 8
25
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
 findById
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 findByCode
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 findDefault
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 findAll
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
5
 save
97.06% covered (success)
97.06%
33 / 34
0.00% covered (danger)
0.00%
0 / 1
8
 delete
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 mapRowToEntity
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
3
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\Infrastructure\Repository;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Instance\Application\Service\InstanceContextManagerInterface;
12use App\Core\Database\Repository\TenantAwareRepositoryTrait;
13use App\Modules\Discount\Domain\Model\DiscountRate;
14use App\Modules\Discount\Domain\Repository\DiscountRepositoryInterface;
15use App\Modules\Discount\Domain\ValueObject\DiscountType;
16use PDO;
17
18/**
19 * Concrete SQL/PDO repository for managing DiscountRate aggregates.
20 */
21final readonly class SqlDiscountRepository implements DiscountRepositoryInterface
22{
23    use TenantAwareRepositoryTrait;
24
25    private const string SELECT_QUERY_HEAD =
26        'SELECT `id`, `discount_code`, `discount_name`, `discount_type`, `discount_value`, '
27        . '`is_default`, `is_active`, `owner`, `created_at`, `updated_at`';
28
29    /**
30     * SqlDiscountRepository constructor.
31     */
32    public function __construct(
33        protected PDO $pdo,
34        private string $tablePrefix = 'a_',
35        protected ?InstanceContextManagerInterface $instanceManager = null,
36        protected ?PDO $clientPdo = null
37    ) {
38    }
39
40    public function findById(int $id): ?DiscountRate
41    {
42        $table = $this->tablePrefix . 'mod_discounts_records';
43        $sql = self::SELECT_QUERY_HEAD . "
44                FROM `{$table}`
45                WHERE `id` = :id AND `special_access` != 0
46                LIMIT 1";
47
48        $stmt = $this->getPdo()->prepare($sql);
49        $stmt->bindValue(':id', $id, PDO::PARAM_INT);
50        $stmt->execute();
51        $row = $stmt->fetch(PDO::FETCH_ASSOC);
52
53        return $row ? $this->mapRowToEntity($row) : null;
54    }
55
56    public function findByCode(string $code): ?DiscountRate
57    {
58        $table = $this->tablePrefix . 'mod_discounts_records';
59        $sql = self::SELECT_QUERY_HEAD . "
60                FROM `{$table}`
61                WHERE `discount_code` = :code AND `special_access` != 0
62                LIMIT 1";
63
64        $stmt = $this->getPdo()->prepare($sql);
65        $stmt->bindValue(':code', $code, PDO::PARAM_STR);
66        $stmt->execute();
67        $row = $stmt->fetch(PDO::FETCH_ASSOC);
68
69        return $row ? $this->mapRowToEntity($row) : null;
70    }
71
72    public function findDefault(): ?DiscountRate
73    {
74        $table = $this->tablePrefix . 'mod_discounts_records';
75        $sql = self::SELECT_QUERY_HEAD . "
76                FROM `{$table}`
77                WHERE `is_default` = 1 AND `is_active` = 1 AND `special_access` != 0
78                ORDER BY `id` ASC
79                LIMIT 1";
80
81        $stmt = $this->getPdo()->query($sql);
82        $row = $stmt->fetch(PDO::FETCH_ASSOC);
83
84        return $row ? $this->mapRowToEntity($row) : null;
85    }
86
87    public function findAll(array $filters = []): array
88    {
89        $table = $this->tablePrefix . 'mod_discounts_records';
90        $where = ['`special_access` != 0'];
91        $params = [];
92
93        if (isset($filters['is_active'])) {
94            $where[] = '`is_active` = :is_active';
95            $params[':is_active'] = (int) (bool) $filters['is_active'];
96        }
97
98        if (!empty($filters['discount_type'])) {
99            $where[] = '`discount_type` = :discount_type';
100            $params[':discount_type'] = (string) $filters['discount_type'];
101        }
102
103        $whereClause = implode(' AND ', $where);
104        $sql = self::SELECT_QUERY_HEAD . "
105                FROM `{$table}`
106                WHERE {$whereClause}
107                ORDER BY `is_default` DESC, `id` ASC";
108
109        $stmt = $this->getPdo()->prepare($sql);
110        foreach ($params as $key => $val) {
111            $stmt->bindValue($key, $val);
112        }
113        $stmt->execute();
114
115        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
116        $result = [];
117        foreach ($rows as $row) {
118            $result[] = $this->mapRowToEntity($row);
119        }
120
121        return $result;
122    }
123
124    public function save(DiscountRate $discount): DiscountRate
125    {
126        $table = $this->tablePrefix . 'mod_discounts_records';
127        $pdo = $this->getPdo();
128
129        if ($discount->isDefault) {
130            $clearDefaultSql = "UPDATE `{$table}` SET `is_default` = 0 WHERE `is_default` = 1";
131            if ($discount->id !== null) {
132                $clearDefaultSql .= " AND `id` != " . (int) $discount->id;
133            }
134            $pdo->exec($clearDefaultSql);
135        }
136
137        if ($discount->id === null) {
138            $sql = "INSERT INTO `{$table}` (
139                        `discount_code`, `discount_name`, `discount_type`, `discount_value`,
140                        `is_default`, `is_active`, `owner`, `created_by`, `created_at`, `updated_at`
141                    ) VALUES (
142                        :discount_code, :discount_name, :discount_type, :discount_value,
143                        :is_default, :is_active, :owner, :created_by, CURRENT_TIMESTAMP(6), CURRENT_TIMESTAMP(6)
144                    )";
145
146            $stmt = $pdo->prepare($sql);
147            $stmt->bindValue(':discount_code', $discount->discountCode);
148            $stmt->bindValue(':discount_name', $discount->discountName);
149            $stmt->bindValue(':discount_type', $discount->discountType->value);
150            $stmt->bindValue(':discount_value', $discount->discountValue);
151            $stmt->bindValue(':is_default', $discount->isDefault ? 1 : 0, PDO::PARAM_INT);
152            $stmt->bindValue(':is_active', $discount->isActive ? 1 : 0, PDO::PARAM_INT);
153            $stmt->bindValue(':owner', $discount->owner, PDO::PARAM_INT);
154            $stmt->bindValue(':created_by', $discount->owner, PDO::PARAM_INT);
155            $stmt->execute();
156
157            $newId = (int) $pdo->lastInsertId();
158            return $this->findById($newId) ?? $discount;
159        }
160
161        $sql = "UPDATE `{$table}` SET
162                    `discount_code` = :discount_code,
163                    `discount_name` = :discount_name,
164                    `discount_type` = :discount_type,
165                    `discount_value` = :discount_value,
166                    `is_default` = :is_default,
167                    `is_active` = :is_active,
168                    `updated_at` = CURRENT_TIMESTAMP(6)
169                WHERE `id` = :id";
170
171        $stmt = $pdo->prepare($sql);
172        $stmt->bindValue(':discount_code', $discount->discountCode);
173        $stmt->bindValue(':discount_name', $discount->discountName);
174        $stmt->bindValue(':discount_type', $discount->discountType->value);
175        $stmt->bindValue(':discount_value', $discount->discountValue);
176        $stmt->bindValue(':is_default', $discount->isDefault ? 1 : 0, PDO::PARAM_INT);
177        $stmt->bindValue(':is_active', $discount->isActive ? 1 : 0, PDO::PARAM_INT);
178        $stmt->bindValue(':id', $discount->id, PDO::PARAM_INT);
179        $stmt->execute();
180
181        return $this->findById($discount->id) ?? $discount;
182    }
183
184    public function delete(int $id): bool
185    {
186        $table = $this->tablePrefix . 'mod_discounts_records';
187        $sql = "UPDATE `{$table}` SET `special_access` = 0, `is_active` = 0 WHERE `id` = :id";
188        $stmt = $this->getPdo()->prepare($sql);
189        $stmt->bindValue(':id', $id, PDO::PARAM_INT);
190
191        return $stmt->execute() && $stmt->rowCount() > 0;
192    }
193
194    /**
195     * Maps database row array to domain DiscountRate aggregate.
196     *
197     * @param array<string, mixed> $row
198     */
199    private function mapRowToEntity(array $row): DiscountRate
200    {
201        return new DiscountRate(
202            id: (int) $row['id'],
203            discountCode: (string) $row['discount_code'],
204            discountName: (string) $row['discount_name'],
205            discountType: DiscountType::from((string) $row['discount_type']),
206            discountValue: (float) $row['discount_value'],
207            isDefault: (bool) (int) $row['is_default'],
208            isActive: (bool) (int) $row['is_active'],
209            owner: (int) ($row['owner'] ?? 1),
210            createdAt: isset($row['created_at']) ? (string) $row['created_at'] : null,
211            updatedAt: isset($row['updated_at']) ? (string) $row['updated_at'] : null,
212        );
213    }
214}