*/ public function validateCorrection(array $existing, array $changes): array { $id = $this->positiveId($existing['id'] ?? null); $errors = $id === null ? ['id' => 'Time-entry ID must be a positive integer.'] : []; if ($this->isVoided($existing)) $errors['voided'] = 'A voided time entry cannot be corrected.'; foreach (['jobcard_id', 'technician_id'] as $field) { if (array_key_exists($field, $changes) && $this->positiveId($changes[$field]) !== $this->positiveId($existing[$field] ?? null)) $errors[$field] = "{$field} cannot be changed during correction."; } $allowed = ['work_date', 'start_time', 'end_time', 'hours', 'notes', 'counts_toward_sla']; $payload = $existing; foreach ($allowed as $field) if (array_key_exists($field, $changes)) $payload[$field] = $changes[$field]; $validated = ($this->entries ?? new TimeEntryCommand())->validate($payload); $errors = [...$validated['errors'], ...$errors]; $entry = [...$payload, ...$validated]; unset($entry['valid'], $entry['errors']); $entry['id'] = $id; return ['valid' => $errors === [], 'action' => 'correct', 'id' => $id, 'entry' => $entry, 'errors' => $errors]; } /** @return array */ public function validate(array $existing, array $changes = []): array { return $this->validateCorrection($existing, $changes); } /** @return array */ public function validateForCorrection(array $existing, array $changes = []): array { return $this->validateCorrection($existing, $changes); } /** @return array */ public function validateVoid(array $existing, array $input = []): array { $id = $this->positiveId($existing['id'] ?? null); $errors = $id === null ? ['id' => 'Time-entry ID must be a positive integer.'] : []; if ($this->isVoided($existing)) $errors['voided'] = 'Time entry is already voided.'; $reason = is_scalar($input['reason'] ?? null) ? trim((string)$input['reason']) : ''; if ($reason === '') $errors['reason'] = 'A void reason is required.'; elseif (mb_strlen($reason) > 1000) $errors['reason'] = 'Void reason must be 1000 characters or fewer.'; return ['valid' => $errors === [], 'action' => 'void', 'id' => $id, 'void_reason' => $reason === '' ? null : $reason, 'entry' => $existing, 'errors' => $errors]; } public function void(array $existing, array $input = []): array { return $this->validateVoid($existing, $input); } /** @return array */ public function validateForVoid(array $existing, array $input = []): array { return $this->validateVoid($existing, $input); } private function positiveId(mixed $value): ?int { if (is_int($value) && $value > 0) return $value; if (is_string($value) && preg_match('/^[1-9]\d*$/', trim($value)) === 1) return filter_var(trim($value), FILTER_VALIDATE_INT) ?: null; return null; } private function isVoided(array $entry): bool { $value = $entry['voided'] ?? false; return $value === true || $value === 1 || (is_string($value) && in_array(strtolower(trim($value)), ['1','true','yes'], true)); } }