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 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
PositionRepository
0.00% covered (danger)
0.00%
0 / 102
0.00% covered (danger)
0.00%
0 / 12
552
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
 findById
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 findAll
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 findActive
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 findByName
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 / 19
0.00% covered (danger)
0.00%
0 / 1
6
 update
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 deactivate
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 reactivate
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 updateSortOrders
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 nameExists
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 count
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace BuyerKiosk\Scheduling\Repositories;
4
5use BuyerKiosk\Scheduling\Models\Position;
6use PDO;
7
8/**
9 * PositionRepository
10 *
11 * Data access layer for store positions.
12 * Handles CRUD operations for scheduling positions.
13 *
14 * @package BuyerKiosk\Scheduling\Repositories
15 */
16class PositionRepository
17{
18    private PDO $db;
19
20    public function __construct(PDO $db)
21    {
22        $this->db = $db;
23    }
24
25    /**
26     * Find a position by ID
27     *
28     * @param int $positionId Position ID
29     * @return Position|null
30     */
31    public function findById(int $positionId): ?Position
32    {
33        $stmt = $this->db->prepare("
34            SELECT * FROM schedulePositions
35            WHERE positionId = :positionId
36        ");
37        $stmt->bindValue(':positionId', $positionId, PDO::PARAM_INT);
38        $stmt->execute();
39
40        $row = $stmt->fetch(PDO::FETCH_ASSOC);
41        if (!$row) {
42            return null;
43        }
44
45        return Position::fromRow($row);
46    }
47
48    /**
49     * Find all positions
50     *
51     * @return Position[]
52     */
53    public function findAll(): array
54    {
55        $stmt = $this->db->prepare("
56            SELECT * FROM schedulePositions
57            ORDER BY sortOrder ASC, name ASC
58        ");
59        $stmt->execute();
60
61        $positions = [];
62        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
63            $positions[] = Position::fromRow($row);
64        }
65
66        return $positions;
67    }
68
69    /**
70     * Find only active positions
71     *
72     * @return Position[]
73     */
74    public function findActive(): array
75    {
76        $stmt = $this->db->prepare("
77            SELECT * FROM schedulePositions
78            WHERE isActive = 1
79            ORDER BY sortOrder ASC, name ASC
80        ");
81        $stmt->execute();
82
83        $positions = [];
84        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
85            $positions[] = Position::fromRow($row);
86        }
87
88        return $positions;
89    }
90
91    /**
92     * Find a position by name (case-insensitive)
93     *
94     * @param string $name Position name
95     * @return Position|null
96     */
97    public function findByName(string $name): ?Position
98    {
99        $stmt = $this->db->prepare("
100            SELECT * FROM schedulePositions
101            WHERE LOWER(name) = LOWER(:name)
102        ");
103        $stmt->bindValue(':name', $name);
104        $stmt->execute();
105
106        $row = $stmt->fetch(PDO::FETCH_ASSOC);
107        if (!$row) {
108            return null;
109        }
110
111        return Position::fromRow($row);
112    }
113
114    /**
115     * Create a new position
116     *
117     * @param Position $position Position to create
118     * @return Position Created position with ID
119     */
120    public function create(Position $position): Position
121    {
122        $data = $position->toDbArray();
123
124        // Get next sort order if not specified
125        if ($data['sortOrder'] === 0) {
126            $stmt = $this->db->prepare("
127                SELECT COALESCE(MAX(sortOrder), 0) + 1 as nextSort
128                FROM schedulePositions
129            ");
130            $stmt->execute();
131            $result = $stmt->fetch(PDO::FETCH_ASSOC);
132            $data['sortOrder'] = (int)$result['nextSort'];
133        }
134
135        $stmt = $this->db->prepare("
136            INSERT INTO schedulePositions (name, color, sortOrder, isActive, createdByUserId)
137            VALUES (:name, :color, :sortOrder, :isActive, :createdByUserId)
138        ");
139
140        $stmt->bindValue(':name', $data['name']);
141        $stmt->bindValue(':color', $data['color']);
142        $stmt->bindValue(':sortOrder', $data['sortOrder'], PDO::PARAM_INT);
143        $stmt->bindValue(':isActive', $data['isActive'], PDO::PARAM_INT);
144        $stmt->bindValue(':createdByUserId', $data['createdByUserId'], PDO::PARAM_INT);
145
146        $stmt->execute();
147
148        $positionId = (int)$this->db->lastInsertId();
149        $position->setPositionId($positionId);
150        $position->setSortOrder($data['sortOrder']);
151
152        return $position;
153    }
154
155    /**
156     * Update an existing position
157     *
158     * @param Position $position Position to update
159     * @return Position Updated position
160     */
161    public function update(Position $position): Position
162    {
163        if ($position->getPositionId() === null) {
164            throw new \InvalidArgumentException('Cannot update position without positionId');
165        }
166
167        $data = $position->toDbArray();
168
169        $stmt = $this->db->prepare("
170            UPDATE schedulePositions
171            SET name = :name,
172                color = :color,
173                sortOrder = :sortOrder,
174                isActive = :isActive
175            WHERE positionId = :positionId
176        ");
177
178        $stmt->bindValue(':positionId', $position->getPositionId(), PDO::PARAM_INT);
179        $stmt->bindValue(':name', $data['name']);
180        $stmt->bindValue(':color', $data['color']);
181        $stmt->bindValue(':sortOrder', $data['sortOrder'], PDO::PARAM_INT);
182        $stmt->bindValue(':isActive', $data['isActive'], PDO::PARAM_INT);
183
184        $stmt->execute();
185
186        return $this->findById($position->getPositionId()) ?? $position;
187    }
188
189    /**
190     * Deactivate a position (soft delete)
191     *
192     * @param int $positionId Position ID
193     * @return bool Success status
194     */
195    public function deactivate(int $positionId): bool
196    {
197        $stmt = $this->db->prepare("
198            UPDATE schedulePositions
199            SET isActive = 0
200            WHERE positionId = :positionId
201        ");
202        $stmt->bindValue(':positionId', $positionId, PDO::PARAM_INT);
203        $stmt->execute();
204
205        return $stmt->rowCount() > 0;
206    }
207
208    /**
209     * Reactivate a position
210     *
211     * @param int $positionId Position ID
212     * @return bool Success status
213     */
214    public function reactivate(int $positionId): bool
215    {
216        $stmt = $this->db->prepare("
217            UPDATE schedulePositions
218            SET isActive = 1
219            WHERE positionId = :positionId
220        ");
221        $stmt->bindValue(':positionId', $positionId, PDO::PARAM_INT);
222        $stmt->execute();
223
224        return $stmt->rowCount() > 0;
225    }
226
227    /**
228     * Update sort orders for multiple positions
229     *
230     * @param array<int, int> $sortOrders Map of positionId => sortOrder
231     * @return bool Success status
232     */
233    public function updateSortOrders(array $sortOrders): bool
234    {
235        $this->db->beginTransaction();
236
237        try {
238            $stmt = $this->db->prepare("
239                UPDATE schedulePositions
240                SET sortOrder = :sortOrder
241                WHERE positionId = :positionId
242            ");
243
244            foreach ($sortOrders as $positionId => $sortOrder) {
245                $stmt->bindValue(':positionId', $positionId, PDO::PARAM_INT);
246                $stmt->bindValue(':sortOrder', $sortOrder, PDO::PARAM_INT);
247                $stmt->execute();
248            }
249
250            $this->db->commit();
251            return true;
252        } catch (\Exception $e) {
253            $this->db->rollBack();
254            throw $e;
255        }
256    }
257
258    /**
259     * Check if a position name already exists (excluding a specific position)
260     *
261     * @param string $name Position name to check
262     * @param int|null $excludePositionId Position ID to exclude from check
263     * @return bool True if name exists
264     */
265    public function nameExists(string $name, ?int $excludePositionId = null): bool
266    {
267        $sql = "
268            SELECT COUNT(*) as count
269            FROM schedulePositions
270            WHERE LOWER(name) = LOWER(:name)
271        ";
272
273        if ($excludePositionId !== null) {
274            $sql .= " AND positionId != :excludePositionId";
275        }
276
277        $stmt = $this->db->prepare($sql);
278        $stmt->bindValue(':name', $name);
279
280        if ($excludePositionId !== null) {
281            $stmt->bindValue(':excludePositionId', $excludePositionId, PDO::PARAM_INT);
282        }
283
284        $stmt->execute();
285        $result = $stmt->fetch(PDO::FETCH_ASSOC);
286
287        return (int)$result['count'] > 0;
288    }
289
290    /**
291     * Get position count
292     *
293     * @param bool $activeOnly Only count active positions
294     * @return int
295     */
296    public function count(bool $activeOnly = false): int
297    {
298        $sql = "SELECT COUNT(*) as count FROM schedulePositions";
299
300        if ($activeOnly) {
301            $sql .= " WHERE isActive = 1";
302        }
303
304        $stmt = $this->db->prepare($sql);
305        $stmt->execute();
306        $result = $stmt->fetch(PDO::FETCH_ASSOC);
307
308        return (int)$result['count'];
309    }
310}