Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 121
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
BinNamingService
0.00% covered (danger)
0.00%
0 / 121
0.00% covered (danger)
0.00%
0 / 11
1260
0.00% covered (danger)
0.00%
0 / 1
 generateName
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
56
 detectSeason
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 updateGeneratedName
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 generateAllNames
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 updateNotes
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 updateItemCount
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 updateEstimatedValue
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 recordAudit
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
42
 getBinsNeedingAudit
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 searchByNotes
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 getBinDetails
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace BuyerKiosk\Backstock;
4
5/**
6 * BinNamingService
7 *
8 * Provides automatic bin name generation based on category, season, and other properties.
9 * Also handles bin notes, item counts, and audit tracking.
10 */
11class BinNamingService extends Backstock
12{
13    /**
14     * Season patterns for auto-detection
15     */
16    private $seasonPatterns = [
17        'Summer' => ['summer', 'swim', 'sandal', 'tank', 'short', 'beach', 'pool'],
18        'Winter' => ['winter', 'snow', 'coat', 'boot', 'fleece', 'sweater', 'parka'],
19        'Spring' => ['spring', 'easter', 'rain', 'pastel', 'flower'],
20        'Fall' => ['fall', 'autumn', 'halloween', 'harvest', 'thanksgiving'],
21        'Christmas' => ['christmas', 'holiday', 'xmas', 'santa', 'ornament'],
22        'Back to School' => ['school', 'backpack', 'uniform', 'lunchbox']
23    ];
24
25    /**
26     * Generate descriptive name from bin properties
27     *
28     * @param int $binId Bin ID
29     * @return string Generated name
30     */
31    public function generateName($binId)
32    {
33        try {
34            $stmt = $this->storeDB->prepare("
35                SELECT b.id, b.name, b.uuid, b.mainCategory, b.subCat1, b.subCat2, b.subCat3,
36                       c.name as categoryName
37                FROM bsBins b
38                LEFT JOIN bsCategories c ON b.mainCategory = c.id
39                WHERE b.id = :binId
40            ");
41            $stmt->bindValue(':binId', $binId, \PDO::PARAM_INT);
42            $stmt->execute();
43            $bin = $stmt->fetch(\PDO::FETCH_ASSOC);
44
45            if (!$bin) {
46                return null;
47            }
48
49            $parts = [];
50
51            // Detect season from category/subcategories
52            $season = $this->detectSeason($bin);
53            if ($season) {
54                $parts[] = $season;
55            }
56
57            // Add main category name
58            if ($bin['categoryName']) {
59                $parts[] = $bin['categoryName'];
60            } elseif ($bin['mainCategory']) {
61                $parts[] = $bin['mainCategory'];
62            }
63
64            // Add subcategory if present
65            if ($bin['subCat1']) {
66                $parts[] = $bin['subCat1'];
67            }
68
69            // Add bin identifier
70            $parts[] = "#{$bin['name']}";
71
72            return implode(' ', $parts);
73
74        } catch (\Exception $e) {
75            $this->log->logError("Error generating bin name: " . $e->getMessage());
76            return null;
77        }
78    }
79
80    /**
81     * Detect season from bin category/subcategories
82     *
83     * @param array $bin Bin data array
84     * @return string|null Season name or null
85     */
86    private function detectSeason($bin)
87    {
88        $searchText = strtolower(implode(' ', [
89            $bin['categoryName'] ?? '',
90            $bin['mainCategory'] ?? '',
91            $bin['subCat1'] ?? '',
92            $bin['subCat2'] ?? '',
93            $bin['subCat3'] ?? ''
94        ]));
95
96        foreach ($this->seasonPatterns as $season => $patterns) {
97            foreach ($patterns as $pattern) {
98                if (strpos($searchText, $pattern) !== false) {
99                    return $season;
100                }
101            }
102        }
103
104        return null;
105    }
106
107    /**
108     * Update bin's generated name
109     *
110     * @param int $binId Bin ID
111     * @param string $generatedName Generated name
112     * @return bool Success
113     */
114    public function updateGeneratedName($binId, $generatedName)
115    {
116        try {
117            $stmt = $this->storeDB->prepare("
118                UPDATE bsBins SET generatedName = :generatedName WHERE id = :binId
119            ");
120            $stmt->bindValue(':generatedName', $generatedName, \PDO::PARAM_STR);
121            $stmt->bindValue(':binId', $binId, \PDO::PARAM_INT);
122            return $stmt->execute();
123
124        } catch (\Exception $e) {
125            $this->log->logError("Error updating generated name: " . $e->getMessage());
126            return false;
127        }
128    }
129
130    /**
131     * Generate names for all bins missing generated names
132     *
133     * @return int Count of bins updated
134     */
135    public function generateAllNames()
136    {
137        try {
138            $stmt = $this->storeDB->query("
139                SELECT id FROM bsBins
140                WHERE deleted = 0
141                AND (generatedName IS NULL OR generatedName = '')
142            ");
143
144            $count = 0;
145
146            while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
147                $name = $this->generateName($row['id']);
148                if ($name) {
149                    $this->updateGeneratedName($row['id'], $name);
150                    $count++;
151                }
152            }
153
154            return $count;
155
156        } catch (\Exception $e) {
157            $this->log->logError("Error generating all names: " . $e->getMessage());
158            return 0;
159        }
160    }
161
162    /**
163     * Update bin notes
164     *
165     * @param int $binId Bin ID
166     * @param string $notes Notes text
167     * @return bool Success
168     */
169    public function updateNotes($binId, $notes)
170    {
171        try {
172            $stmt = $this->storeDB->prepare("
173                UPDATE bsBins SET notes = :notes WHERE id = :binId
174            ");
175            $stmt->bindValue(':notes', $notes, \PDO::PARAM_STR);
176            $stmt->bindValue(':binId', $binId, \PDO::PARAM_INT);
177            return $stmt->execute();
178
179        } catch (\Exception $e) {
180            $this->log->logError("Error updating bin notes: " . $e->getMessage());
181            return false;
182        }
183    }
184
185    /**
186     * Update bin item count
187     *
188     * @param int $binId Bin ID
189     * @param int $itemCount Item count
190     * @return bool Success
191     */
192    public function updateItemCount($binId, $itemCount)
193    {
194        try {
195            $stmt = $this->storeDB->prepare("
196                UPDATE bsBins SET itemCount = :itemCount WHERE id = :binId
197            ");
198            $stmt->bindValue(':itemCount', $itemCount, \PDO::PARAM_INT);
199            $stmt->bindValue(':binId', $binId, \PDO::PARAM_INT);
200            return $stmt->execute();
201
202        } catch (\Exception $e) {
203            $this->log->logError("Error updating item count: " . $e->getMessage());
204            return false;
205        }
206    }
207
208    /**
209     * Update bin estimated value
210     *
211     * @param int $binId Bin ID
212     * @param float $estimatedValue Estimated value
213     * @return bool Success
214     */
215    public function updateEstimatedValue($binId, $estimatedValue)
216    {
217        try {
218            $stmt = $this->storeDB->prepare("
219                UPDATE bsBins SET estimatedValue = :estimatedValue WHERE id = :binId
220            ");
221            $stmt->bindValue(':estimatedValue', $estimatedValue, \PDO::PARAM_STR);
222            $stmt->bindValue(':binId', $binId, \PDO::PARAM_INT);
223            return $stmt->execute();
224
225        } catch (\Exception $e) {
226            $this->log->logError("Error updating estimated value: " . $e->getMessage());
227            return false;
228        }
229    }
230
231    /**
232     * Record bin audit
233     *
234     * @param int $binId Bin ID
235     * @param int $employeeId Employee who performed audit
236     * @param int|null $itemCount Updated item count (optional)
237     * @param float|null $estimatedValue Updated value (optional)
238     * @param string|null $notes Audit notes (optional)
239     * @return bool Success
240     */
241    public function recordAudit($binId, $employeeId, $itemCount = null, $estimatedValue = null, $notes = null)
242    {
243        try {
244            $updates = ['lastAuditDate = NOW()', 'lastAuditBy = :employeeId'];
245            $params = [':employeeId' => $employeeId, ':binId' => $binId];
246
247            if ($itemCount !== null) {
248                $updates[] = 'itemCount = :itemCount';
249                $params[':itemCount'] = $itemCount;
250            }
251
252            if ($estimatedValue !== null) {
253                $updates[] = 'estimatedValue = :estimatedValue';
254                $params[':estimatedValue'] = $estimatedValue;
255            }
256
257            if ($notes !== null) {
258                $updates[] = 'notes = :notes';
259                $params[':notes'] = $notes;
260            }
261
262            $sql = "UPDATE bsBins SET " . implode(', ', $updates) . " WHERE id = :binId";
263            $stmt = $this->storeDB->prepare($sql);
264
265            foreach ($params as $key => $value) {
266                $stmt->bindValue($key, $value);
267            }
268
269            return $stmt->execute();
270
271        } catch (\Exception $e) {
272            $this->log->logError("Error recording audit: " . $e->getMessage());
273            return false;
274        }
275    }
276
277    /**
278     * Get bins needing audit (not audited in X days)
279     *
280     * @param int $daysSinceAudit Days threshold
281     * @return array Bins needing audit
282     */
283    public function getBinsNeedingAudit($daysSinceAudit = 90)
284    {
285        try {
286            $stmt = $this->storeDB->prepare("
287                SELECT b.id, b.uuid, b.name, b.generatedName, b.mainCategory, b.notes,
288                       b.itemCount, b.estimatedValue, b.lastAuditDate, b.lastAuditBy,
289                       DATEDIFF(NOW(), b.lastAuditDate) as daysSinceAudit,
290                       l.name as locationName
291                FROM bsBins b
292                LEFT JOIN bsLocations l ON b.location = l.id
293                WHERE b.deleted = 0
294                AND (b.lastAuditDate IS NULL OR DATEDIFF(NOW(), b.lastAuditDate) > :days)
295                ORDER BY b.lastAuditDate ASC, b.name ASC
296            ");
297            $stmt->bindValue(':days', $daysSinceAudit, \PDO::PARAM_INT);
298            $stmt->execute();
299
300            return $stmt->fetchAll(\PDO::FETCH_ASSOC);
301
302        } catch (\Exception $e) {
303            $this->log->logError("Error getting bins needing audit: " . $e->getMessage());
304            return [];
305        }
306    }
307
308    /**
309     * Search bins by notes content
310     *
311     * @param string $searchTerm Search term
312     * @return array Matching bins
313     */
314    public function searchByNotes($searchTerm)
315    {
316        try {
317            $stmt = $this->storeDB->prepare("
318                SELECT b.id, b.uuid, b.name, b.generatedName, b.mainCategory, b.notes,
319                       b.itemCount, b.estimatedValue, l.name as locationName
320                FROM bsBins b
321                LEFT JOIN bsLocations l ON b.location = l.id
322                WHERE b.deleted = 0
323                AND MATCH(notes) AGAINST(:searchTerm IN NATURAL LANGUAGE MODE)
324                ORDER BY b.name ASC
325            ");
326            $stmt->bindValue(':searchTerm', $searchTerm, \PDO::PARAM_STR);
327            $stmt->execute();
328
329            return $stmt->fetchAll(\PDO::FETCH_ASSOC);
330
331        } catch (\Exception $e) {
332            $this->log->logError("Error searching by notes: " . $e->getMessage());
333            return [];
334        }
335    }
336
337    /**
338     * Get bin details with enhanced fields
339     *
340     * @param int $binId Bin ID
341     * @return array|null Bin details
342     */
343    public function getBinDetails($binId)
344    {
345        try {
346            $stmt = $this->storeDB->prepare("
347                SELECT b.id, b.uuid, b.name, b.generatedName, b.mainCategory, b.subCat1,
348                       b.subCat2, b.subCat3, b.notes, b.itemCount, b.estimatedValue,
349                       b.lastAuditDate, b.lastAuditBy, b.ageDate, b.location,
350                       DATEDIFF(NOW(), b.ageDate) as age,
351                       l.name as locationName, l.onsite,
352                       c.name as categoryName, c.color as categoryColor,
353                       CONCAT(e.employeeFirstName, ' ', e.employeeLastName) as lastAuditByName
354                FROM bsBins b
355                LEFT JOIN bsLocations l ON b.location = l.id
356                LEFT JOIN bsCategories c ON b.mainCategory = c.name
357                LEFT JOIN employees e ON b.lastAuditBy = e.employeeID
358                WHERE b.id = :binId AND b.deleted = 0
359            ");
360            $stmt->bindValue(':binId', $binId, \PDO::PARAM_INT);
361            $stmt->execute();
362
363            return $stmt->fetch(\PDO::FETCH_ASSOC);
364
365        } catch (\Exception $e) {
366            $this->log->logError("Error getting bin details: " . $e->getMessage());
367            return null;
368        }
369    }
370}