feat: expand client jobcard and reporting workflows
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\Client;
|
||||
|
||||
/**
|
||||
* Normalizes and validates client records without a framework or persistence
|
||||
* dependency. The output shape is stable for PDO adapters and controllers.
|
||||
*/
|
||||
final class ClientRecord
|
||||
{
|
||||
/** @var list<string> */
|
||||
private const FIELDS = [
|
||||
'name',
|
||||
'registration_number',
|
||||
'status',
|
||||
'support_email',
|
||||
'support_phone',
|
||||
'preferred_contact_method',
|
||||
'physical_address',
|
||||
'postal_address',
|
||||
'general_notes',
|
||||
];
|
||||
|
||||
/** @var list<string> */
|
||||
private const DISPLAY_FIELDS = [
|
||||
'id',
|
||||
'name',
|
||||
'registration_number',
|
||||
'status',
|
||||
'support_email',
|
||||
'support_phone',
|
||||
'preferred_contact_method',
|
||||
'physical_address',
|
||||
'postal_address',
|
||||
'general_notes',
|
||||
];
|
||||
|
||||
/** @return array<string, string|null> */
|
||||
public function normalize(array $record): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->text($record['name'] ?? null) ?? '',
|
||||
'registration_number' => $this->text($record['registration_number'] ?? null),
|
||||
'status' => strtolower($this->text($record['status'] ?? null) ?? 'active'),
|
||||
'support_email' => $this->lowerText($record['support_email'] ?? null),
|
||||
'support_phone' => $this->text($record['support_phone'] ?? null),
|
||||
'preferred_contact_method' => $this->text($record['preferred_contact_method'] ?? null),
|
||||
'physical_address' => $this->text($record['physical_address'] ?? null),
|
||||
'postal_address' => $this->text($record['postal_address'] ?? null),
|
||||
'general_notes' => $this->text($record['general_notes'] ?? null),
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array{valid: bool, errors: array<string, string>, name: string, registration_number: string|null, status: string, support_email: string|null, support_phone: string|null, preferred_contact_method: string|null, physical_address: string|null, postal_address: string|null, general_notes: string|null} */
|
||||
public function validate(array $record): array
|
||||
{
|
||||
$normalized = $this->normalize($record);
|
||||
$errors = [];
|
||||
|
||||
if ($normalized['name'] === '') {
|
||||
$errors['name'] = 'Client name is required.';
|
||||
} elseif (mb_strlen($normalized['name']) > 190) {
|
||||
$errors['name'] = 'Client name must be 190 characters or fewer.';
|
||||
}
|
||||
if (!in_array($normalized['status'], ['active', 'inactive'], true)) {
|
||||
$errors['status'] = 'Invalid client status.';
|
||||
}
|
||||
if ($normalized['registration_number'] !== null && mb_strlen($normalized['registration_number']) > 120) {
|
||||
$errors['registration_number'] = 'Registration number must be 120 characters or fewer.';
|
||||
}
|
||||
if ($normalized['support_email'] !== null) {
|
||||
if (filter_var($normalized['support_email'], FILTER_VALIDATE_EMAIL) === false) {
|
||||
$errors['support_email'] = 'Support email must be a valid email address.';
|
||||
} elseif (mb_strlen($normalized['support_email']) > 190) {
|
||||
$errors['support_email'] = 'Support email must be 190 characters or fewer.';
|
||||
}
|
||||
}
|
||||
if ($normalized['support_phone'] !== null && !$this->validPhone($normalized['support_phone'])) {
|
||||
$errors['support_phone'] = 'Support phone must contain a valid phone number.';
|
||||
}
|
||||
|
||||
return [...$normalized, 'valid' => $errors === [], 'errors' => $errors];
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function display(array $record): array
|
||||
{
|
||||
$safe = [];
|
||||
foreach (self::DISPLAY_FIELDS as $field) {
|
||||
if (array_key_exists($field, $record)) {
|
||||
$safe[$field] = $record[$field];
|
||||
}
|
||||
}
|
||||
return $safe;
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function toDisplay(array $record): array
|
||||
{
|
||||
return $this->display($record);
|
||||
}
|
||||
|
||||
private function text(mixed $value): ?string
|
||||
{
|
||||
if ($value === null) return null;
|
||||
$text = trim(is_scalar($value) ? (string) $value : '');
|
||||
return $text === '' ? null : $text;
|
||||
}
|
||||
|
||||
private function lowerText(mixed $value): ?string
|
||||
{
|
||||
$text = $this->text($value);
|
||||
return $text === null ? null : strtolower($text);
|
||||
}
|
||||
|
||||
private function validPhone(string $phone): bool
|
||||
{
|
||||
if (mb_strlen($phone) > 60 || preg_match('/^[0-9+().\-\s]+$/', $phone) !== 1) {
|
||||
return false;
|
||||
}
|
||||
return preg_match('/\d.*\d.*\d.*\d.*\d.*\d.*\d/', $phone) === 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user