Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 138
0.00% covered (danger)
0.00%
0 / 36
CRAP
0.00% covered (danger)
0.00%
0 / 1
Employee
0.00% covered (danger)
0.00%
0 / 138
0.00% covered (danger)
0.00%
0 / 36
2162
0.00% covered (danger)
0.00%
0 / 1
 fromRow
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
12
 fromArray
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
12
 getId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFirstName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLastName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFullName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEmail
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPhone
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPhotoUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasAvatarOverride
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEmergencyContactName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEmergencyContactPhone
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHireDate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTerminationDate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLeaveStartDate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLeaveEndDate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSource
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExternalId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getClockPin
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasClockPin
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 verifyClockPin
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getDrsEmployeeId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isDailyEmailEnabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPosition
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHourlyRate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRole
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMappedRole
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRoleColor
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getDefaultRoleColor
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 isActive
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLastSyncedAt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCreatedAt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUpdatedAt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isOnLeave
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 isFromExternalProvider
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 toArray
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace BuyerKiosk\Employee;
4
5/**
6 * Enhanced Employee model
7 *
8 * This is the new Employee model for the workbook system with support for:
9 * - External providers (WhenIWork, Homebase)
10 * - Enhanced profile information (photos, emergency contacts)
11 * - Employment lifecycle tracking (hire date, termination, leave)
12 * - Contact information and communication preferences
13 *
14 * Note: This is separate from the legacy Employee class in models/Class/Employee.php
15 */
16class Employee
17{
18    private int $id;
19    private string $firstName;
20    private string $lastName;
21    private ?string $email;
22    private ?string $phone;
23    private ?string $photoUrl;
24    private bool $avatarOverride = false;
25    private ?string $emergencyContactName;
26    private ?string $emergencyContactPhone;
27    private ?string $hireDate;
28    private ?string $terminationDate;
29    private ?string $leaveStartDate;
30    private ?string $leaveEndDate;
31    private string $source;
32    private ?string $externalId;
33    private ?string $clockPin;
34    private ?string $drsEmployeeId;
35    private bool $dailyEmailEnabled;
36    private ?string $position;
37    private ?float $hourlyRate;
38    private int $role;
39    private ?string $mappedRole;
40    private ?string $roleColor;
41    private bool $active;
42    private ?string $lastSyncedAt;
43    private ?string $createdAt;
44    private ?string $updatedAt;
45
46    /**
47     * Create Employee instance from database row
48     *
49     * @param array $row Associative array from database query
50     * @return self New Employee instance
51     */
52    public static function fromRow(array $row): self
53    {
54        $emp = new self();
55        $emp->id = (int) $row['employeeID'];
56        $emp->firstName = $row['employeeFirstName'] ?? '';
57        $emp->lastName = $row['employeeLastName'] ?? '';
58        $emp->email = $row['email'] ?? null;
59        $emp->phone = $row['phone'] ?? null;
60        $emp->photoUrl = $row['photoUrl'] ?? null;
61        $emp->avatarOverride = (bool) ($row['avatarOverride'] ?? false);
62        $emp->emergencyContactName = $row['emergencyContactName'] ?? null;
63        $emp->emergencyContactPhone = $row['emergencyContactPhone'] ?? null;
64        $emp->hireDate = $row['hireDate'] ?? null;
65        $emp->terminationDate = $row['terminationDate'] ?? null;
66        $emp->leaveStartDate = $row['leaveStartDate'] ?? null;
67        $emp->leaveEndDate = $row['leaveEndDate'] ?? null;
68        $emp->source = $row['source'] ?? 'homegrown';
69        $emp->externalId = $row['externalId'] ?? null;
70        $emp->clockPin = $row['clockPin'] ?? null;
71        $emp->drsEmployeeId = $row['drsEmployeeId'] ?? null;
72        $emp->dailyEmailEnabled = (bool) ($row['dailyEmailEnabled'] ?? false);
73        $emp->position = $row['position'] ?? null;
74        $emp->hourlyRate = isset($row['hourlyRate']) && $row['hourlyRate'] !== null ? (float) $row['hourlyRate'] : null;
75        $emp->role = (int) ($row['role'] ?? 4);
76        $emp->mappedRole = $row['mappedRole'] ?? null;
77        $emp->roleColor = $row['roleColor'] ?? null;
78        $emp->active = (bool) ($row['active'] ?? true);
79        $emp->lastSyncedAt = $row['lastSyncedAt'] ?? null;
80        $emp->createdAt = $row['createdAt'] ?? null;
81        $emp->updatedAt = $row['updatedAt'] ?? null;
82        return $emp;
83    }
84
85    /**
86     * Create Employee instance from toArray() output (for cache restoration)
87     *
88     * @param array $data Associative array from toArray()
89     * @return self New Employee instance
90     */
91    public static function fromArray(array $data): self
92    {
93        $emp = new self();
94        $emp->id = (int) $data['id'];
95        $emp->firstName = $data['firstName'] ?? '';
96        $emp->lastName = $data['lastName'] ?? '';
97        $emp->email = $data['email'] ?? null;
98        $emp->phone = $data['phone'] ?? null;
99        $emp->photoUrl = $data['photoUrl'] ?? null;
100        $emp->avatarOverride = (bool) ($data['avatarOverride'] ?? false);
101        $emp->emergencyContactName = $data['emergencyContactName'] ?? null;
102        $emp->emergencyContactPhone = $data['emergencyContactPhone'] ?? null;
103        $emp->hireDate = $data['hireDate'] ?? null;
104        $emp->terminationDate = $data['terminationDate'] ?? null;
105        $emp->leaveStartDate = $data['leaveStartDate'] ?? null;
106        $emp->leaveEndDate = $data['leaveEndDate'] ?? null;
107        $emp->source = $data['source'] ?? 'homegrown';
108        $emp->externalId = $data['externalId'] ?? null;
109        $emp->clockPin = $data['clockPin'] ?? null;
110        $emp->drsEmployeeId = $data['drsEmployeeId'] ?? null;
111        $emp->dailyEmailEnabled = (bool) ($data['dailyEmailEnabled'] ?? false);
112        $emp->position = $data['position'] ?? null;
113        $emp->hourlyRate = isset($data['hourlyRate']) && $data['hourlyRate'] !== null ? (float) $data['hourlyRate'] : null;
114        $emp->role = (int) ($data['role'] ?? 4);
115        $emp->mappedRole = $data['mappedRole'] ?? null;
116        $emp->roleColor = $data['roleColor'] ?? null;
117        $emp->active = (bool) ($data['active'] ?? true);
118        $emp->lastSyncedAt = $data['lastSyncedAt'] ?? null;
119        $emp->createdAt = $data['createdAt'] ?? null;
120        $emp->updatedAt = $data['updatedAt'] ?? null;
121        return $emp;
122    }
123
124    /**
125     * Get employee ID
126     * @return int
127     */
128    public function getId(): int
129    {
130        return $this->id;
131    }
132
133    /**
134     * Get first name
135     * @return string
136     */
137    public function getFirstName(): string
138    {
139        return $this->firstName;
140    }
141
142    /**
143     * Get last name
144     * @return string
145     */
146    public function getLastName(): string
147    {
148        return $this->lastName;
149    }
150
151    /**
152     * Get full name (first + last)
153     * @return string
154     */
155    public function getFullName(): string
156    {
157        return trim($this->firstName . ' ' . $this->lastName);
158    }
159
160    /**
161     * Get email address
162     * @return string|null
163     */
164    public function getEmail(): ?string
165    {
166        return $this->email;
167    }
168
169    /**
170     * Get phone number
171     * @return string|null
172     */
173    public function getPhone(): ?string
174    {
175        return $this->phone;
176    }
177
178    /**
179     * Get photo URL
180     * @return string|null
181     */
182    public function getPhotoUrl(): ?string
183    {
184        return $this->photoUrl;
185    }
186
187    /**
188     * Check if avatar is overridden (custom upload that should not be replaced by sync)
189     * @return bool
190     */
191    public function hasAvatarOverride(): bool
192    {
193        return $this->avatarOverride;
194    }
195
196    /**
197     * Get emergency contact name
198     * @return string|null
199     */
200    public function getEmergencyContactName(): ?string
201    {
202        return $this->emergencyContactName;
203    }
204
205    /**
206     * Get emergency contact phone
207     * @return string|null
208     */
209    public function getEmergencyContactPhone(): ?string
210    {
211        return $this->emergencyContactPhone;
212    }
213
214    /**
215     * Get hire date
216     * @return string|null
217     */
218    public function getHireDate(): ?string
219    {
220        return $this->hireDate;
221    }
222
223    /**
224     * Get termination date
225     * @return string|null
226     */
227    public function getTerminationDate(): ?string
228    {
229        return $this->terminationDate;
230    }
231
232    /**
233     * Get leave start date
234     * @return string|null
235     */
236    public function getLeaveStartDate(): ?string
237    {
238        return $this->leaveStartDate;
239    }
240
241    /**
242     * Get leave end date
243     * @return string|null
244     */
245    public function getLeaveEndDate(): ?string
246    {
247        return $this->leaveEndDate;
248    }
249
250    /**
251     * Get employee source (homegrown, wheniwork, homebase)
252     * @return string
253     */
254    public function getSource(): string
255    {
256        return $this->source;
257    }
258
259    /**
260     * Get external provider ID (WhenIWork or Homebase user ID)
261     * @return string|null
262     */
263    public function getExternalId(): ?string
264    {
265        return $this->externalId;
266    }
267
268    /**
269     * Get clock PIN (for time clock verification)
270     * @return string|null
271     */
272    public function getClockPin(): ?string
273    {
274        return $this->clockPin;
275    }
276
277    /**
278     * Check if employee has a clock PIN set
279     * @return bool
280     */
281    public function hasClockPin(): bool
282    {
283        return !empty($this->clockPin);
284    }
285
286    /**
287     * Verify a clock PIN against the stored PIN
288     * @param string $pin The PIN to verify
289     * @return bool True if PIN matches
290     */
291    public function verifyClockPin(string $pin): bool
292    {
293        if (empty($this->clockPin)) {
294            return false;
295        }
296        return $this->clockPin === $pin;
297    }
298
299    /**
300     * Get DRS employee ID
301     * @return string|null
302     */
303    public function getDrsEmployeeId(): ?string
304    {
305        return $this->drsEmployeeId;
306    }
307
308    /**
309     * Check if daily email is enabled
310     * @return bool
311     */
312    public function isDailyEmailEnabled(): bool
313    {
314        return $this->dailyEmailEnabled;
315    }
316
317    /**
318     * Get position/job title
319     * @return string|null
320     */
321    public function getPosition(): ?string
322    {
323        return $this->position;
324    }
325
326    /**
327     * Get hourly rate
328     * @return float|null
329     */
330    public function getHourlyRate(): ?float
331    {
332        return $this->hourlyRate;
333    }
334
335    /**
336     * Get role (permission level)
337     * @return int
338     */
339    public function getRole(): int
340    {
341        return $this->role;
342    }
343
344    /**
345     * Get mapped role (buyer, sorter, manager, shift_lead, associate)
346     * @return string|null
347     */
348    public function getMappedRole(): ?string
349    {
350        return $this->mappedRole;
351    }
352
353    /**
354     * Get role color (computed from mappedRole if not explicitly set)
355     * @return string|null Hex color code
356     */
357    public function getRoleColor(): ?string
358    {
359        // If roleColor is explicitly set, use that
360        if ($this->roleColor) {
361            return $this->roleColor;
362        }
363
364        // Otherwise, compute from mappedRole
365        return $this->getDefaultRoleColor($this->mappedRole);
366    }
367
368    /**
369     * Get default color for a given role
370     *
371     * @param string|null $role The role name
372     * @return string|null Hex color code
373     */
374    private function getDefaultRoleColor(?string $role): ?string
375    {
376        if (!$role) {
377            return null;
378        }
379
380        $colorMap = [
381            'buyer' => '#3b82f6',       // blue
382            'sorter' => '#22c55e',      // green
383            'manager' => '#7c3aed',     // purple
384            'shift_lead' => '#f59e0b',  // amber
385            'associate' => '#64748b',   // grey
386        ];
387
388        return $colorMap[$role] ?? null;
389    }
390
391    /**
392     * Check if employee is active
393     * @return bool
394     */
395    public function isActive(): bool
396    {
397        return $this->active;
398    }
399
400    /**
401     * Get last synced timestamp
402     * @return string|null
403     */
404    public function getLastSyncedAt(): ?string
405    {
406        return $this->lastSyncedAt;
407    }
408
409    /**
410     * Get created timestamp
411     * @return string|null
412     */
413    public function getCreatedAt(): ?string
414    {
415        return $this->createdAt;
416    }
417
418    /**
419     * Get updated timestamp
420     * @return string|null
421     */
422    public function getUpdatedAt(): ?string
423    {
424        return $this->updatedAt;
425    }
426
427    /**
428     * Check if employee is currently on leave
429     *
430     * @return bool True if employee is on leave today
431     */
432    public function isOnLeave(): bool
433    {
434        if (!$this->leaveStartDate) {
435            return false;
436        }
437
438        $today = date('Y-m-d');
439        $started = $this->leaveStartDate <= $today;
440        $notEnded = !$this->leaveEndDate || $this->leaveEndDate >= $today;
441
442        return $started && $notEnded;
443    }
444
445    /**
446     * Check if employee was created from external provider
447     *
448     * @return bool True if from WhenIWork or Homebase
449     */
450    public function isFromExternalProvider(): bool
451    {
452        return in_array($this->source, ['wheniwork', 'homebase']);
453    }
454
455    /**
456     * Convert to array for API responses
457     *
458     * @return array Associative array of all employee data
459     */
460    public function toArray(): array
461    {
462        return [
463            'id' => $this->id,
464            'firstName' => $this->firstName,
465            'lastName' => $this->lastName,
466            'fullName' => $this->getFullName(),
467            'email' => $this->email,
468            'phone' => $this->phone,
469            'photoUrl' => $this->photoUrl,
470            'avatarOverride' => $this->avatarOverride,
471            'emergencyContactName' => $this->emergencyContactName,
472            'emergencyContactPhone' => $this->emergencyContactPhone,
473            'hireDate' => $this->hireDate,
474            'terminationDate' => $this->terminationDate,
475            'leaveStartDate' => $this->leaveStartDate,
476            'leaveEndDate' => $this->leaveEndDate,
477            'source' => $this->source,
478            'externalId' => $this->externalId,
479            'hasClockPin' => $this->hasClockPin(),
480            'drsEmployeeId' => $this->drsEmployeeId,
481            'dailyEmailEnabled' => $this->dailyEmailEnabled,
482            'position' => $this->position,
483            'hourlyRate' => $this->hourlyRate,
484            'role' => $this->role,
485            'mappedRole' => $this->mappedRole,
486            'roleColor' => $this->getRoleColor(), // Use getter to compute default if needed
487            'active' => $this->active,
488            'isOnLeave' => $this->isOnLeave(),
489            'isFromExternalProvider' => $this->isFromExternalProvider(),
490        ];
491    }
492}