Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 205
0.00% covered (danger)
0.00%
0 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
Note
0.00% covered (danger)
0.00%
0 / 205
0.00% covered (danger)
0.00%
0 / 16
2162
0.00% covered (danger)
0.00%
0 / 1
 populateFromRow
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 getActiveNotes
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
30
 getById
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
 create
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 update
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
 delete
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 togglePin
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 addReaction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 removeReaction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 toggleReaction
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
12
 getReactions
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 addComment
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 deleteComment
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 getComments
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
 getPinnedCount
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 toArray
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace BuyerKiosk\Backstock;
4
5/**
6 * Note Model
7 *
8 * Represents a backstock note for team communication.
9 * Supports pinning, reactions, comments, and expiration dates.
10 */
11class Note extends Backstock
12{
13    public $id;
14    public $authorEmployeeId;
15    public $authorName;
16    public $title;
17    public $content;
18    public $contentHtml;
19    public $isPinned;
20    public $startDate;
21    public $endDate;
22    public $createdAt;
23    public $updatedAt;
24    public $deletedAt;
25
26    // Computed properties
27    public $reactionCount = 0;
28    public $commentCount = 0;
29    public $reactions = [];
30    public $comments = [];
31
32    /**
33     * Populate note from database row
34     *
35     * @param array $row Database row
36     * @return void
37     */
38    public function populateFromRow($row)
39    {
40        $this->id = (int)$row['id'];
41        $this->authorEmployeeId = $row['authorEmployeeId'] ? (int)$row['authorEmployeeId'] : null;
42        $this->authorName = $row['authorName'];
43        $this->title = $row['title'];
44        $this->content = $row['content'];
45        $this->contentHtml = $row['contentHtml'];
46        $this->isPinned = (bool)$row['isPinned'];
47        $this->startDate = $row['startDate'];
48        $this->endDate = $row['endDate'];
49        $this->createdAt = $row['createdAt'];
50        $this->updatedAt = $row['updatedAt'];
51        $this->deletedAt = $row['deletedAt'];
52    }
53
54    /**
55     * Get all active notes
56     *
57     * @param bool $includePinned Include pinned notes first
58     * @param int $limit Maximum notes to return
59     * @param int $offset Pagination offset
60     * @return array Notes
61     */
62    public function getActiveNotes($includePinned = true, $limit = 20, $offset = 0)
63    {
64        try {
65            $sql = "
66                SELECT n.*,
67                       CONCAT(e.employeeFirstName, ' ', e.employeeLastName) as authorFullName,
68                       (SELECT COUNT(*) FROM bsNote_Reactions r WHERE r.noteId = n.id) as reactionCount,
69                       (SELECT COUNT(*) FROM bsNote_Comments c WHERE c.noteId = n.id) as commentCount
70                FROM bsNotes n
71                LEFT JOIN employees e ON n.authorEmployeeId = e.employeeID
72                WHERE n.deletedAt IS NULL
73                AND n.startDate <= CURDATE()
74                AND (n.endDate IS NULL OR n.endDate >= CURDATE())
75            ";
76
77            if ($includePinned) {
78                $sql .= " ORDER BY n.isPinned DESC, n.createdAt DESC";
79            } else {
80                $sql .= " ORDER BY n.createdAt DESC";
81            }
82
83            $sql .= " LIMIT :limit OFFSET :offset";
84
85            $stmt = $this->storeDB->prepare($sql);
86            $stmt->bindValue(':limit', $limit, \PDO::PARAM_INT);
87            $stmt->bindValue(':offset', $offset, \PDO::PARAM_INT);
88            $stmt->execute();
89
90            $notes = [];
91            while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
92                $note = new Note($this->store);
93                $note->populateFromRow($row);
94                $note->authorName = $row['authorFullName'] ?: $row['authorName'];
95                $note->reactionCount = (int)$row['reactionCount'];
96                $note->commentCount = (int)$row['commentCount'];
97                $notes[] = $note;
98            }
99
100            return $notes;
101
102        } catch (\Exception $e) {
103            $this->log->logError("Error getting active notes: " . $e->getMessage());
104            return [];
105        }
106    }
107
108    /**
109     * Get note by ID with reactions and comments
110     *
111     * @param int $noteId Note ID
112     * @return Note|null
113     */
114    public function getById($noteId)
115    {
116        try {
117            $stmt = $this->storeDB->prepare("
118                SELECT n.*,
119                       CONCAT(e.employeeFirstName, ' ', e.employeeLastName) as authorFullName
120                FROM bsNotes n
121                LEFT JOIN employees e ON n.authorEmployeeId = e.employeeID
122                WHERE n.id = :noteId AND n.deletedAt IS NULL
123            ");
124            $stmt->bindValue(':noteId', $noteId, \PDO::PARAM_INT);
125            $stmt->execute();
126
127            $row = $stmt->fetch(\PDO::FETCH_ASSOC);
128            if (!$row) {
129                return null;
130            }
131
132            $note = new Note($this->store);
133            $note->populateFromRow($row);
134            $note->authorName = $row['authorFullName'] ?: $row['authorName'];
135
136            // Load reactions
137            $note->reactions = $this->getReactions($noteId);
138            $note->reactionCount = count($note->reactions);
139
140            // Load comments
141            $note->comments = $this->getComments($noteId);
142            $note->commentCount = count($note->comments);
143
144            return $note;
145
146        } catch (\Exception $e) {
147            $this->log->logError("Error getting note by ID: " . $e->getMessage());
148            return null;
149        }
150    }
151
152    /**
153     * Create a new note
154     *
155     * @param int $employeeId Author employee ID
156     * @param string $employeeName Author name (fallback)
157     * @param string $content Note content
158     * @param string|null $title Optional title
159     * @param bool $isPinned Pin the note
160     * @param string|null $endDate Expiration date
161     * @return int|false New note ID or false
162     */
163    public function create($employeeId, $employeeName, $content, $title = null, $isPinned = false, $endDate = null)
164    {
165        try {
166            $stmt = $this->storeDB->prepare("
167                INSERT INTO bsNotes (authorEmployeeId, authorName, title, content, isPinned, startDate, endDate)
168                VALUES (:employeeId, :employeeName, :title, :content, :isPinned, CURDATE(), :endDate)
169            ");
170            $stmt->bindValue(':employeeId', $employeeId, \PDO::PARAM_INT);
171            $stmt->bindValue(':employeeName', $employeeName, \PDO::PARAM_STR);
172            $stmt->bindValue(':title', $title, \PDO::PARAM_STR);
173            $stmt->bindValue(':content', $content, \PDO::PARAM_STR);
174            $stmt->bindValue(':isPinned', $isPinned ? 1 : 0, \PDO::PARAM_INT);
175            $stmt->bindValue(':endDate', $endDate, \PDO::PARAM_STR);
176
177            if ($stmt->execute()) {
178                return (int)$this->storeDB->lastInsertId();
179            }
180
181            return false;
182
183        } catch (\Exception $e) {
184            $this->log->logError("Error creating note: " . $e->getMessage());
185            return false;
186        }
187    }
188
189    /**
190     * Update note
191     *
192     * @param int $noteId Note ID
193     * @param array $data Fields to update
194     * @return bool Success
195     */
196    public function update($noteId, $data)
197    {
198        try {
199            $allowedFields = ['title', 'content', 'contentHtml', 'isPinned', 'startDate', 'endDate'];
200            $updates = [];
201            $params = [':noteId' => $noteId];
202
203            foreach ($data as $field => $value) {
204                if (in_array($field, $allowedFields)) {
205                    $updates[] = "$field = :$field";
206                    $params[":$field"] = $value;
207                }
208            }
209
210            if (empty($updates)) {
211                return false;
212            }
213
214            $sql = "UPDATE bsNotes SET " . implode(', ', $updates) . " WHERE id = :noteId";
215            $stmt = $this->storeDB->prepare($sql);
216
217            foreach ($params as $key => $value) {
218                $stmt->bindValue($key, $value);
219            }
220
221            return $stmt->execute();
222
223        } catch (\Exception $e) {
224            $this->log->logError("Error updating note: " . $e->getMessage());
225            return false;
226        }
227    }
228
229    /**
230     * Soft delete note
231     *
232     * @param int $noteId Note ID
233     * @return bool Success
234     */
235    public function delete($noteId)
236    {
237        try {
238            $stmt = $this->storeDB->prepare("
239                UPDATE bsNotes SET deletedAt = NOW() WHERE id = :noteId
240            ");
241            $stmt->bindValue(':noteId', $noteId, \PDO::PARAM_INT);
242            return $stmt->execute();
243
244        } catch (\Exception $e) {
245            $this->log->logError("Error deleting note: " . $e->getMessage());
246            return false;
247        }
248    }
249
250    /**
251     * Toggle pin status
252     *
253     * @param int $noteId Note ID
254     * @return bool New pin status
255     */
256    public function togglePin($noteId)
257    {
258        try {
259            $stmt = $this->storeDB->prepare("
260                UPDATE bsNotes SET isPinned = NOT isPinned WHERE id = :noteId
261            ");
262            $stmt->bindValue(':noteId', $noteId, \PDO::PARAM_INT);
263            $stmt->execute();
264
265            // Get new status
266            $stmt = $this->storeDB->prepare("SELECT isPinned FROM bsNotes WHERE id = :noteId");
267            $stmt->bindValue(':noteId', $noteId, \PDO::PARAM_INT);
268            $stmt->execute();
269            $row = $stmt->fetch(\PDO::FETCH_ASSOC);
270
271            return (bool)$row['isPinned'];
272
273        } catch (\Exception $e) {
274            $this->log->logError("Error toggling pin: " . $e->getMessage());
275            return false;
276        }
277    }
278
279    /**
280     * Add reaction to note
281     *
282     * @param int $noteId Note ID
283     * @param int $employeeId Employee ID
284     * @param string $reactionType Reaction type (like, heart)
285     * @return bool Success
286     */
287    public function addReaction($noteId, $employeeId, $reactionType = 'like')
288    {
289        try {
290            $stmt = $this->storeDB->prepare("
291                INSERT IGNORE INTO bsNote_Reactions (noteId, employeeId, reactionType)
292                VALUES (:noteId, :employeeId, :reactionType)
293            ");
294            $stmt->bindValue(':noteId', $noteId, \PDO::PARAM_INT);
295            $stmt->bindValue(':employeeId', $employeeId, \PDO::PARAM_INT);
296            $stmt->bindValue(':reactionType', $reactionType, \PDO::PARAM_STR);
297            return $stmt->execute();
298
299        } catch (\Exception $e) {
300            $this->log->logError("Error adding reaction: " . $e->getMessage());
301            return false;
302        }
303    }
304
305    /**
306     * Remove reaction from note
307     *
308     * @param int $noteId Note ID
309     * @param int $employeeId Employee ID
310     * @param string $reactionType Reaction type
311     * @return bool Success
312     */
313    public function removeReaction($noteId, $employeeId, $reactionType = 'like')
314    {
315        try {
316            $stmt = $this->storeDB->prepare("
317                DELETE FROM bsNote_Reactions
318                WHERE noteId = :noteId AND employeeId = :employeeId AND reactionType = :reactionType
319            ");
320            $stmt->bindValue(':noteId', $noteId, \PDO::PARAM_INT);
321            $stmt->bindValue(':employeeId', $employeeId, \PDO::PARAM_INT);
322            $stmt->bindValue(':reactionType', $reactionType, \PDO::PARAM_STR);
323            return $stmt->execute();
324
325        } catch (\Exception $e) {
326            $this->log->logError("Error removing reaction: " . $e->getMessage());
327            return false;
328        }
329    }
330
331    /**
332     * Toggle reaction (add if not exists, remove if exists)
333     *
334     * @param int $noteId Note ID
335     * @param int $employeeId Employee ID
336     * @param string $reactionType Reaction type
337     * @return array Result with 'action' (added/removed) and 'count'
338     */
339    public function toggleReaction($noteId, $employeeId, $reactionType = 'like')
340    {
341        try {
342            // Check if reaction exists
343            $stmt = $this->storeDB->prepare("
344                SELECT id FROM bsNote_Reactions
345                WHERE noteId = :noteId AND employeeId = :employeeId AND reactionType = :reactionType
346            ");
347            $stmt->bindValue(':noteId', $noteId, \PDO::PARAM_INT);
348            $stmt->bindValue(':employeeId', $employeeId, \PDO::PARAM_INT);
349            $stmt->bindValue(':reactionType', $reactionType, \PDO::PARAM_STR);
350            $stmt->execute();
351
352            $exists = $stmt->fetch(\PDO::FETCH_ASSOC);
353
354            if ($exists) {
355                $this->removeReaction($noteId, $employeeId, $reactionType);
356                $action = 'removed';
357            } else {
358                $this->addReaction($noteId, $employeeId, $reactionType);
359                $action = 'added';
360            }
361
362            // Get new count
363            $stmt = $this->storeDB->prepare("
364                SELECT COUNT(*) as count FROM bsNote_Reactions WHERE noteId = :noteId
365            ");
366            $stmt->bindValue(':noteId', $noteId, \PDO::PARAM_INT);
367            $stmt->execute();
368            $count = (int)$stmt->fetch(\PDO::FETCH_ASSOC)['count'];
369
370            return ['action' => $action, 'count' => $count];
371
372        } catch (\Exception $e) {
373            $this->log->logError("Error toggling reaction: " . $e->getMessage());
374            return ['action' => 'error', 'count' => 0];
375        }
376    }
377
378    /**
379     * Get reactions for a note
380     *
381     * @param int $noteId Note ID
382     * @return array Reactions with employee info
383     */
384    public function getReactions($noteId)
385    {
386        try {
387            $stmt = $this->storeDB->prepare("
388                SELECT r.*, CONCAT(e.employeeFirstName, ' ', e.employeeLastName) as employeeName
389                FROM bsNote_Reactions r
390                LEFT JOIN employees e ON r.employeeId = e.employeeID
391                WHERE r.noteId = :noteId
392                ORDER BY r.createdAt ASC
393            ");
394            $stmt->bindValue(':noteId', $noteId, \PDO::PARAM_INT);
395            $stmt->execute();
396
397            return $stmt->fetchAll(\PDO::FETCH_ASSOC);
398
399        } catch (\Exception $e) {
400            $this->log->logError("Error getting reactions: " . $e->getMessage());
401            return [];
402        }
403    }
404
405    /**
406     * Add comment to note
407     *
408     * @param int $noteId Note ID
409     * @param int $employeeId Employee ID
410     * @param string $employeeName Employee name
411     * @param string $comment Comment text
412     * @return int|false New comment ID or false
413     */
414    public function addComment($noteId, $employeeId, $employeeName, $comment)
415    {
416        try {
417            $stmt = $this->storeDB->prepare("
418                INSERT INTO bsNote_Comments (noteId, employeeId, employeeName, comment)
419                VALUES (:noteId, :employeeId, :employeeName, :comment)
420            ");
421            $stmt->bindValue(':noteId', $noteId, \PDO::PARAM_INT);
422            $stmt->bindValue(':employeeId', $employeeId, \PDO::PARAM_INT);
423            $stmt->bindValue(':employeeName', $employeeName, \PDO::PARAM_STR);
424            $stmt->bindValue(':comment', $comment, \PDO::PARAM_STR);
425
426            if ($stmt->execute()) {
427                return (int)$this->storeDB->lastInsertId();
428            }
429
430            return false;
431
432        } catch (\Exception $e) {
433            $this->log->logError("Error adding comment: " . $e->getMessage());
434            return false;
435        }
436    }
437
438    /**
439     * Delete comment
440     *
441     * @param int $commentId Comment ID
442     * @param int $employeeId Employee ID (for authorization)
443     * @return bool Success
444     */
445    public function deleteComment($commentId, $employeeId)
446    {
447        try {
448            $stmt = $this->storeDB->prepare("
449                DELETE FROM bsNote_Comments
450                WHERE id = :commentId AND employeeId = :employeeId
451            ");
452            $stmt->bindValue(':commentId', $commentId, \PDO::PARAM_INT);
453            $stmt->bindValue(':employeeId', $employeeId, \PDO::PARAM_INT);
454            return $stmt->execute();
455
456        } catch (\Exception $e) {
457            $this->log->logError("Error deleting comment: " . $e->getMessage());
458            return false;
459        }
460    }
461
462    /**
463     * Get comments for a note
464     *
465     * @param int $noteId Note ID
466     * @return array Comments with employee info
467     */
468    public function getComments($noteId)
469    {
470        try {
471            $stmt = $this->storeDB->prepare("
472                SELECT c.*, CONCAT(e.employeeFirstName, ' ', e.employeeLastName) as employeeFullName
473                FROM bsNote_Comments c
474                LEFT JOIN employees e ON c.employeeId = e.employeeID
475                WHERE c.noteId = :noteId
476                ORDER BY c.createdAt ASC
477            ");
478            $stmt->bindValue(':noteId', $noteId, \PDO::PARAM_INT);
479            $stmt->execute();
480
481            $comments = [];
482            while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
483                $row['employeeName'] = $row['employeeFullName'] ?: $row['employeeName'];
484                unset($row['employeeFullName']);
485                $comments[] = $row;
486            }
487
488            return $comments;
489
490        } catch (\Exception $e) {
491            $this->log->logError("Error getting comments: " . $e->getMessage());
492            return [];
493        }
494    }
495
496    /**
497     * Get pinned notes count
498     *
499     * @return int Count of pinned notes
500     */
501    public function getPinnedCount()
502    {
503        try {
504            $stmt = $this->storeDB->query("
505                SELECT COUNT(*) as count FROM bsNotes
506                WHERE deletedAt IS NULL AND isPinned = 1
507                AND startDate <= CURDATE()
508                AND (endDate IS NULL OR endDate >= CURDATE())
509            ");
510            return (int)$stmt->fetch(\PDO::FETCH_ASSOC)['count'];
511
512        } catch (\Exception $e) {
513            $this->log->logError("Error getting pinned count: " . $e->getMessage());
514            return 0;
515        }
516    }
517
518    /**
519     * Convert note to array for JSON
520     *
521     * @return array
522     */
523    public function toArray()
524    {
525        return [
526            'id' => $this->id,
527            'authorEmployeeId' => $this->authorEmployeeId,
528            'authorName' => $this->authorName,
529            'title' => $this->title,
530            'content' => $this->content,
531            'contentHtml' => $this->contentHtml,
532            'isPinned' => $this->isPinned,
533            'startDate' => $this->startDate,
534            'endDate' => $this->endDate,
535            'createdAt' => $this->createdAt,
536            'updatedAt' => $this->updatedAt,
537            'reactionCount' => $this->reactionCount,
538            'commentCount' => $this->commentCount,
539            'reactions' => $this->reactions,
540            'comments' => $this->comments
541        ];
542    }
543}