Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 119
0.00% covered (danger)
0.00%
0 / 39
CRAP
0.00% covered (danger)
0.00%
0 / 1
TimePunch
0.00% covered (danger)
0.00%
0 / 119
0.00% covered (danger)
0.00%
0 / 39
2862
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 fromRow
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
156
 getPunchId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEmployeeId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getShiftId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPunchType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPunchTime
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBreakType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isManualEntry
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getManualEntryNote
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isUnscheduled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isManagerOverride
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getApprovedByUserId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getApprovedAt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getApprovalNote
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEnteredByUserId
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
 getEditedAt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEditedByUserId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEditNote
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEmployeeName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setPunchId
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setShiftId
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setPunchType
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setPunchTime
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setBreakType
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setIsManualEntry
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setManualEntryNote
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setIsUnscheduled
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setIsManagerOverride
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setApproval
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 setEdit
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 setEmployeeName
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 isClockIn
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isClockOut
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isBreakPunch
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasBeenEdited
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 toDbArray
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
 jsonSerialize
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace BuyerKiosk\Scheduling\Models;
4
5use DateTime;
6use DateTimeInterface;
7use DateTimeZone;
8use JsonSerializable;
9
10/**
11 * TimePunch Entity Model
12 *
13 * Represents a time punch from the scheduleTimePunches table.
14 * All times are stored in UTC.
15 *
16 * @package BuyerKiosk\Scheduling\Models
17 */
18class TimePunch implements JsonSerializable
19{
20    public const TYPE_CLOCK_IN = 'clockIn';
21    public const TYPE_CLOCK_OUT = 'clockOut';
22    public const TYPE_BREAK_START = 'breakStart';
23    public const TYPE_BREAK_END = 'breakEnd';
24
25    public const BREAK_TYPE_PAID = 'paid';
26    public const BREAK_TYPE_UNPAID = 'unpaid';
27
28    private ?int $punchId = null;
29    private int $employeeId;
30    private ?int $shiftId = null;
31    private string $punchType;
32    private DateTime $punchTime;
33    private ?string $breakType = null;
34    private bool $isManualEntry = false;
35    private ?string $manualEntryNote = null;
36    private bool $isUnscheduled = false;
37    private bool $isManagerOverride = false;
38    private ?int $approvedByUserId = null;
39    private ?DateTime $approvedAt = null;
40    private ?string $approvalNote = null;
41    private int $enteredByUserId;
42    private ?DateTime $createdAt = null;
43    private ?DateTime $editedAt = null;
44    private ?int $editedByUserId = null;
45    private ?string $editNote = null;
46
47    // Hydrated from JOINs (not persisted)
48    private ?string $employeeName = null;
49
50    /**
51     * Create a new TimePunch instance
52     *
53     * @param int $employeeId Employee ID
54     * @param string $punchType Punch type (clockIn, clockOut, breakStart, breakEnd)
55     * @param DateTime $punchTime Punch time (UTC)
56     * @param int $enteredByUserId User ID who entered the punch
57     */
58    public function __construct(
59        int $employeeId,
60        string $punchType,
61        DateTime $punchTime,
62        int $enteredByUserId
63    ) {
64        $this->employeeId = $employeeId;
65        $this->punchType = $punchType;
66        $this->punchTime = $punchTime;
67        $this->enteredByUserId = $enteredByUserId;
68    }
69
70    /**
71     * Create TimePunch from database row
72     *
73     * @param array $row Database row
74     * @return self
75     */
76    public static function fromRow(array $row): self
77    {
78        $utc = new DateTimeZone('UTC');
79
80        $punch = new self(
81            (int)$row['employeeId'],
82            $row['punchType'],
83            new DateTime($row['punchTime'], $utc),
84            (int)$row['enteredByUserId']
85        );
86
87        $punch->punchId = isset($row['punchId']) ? (int)$row['punchId'] : null;
88        $punch->shiftId = isset($row['shiftId']) ? (int)$row['shiftId'] : null;
89        $punch->breakType = $row['breakType'] ?? null;
90        $punch->isManualEntry = (bool)($row['isManualEntry'] ?? false);
91        $punch->manualEntryNote = $row['manualEntryNote'] ?? null;
92        $punch->isUnscheduled = (bool)($row['isUnscheduled'] ?? false);
93        $punch->isManagerOverride = (bool)($row['isManagerOverride'] ?? false);
94        $punch->approvedByUserId = isset($row['approvedByUserId']) ? (int)$row['approvedByUserId'] : null;
95        $punch->approvalNote = $row['approvalNote'] ?? null;
96        $punch->editedByUserId = isset($row['editedByUserId']) ? (int)$row['editedByUserId'] : null;
97        $punch->editNote = $row['editNote'] ?? null;
98
99        if (isset($row['approved_at']) && $row['approved_at'] !== null) {
100            $punch->approvedAt = new DateTime($row['approved_at'], $utc);
101        }
102        if (isset($row['created_at'])) {
103            $punch->createdAt = new DateTime($row['created_at'], $utc);
104        }
105        if (isset($row['edited_at']) && $row['edited_at'] !== null) {
106            $punch->editedAt = new DateTime($row['edited_at'], $utc);
107        }
108
109        // Hydrate joined fields if present
110        if (isset($row['employeeName'])) {
111            $punch->employeeName = $row['employeeName'];
112        } elseif (isset($row['employeeFirstName'], $row['employeeLastName'])) {
113            $punch->employeeName = trim($row['employeeFirstName'] . ' ' . $row['employeeLastName']);
114        }
115
116        return $punch;
117    }
118
119    // Getters
120
121    public function getPunchId(): ?int
122    {
123        return $this->punchId;
124    }
125
126    public function getEmployeeId(): int
127    {
128        return $this->employeeId;
129    }
130
131    public function getShiftId(): ?int
132    {
133        return $this->shiftId;
134    }
135
136    public function getPunchType(): string
137    {
138        return $this->punchType;
139    }
140
141    public function getPunchTime(): DateTime
142    {
143        return $this->punchTime;
144    }
145
146    public function getBreakType(): ?string
147    {
148        return $this->breakType;
149    }
150
151    public function isManualEntry(): bool
152    {
153        return $this->isManualEntry;
154    }
155
156    public function getManualEntryNote(): ?string
157    {
158        return $this->manualEntryNote;
159    }
160
161    public function isUnscheduled(): bool
162    {
163        return $this->isUnscheduled;
164    }
165
166    public function isManagerOverride(): bool
167    {
168        return $this->isManagerOverride;
169    }
170
171    public function getApprovedByUserId(): ?int
172    {
173        return $this->approvedByUserId;
174    }
175
176    public function getApprovedAt(): ?DateTime
177    {
178        return $this->approvedAt;
179    }
180
181    public function getApprovalNote(): ?string
182    {
183        return $this->approvalNote;
184    }
185
186    public function getEnteredByUserId(): int
187    {
188        return $this->enteredByUserId;
189    }
190
191    public function getCreatedAt(): ?DateTime
192    {
193        return $this->createdAt;
194    }
195
196    public function getEditedAt(): ?DateTime
197    {
198        return $this->editedAt;
199    }
200
201    public function getEditedByUserId(): ?int
202    {
203        return $this->editedByUserId;
204    }
205
206    public function getEditNote(): ?string
207    {
208        return $this->editNote;
209    }
210
211    public function getEmployeeName(): ?string
212    {
213        return $this->employeeName;
214    }
215
216    // Setters
217
218    public function setPunchId(int $punchId): self
219    {
220        $this->punchId = $punchId;
221        return $this;
222    }
223
224    public function setShiftId(?int $shiftId): self
225    {
226        $this->shiftId = $shiftId;
227        return $this;
228    }
229
230    public function setPunchType(string $punchType): self
231    {
232        $this->punchType = $punchType;
233        return $this;
234    }
235
236    public function setPunchTime(DateTime $punchTime): self
237    {
238        $this->punchTime = $punchTime;
239        return $this;
240    }
241
242    public function setBreakType(?string $breakType): self
243    {
244        $this->breakType = $breakType;
245        return $this;
246    }
247
248    public function setIsManualEntry(bool $isManualEntry): self
249    {
250        $this->isManualEntry = $isManualEntry;
251        return $this;
252    }
253
254    public function setManualEntryNote(?string $note): self
255    {
256        $this->manualEntryNote = $note;
257        return $this;
258    }
259
260    public function setIsUnscheduled(bool $isUnscheduled): self
261    {
262        $this->isUnscheduled = $isUnscheduled;
263        return $this;
264    }
265
266    public function setIsManagerOverride(bool $isManagerOverride): self
267    {
268        $this->isManagerOverride = $isManagerOverride;
269        return $this;
270    }
271
272    public function setApproval(int $approverUserId, ?string $note = null): self
273    {
274        $this->approvedByUserId = $approverUserId;
275        $this->approvedAt = new DateTime();
276        $this->approvalNote = $note;
277        return $this;
278    }
279
280    public function setEdit(int $editorUserId, string $note, DateTime $newTime): self
281    {
282        $this->editedByUserId = $editorUserId;
283        $this->editedAt = new DateTime();
284        $this->editNote = $note;
285        $this->punchTime = $newTime;
286        return $this;
287    }
288
289    public function setEmployeeName(?string $name): self
290    {
291        $this->employeeName = $name;
292        return $this;
293    }
294
295    // Business Logic
296
297    /**
298     * Check if this is a clock in punch
299     *
300     * @return bool
301     */
302    public function isClockIn(): bool
303    {
304        return $this->punchType === self::TYPE_CLOCK_IN;
305    }
306
307    /**
308     * Check if this is a clock out punch
309     *
310     * @return bool
311     */
312    public function isClockOut(): bool
313    {
314        return $this->punchType === self::TYPE_CLOCK_OUT;
315    }
316
317    /**
318     * Check if this is a break punch (start or end)
319     *
320     * @return bool
321     */
322    public function isBreakPunch(): bool
323    {
324        return in_array($this->punchType, [self::TYPE_BREAK_START, self::TYPE_BREAK_END], true);
325    }
326
327    /**
328     * Check if this punch has been edited
329     *
330     * @return bool
331     */
332    public function hasBeenEdited(): bool
333    {
334        return $this->editedAt !== null;
335    }
336
337    /**
338     * Convert to array for database insert/update
339     *
340     * @return array
341     */
342    public function toDbArray(): array
343    {
344        return [
345            'employeeId' => $this->employeeId,
346            'shiftId' => $this->shiftId,
347            'punchType' => $this->punchType,
348            'punchTime' => $this->punchTime->format('Y-m-d H:i:s'),
349            'breakType' => $this->breakType,
350            'isManualEntry' => $this->isManualEntry ? 1 : 0,
351            'manualEntryNote' => $this->manualEntryNote,
352            'isUnscheduled' => $this->isUnscheduled ? 1 : 0,
353            'isManagerOverride' => $this->isManagerOverride ? 1 : 0,
354            'approvedByUserId' => $this->approvedByUserId,
355            'approved_at' => $this->approvedAt?->format('Y-m-d H:i:s'),
356            'approvalNote' => $this->approvalNote,
357            'enteredByUserId' => $this->enteredByUserId,
358            'edited_at' => $this->editedAt?->format('Y-m-d H:i:s'),
359            'editedByUserId' => $this->editedByUserId,
360            'editNote' => $this->editNote,
361        ];
362    }
363
364    /**
365     * JSON serialization for API responses
366     *
367     * @return array
368     */
369    public function jsonSerialize(): array
370    {
371        return [
372            'punchId' => $this->punchId,
373            'employeeId' => $this->employeeId,
374            'employeeName' => $this->employeeName,
375            'shiftId' => $this->shiftId,
376            'punchType' => $this->punchType,
377            'punchTime' => $this->punchTime->format(DateTimeInterface::ATOM),
378            'breakType' => $this->breakType,
379            'isManualEntry' => $this->isManualEntry,
380            'manualEntryNote' => $this->manualEntryNote,
381            'isUnscheduled' => $this->isUnscheduled,
382            'isManagerOverride' => $this->isManagerOverride,
383            'hasBeenEdited' => $this->hasBeenEdited(),
384            'editedAt' => $this->editedAt?->format(DateTimeInterface::ATOM),
385            'editNote' => $this->editNote,
386        ];
387    }
388}