feat: complete jobcard management workflows and UI

This commit is contained in:
Marco0300
2026-09-01 21:47:35 +02:00
parent b983f90dcb
commit 9ce08bc6f8
27 changed files with 1081 additions and 115 deletions
+39 -20
View File
@@ -16,24 +16,36 @@ final class ContactEditCommand
}
/** @return array<string,mixed> */
public function validateEdit(int $id, array $input, array $existingContacts = []): array
public function validateEdit(mixed $id, array $input, array $existingContacts = []): array
{
$normalizedId = $this->id($id);
$result = ($this->contacts ?? new ContactUpdateCommand())->validateForEdit($id, $input, $existingContacts);
if ($id < 1) {
if ($normalizedId === null) {
$result['errors']['id'] = 'Contact ID must be a positive integer.';
$result['valid'] = false;
} else {
$target = $this->find($normalizedId, $existingContacts);
if ($target === null) $result['errors']['id'] = 'Contact was not found.';
else {
$oldClient = $this->id($target['client_id'] ?? null);
$newClient = $this->id($input['client_id'] ?? null);
if ($oldClient !== null && $newClient !== $oldClient) $result['errors']['client_id'] = 'Contact client ID cannot be changed during edit.';
}
}
$result['id'] = $normalizedId;
$result['valid'] = $result['errors'] === [];
$result['action'] = 'edit';
$result['audit'] = ['event' => 'client_contact_updated', 'entity_type' => 'client_contact', 'entity_id' => $normalizedId, 'client_id' => $result['client_id'] ?? null];
return $result;
}
/** Alias matching the create/update command vocabulary. */
public function validateForEdit(int $id, array $input, array $existingContacts = []): array
public function validateForEdit(mixed $id, array $input, array $existingContacts = []): array
{
return $this->validateEdit($id, $input, $existingContacts);
}
/** @return array<string,mixed> */
public function validate(array $input, array $existingContacts = [], ?int $currentId = null): array
public function validate(array $input, array $existingContacts = [], mixed $currentId = null): array
{
return $currentId === null
? ($this->contacts ?? new ContactUpdateCommand())->validateForCreate($input, $existingContacts)
@@ -45,27 +57,28 @@ final class ContactEditCommand
* contact for the same client; deleting the sole contact is not allowed.
* @return array<string,mixed>
*/
public function validateDelete(int $id, array $existingContacts = []): array
public function validateDelete(mixed $id, array $existingContacts = []): array
{
$normalizedId = $this->id($id);
$errors = [];
$target = null;
foreach ($existingContacts as $contact) {
if (is_array($contact) && $this->id($contact['id'] ?? null) === $id) {
if ($normalizedId !== null && is_array($contact) && $this->id($contact['id'] ?? null) === $normalizedId) {
$target = $contact;
break;
}
}
if ($id < 1) $errors['id'] = 'Contact ID must be a positive integer.';
if ($normalizedId === null) $errors['id'] = 'Contact ID must be a positive integer.';
if ($target === null) {
$errors['id'] = 'Contact was not found.';
return ['valid' => false, 'id' => $id, 'client_id' => null, 'replacement_primary_contact_id' => null, 'errors' => $errors];
if ($normalizedId !== null) $errors['id'] = 'Contact was not found.';
return ['valid' => false, 'action' => 'delete', 'id' => $normalizedId, 'client_id' => null, 'replacement_primary_contact_id' => null, 'errors' => $errors, 'audit' => null];
}
$clientId = $this->id($target['client_id'] ?? null);
if ($clientId === null) {
$errors['client_id'] = 'Contact client ID must be a positive integer.';
return ['valid' => false, 'id' => $id, 'client_id' => null, 'replacement_primary_contact_id' => null, 'errors' => $errors];
}
$sameClient = array_values(array_filter($existingContacts, fn ($row): bool => is_array($row) && $this->id($row['client_id'] ?? null) === $clientId && $this->id($row['id'] ?? null) !== $id));
$sameClient = array_values(array_filter($existingContacts, fn ($row): bool => is_array($row) && $this->id($row['client_id'] ?? null) === $clientId && $this->id($row['id'] ?? null) !== $normalizedId));
$replacement = null;
if (count($sameClient) === 0) {
$errors['delete'] = 'The only contact cannot be deleted; add another contact first.';
@@ -74,36 +87,37 @@ final class ContactEditCommand
$replacement = isset($sameClient[0]) ? $this->id($sameClient[0]['id'] ?? null) : null;
if ($replacement === null) $errors['delete'] = 'The only contact cannot be deleted; add another contact first.';
}
return ['valid' => $errors === [], 'id' => $id, 'client_id' => $clientId, 'replacement_primary_contact_id' => $replacement, 'errors' => $errors];
return ['valid' => $errors === [], 'action' => 'delete', 'id' => $normalizedId, 'client_id' => $clientId, 'replacement_primary_contact_id' => $replacement, 'errors' => $errors, 'audit' => $errors === [] ? ['event' => 'client_contact_deleted', 'entity_type' => 'client_contact', 'entity_id' => $normalizedId, 'client_id' => $clientId, 'replacement_primary_contact_id' => $replacement] : null];
}
public function validateForDelete(int $id, array $existingContacts = []): array
public function validateForDelete(mixed $id, array $existingContacts = []): array
{
return $this->validateDelete($id, $existingContacts);
}
/** @return array<string,mixed> */
public function delete(int $id, array $existingContacts = []): array
public function delete(mixed $id, array $existingContacts = []): array
{
return $this->validateDelete($id, $existingContacts);
}
/** @return array<string,mixed> */
public function validatePrimary(int $id, array $existingContacts = []): array
public function validatePrimary(mixed $id, array $existingContacts = []): array
{
$normalizedId = $this->id($id);
foreach ($existingContacts as $row) {
if (is_array($row) && $this->id($row['id'] ?? null) === $id) {
if ($normalizedId !== null && is_array($row) && $this->id($row['id'] ?? null) === $normalizedId) {
$clientId = $this->id($row['client_id'] ?? null);
$demote = [];
foreach ($existingContacts as $other) if (is_array($other) && $this->id($other['client_id'] ?? null) === $clientId && $this->id($other['id'] ?? null) !== $id && $this->boolean($other['is_primary'] ?? false)) $demote[] = $this->id($other['id'] ?? null);
return ['valid' => true, 'id' => $id, 'client_id' => $clientId, 'replace_primary_contact_ids' => array_values(array_filter($demote)), 'errors' => []];
foreach ($existingContacts as $other) if (is_array($other) && $this->id($other['client_id'] ?? null) === $clientId && $this->id($other['id'] ?? null) !== $normalizedId && $this->boolean($other['is_primary'] ?? false)) $demote[] = $this->id($other['id'] ?? null);
return ['valid' => true, 'action' => 'set_primary', 'id' => $normalizedId, 'client_id' => $clientId, 'replace_primary_contact_ids' => array_values(array_filter($demote)), 'errors' => [], 'audit' => ['event' => 'client_contact_primary_set', 'entity_type' => 'client_contact', 'entity_id' => $normalizedId, 'client_id' => $clientId, 'demoted_contact_ids' => array_values(array_filter($demote))]];
}
}
return ['valid' => false, 'id' => $id, 'client_id' => null, 'replace_primary_contact_ids' => [], 'errors' => ['id' => 'Contact was not found.']];
return ['valid' => false, 'action' => 'set_primary', 'id' => $normalizedId, 'client_id' => null, 'replace_primary_contact_ids' => [], 'errors' => ['id' => $normalizedId === null ? 'Contact ID must be a positive integer.' : 'Contact was not found.'], 'audit' => null];
}
/** @return array<string,mixed> */
public function setPrimary(int $id, array $existingContacts = []): array
public function setPrimary(mixed $id, array $existingContacts = []): array
{
return $this->validatePrimary($id, $existingContacts);
}
@@ -115,4 +129,9 @@ final class ContactEditCommand
return null;
}
private function boolean(mixed $value): bool { return $value === true || $value === 1 || (is_string($value) && in_array(strtolower(trim($value)), ['1','true','yes','on'], true)); }
private function find(int $id, array $rows): ?array
{
foreach ($rows as $row) if (is_array($row) && $this->id($row['id'] ?? null) === $id) return $row;
return null;
}
}
+5 -4
View File
@@ -61,11 +61,12 @@ final class ContactUpdateCommand
}
/** @return array<string, mixed> */
public function validateForEdit(int $id, array $input, array $existingContacts = []): array
public function validateForEdit(mixed $id, array $input, array $existingContacts = []): array
{
$result = $this->validate($input, $existingContacts, $id);
$result['id'] = $id;
if ($id < 1) $result['errors']['id'] = 'Contact ID must be a positive integer.';
$normalizedId = $this->positiveId($id);
$result = $this->validate($input, $existingContacts, $normalizedId);
$result['id'] = $normalizedId;
if ($normalizedId === null) $result['errors']['id'] = 'Contact ID must be a positive integer.';
$result['valid'] = $result['errors'] === [];
return $result;
}