Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 106
0.00% covered (danger)
0.00%
0 / 24
CRAP
0.00% covered (danger)
0.00%
0 / 1
WorkbookAbly
0.00% covered (danger)
0.00%
0 / 106
0.00% covered (danger)
0.00%
0 / 24
930
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 publish
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 taskCompleted
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 taskUncompleted
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 taskProgress
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 taskComment
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 taskAssigned
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 noteCreated
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 noteUpdated
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 noteDeleted
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 noteReaction
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 noteComment
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 whiteboardSave
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 whiteboardClear
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 whiteboardStickyAdd
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 whiteboardStickyMove
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 whiteboardStickyEdit
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 whiteboardStickyDelete
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 scheduleRefresh
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 kpiRefresh
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 employeeClockedIn
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 employeeClockedOut
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 employeeBreakStarted
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 employeeBreakEnded
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2namespace BuyerKiosk\Workbook;
3
4/**
5 * WorkbookAbly - Real-time event publishing for Workbook
6 *
7 * Publishes events to Ably for real-time synchronization across
8 * clients viewing the Workbook dashboard.
9 *
10 * @package BuyerKiosk\Workbook
11 */
12class WorkbookAbly
13{
14    /**
15     * @var \Ably\AblyRest Ably REST client instance
16     */
17    private $ably;
18
19    /**
20     * @var string Store identifier (used as channel name)
21     */
22    private $typeNum;
23
24    /**
25     * @var bool Whether Ably is enabled and available
26     */
27    private $enabled;
28
29    /**
30     * Constructor
31     *
32     * @param string $typeNum Store identifier (e.g., 'ou00', 'pa00')
33     */
34    public function __construct(string $typeNum)
35    {
36        $this->typeNum = $typeNum;
37        $this->enabled = false;
38
39        // Check if Ably key is configured
40        if (!isset($_ENV['ABLY_KEY']) || empty($_ENV['ABLY_KEY'])) {
41            error_log("WorkbookAbly: ABLY_KEY not configured, real-time events disabled");
42            return;
43        }
44
45        try {
46            $this->ably = new \Ably\AblyRest($_ENV['ABLY_KEY']);
47            $this->enabled = true;
48        } catch (\Exception $e) {
49            error_log("WorkbookAbly: Failed to initialize Ably - " . $e->getMessage());
50            $this->enabled = false;
51        }
52    }
53
54    /**
55     * Publish an event to the Ably channel
56     *
57     * @param string $action Event action name
58     * @param array $data Event data payload
59     * @return void
60     */
61    public function publish(string $action, array $data): void
62    {
63        if (!$this->enabled) {
64            return;
65        }
66
67        try {
68            $payload = array_merge($data, [
69                'action' => $action,
70                'category' => $this->typeNum,
71                'timestamp' => time(),
72                'source' => 'workbook'
73            ]);
74
75            $channel = $this->ably->channel($this->typeNum);
76            $channel->publish($action, $payload);
77        } catch (\Exception $e) {
78            error_log("WorkbookAbly: Failed to publish event '{$action}' - " . $e->getMessage());
79        }
80    }
81
82    // === Task Events ===
83
84    /**
85     * Publish task completed event
86     *
87     * @param int $taskId Task ID
88     * @param int|null $employeeId Employee who completed the task
89     * @param string $date Date the task was completed (YYYY-MM-DD)
90     * @return void
91     */
92    public function taskCompleted(int $taskId, ?int $employeeId, string $date): void
93    {
94        $this->publish('workbook:task:complete', [
95            'taskId' => $taskId,
96            'employeeId' => $employeeId,
97            'date' => $date
98        ]);
99    }
100
101    /**
102     * Publish task uncompleted event
103     *
104     * @param int $taskId Task ID
105     * @param string $date Date the task was uncompleted (YYYY-MM-DD)
106     * @return void
107     */
108    public function taskUncompleted(int $taskId, string $date): void
109    {
110        $this->publish('workbook:task:uncomplete', [
111            'taskId' => $taskId,
112            'date' => $date
113        ]);
114    }
115
116    /**
117     * Publish task progress update event
118     *
119     * @param int $taskId Task ID
120     * @param int|null $employeeId Employee who updated progress
121     * @param string $date Date of the progress update (YYYY-MM-DD)
122     * @return void
123     */
124    public function taskProgress(int $taskId, ?int $employeeId, string $date): void
125    {
126        $this->publish('workbook:task:progress', [
127            'taskId' => $taskId,
128            'employeeId' => $employeeId,
129            'date' => $date
130        ]);
131    }
132
133    /**
134     * Publish task comment event
135     *
136     * @param int $taskId Task ID
137     * @param int $commentId Comment ID
138     * @param int $employeeId Employee who created the comment
139     * @param string $comment Comment text
140     * @return void
141     */
142    public function taskComment(int $taskId, int $commentId, int $employeeId, string $comment): void
143    {
144        $this->publish('workbook:task:comment', [
145            'taskId' => $taskId,
146            'commentId' => $commentId,
147            'employeeId' => $employeeId,
148            'comment' => $comment
149        ]);
150    }
151
152    /**
153     * Publish task assigned event
154     *
155     * @param int $taskId Task ID
156     * @param int|null $employeeId Employee assigned to the task
157     * @param string $dueDate Due date for the task (YYYY-MM-DD)
158     * @return void
159     */
160    public function taskAssigned(int $taskId, ?int $employeeId, string $dueDate): void
161    {
162        $this->publish('workbook:task:assign', [
163            'taskId' => $taskId,
164            'employeeId' => $employeeId,
165            'dueDate' => $dueDate
166        ]);
167    }
168
169    // === Note Events ===
170
171    /**
172     * Publish note created event
173     *
174     * @param int $noteId Note ID
175     * @param array $noteData Note data (content, employeeId, priority, etc.)
176     * @return void
177     */
178    public function noteCreated(int $noteId, array $noteData): void
179    {
180        $this->publish('workbook:note:create', array_merge(['noteId' => $noteId], $noteData));
181    }
182
183    /**
184     * Publish note updated event
185     *
186     * @param int $noteId Note ID
187     * @param array $noteData Updated note data
188     * @return void
189     */
190    public function noteUpdated(int $noteId, array $noteData): void
191    {
192        $this->publish('workbook:note:update', array_merge(['noteId' => $noteId], $noteData));
193    }
194
195    /**
196     * Publish note deleted event
197     *
198     * @param int $noteId Note ID
199     * @return void
200     */
201    public function noteDeleted(int $noteId): void
202    {
203        $this->publish('workbook:note:delete', [
204            'noteId' => $noteId
205        ]);
206    }
207
208    /**
209     * Publish note reaction event
210     *
211     * @param int $noteId Note ID
212     * @param int $employeeId Employee who reacted
213     * @param string $reaction Reaction type (emoji or identifier)
214     * @param bool $added True if reaction added, false if removed
215     * @return void
216     */
217    public function noteReaction(int $noteId, int $employeeId, string $reaction, bool $added): void
218    {
219        $action = $added ? 'workbook:note:react' : 'workbook:note:unreact';
220        $this->publish($action, [
221            'noteId' => $noteId,
222            'employeeId' => $employeeId,
223            'reaction' => $reaction
224        ]);
225    }
226
227    /**
228     * Publish note comment event
229     *
230     * @param int $noteId Note ID
231     * @param int $commentId Comment ID
232     * @param int $employeeId Employee who created the comment
233     * @param string $comment Comment text
234     * @return void
235     */
236    public function noteComment(int $noteId, int $commentId, int $employeeId, string $comment): void
237    {
238        $this->publish('workbook:note:comment', [
239            'noteId' => $noteId,
240            'commentId' => $commentId,
241            'employeeId' => $employeeId,
242            'comment' => $comment
243        ]);
244    }
245
246    // === Whiteboard Events ===
247
248    /**
249     * Publish whiteboard save event
250     *
251     * @param int $employeeId Employee who saved the whiteboard
252     * @return void
253     */
254    public function whiteboardSave(int $employeeId): void
255    {
256        $this->publish('workbook:whiteboard:save', [
257            'employeeId' => $employeeId
258        ]);
259    }
260
261    /**
262     * Publish whiteboard clear event
263     *
264     * @param int $employeeId Employee who cleared the whiteboard
265     * @return void
266     */
267    public function whiteboardClear(int $employeeId): void
268    {
269        $this->publish('workbook:whiteboard:clear', [
270            'employeeId' => $employeeId
271        ]);
272    }
273
274    /**
275     * Publish whiteboard sticky add event
276     *
277     * @param int $itemId Sticky item ID
278     * @param array $itemData Sticky data (content, color, position, etc.)
279     * @return void
280     */
281    public function whiteboardStickyAdd(int $itemId, array $itemData): void
282    {
283        $this->publish('workbook:whiteboard:sticky:add', array_merge(['itemId' => $itemId], $itemData));
284    }
285
286    /**
287     * Publish whiteboard sticky move event
288     *
289     * @param int $itemId Sticky item ID
290     * @param int $positionX X coordinate
291     * @param int $positionY Y coordinate
292     * @return void
293     */
294    public function whiteboardStickyMove(int $itemId, int $positionX, int $positionY): void
295    {
296        $this->publish('workbook:whiteboard:sticky:move', [
297            'itemId' => $itemId,
298            'positionX' => $positionX,
299            'positionY' => $positionY
300        ]);
301    }
302
303    /**
304     * Publish whiteboard sticky edit event
305     *
306     * @param int $itemId Sticky item ID
307     * @param array $itemData Updated sticky data
308     * @return void
309     */
310    public function whiteboardStickyEdit(int $itemId, array $itemData): void
311    {
312        $this->publish('workbook:whiteboard:sticky:edit', array_merge(['itemId' => $itemId], $itemData));
313    }
314
315    /**
316     * Publish whiteboard sticky delete event
317     *
318     * @param int $itemId Sticky item ID
319     * @return void
320     */
321    public function whiteboardStickyDelete(int $itemId): void
322    {
323        $this->publish('workbook:whiteboard:sticky:delete', [
324            'itemId' => $itemId
325        ]);
326    }
327
328    // === Schedule/KPI Events ===
329
330    /**
331     * Publish schedule refresh event
332     *
333     * @return void
334     */
335    public function scheduleRefresh(): void
336    {
337        $this->publish('workbook:schedule:refresh', []);
338    }
339
340    /**
341     * Publish KPI refresh event
342     *
343     * @return void
344     */
345    public function kpiRefresh(): void
346    {
347        $this->publish('workbook:kpi:refresh', []);
348    }
349
350    // === Time Punch Events ===
351
352    /**
353     * Publish employee clocked in event
354     *
355     * @param int $employeeId Employee ID
356     * @param string $employeeName Employee full name
357     * @param string|null $startTime Clock in time
358     * @return void
359     */
360    public function employeeClockedIn(int $employeeId, string $employeeName, ?string $startTime = null): void
361    {
362        $this->publish('workbook:timepunch:clockin', [
363            'employeeId' => $employeeId,
364            'employeeName' => $employeeName,
365            'startTime' => $startTime,
366            'isClockedIn' => true
367        ]);
368    }
369
370    /**
371     * Publish employee clocked out event
372     *
373     * @param int $employeeId Employee ID
374     * @param string $employeeName Employee full name
375     * @param string|null $endTime Clock out time
376     * @return void
377     */
378    public function employeeClockedOut(int $employeeId, string $employeeName, ?string $endTime = null): void
379    {
380        $this->publish('workbook:timepunch:clockout', [
381            'employeeId' => $employeeId,
382            'employeeName' => $employeeName,
383            'endTime' => $endTime,
384            'isClockedIn' => false
385        ]);
386    }
387
388    /**
389     * Publish employee break started event
390     *
391     * @param int $employeeId Employee ID
392     * @param string $employeeName Employee full name
393     * @param string|null $startTime Break start time
394     * @return void
395     */
396    public function employeeBreakStarted(int $employeeId, string $employeeName, ?string $startTime = null): void
397    {
398        $this->publish('workbook:timepunch:breakstart', [
399            'employeeId' => $employeeId,
400            'employeeName' => $employeeName,
401            'startTime' => $startTime,
402            'onBreak' => true
403        ]);
404    }
405
406    /**
407     * Publish employee break ended event
408     *
409     * @param int $employeeId Employee ID
410     * @param string $employeeName Employee full name
411     * @param string|null $endTime Break end time
412     * @return void
413     */
414    public function employeeBreakEnded(int $employeeId, string $employeeName, ?string $endTime = null): void
415    {
416        $this->publish('workbook:timepunch:breakend', [
417            'employeeId' => $employeeId,
418            'employeeName' => $employeeName,
419            'endTime' => $endTime,
420            'onBreak' => false
421        ]);
422    }
423}