Files
JobcardSystem/app/Domain/Jobcard/TimeEntryCorrectionCommand.php
T

88 lines
4.4 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace App\Domain\Jobcard;
require_once __DIR__ . '/TimeEntryCommand.php';
/**
* Validates immutable time-entry corrections and logical voids.
* No database deletion is represented: adapters should persist the returned
* action in a transaction and retain the original entry for auditability.
*/
final class TimeEntryCorrectionCommand
{
public function __construct(private readonly ?TimeEntryCommand $entries = null)
{
}
/** @return array<string,mixed> */
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'];
foreach (array_keys($changes) as $field) if (!in_array($field, $allowed, true) && !in_array($field, ['jobcard_id', 'technician_id'], true)) $errors[$field] = "{$field} cannot be changed during correction.";
$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;
$changed = []; foreach ($allowed as $field) if (array_key_exists($field, $changes) && ($existing[$field] ?? null) !== ($entry[$field] ?? null)) $changed[] = $field;
return ['valid' => $errors === [], 'action' => 'correct', 'id' => $id, 'entry' => $entry, 'errors' => $errors, 'audit' => ['event' => 'time_entry_corrected', 'entity_type' => 'time_entry', 'entity_id' => $id, 'changed_fields' => $changed, 'before' => $existing, 'after' => $entry]];
}
/** @return array<string,mixed> */
public function validate(array $existing, array $changes = []): array
{
return $this->validateCorrection($existing, $changes);
}
/** @return array<string,mixed> */
public function validateForCorrection(array $existing, array $changes = []): array
{
return $this->validateCorrection($existing, $changes);
}
/** @return array<string,mixed> */
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, 'audit' => $errors === [] ? ['event' => 'time_entry_voided', 'entity_type' => 'time_entry', 'entity_id' => $id, 'void_reason' => $reason] : null];
}
public function void(array $existing, array $input = []): array
{
return $this->validateVoid($existing, $input);
}
/** @return array<string,mixed> */
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));
}
}