Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 75
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
BuyerKioskSchedule
0.00% covered (danger)
0.00%
0 / 75
0.00% covered (danger)
0.00%
0 / 7
342
0.00% covered (danger)
0.00%
0 / 1
 isEnabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getProviderName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getScheduleForDate
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 getEmployeeShift
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 syncScheduleToCache
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 fetchShiftsFromDatabase
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 formatShifts
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3namespace BuyerKiosk\Scheduling;
4
5use BuyerKiosk\Workbook\ScheduleProvider;
6use Exception;
7
8/**
9 * BuyerKiosk Native Schedule Provider
10 *
11 * Provides schedule management using the native BuyerKiosk scheduling system.
12 * Data is stored in scheduleShifts, scheduleTimePunches, and scheduleTimesheets tables.
13 *
14 * @package BuyerKiosk\Scheduling
15 */
16class BuyerKioskSchedule extends ScheduleProvider
17{
18    /**
19     * Provider name identifier
20     */
21    private const PROVIDER_NAME = 'buyerkiosk';
22
23    /**
24     * Check if BuyerKiosk scheduling is enabled for this store
25     *
26     * @return bool True if enabled
27     */
28    public function isEnabled(): bool
29    {
30        return $this->store->getSchedulingProvider() === self::PROVIDER_NAME;
31    }
32
33    /**
34     * Get provider name
35     *
36     * @return string Provider identifier
37     */
38    public function getProviderName(): string
39    {
40        return self::PROVIDER_NAME;
41    }
42
43    /**
44     * Get schedule for a specific date
45     *
46     * @param \DateTime $date Date to retrieve schedule for
47     * @return array Array of formatted shift data
48     */
49    public function getScheduleForDate(\DateTime $date): array
50    {
51        if (!$this->isEnabled()) {
52            return [];
53        }
54
55        // Check cache first
56        $cached = $this->getFromCache($date);
57        if ($cached !== null) {
58            return $cached;
59        }
60
61        try {
62            // Fetch from database
63            $shifts = $this->fetchShiftsFromDatabase($date);
64            $formatted = $this->formatShifts($shifts);
65
66            // Save to cache
67            $this->saveToCache($date, $formatted);
68
69            return $formatted;
70        } catch (Exception $e) {
71            error_log("BuyerKioskSchedule::getScheduleForDate error: " . $e->getMessage());
72            return [];
73        }
74    }
75
76    /**
77     * Get a specific employee's shift for a date
78     *
79     * @param int $employeeId Local employee ID
80     * @param \DateTime $date Date to check
81     * @return array|null Shift data or null if not scheduled
82     */
83    public function getEmployeeShift(int $employeeId, \DateTime $date): ?array
84    {
85        $schedule = $this->getScheduleForDate($date);
86
87        foreach ($schedule as $shift) {
88            if ($shift['employeeId'] == $employeeId) {
89                return $shift;
90            }
91        }
92
93        return null;
94    }
95
96    /**
97     * Sync schedule data to Redis cache
98     *
99     * For BuyerKiosk provider, this clears and refreshes the cache from database
100     *
101     * @param \DateTime $date Date to sync
102     * @return bool Success status
103     */
104    public function syncScheduleToCache(\DateTime $date): bool
105    {
106        if (!$this->isEnabled()) {
107            return false;
108        }
109
110        try {
111            // Clear existing cache
112            $this->clearCache($date);
113
114            // Fetch fresh data from database
115            $shifts = $this->fetchShiftsFromDatabase($date);
116            $formatted = $this->formatShifts($shifts);
117
118            // Save to cache
119            $this->saveToCache($date, $formatted);
120
121            return true;
122        } catch (Exception $e) {
123            error_log("BuyerKioskSchedule::syncScheduleToCache error: " . $e->getMessage());
124            return false;
125        }
126    }
127
128    /**
129     * Fetch shifts from database for a specific date
130     *
131     * @param \DateTime $date Date to fetch shifts for
132     * @return array Raw shift data from database
133     */
134    private function fetchShiftsFromDatabase(\DateTime $date): array
135    {
136        $storeTimezone = new \DateTimeZone($this->store->getTimeZone() ?: 'America/Chicago');
137        $utc = new \DateTimeZone('UTC');
138
139        // Interpret the requested date in the store timezone and query using UTC bounds.
140        $local = clone $date;
141        $local->setTimezone($storeTimezone);
142
143        $dayStartLocal = clone $local;
144        $dayStartLocal->setTime(0, 0, 0);
145        $dayEndLocal = (clone $dayStartLocal)->modify('+1 day');
146
147        $dayStartUtc = clone $dayStartLocal;
148        $dayStartUtc->setTimezone($utc);
149        $dayEndUtc = clone $dayEndLocal;
150        $dayEndUtc->setTimezone($utc);
151
152        $stmt = $this->getDb()->prepare("
153            SELECT
154                s.shiftId,
155                s.employeeId,
156                s.positionId,
157                s.notes,
158                s.shiftStart,
159                s.shiftEnd,
160                u.firstName AS employeeFirstName,
161                u.lastName AS employeeLastName,
162                p.name AS positionName,
163                p.color AS positionColor
164            FROM scheduleShifts s
165            INNER JOIN kiosk_users.users u ON s.employeeId = u.id
166            LEFT JOIN schedulePositions p ON s.positionId = p.positionId
167            WHERE s.shiftStart >= :startUtc
168              AND s.shiftStart < :endUtc
169              AND s.deleted_at IS NULL
170              AND u.enabled = 1
171            ORDER BY s.shiftStart ASC, u.lastName ASC
172        ");
173        $stmt->bindValue(':startUtc', $dayStartUtc->format('Y-m-d H:i:s'));
174        $stmt->bindValue(':endUtc', $dayEndUtc->format('Y-m-d H:i:s'));
175        $stmt->execute();
176
177        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
178    }
179
180    /**
181     * Format shifts from database format to panel-compatible format
182     *
183     * Returns the same format as WhenIWorkSchedule for panel compatibility
184     *
185     * @param array $shifts Raw shifts from database
186     * @return array Formatted shifts
187     */
188    private function formatShifts(array $shifts): array
189    {
190        $formatted = [];
191        $storeTimezone = new \DateTimeZone($this->store->getTimeZone() ?: 'America/Chicago');
192        $utc = new \DateTimeZone('UTC');
193
194        foreach ($shifts as $shift) {
195            $shiftStartUtc = new \DateTime($shift['shiftStart'], $utc);
196            $shiftEndUtc = new \DateTime($shift['shiftEnd'], $utc);
197
198            $shiftStartLocal = clone $shiftStartUtc;
199            $shiftStartLocal->setTimezone($storeTimezone);
200            $shiftEndLocal = clone $shiftEndUtc;
201            $shiftEndLocal->setTimezone($storeTimezone);
202
203            $formatted[] = [
204                'shiftId' => (int)$shift['shiftId'],
205                'employeeId' => (int)$shift['employeeId'],
206                'providerEmployeeId' => (string)$shift['employeeId'],
207                'firstName' => $shift['employeeFirstName'],
208                'lastName' => $shift['employeeLastName'],
209                // Match WhenIWorkSchedule output format (store-local strings for display)
210                'shiftStart' => $shiftStartLocal->format('Y-m-d H:i:s'),
211                'shiftEnd' => $shiftEndLocal->format('Y-m-d H:i:s'),
212                'startTime' => $shiftStartLocal->format('g:ia'),
213                'endTime' => $shiftEndLocal->format('g:ia'),
214                'position' => $shift['positionName'],
215                'positionId' => $shift['positionId'] ? (int)$shift['positionId'] : null,
216                'positionColor' => $shift['positionColor'],
217                'breakMinutes' => 0,
218                'notes' => $shift['notes'],
219                'version' => 1,
220                'provider' => self::PROVIDER_NAME
221            ];
222        }
223
224        return $formatted;
225    }
226}