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
+41 -4
View File
@@ -49,7 +49,24 @@ final class RolePermissionService
return ['role_id' => $roleId ?? 0, 'permissions' => $normalized, 'valid' => $errors === [], 'errors' => $errors];
}
/** Alias matching controller command terminology. */
/** Validate creation of a custom role and its optional permission set. */
public function validateForCreate(array $input, array $available = []): array
{
$record = $this->roles ?? new RoleRecord();
$result = $record->validate($input);
if ($record->isAdministrator($input)) {
$result['errors']['role'] = 'The protected Administrator role cannot be created or renamed.';
}
if (array_key_exists('permissions', $input)) {
$assignment = $this->validateAssignment(['id' => 2, 'name' => $result['name']], is_array($input['permissions']) ? $input['permissions'] : [], $available);
$result['permissions'] = $assignment['permissions'];
$result['errors'] = [...$result['errors'], ...$assignment['errors']];
}
$result['valid'] = $result['errors'] === [];
return $result;
}
/** @return array{role_id: int, permissions: list<string>, valid: bool, errors: array<string, string>} */
public function validateForAssignment(array $role, array $selected, array $available = []): array
{
return $this->validateAssignment($role, $selected, $available);
@@ -68,9 +85,7 @@ final class RolePermissionService
$result['id'] = $id;
if ($id < 1) $result['errors']['id'] = 'Role ID must be a positive integer.';
$current = ['id' => $id, 'name' => $id === 1 ? 'Administrator' : ($input['current_name'] ?? ($input['name'] ?? null))];
if (array_key_exists('current_name', $input)) {
if (!$record->canRename($current, $result['name'])) $result['errors']['role'] = 'The protected Administrator role cannot be renamed.';
}
if (!$record->canRename($current, $result['name'])) $result['errors']['role'] = 'The protected Administrator role cannot be renamed.';
if (array_key_exists('permissions', $input)) {
$assignment = $this->validateAssignment(['id' => $id, 'name' => $current['name']], is_array($input['permissions']) ? $input['permissions'] : [], $available);
$result['permissions'] = $assignment['permissions'];
@@ -80,6 +95,28 @@ final class RolePermissionService
return $result;
}
/** Alias matching edit controller terminology. */
public function validateEdit(int $id, array $input, array $available = []): array
{
return $this->validateForEdit($id, $input, $available);
}
/** Validate deletion of a custom role; ID 1 is always protected. */
public function validateDelete(int $id, array $role = []): array
{
$errors = [];
if ($id < 1) $errors['id'] = 'Role ID must be a positive integer.';
if ($id === 1 || ($role !== [] && !($this->roles ?? new RoleRecord())->canDelete(['id' => $id, ...$role]))) {
$errors['role'] = 'The protected Administrator role cannot be deleted.';
}
return ['valid' => $errors === [], 'id' => $id, 'errors' => $errors];
}
public function delete(int $id, array $role = []): array
{
return $this->validateDelete($id, $role);
}
public function canAssignPermissions(array $role): bool
{
return !($this->roles ?? new RoleRecord())->isAdministrator($role);
+1
View File
@@ -48,6 +48,7 @@ final class RoleRecord
public function isAdministrator(array $record): bool
{
if (isset($record['id']) && (int) $record['id'] === 1) return true;
return $this->canonicalName($record['name'] ?? null) === self::ADMINISTRATOR;
}
+20 -1
View File
@@ -30,6 +30,18 @@ final class UserAdminService
return $result;
}
/** Controller-friendly user-create DTO. */
public function validateForCreate(array $input, array $existingUsers = []): array
{
$result = ($this->users ?? new UserRecord())->validateForCreate($input);
$email = $result['email'] ?? '';
if (is_string($email) && $email !== '' && $this->hasDuplicateEmail($email, $existingUsers, 0)) {
$result['errors']['email'] = 'Email address is already in use.';
$result['valid'] = false;
}
return $result;
}
/** @return array<string, mixed> */
public function validateForEdit(int $id, array $input, array $existingUsers = []): array
{
@@ -40,7 +52,7 @@ final class UserAdminService
$result = ($this->users ?? new UserRecord())->validate($input);
$existing = $existingUsers[array_search($id, array_map(static fn($row) => is_array($row) ? (int)($row['id'] ?? 0) : 0, $existingUsers), true)] ?? [];
if ($this->isProtectedAdministrator($existing) && array_key_exists('role_id', $input) && (int)$input['role_id'] !== (int)($existing['role_id'] ?? 1)) $result['errors']['role_id'] = 'The protected Administrator account cannot be reassigned.';
if ($this->isProtectedAdministrator(['id' => $id, ...$existing]) && array_key_exists('role_id', $input) && (int)$input['role_id'] !== 1) $result['errors']['role_id'] = 'The protected Administrator account cannot be reassigned.';
$email = $result['email'] ?? '';
if (is_string($email) && $email !== '' && $this->hasDuplicateEmail($email, $existingUsers, $id)) {
$result['errors']['email'] = 'Email address is already in use.';
@@ -52,6 +64,12 @@ final class UserAdminService
return $result;
}
/** Alias matching controller command terminology. */
public function validateEdit(int $id, array $input, array $existingUsers = []): array
{
return $this->validateForEdit($id, $input, $existingUsers);
}
/** @return array{valid: bool, id: int, is_active: bool, errors: array<string, string>} */
public function validateDeactivate(array $user): array
{
@@ -121,6 +139,7 @@ final class UserAdminService
public function isProtectedAdministrator(array $user): bool
{
if (isset($user['id']) && (int)$user['id'] === 1) return true;
if (isset($user['role_id']) && (int)$user['role_id'] === 1) return true;
$role = $user['role_name'] ?? $user['role'] ?? null;
return is_scalar($role) && strtolower(trim((string) $role)) === 'administrator';