2026-02-04 23:31:53 +01:00
|
|
|
<?php
|
|
|
|
|
|
2026-02-11 19:28:12 +01:00
|
|
|
namespace MintyPHP\Service\Org;
|
2026-02-04 23:31:53 +01:00
|
|
|
|
2026-03-05 08:26:51 +01:00
|
|
|
use MintyPHP\Repository\Org\DepartmentRepositoryInterface;
|
2026-03-04 15:56:58 +01:00
|
|
|
use MintyPHP\Service\Audit\SystemAuditService;
|
2026-02-23 12:58:19 +01:00
|
|
|
use MintyPHP\Service\Directory\DirectoryScopeGateway;
|
|
|
|
|
use MintyPHP\Service\Directory\DirectorySettingsGateway;
|
|
|
|
|
use MintyPHP\Service\User\UserServicesFactory;
|
2026-02-04 23:31:53 +01:00
|
|
|
|
|
|
|
|
class DepartmentService
|
|
|
|
|
{
|
2026-02-23 12:58:19 +01:00
|
|
|
public function __construct(
|
2026-03-04 15:56:58 +01:00
|
|
|
private readonly UserServicesFactory $userServicesFactory,
|
2026-03-05 08:26:51 +01:00
|
|
|
private readonly DepartmentRepositoryInterface $departmentRepository,
|
2026-03-04 15:56:58 +01:00
|
|
|
private readonly DirectorySettingsGateway $settingsGateway,
|
|
|
|
|
private readonly DirectoryScopeGateway $scopeGateway,
|
|
|
|
|
private readonly SystemAuditService $systemAuditService
|
2026-02-23 12:58:19 +01:00
|
|
|
) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function list(): array
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
2026-03-04 15:56:58 +01:00
|
|
|
return $this->departmentRepository->list();
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function listPaged(array $options): array
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
2026-03-06 00:44:52 +01:00
|
|
|
// Global-scope users see all departments — remove tenant filter so the query isn't restricted.
|
2026-02-04 23:31:53 +01:00
|
|
|
if (!empty($options['tenantUserId'])) {
|
|
|
|
|
$tenantUserId = (int) $options['tenantUserId'];
|
2026-03-04 15:56:58 +01:00
|
|
|
if ($tenantUserId > 0 && $this->scopeGateway->hasGlobalAccess($tenantUserId)) {
|
2026-02-04 23:31:53 +01:00
|
|
|
unset($options['tenantUserId'], $options['tenantIds']);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-04 15:56:58 +01:00
|
|
|
return $this->departmentRepository->listPaged($options);
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function listByTenantIds(array $tenantIds): array
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
2026-03-04 15:56:58 +01:00
|
|
|
return $this->departmentRepository->listByTenantIds($tenantIds);
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function groupActiveByTenantIds(array $tenantIds): array
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
|
|
|
|
$tenantIds = array_values(array_unique(array_map('intval', $tenantIds)));
|
|
|
|
|
$tenantIds = array_values(array_filter($tenantIds, static fn ($id) => $id > 0));
|
|
|
|
|
if (!$tenantIds) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
$departments = $this->listByTenantIds($tenantIds);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
if (!$departments) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$grouped = [];
|
|
|
|
|
foreach ($departments as $department) {
|
|
|
|
|
$tenantId = (int) ($department['tenant_id'] ?? 0);
|
|
|
|
|
if ($tenantId <= 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$grouped[$tenantId] ??= [];
|
|
|
|
|
$grouped[$tenantId][] = $department;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($grouped as &$items) {
|
|
|
|
|
usort($items, static function (array $a, array $b): int {
|
|
|
|
|
return strcmp((string) ($a['description'] ?? ''), (string) ($b['description'] ?? ''));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
unset($items);
|
|
|
|
|
|
|
|
|
|
return $grouped;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function listByIds(array $departmentIds): array
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
2026-03-04 15:56:58 +01:00
|
|
|
return $this->departmentRepository->listByIds($departmentIds);
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-06 00:44:52 +01:00
|
|
|
// Merges the tenant-scoped list with any already-selected departments outside the filter —
|
|
|
|
|
// ensures a user keeps visibility of departments they're already assigned to.
|
2026-02-23 12:58:19 +01:00
|
|
|
public function listForUserAssignments(array $tenantIds, array $selectedDepartmentIds): array
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
2026-02-23 12:58:19 +01:00
|
|
|
$departments = $tenantIds ? $this->listByTenantIds($tenantIds) : $this->list();
|
|
|
|
|
$extraDepartments = $selectedDepartmentIds ? $this->listByIds($selectedDepartmentIds) : [];
|
2026-02-04 23:31:53 +01:00
|
|
|
if (!$extraDepartments) {
|
|
|
|
|
return $departments;
|
|
|
|
|
}
|
|
|
|
|
$departmentMap = [];
|
|
|
|
|
foreach ($departments as $department) {
|
|
|
|
|
if (isset($department['id'])) {
|
|
|
|
|
$departmentMap[(int) $department['id']] = $department;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
foreach ($extraDepartments as $department) {
|
|
|
|
|
if (isset($department['id'])) {
|
|
|
|
|
$departmentMap[(int) $department['id']] = $department;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return array_values($departmentMap);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function findByUuid(string $uuid): ?array
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
2026-03-04 15:56:58 +01:00
|
|
|
return $this->departmentRepository->findByUuid($uuid);
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function findById(int $id): ?array
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
2026-03-04 15:56:58 +01:00
|
|
|
return $this->departmentRepository->find($id);
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function createFromAdmin(array $input, int $currentUserId = 0): array
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
2026-02-23 12:58:19 +01:00
|
|
|
$form = $this->sanitizeBase($input);
|
|
|
|
|
$errors = $this->validateBase($form);
|
|
|
|
|
$warnings = $this->warningsForCode($form, 0);
|
2026-02-04 23:31:53 +01:00
|
|
|
|
|
|
|
|
if ($errors) {
|
2026-02-11 19:28:12 +01:00
|
|
|
return ['ok' => false, 'errors' => $errors, 'warnings' => $warnings, 'form' => $form];
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-04 15:56:58 +01:00
|
|
|
$createdId = $this->departmentRepository->create([
|
2026-02-04 23:31:53 +01:00
|
|
|
'description' => $form['description'],
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
'tenant_id' => $form['tenant_id'],
|
2026-02-11 19:28:12 +01:00
|
|
|
'code' => $form['code'] ?: null,
|
|
|
|
|
'cost_center' => $form['cost_center'] ?: null,
|
|
|
|
|
'active' => $form['active'],
|
2026-02-04 23:31:53 +01:00
|
|
|
'created_by' => $currentUserId > 0 ? $currentUserId : null,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (!$createdId) {
|
|
|
|
|
return ['ok' => false, 'errors' => [t('Department can not be created')], 'form' => $form];
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 15:56:58 +01:00
|
|
|
$createdDepartment = $this->departmentRepository->find((int) $createdId);
|
2026-02-04 23:31:53 +01:00
|
|
|
$uuid = $createdDepartment['uuid'] ?? null;
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
if (!empty($input['is_default'])) {
|
2026-03-04 15:56:58 +01:00
|
|
|
$this->settingsGateway->setDefaultDepartmentId((int) $createdId);
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
2026-03-04 15:56:58 +01:00
|
|
|
|
|
|
|
|
$this->systemAuditService->record('admin.departments.create', 'success', [
|
|
|
|
|
'actor_user_id' => $currentUserId > 0 ? $currentUserId : null,
|
|
|
|
|
'target_type' => 'department',
|
|
|
|
|
'target_id' => (int) $createdId,
|
|
|
|
|
'target_uuid' => is_string($uuid) ? $uuid : '',
|
|
|
|
|
'metadata' => ['tenant_id' => $form['tenant_id']],
|
|
|
|
|
]);
|
|
|
|
|
|
2026-02-11 19:28:12 +01:00
|
|
|
return ['ok' => true, 'form' => $form, 'warnings' => $warnings, 'uuid' => $uuid, 'id' => (int) $createdId];
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function updateFromAdmin(int $departmentId, array $input, int $currentUserId = 0): array
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
2026-02-23 12:58:19 +01:00
|
|
|
$form = $this->sanitizeBase($input);
|
|
|
|
|
$errors = $this->validateBase($form);
|
|
|
|
|
$warnings = $this->warningsForCode($form, $departmentId);
|
2026-02-04 23:31:53 +01:00
|
|
|
|
|
|
|
|
if ($errors) {
|
2026-02-11 19:28:12 +01:00
|
|
|
return ['ok' => false, 'errors' => $errors, 'warnings' => $warnings, 'form' => $form];
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-04 15:56:58 +01:00
|
|
|
$existingBefore = $this->departmentRepository->find($departmentId) ?? [];
|
|
|
|
|
|
|
|
|
|
$updated = $this->departmentRepository->update($departmentId, [
|
2026-02-04 23:31:53 +01:00
|
|
|
'description' => $form['description'],
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
'tenant_id' => $form['tenant_id'],
|
2026-02-11 19:28:12 +01:00
|
|
|
'code' => $form['code'] ?: null,
|
|
|
|
|
'cost_center' => $form['cost_center'] ?: null,
|
|
|
|
|
'active' => $form['active'],
|
2026-02-04 23:31:53 +01:00
|
|
|
'modified_by' => $currentUserId > 0 ? $currentUserId : null,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (!$updated) {
|
|
|
|
|
return ['ok' => false, 'errors' => [t('Department can not be updated')], 'form' => $form];
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 15:56:58 +01:00
|
|
|
$this->systemAuditService->record('admin.departments.update', 'success', [
|
|
|
|
|
'actor_user_id' => $currentUserId > 0 ? $currentUserId : null,
|
|
|
|
|
'target_type' => 'department',
|
|
|
|
|
'target_id' => $departmentId,
|
|
|
|
|
'target_uuid' => (string) ($existingBefore['uuid'] ?? ''),
|
|
|
|
|
'before' => [
|
|
|
|
|
'active' => $existingBefore['active'] ?? null,
|
|
|
|
|
'tenant_id' => $existingBefore['tenant_id'] ?? null,
|
|
|
|
|
],
|
|
|
|
|
'after' => [
|
|
|
|
|
'active' => $form['active'],
|
|
|
|
|
'tenant_id' => $form['tenant_id'],
|
|
|
|
|
],
|
|
|
|
|
]);
|
|
|
|
|
|
2026-02-11 19:28:12 +01:00
|
|
|
return ['ok' => true, 'form' => $form, 'warnings' => $warnings];
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-06 00:44:52 +01:00
|
|
|
// After moving a department to a different tenant, users who aren't in the new tenant
|
|
|
|
|
// are no longer valid members — remove their assignments immediately.
|
2026-02-23 12:58:19 +01:00
|
|
|
public function syncTenant(int $departmentId, int $tenantId): int
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
2026-03-04 15:56:58 +01:00
|
|
|
$updated = $this->departmentRepository->setTenant($departmentId, $tenantId);
|
2026-02-04 23:31:53 +01:00
|
|
|
if (!$updated) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
2026-02-23 12:58:19 +01:00
|
|
|
return $this->cleanupUserAssignments($departmentId);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-06 00:44:52 +01:00
|
|
|
// Departments belong to exactly one tenant — only the first ID in the array is used.
|
2026-02-23 12:58:19 +01:00
|
|
|
public function syncTenants(int $departmentId, array $tenantIds): int
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
|
|
|
|
$tenantId = (int) ($tenantIds[0] ?? 0);
|
|
|
|
|
if ($tenantId <= 0) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
2026-02-23 12:58:19 +01:00
|
|
|
return $this->syncTenant($departmentId, $tenantId);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function cleanupUserAssignments(int $departmentId): int
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
2026-03-04 15:56:58 +01:00
|
|
|
return $this->userServicesFactory
|
2026-02-23 12:58:19 +01:00
|
|
|
->createUserDepartmentRepository()
|
|
|
|
|
->removeInvalidForDepartment($departmentId);
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function deleteByUuid(string $uuid): array
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
|
|
|
|
$uuid = trim($uuid);
|
|
|
|
|
if ($uuid === '') {
|
|
|
|
|
return ['ok' => false, 'status' => 404, 'error' => 'not_found'];
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 15:56:58 +01:00
|
|
|
$department = $this->departmentRepository->findByUuid($uuid);
|
2026-02-04 23:31:53 +01:00
|
|
|
if (!$department || !isset($department['id'])) {
|
|
|
|
|
return ['ok' => false, 'status' => 404, 'error' => 'not_found'];
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 15:56:58 +01:00
|
|
|
$deleted = $this->departmentRepository->delete((int) $department['id']);
|
2026-02-04 23:31:53 +01:00
|
|
|
if (!$deleted) {
|
|
|
|
|
return ['ok' => false, 'status' => 500, 'error' => 'delete_failed'];
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 15:56:58 +01:00
|
|
|
$this->systemAuditService->record('admin.departments.delete', 'success', [
|
|
|
|
|
'target_type' => 'department',
|
|
|
|
|
'target_id' => (int) $department['id'],
|
|
|
|
|
'target_uuid' => (string) ($department['uuid'] ?? ''),
|
|
|
|
|
]);
|
|
|
|
|
|
2026-02-04 23:31:53 +01:00
|
|
|
return ['ok' => true, 'department' => $department];
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
private function sanitizeBase(array $input): array
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
|
|
|
|
return [
|
|
|
|
|
'description' => trim((string) ($input['description'] ?? '')),
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
'tenant_id' => (int) ($input['tenant_id'] ?? 0),
|
2026-02-11 19:28:12 +01:00
|
|
|
'code' => trim((string) ($input['code'] ?? '')),
|
|
|
|
|
'cost_center' => trim((string) ($input['cost_center'] ?? '')),
|
2026-02-23 12:58:19 +01:00
|
|
|
'active' => $this->normalizeActive($input['active'] ?? 1),
|
2026-02-04 23:31:53 +01:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
private function validateBase(array $form): array
|
2026-02-04 23:31:53 +01:00
|
|
|
{
|
|
|
|
|
$errors = [];
|
|
|
|
|
if ($form['description'] === '') {
|
|
|
|
|
$errors[] = t('Description cannot be empty');
|
|
|
|
|
}
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
if ((int) ($form['tenant_id'] ?? 0) <= 0) {
|
|
|
|
|
$errors[] = t('Please select at least one tenant');
|
|
|
|
|
}
|
2026-02-04 23:31:53 +01:00
|
|
|
return $errors;
|
|
|
|
|
}
|
2026-02-11 19:28:12 +01:00
|
|
|
|
2026-03-06 00:44:52 +01:00
|
|
|
// Duplicate codes are a warning, not a hard error — codes are optional identifiers,
|
|
|
|
|
// not unique keys, so duplicates are allowed but surfaced to the user.
|
2026-02-23 12:58:19 +01:00
|
|
|
private function warningsForCode(array $form, int $excludeId): array
|
2026-02-11 19:28:12 +01:00
|
|
|
{
|
|
|
|
|
$warnings = [];
|
|
|
|
|
$code = $form['code'] ?? '';
|
2026-03-04 15:56:58 +01:00
|
|
|
if ($code !== '' && $this->departmentRepository->existsByCode($code, $excludeId)) {
|
2026-02-11 19:28:12 +01:00
|
|
|
$warnings[] = t('Department code already exists');
|
|
|
|
|
}
|
|
|
|
|
return $warnings;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
private function normalizeActive($value): int
|
2026-02-11 19:28:12 +01:00
|
|
|
{
|
|
|
|
|
$value = strtolower(trim((string) $value));
|
|
|
|
|
if (in_array($value, ['0', 'false', 'inactive'], true)) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2026-02-23 12:58:19 +01:00
|
|
|
|
2026-02-04 23:31:53 +01:00
|
|
|
}
|