} */ public function validate(array $command): array { $jobcardId = $this->positiveInteger($command['jobcard_id'] ?? null); $technicianId = $this->positiveInteger($command['technician_id'] ?? null); $workDate = $this->text($command['work_date'] ?? null) ?? ''; $startTime = $this->text($command['start_time'] ?? null); $endTime = $this->text($command['end_time'] ?? null); $notes = $this->text($command['notes'] ?? null); $slaFlag = $this->boolean($command['counts_toward_sla'] ?? true); $manualHours = $command['hours'] ?? null; if ($manualHours === '') $manualHours = null; $entry = ($this->timeEntries ?? new TimeEntryValidator())->validate([ 'work_date' => $workDate, 'start_time' => $startTime, 'end_time' => $endTime, 'hours' => $manualHours, ]); $errors = $entry['errors']; if ($jobcardId === null) $errors['jobcard_id'] = 'Jobcard ID must be a positive integer.'; if ($technicianId === null) $errors['technician_id'] = 'Technician ID must be a positive integer.'; if ($manualHours !== null && ($startTime !== null || $endTime !== null)) $errors['time'] = 'Manual hours and start/end times cannot both be supplied.'; if ($notes !== null && mb_strlen($notes) > self::MAX_NOTES_LENGTH) $errors['notes'] = 'Notes are too long.'; if ($slaFlag === null) $errors['counts_toward_sla'] = 'SLA flag must be boolean.'; return [ 'valid' => $errors === [], 'jobcard_id' => $jobcardId, 'technician_id' => $technicianId, 'work_date' => $workDate, 'start_time' => $startTime, 'end_time' => $endTime, 'hours' => $entry['hours'], 'notes' => $notes, 'counts_toward_sla' => $slaFlag ?? false, 'errors' => $errors, ]; } private function positiveInteger(mixed $value): ?int { if (is_int($value)) { return $value > 0 ? $value : null; } if (is_string($value) && preg_match('/^[1-9]\d*$/', $value) === 1) { $integer = filter_var($value, FILTER_VALIDATE_INT); return $integer !== false ? $integer : null; } return null; } private function boolean(mixed $value): ?bool { if (is_bool($value)) return $value; if ($value === 1 || $value === 0) return $value === 1; if (is_string($value)) { return match (strtolower(trim($value))) { '1', 'true', 'yes', 'on' => true, '0', 'false', 'no', 'off', '' => false, default => null, }; } return null; } private function text(mixed $value): ?string { if (!is_scalar($value)) return null; $text = trim((string) $value); return $text === '' ? null : $text; } }