Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 129
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
EditSuggestion
0.00% covered (danger)
0.00%
0 / 129
0.00% covered (danger)
0.00%
0 / 10
756
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
 save
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
72
 approve
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 reject
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 toArray
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 getPendingForArticle
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 getPendingAll
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 getAllForArticle
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 hydrateResults
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 getPendingCountForArticle
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace BuyerKiosk\Support;
4
5use \PDO;
6use \PDOException;
7
8/**
9 * EditSuggestion - Edit suggestion model (store-level)
10 *
11 * Manages employee suggestions for article improvements with review workflow.
12 * Stored in individual store databases for multi-tenant isolation.
13 */
14class EditSuggestion
15{
16    /**
17     * @var int|null Suggestion ID
18     */
19    public $id;
20
21    /**
22     * @var int Article ID (from kiosk_buykiosk.support_articles)
23     */
24    public $articleId;
25
26    /**
27     * @var int Employee ID who made the suggestion
28     */
29    public $employeeId;
30
31    /**
32     * @var string|null Employee name (for display)
33     */
34    public $employeeName;
35
36    /**
37     * @var string Suggested markdown content
38     */
39    public $suggestedContentMd;
40
41    /**
42     * @var string|null Note explaining the suggestion
43     */
44    public $suggestionNote;
45
46    /**
47     * @var string Status (pending, approved, rejected)
48     */
49    public $status;
50
51    /**
52     * @var int|null Reviewer employee ID
53     */
54    public $reviewerId;
55
56    /**
57     * @var string|null Reviewer note/feedback
58     */
59    public $reviewerNote;
60
61    /**
62     * @var string|null Review timestamp
63     */
64    public $reviewedAt;
65
66    /**
67     * @var string Created timestamp
68     */
69    public $createdAt;
70
71    /**
72     * @var PDO Database connection (store DB)
73     */
74    private $db;
75
76    /**
77     * Constructor
78     *
79     * @param PDO $db Store database connection
80     */
81    public function __construct(PDO $db)
82    {
83        $this->db = $db;
84    }
85
86    /**
87     * Save edit suggestion (insert or update)
88     *
89     * Uses INSERT or UPDATE based on whether ID is set
90     *
91     * @return bool True if successful
92     */
93    public function save(): bool
94    {
95        if (!isset($this->articleId) || !isset($this->employeeId) || !isset($this->suggestedContentMd)) {
96            error_log("EditSuggestion::save error: articleId, employeeId, and suggestedContentMd are required");
97            return false;
98        }
99
100        // Set defaults
101        if (!isset($this->status)) {
102            $this->status = 'pending';
103        }
104
105        try {
106            if ($this->id) {
107                // Update existing suggestion
108                $stmt = $this->db->prepare("
109                    UPDATE support_edit_suggestions
110                    SET suggested_content_md = :suggested_content_md,
111                        suggestion_note = :suggestion_note,
112                        status = :status,
113                        reviewer_id = :reviewer_id,
114                        reviewer_note = :reviewer_note,
115                        reviewed_at = :reviewed_at
116                    WHERE id = :id
117                ");
118
119                return $stmt->execute([
120                    ':id' => $this->id,
121                    ':suggested_content_md' => $this->suggestedContentMd,
122                    ':suggestion_note' => $this->suggestionNote,
123                    ':status' => $this->status,
124                    ':reviewer_id' => $this->reviewerId,
125                    ':reviewer_note' => $this->reviewerNote,
126                    ':reviewed_at' => $this->reviewedAt
127                ]);
128            } else {
129                // Insert new suggestion
130                $stmt = $this->db->prepare("
131                    INSERT INTO support_edit_suggestions
132                    (article_id, employee_id, suggested_content_md, suggestion_note, status)
133                    VALUES
134                    (:article_id, :employee_id, :suggested_content_md, :suggestion_note, :status)
135                ");
136
137                $result = $stmt->execute([
138                    ':article_id' => $this->articleId,
139                    ':employee_id' => $this->employeeId,
140                    ':suggested_content_md' => $this->suggestedContentMd,
141                    ':suggestion_note' => $this->suggestionNote,
142                    ':status' => $this->status
143                ]);
144
145                if ($result) {
146                    $this->id = (int) $this->db->lastInsertId();
147                }
148
149                return $result;
150            }
151        } catch (PDOException $e) {
152            error_log("EditSuggestion::save error: " . $e->getMessage());
153            return false;
154        }
155    }
156
157    /**
158     * Approve edit suggestion
159     *
160     * Sets status to 'approved' and records reviewer information
161     *
162     * @param int $reviewerId Reviewer employee ID
163     * @param string|null $reviewerNote Optional feedback note
164     * @return bool True if successful
165     */
166    public function approve(int $reviewerId, ?string $reviewerNote = null): bool
167    {
168        if (!isset($this->id)) {
169            error_log("EditSuggestion::approve error: id is required");
170            return false;
171        }
172
173        try {
174            $this->status = 'approved';
175            $this->reviewerId = $reviewerId;
176            $this->reviewerNote = $reviewerNote;
177            $this->reviewedAt = date('Y-m-d H:i:s');
178
179            $stmt = $this->db->prepare("
180                UPDATE support_edit_suggestions
181                SET status = 'approved',
182                    reviewer_id = :reviewer_id,
183                    reviewer_note = :reviewer_note,
184                    reviewed_at = :reviewed_at
185                WHERE id = :id
186            ");
187
188            return $stmt->execute([
189                ':id' => $this->id,
190                ':reviewer_id' => $this->reviewerId,
191                ':reviewer_note' => $this->reviewerNote,
192                ':reviewed_at' => $this->reviewedAt
193            ]);
194        } catch (PDOException $e) {
195            error_log("EditSuggestion::approve error: " . $e->getMessage());
196            return false;
197        }
198    }
199
200    /**
201     * Reject edit suggestion
202     *
203     * Sets status to 'rejected' and records reviewer information
204     *
205     * @param int $reviewerId Reviewer employee ID
206     * @param string|null $reviewerNote Optional feedback note
207     * @return bool True if successful
208     */
209    public function reject(int $reviewerId, ?string $reviewerNote = null): bool
210    {
211        if (!isset($this->id)) {
212            error_log("EditSuggestion::reject error: id is required");
213            return false;
214        }
215
216        try {
217            $this->status = 'rejected';
218            $this->reviewerId = $reviewerId;
219            $this->reviewerNote = $reviewerNote;
220            $this->reviewedAt = date('Y-m-d H:i:s');
221
222            $stmt = $this->db->prepare("
223                UPDATE support_edit_suggestions
224                SET status = 'rejected',
225                    reviewer_id = :reviewer_id,
226                    reviewer_note = :reviewer_note,
227                    reviewed_at = :reviewed_at
228                WHERE id = :id
229            ");
230
231            return $stmt->execute([
232                ':id' => $this->id,
233                ':reviewer_id' => $this->reviewerId,
234                ':reviewer_note' => $this->reviewerNote,
235                ':reviewed_at' => $this->reviewedAt
236            ]);
237        } catch (PDOException $e) {
238            error_log("EditSuggestion::reject error: " . $e->getMessage());
239            return false;
240        }
241    }
242
243    /**
244     * Convert EditSuggestion to array for API responses
245     *
246     * @return array Associative array representation
247     */
248    public function toArray(): array
249    {
250        return [
251            'id' => $this->id,
252            'articleId' => $this->articleId,
253            'employeeId' => $this->employeeId,
254            'employeeName' => $this->employeeName,
255            'suggestedContentMd' => $this->suggestedContentMd,
256            'suggestionNote' => $this->suggestionNote,
257            'status' => $this->status,
258            'reviewerId' => $this->reviewerId,
259            'reviewerNote' => $this->reviewerNote,
260            'reviewedAt' => $this->reviewedAt,
261            'createdAt' => $this->createdAt
262        ];
263    }
264
265    /**
266     * Get pending edit suggestions for a specific article
267     *
268     * @param PDO $db Store database connection
269     * @param int $articleId Article ID
270     * @return array Array of EditSuggestion objects
271     */
272    public static function getPendingForArticle(PDO $db, int $articleId): array
273    {
274        try {
275            $stmt = $db->prepare("
276                SELECT
277                    es.*,
278                    e.employeeFirstName,
279                    e.employeeLastName
280                FROM support_edit_suggestions es
281                LEFT JOIN employees e ON es.employee_id = e.employeeID
282                WHERE es.article_id = :article_id AND es.status = 'pending'
283                ORDER BY es.created_at DESC
284            ");
285
286            $stmt->execute([':article_id' => $articleId]);
287
288            return self::hydrateResults($db, $stmt->fetchAll(PDO::FETCH_ASSOC));
289        } catch (PDOException $e) {
290            error_log("EditSuggestion::getPendingForArticle error: " . $e->getMessage());
291            return [];
292        }
293    }
294
295    /**
296     * Get all pending edit suggestions across all articles
297     *
298     * @param PDO $db Store database connection
299     * @param int $limit Maximum number of suggestions to return
300     * @return array Array of EditSuggestion objects
301     */
302    public static function getPendingAll(PDO $db, int $limit = 50): array
303    {
304        try {
305            $stmt = $db->prepare("
306                SELECT
307                    es.*,
308                    e.employeeFirstName,
309                    e.employeeLastName
310                FROM support_edit_suggestions es
311                LEFT JOIN employees e ON es.employee_id = e.employeeID
312                WHERE es.status = 'pending'
313                ORDER BY es.created_at DESC
314                LIMIT :limit
315            ");
316
317            $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
318            $stmt->execute();
319
320            return self::hydrateResults($db, $stmt->fetchAll(PDO::FETCH_ASSOC));
321        } catch (PDOException $e) {
322            error_log("EditSuggestion::getPendingAll error: " . $e->getMessage());
323            return [];
324        }
325    }
326
327    /**
328     * Get all edit suggestions for a specific article (any status)
329     *
330     * @param PDO $db Store database connection
331     * @param int $articleId Article ID
332     * @return array Array of EditSuggestion objects
333     */
334    public static function getAllForArticle(PDO $db, int $articleId): array
335    {
336        try {
337            $stmt = $db->prepare("
338                SELECT
339                    es.*,
340                    e.employeeFirstName as suggesterFirstName,
341                    e.employeeLastName as suggesterLastName,
342                    r.employeeFirstName as reviewerFirstName,
343                    r.employeeLastName as reviewerLastName
344                FROM support_edit_suggestions es
345                LEFT JOIN employees e ON es.employee_id = e.employeeID
346                LEFT JOIN employees r ON es.reviewer_id = r.employeeID
347                WHERE es.article_id = :article_id
348                ORDER BY es.created_at DESC
349            ");
350
351            $stmt->execute([':article_id' => $articleId]);
352
353            return self::hydrateResults($db, $stmt->fetchAll(PDO::FETCH_ASSOC));
354        } catch (PDOException $e) {
355            error_log("EditSuggestion::getAllForArticle error: " . $e->getMessage());
356            return [];
357        }
358    }
359
360    /**
361     * Hydrate EditSuggestion objects from database rows
362     *
363     * @param PDO $db Store database connection
364     * @param array $rows Database rows
365     * @return array Array of EditSuggestion objects
366     */
367    private static function hydrateResults(PDO $db, array $rows): array
368    {
369        $suggestions = [];
370
371        foreach ($rows as $row) {
372            $suggestion = new self($db);
373            $suggestion->id = (int) $row['id'];
374            $suggestion->articleId = (int) $row['article_id'];
375            $suggestion->employeeId = (int) $row['employee_id'];
376
377            // Build employee name from employee record
378            $firstName = $row['suggesterFirstName'] ?? $row['employeeFirstName'] ?? '';
379            $lastName = $row['suggesterLastName'] ?? $row['employeeLastName'] ?? '';
380            $suggestion->employeeName = trim($firstName . ' ' . $lastName);
381
382            $suggestion->suggestedContentMd = $row['suggested_content_md'];
383            $suggestion->suggestionNote = $row['suggestion_note'] ?? null;
384            $suggestion->status = $row['status'];
385            $suggestion->reviewerId = isset($row['reviewer_id']) ? (int) $row['reviewer_id'] : null;
386            $suggestion->reviewerNote = $row['reviewer_note'] ?? null;
387            $suggestion->reviewedAt = $row['reviewed_at'] ?? null;
388            $suggestion->createdAt = $row['created_at'];
389
390            $suggestions[] = $suggestion;
391        }
392
393        return $suggestions;
394    }
395
396    /**
397     * Get count of pending suggestions for an article
398     *
399     * @param PDO $db Store database connection
400     * @param int $articleId Article ID
401     * @return int Count of pending suggestions
402     */
403    public static function getPendingCountForArticle(PDO $db, int $articleId): int
404    {
405        try {
406            $stmt = $db->prepare("
407                SELECT COUNT(*) FROM support_edit_suggestions
408                WHERE article_id = :article_id AND status = 'pending'
409            ");
410
411            $stmt->execute([':article_id' => $articleId]);
412
413            return (int) $stmt->fetchColumn();
414        } catch (PDOException $e) {
415            error_log("EditSuggestion::getPendingCountForArticle error: " . $e->getMessage());
416            return 0;
417        }
418    }
419}