Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 102
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
StoreHolidayRepository
0.00% covered (danger)
0.00%
0 / 102
0.00% covered (danger)
0.00%
0 / 9
702
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 findByTypeNum
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 findByDate
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 findById
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 create
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
20
 update
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
132
 delete
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 deleteByTypeNum
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 formatRow
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace BuyerKiosk\StoreConfig\Repositories;
4
5use DateTimeInterface;
6use PDO;
7
8/**
9 * StoreHolidayRepository
10 *
11 * Data access layer for store holiday hours (storeHolidayHours table).
12 * Holiday overrides take precedence over both default and day-of-week hours.
13 *
14 * @package BuyerKiosk\StoreConfig\Repositories
15 */
16class StoreHolidayRepository
17{
18    private PDO $db;
19
20    public function __construct(PDO $db)
21    {
22        $this->db = $db;
23    }
24
25    /**
26     * Find all holiday hours for a store
27     *
28     * @param string $typeNum Store identifier
29     * @param bool $futureOnly Only return future holidays (default: false)
30     * @return array<array{id: int, typeNum: string, date: string, name: string|null, openTime: string|null, closeTime: string|null, isClosed: bool}>
31     */
32    public function findByTypeNum(string $typeNum, bool $futureOnly = false): array
33    {
34        $sql = "
35            SELECT id, typeNum, holidayDate, holidayName, openTime, closeTime, isClosed, created_at, updated_at
36            FROM storeHolidayHours
37            WHERE typeNum = :typeNum
38        ";
39
40        if ($futureOnly) {
41            $sql .= " AND holidayDate >= CURDATE()";
42        }
43
44        $sql .= " ORDER BY holidayDate ASC";
45
46        $stmt = $this->db->prepare($sql);
47        $stmt->bindValue(':typeNum', $typeNum);
48        $stmt->execute();
49
50        $holidays = [];
51        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
52            $holidays[] = $this->formatRow($row);
53        }
54
55        return $holidays;
56    }
57
58    /**
59     * Find a holiday by specific date
60     *
61     * @param string $typeNum Store identifier
62     * @param DateTimeInterface $date The date to check
63     * @return array{id: int, typeNum: string, date: string, name: string|null, openTime: string|null, closeTime: string|null, isClosed: bool}|null
64     */
65    public function findByDate(string $typeNum, DateTimeInterface $date): ?array
66    {
67        $stmt = $this->db->prepare("
68            SELECT id, typeNum, holidayDate, holidayName, openTime, closeTime, isClosed, created_at, updated_at
69            FROM storeHolidayHours
70            WHERE typeNum = :typeNum AND holidayDate = :date
71            LIMIT 1
72        ");
73        $stmt->bindValue(':typeNum', $typeNum);
74        $stmt->bindValue(':date', $date->format('Y-m-d'));
75        $stmt->execute();
76
77        $row = $stmt->fetch(PDO::FETCH_ASSOC);
78        if (!$row) {
79            return null;
80        }
81
82        return $this->formatRow($row);
83    }
84
85    /**
86     * Find a holiday by ID
87     *
88     * @param int $id Holiday ID
89     * @return array{id: int, typeNum: string, date: string, name: string|null, openTime: string|null, closeTime: string|null, isClosed: bool}|null
90     */
91    public function findById(int $id): ?array
92    {
93        $stmt = $this->db->prepare("
94            SELECT id, typeNum, holidayDate, holidayName, openTime, closeTime, isClosed, created_at, updated_at
95            FROM storeHolidayHours
96            WHERE id = :id
97            LIMIT 1
98        ");
99        $stmt->bindValue(':id', $id, PDO::PARAM_INT);
100        $stmt->execute();
101
102        $row = $stmt->fetch(PDO::FETCH_ASSOC);
103        if (!$row) {
104            return null;
105        }
106
107        return $this->formatRow($row);
108    }
109
110    /**
111     * Create a new holiday hour record
112     *
113     * @param string $typeNum Store identifier
114     * @param array{date: string, name?: string|null, openTime?: string|null, closeTime?: string|null, isClosed?: bool} $data
115     * @return array{id: int, typeNum: string, date: string, name: string|null, openTime: string|null, closeTime: string|null, isClosed: bool}
116     * @throws \RuntimeException If a holiday already exists for this date
117     */
118    public function create(string $typeNum, array $data): array
119    {
120        if (!isset($data['date'])) {
121            throw new \InvalidArgumentException('date is required');
122        }
123
124        // Check for duplicate date
125        $checkStmt = $this->db->prepare("
126            SELECT id FROM storeHolidayHours
127            WHERE typeNum = :typeNum AND holidayDate = :date
128        ");
129        $checkStmt->bindValue(':typeNum', $typeNum);
130        $checkStmt->bindValue(':date', $data['date']);
131        $checkStmt->execute();
132
133        if ($checkStmt->fetch()) {
134            throw new \RuntimeException('DUPLICATE_DATE: A holiday already exists for this date');
135        }
136
137        $stmt = $this->db->prepare("
138            INSERT INTO storeHolidayHours (typeNum, holidayDate, holidayName, openTime, closeTime, isClosed)
139            VALUES (:typeNum, :holidayDate, :holidayName, :openTime, :closeTime, :isClosed)
140        ");
141
142        $stmt->bindValue(':typeNum', $typeNum);
143        $stmt->bindValue(':holidayDate', $data['date']);
144        $stmt->bindValue(':holidayName', $data['name'] ?? null);
145        $stmt->bindValue(':openTime', $data['openTime'] ?? null);
146        $stmt->bindValue(':closeTime', $data['closeTime'] ?? null);
147        $stmt->bindValue(':isClosed', (int) ($data['isClosed'] ?? false), PDO::PARAM_INT);
148
149        $stmt->execute();
150
151        $id = (int) $this->db->lastInsertId();
152        $created = $this->findById($id);
153        if (!$created) {
154            throw new \RuntimeException('Failed to load created holiday record');
155        }
156
157        return $created;
158    }
159
160    /**
161     * Update an existing holiday hour record
162     *
163     * @param int $id Holiday ID
164     * @param array{name?: string|null, openTime?: string|null, closeTime?: string|null, isClosed?: bool} $data
165     * @return array{id: int, typeNum: string, date: string, name: string|null, openTime: string|null, closeTime: string|null, isClosed: bool}
166     * @throws \RuntimeException If holiday not found
167     */
168    public function update(int $id, array $data): array
169    {
170        // Check if holiday exists
171        $existing = $this->findById($id);
172        if (!$existing) {
173            throw new \RuntimeException('NOT_FOUND: Holiday not found');
174        }
175
176        // Build update statement dynamically based on provided fields
177        $updates = [];
178        $params = [':id' => $id];
179
180        if (array_key_exists('name', $data)) {
181            $updates[] = 'holidayName = :holidayName';
182            $params[':holidayName'] = $data['name'];
183        }
184
185        if (array_key_exists('openTime', $data)) {
186            $updates[] = 'openTime = :openTime';
187            $params[':openTime'] = $data['openTime'];
188        }
189
190        if (array_key_exists('closeTime', $data)) {
191            $updates[] = 'closeTime = :closeTime';
192            $params[':closeTime'] = $data['closeTime'];
193        }
194
195        if (array_key_exists('isClosed', $data)) {
196            $updates[] = 'isClosed = :isClosed';
197            $params[':isClosed'] = (int) $data['isClosed'];
198        }
199
200        if (empty($updates)) {
201            // Nothing to update
202            return $existing;
203        }
204
205        $sql = "UPDATE storeHolidayHours SET " . implode(', ', $updates) . " WHERE id = :id";
206        $stmt = $this->db->prepare($sql);
207
208        foreach ($params as $key => $value) {
209            if ($key === ':isClosed') {
210                $stmt->bindValue($key, $value, PDO::PARAM_INT);
211            } elseif ($key === ':id') {
212                $stmt->bindValue($key, $value, PDO::PARAM_INT);
213            } else {
214                $stmt->bindValue($key, $value);
215            }
216        }
217
218        $stmt->execute();
219
220        $updated = $this->findById($id);
221        if (!$updated) {
222            throw new \RuntimeException('Failed to load updated holiday record');
223        }
224
225        return $updated;
226    }
227
228    /**
229     * Delete a holiday hour record
230     *
231     * @param int $id Holiday ID
232     * @return bool True if deleted, false if not found
233     */
234    public function delete(int $id): bool
235    {
236        $stmt = $this->db->prepare("DELETE FROM storeHolidayHours WHERE id = :id");
237        $stmt->bindValue(':id', $id, PDO::PARAM_INT);
238        $stmt->execute();
239
240        return $stmt->rowCount() > 0;
241    }
242
243    /**
244     * Delete all holiday hours for a store
245     *
246     * @param string $typeNum Store identifier
247     * @return int Number of rows deleted
248     */
249    public function deleteByTypeNum(string $typeNum): int
250    {
251        $stmt = $this->db->prepare("DELETE FROM storeHolidayHours WHERE typeNum = :typeNum");
252        $stmt->bindValue(':typeNum', $typeNum);
253        $stmt->execute();
254        return $stmt->rowCount();
255    }
256
257    /**
258     * Format a database row into the standard return format
259     *
260     * @param array $row Database row
261     * @return array{id: int, typeNum: string, date: string, name: string|null, openTime: string|null, closeTime: string|null, isClosed: bool}
262     */
263    private function formatRow(array $row): array
264    {
265        return [
266            'id' => (int) $row['id'],
267            'typeNum' => $row['typeNum'],
268            'date' => $row['holidayDate'],
269            'name' => $row['holidayName'],
270            'openTime' => $row['openTime'],
271            'closeTime' => $row['closeTime'],
272            'isClosed' => (bool) $row['isClosed'],
273        ];
274    }
275}