getRequestParameter('filters') ?? []; if (is_string($filters) || is_array($filters)) { $filters = $departments->filter_string_to_array($filters); } else { $filters = []; } $archived = 0; if ( $canListArchived && array_key_exists('archived', $filters) && self::isTruthyBooleanValue($filters['archived']) ) { $archived = 1; } unset($filters['visible'], $filters['archived']); $filters['visible'] = 1; $filters['archived'] = $archived; if ($departmentScope !== null) { $filters['id'] = array_values(array_map('intval', $departmentScope)); } return $filters; } private static function isTruthyBooleanValue(mixed $value): bool { if (is_array($value)) { foreach ($value as $singleValue) { if (self::isTruthyBooleanValue($singleValue)) { return true; } } return false; } if (is_bool($value)) { return $value; } if (is_numeric($value)) { return (int)$value === 1; } if (is_string($value)) { return in_array(strtolower(trim($value)), ['1', 'true', 'yes', 'on'], true); } return false; } public function run(): void { $this->get('/departments', function () { // Require the user to be logged in global $response; $this->requirePermission('list_departments'); // Get the user object $user = (new authentication())->get_user(); // Check if the request was successful if ($user) { $departmentScope = $this->limitedBackofficeDepartmentScope($user); // Log the incident (new logs_o())->add('departments', 'global', 1, $user->id, 'LIST_DEPARTMENTS', 'Successfully listed departments'); // Check if the id is set in the request if (self::isParametersSet(['id'])) { $departmentId = (int)self::getParameter('id'); $this->requireLimitedBackofficeDepartmentAccess($user, $departmentId); // Return the department $response->success( (new departments_o())->select($departmentId)->asArray([ 'slack_webhook' => $user->hasPermission('view_slack_webhook') ]) ); } if ($departmentScope === []) { $response->success([]); } $departments_o = new departments_o(); // Return the list of departments $response->success( $departments_o ->setSearchableFields([ // The fields that can be searched. This would otherwise make it possible to get secret information from the database, simply by searching for it and getting the result count back 'id', 'name', 'description', 'economic_department_id', 'visible', 'archived', 'custom_pricing_only', 'longitude', 'latitude', ]) ->listObjectsWithPaginationIfSet( function ($department) use ($user) { $tmp_department = [ 'id' => (int)$department['id'], 'name' => (string)$department['name'], 'description' => $department['description'], 'economic_department_id' => (int)$department['economic_department_id'], 'created_at' => (string)$department['created_at'], 'updated_at' => (string)$department['updated_at'], 'visible' => (int)$department['visible'], 'dimension' => (int)$department['dimension'], 'branding' => (int)$department['branding'], 'archived' => (bool)(int)($department['archived'] ?? 0), 'longitude' => (float)$department['longitude'], 'latitude' => (float)$department['latitude'], 'order_priority' => (int)$department['order_priority'], ]; if ( $user->hasPermission('superuser_fetch_department') || $user->hasPermission('edit_department') ) { $tmp_department['custom_pricing_only'] = (bool)(int)($department['custom_pricing_only'] ?? 0); } // If the user has the permission to view the slack webhook, add it to the response if ($user->hasPermission('view_slack_webhook')) { $tmp_department['slack_webhook'] = $department['slack_webhook']; } return $tmp_department; }, $this->buildDepartmentListFilters( $departments_o, $user->hasPermission('superuser_fetch_department'), $departmentScope ) ) ); } else { // Log the incident (new logs_o())->add('departments', 'global', 1, 0, 'LIST_DEPARTMENTS', 'No user found, or invalid session'); // Return an error $response->error('Invalid session', 400); } }, [ 'list_departments' => 'List all departments', 'view_slack_webhook' => 'View the slack webhook' ] ); $this->post('/departments', function () { // Require the user to be logged in global $response; $this->requirePermission('add_department'); // Get the user object $user = (new authentication())->get_user(); // Check if the request was successful if ($user) { // Get the post data $data = json_decode(file_get_contents('php://input'), true); // Check if the required fields are set if (!isset($data['name'])) { $response->error('Name is required', 400); } if (!isset($data['description'])) { $response->error('Description is required', 400); } // Add the department (new departments_o())->create($data['name'], $data['description']); // Log the incident (new logs_o())->add('departments', 'global', 1, $user->id, 'ADD_DEPARTMENT', 'Successfully added a department ' . $data['name']); // Return a success message $response->success(['message' => 'Department added successfully']); } else { // Log the incident (new logs_o())->add('departments', 'global', 1, 0, 'ADD_DEPARTMENT', 'No user found, or invalid session'); // Return an error $response->error('Invalid session', 400); } }, [ 'add_department' => 'Add a department' ] ); $this->put('/departments', function () { // Require the user to be logged in global $response; $this->requirePermission('edit_department'); // Get the user object $user = (new authentication())->get_user(); // Check if the request was successful if ($user) { // Require the required fields self::requireParameters(['id']); // Validate the required fields self::requireType((int)self::getParameter('id'), self::TYPE_INT()); // Get the department object $department = (new departments_o())->select(self::getParameter('id')); // Update the fields provided if (self::isParametersSet(['name'])) { $department->name->set(self::getParameter('name')); } if (self::isParametersSet(['description'])) { $department->description->set(self::getParameter('description')); } if (self::isParametersSet(['economic_department_id'])) { $department->economic_department_id->set(self::getParameter('economic_department_id')); } if (self::isParametersSet(['latitude'])) { $department->latitude->set(self::getParameter('latitude')); } if (self::isParametersSet(['longitude'])) { $department->longitude->set(self::getParameter('longitude')); } if (self::isParametersSet(['order_priority'])) { $department->order_priority->set((int)self::getParameter('order_priority')); } if (self::isParametersSet(['archived'])) { $department->archived->set(self::isTruthyBooleanValue(self::getParameter('archived'))); } if (self::isParametersSet(['custom_pricing_only'])) { $department->custom_pricing_only->set(self::isTruthyBooleanValue(self::getParameter('custom_pricing_only'))); } $department->objectChanged(); // Log the incident (new logs_o())->add('departments', (int)self::getParameter('id'), 1, $user->id, 'EDIT_DEPARTMENT', 'Successfully edited a department'); // Return a success message $response->success(['message' => 'Department updated successfully']); } else { // Log the incident (new logs_o())->add('departments', 'global', 1, 0, 'EDIT_DEPARTMENT', 'No user found, or invalid session'); // Return an error $response->error('Invalid session', 400); } }, [ 'edit_department' => 'Edit a department' ] ); $this->get('/departments/categories', function () { // Require the user to be logged in global $response; $auth = new authentication(); $user = $auth->get_user(); $subuser = $auth->get_subuser(); // Check if the request was successful if ($user || $subuser) { $isCustomerBookingSession = ($user && $this->hasPermission('user')) || $subuser; if (!$isCustomerBookingSession && !$this->hasPermission('list_department_categories')) { $this->emitForbidden(['list_department_categories']); } $responsibleUserId = $user ? (int)$user->id : 0; // Require the department id self::requireParameters(['id']); self::requireType((int)self::getParameter('id'), self::TYPE_INT()); // Get the department object $department = (new departments_o())->select(self::getParameter('id')); // Validate the department categories object if (!$department->exists()) { // Log the incident (new logs_o())->add('departments', 'global', 1, $responsibleUserId, 'LIST_DEPARTMENT_CATEGORIES', 'Department categories not found'); // Return an error $response->error('Department categories not found', 400); } // Get the department categories $department_categories = new department_categories_o(); // Log the incident (new logs_o())->add('departments', 'global', 1, $responsibleUserId, 'LIST_DEPARTMENT_CATEGORIES', 'Successfully listed department categories'); // Return the list of department categories $response->success( $department_categories ->getCategoriesForDepartment(self::getParameter('id'), function ($department_category) { return [ 'id' => (int)$department_category['id'], 'department_id' => (int)$department_category['department_id'], 'category_id' => (int)$department_category['category_id'], 'created_at' => (string)$department_category['created_at'], 'updated_at' => $department_category['updated_at'], 'category' => $department_category['category']->asArray() ]; } ) ); } else { // Log the incident (new logs_o())->add('departments', 'global', 1, 0, 'LIST_DEPARTMENT_CATEGORIES', 'No user found, or invalid session'); // Return an error $response->error('Invalid session', 400); } }, [ 'list_department_categories' => 'List all department categories. Authenticated customer booking sessions may read this endpoint without the permission.' ] ); $this->get('/departments/self-serve/enabled', function () { // Require the user to be logged in global $response; $this->requirePermission('view_department_selfserve_enabled'); // Get the user object $user = (new authentication())->get_user(); // Check if the request was successful if ($user) { // Require the department id self::requireParameters(['id']); self::requireType((int)self::getParameter('id'), self::type_int()); // Get the department object $department = (new departments_o())->select(self::getParameter('id')); // Validate the department object if (!$department->exists()) { // Return an error $response->error('Department not found', 404); } // Check if the user has access to the department self::requireDepartmentAccess((string)$department->id); // Get the department variable $department_variables = (new department_variables_o())->selectDepartment($department->id); $enabled = $department_variables->getVariable('selfserve_enabled'); // Return the status $response->success( $this->buildDepartmentSelfServeEnabledPayload((int)$department->id, $enabled === true) ); } else { // Return an error $response->error('Invalid session', 400); } }, [ 'view_department_selfserve_enabled' => 'View if department self-serve is enabled', 'department_access_:id' => 'Access the department' ] ); $this->put('/departments/self-serve/enabled', function () { // Require the user to be logged in global $response; $this->requirePermission('edit_department_selfserve_enabled'); // Get the user object $user = (new authentication())->get_user(); // Check if the request was successful if ($user) { // Require the department id and enabled status self::requireParameters(['id', 'enabled']); self::requireType((int)self::getParameter('id'), self::type_int()); self::requireType((bool)self::getParameter('enabled'), self::type_bool()); // Get the department object $department = (new departments_o())->select(self::getParameter('id')); // Validate the department object if (!$department->exists()) { // Return an error $response->error('Department not found', 404); } // Check if the user has access to the department self::requireDepartmentAccess((string)$department->id); // Set the department variable $department_variables = (new department_variables_o())->selectDepartment($department->id); $enabled = self::getParameter('enabled') === 'true' || self::getParameter('enabled') === true || self::getParameter('enabled') === 1 || self::getParameter('enabled') === '1'; department_variables_o::withSelfServeTransitionLock( (int)$department->id, function () use ($department_variables, $department, $enabled): void { $department_variables->set('selfserve_enabled', $enabled ? 'true' : 'false'); $this->syncDepartmentSelfServeRelayStates((int)$department->id, $enabled); } ); // Log the incident (new logs_o())->add('departments', $department->id, 1, $user->id, 'EDIT_DEPARTMENT_SELFSERVE_ENABLED', 'Successfully edited department self-serve enabled status to ' . ($enabled ? 'true' : 'false')); // Return a success message $response->success([ 'message' => 'Department self-serve enabled status updated successfully', ...$this->buildDepartmentSelfServeEnabledPayload((int)$department->id, $enabled) ]); } else { // Return an error $response->error('Invalid session', 400); } }, [ 'edit_department_selfserve_enabled' => 'Edit if department self-serve is enabled (Requires department access)', 'department_access_:id' => 'Access the department' ] ); $this->post('/departments/categories', function () { // Require the user to be logged in global $response; $this->requirePermission('add_department_category'); // Get the user object $user = (new authentication())->get_user(); // Check if the request was successful if ($user) { // Require the parameters self::requireParameters(['department_id', 'category_id']); self::requireType((int)self::getParameter('department_id'), self::TYPE_INT()); self::requireType((int)self::getParameter('category_id'), self::TYPE_INT()); // Get the department object $department = (new departments_o())->select(self::getParameter('department_id')); // Validate the department object if (!$department->exists()) { // Log the incident (new logs_o())->add('departments', 'global', 1, $user->id, 'ADD_DEPARTMENT_CATEGORY', 'Department not found'); // Return an error $response->error('Department not found', 400); } // Get the category object $category = (new categories_o())->select(self::getParameter('category_id')); // Validate the category object if (!$category->exists()) { // Log the incident (new logs_o())->add('departments', 'global', 1, $user->id, 'ADD_DEPARTMENT_CATEGORY', 'Category not found'); // Return an error $response->error('Category not found', 400); } // Add the department category $department_categories = new department_categories_o(); $department_categories->add( self::getParameter('department_id'), self::getParameter('category_id') ); // Log the incident (new logs_o())->add('departments', 'global', 1, $user->id, 'ADD_DEPARTMENT_CATEGORY', 'Successfully added a department category'); // Return a success message $response->success(['message' => 'Department category added successfully']); } else { // Log the incident (new logs_o())->add('departments', 'global', 1, 0, 'ADD_DEPARTMENT_CATEGORY', 'No user found, or invalid session'); // Return an error $response->error('Invalid session', 400); } }, [ 'add_department_category' => 'Add a department category' ] ); $this->delete('/departments/categories', function () { // Require the user to be logged in global $response; $this->requirePermission('delete_department_category'); // Get the user object $user = (new authentication())->get_user(); // Check if the request was successful if ($user) { // Require the parameters self::requireParameters(['id']); self::requireType((int)self::getParameter('id'), self::TYPE_INT()); // Get the department category object $department_category = (new department_categories_o())->select(self::getParameter('id')); // Validate the department category object if (!$department_category->exists()) { // Log the incident (new logs_o())->add('departments', 'global', 1, $user->id, 'DELETE_DEPARTMENT_CATEGORY', 'Department category not found'); // Return an error $response->error('Department category not found', 400); } // Delete the department category $department_category->delete(); // Log the incident (new logs_o())->add('departments', 'global', 1, $user->id, 'DELETE_DEPARTMENT_CATEGORY', 'Successfully deleted a department category'); // Return a success message $response->success(['message' => 'Department category deleted successfully']); } else { // Log the incident (new logs_o())->add('departments', 'global', 1, 0, 'DELETE_DEPARTMENT_CATEGORY', 'No user found, or invalid session'); // Return an error $response->error('Invalid session', 400); } }, [ 'delete_department_category' => 'Delete a department category' ] ); self::get('/departments/order/recommended', function () { // Require the user to be logged in global $response; $this->requirePermission('list_department_order_recommended'); // Get the user object $user = (new authentication())->get_user(); // Check if the request was successful if ($user) { // Check if the required fields are set self::requireParameters(['id']); self::requireType((int)self::getParameter('id'), self::TYPE_INT()); // Get the order $order = (new orders_o())->select((int)self::getParameter('id')); $order->requireSelected(); // Check if the user is allowed to view the recommended order for the department self::requireDepartmentAccess($order->department_id->value()); // Get the recommended order $recommended_order = $order->getRecommendedOrder(); // Log the incident (new logs_o())->add('departments', 'global', 1, $user->id, 'LIST_DEPARTMENT_ORDER_RECOMMENDED', 'Successfully listed the recommended department order'); // Return the recommended order $response->success($recommended_order); } else { // Log the incident (new logs_o())->add('departments', 'global', 1, 0, 'LIST_DEPARTMENT_ORDER_RECOMMENDED', 'No user found, or invalid session'); // Return an error $response->error('Invalid session', 400); } }, [ 'list_department_order_recommended' => 'List the recommended department order', 'order_access_:id' => 'Access the order', 'department_access_:id' => 'Access the department' ] ); self::get('/departments/weekly-results', function () { // Require the user to be logged in global $response; $this->requirePermission('view_department_weekly_results'); // Get the user object $user = (new authentication())->get_user(); // Check if the request was successful if ($user) { // Get the department results for the week $departments_o = new departments_o(); $weeks = 3; $results = []; for ($i = 0; $i < $weeks; $i++) { // Get the week number $week_number = date('W', strtotime("-$i week")); // Skip the next week, only show show weeks where at least monday at 00:00:00 is in the past, to prevent showing incomplete data for the current week // Get the weeks monday $week_start = strtotime("-$i week Monday"); if ($week_start < strtotime('now')) { $week_monday = date('Y-m-d 00:00:00', $week_start); // Get the weeks sunday from monday $week_sunday = date('Y-m-d 23:59:59', strtotime("$week_monday +6 days")); $results[$week_number] = [ 'week_number' => $week_number, 'week_monday' => $week_monday, 'week_sunday' => $week_sunday, 'results' => $departments_o->sendSlackInternalStatisticNotification( $week_monday, $week_sunday, [], // Default value [ 25, [23, 24], // Used to merge two products into one percentage (Spot Free) 22, 27, 21, 26 ], true // Return the results instead of sending the notification (This is used to show the results in the frontend, instead of sending them to slack. ) ]; } } // Log the incident (new logs_o())->add('departments', 'global', 1, $user->id, 'VIEW_DEPARTMENT_WEEKLY_RESULTS', 'Successfully viewed department weekly results'); // Return the results $response->success($results); } else { // Log the incident (new logs_o())->add('departments', 'global', 1, 0, 'VIEW_DEPARTMENT_WEEKLY_RESULTS', 'No user found, or invalid session'); // Return an error $response->error('Invalid session', 400); } }, [ 'view_department_weekly_results' => 'View department weekly results' ] ); } protected function syncDepartmentSelfServeRelayStates(int $departmentId, bool $enabled): void { $selfserve = new selfserve(); $lanes = (new department_lanes_o())->getDepartmentLanes($departmentId); foreach ($lanes as $department_lane) { $lane_id = (int)$department_lane->id; if ($lane_id <= 0) { continue; } try { $lane = $selfserve->lane($lane_id); } catch (\Throwable) { continue; } if (!$enabled) { // Self-serve disabled: restore normal/manual relay operation. $this->setOptionalLaneRelayState($lane, 'relay_machine_program_picker_id', static function () use ($lane): void { $lane->setMachineProgramPickerRelayStatusForDepartmentOperation(true); }); $this->setOptionalLaneRelayState($lane, 'relay_machine_cleaner_id', static function () use ($lane): void { $lane->setMachineCleanerRelayStatusForDepartmentOperation(true); }); $this->setOptionalLaneRelayState($lane, 'relay_machine_id', static function () use ($lane): void { $lane->setMachineRelayStatusForDepartmentOperation(true); }); continue; } if (!$department_lane->isSelfServeEnabled()) { continue; } // Self-serve enabled: lances must be usable; machine-only relays stay off. $this->setOptionalLaneRelayState($lane, 'relay_machine_program_picker_id', static function () use ($lane): void { $lane->setMachineProgramPickerRelayStatus(false); }); $this->setOptionalLaneRelayState($lane, 'relay_machine_cleaner_id', static function () use ($lane): void { $lane->setMachineCleanerRelayStatusHard(true); }); try { $lane->setMachineRelayStatus(false); } catch (\Throwable) {} } } protected function setOptionalLaneRelayState(object $lane, string $relayProperty, callable $callback): void { if ( empty($lane->department_lane) || !isset($lane->department_lane->{$relayProperty}) || !is_object($lane->department_lane->{$relayProperty}) || !method_exists($lane->department_lane->{$relayProperty}, 'value') ) { return; } $relay_id = trim((string)$lane->department_lane->{$relayProperty}->value()); if ($relay_id === '') { return; } try { $callback(); } catch (\Throwable) { // Best effort only; this endpoint should still update the department variable. } } private function buildDepartmentSelfServeEnabledPayload(int $departmentId, bool $enabled): array { $nextDeactivationAt = $enabled ? $this->getDepartmentSelfServeAutoDeactivationAt($departmentId) : null; return [ 'enabled' => $enabled, 'auto_deactivation' => [ 'at' => $nextDeactivationAt?->format(DATE_ATOM), 'timezone' => 'Europe/Copenhagen', 'label' => $nextDeactivationAt === null ? 'NEVER' : $nextDeactivationAt->format('Y-m-d H:i:s'), ], ]; } private function getDepartmentSelfServeAutoDeactivationAt(int $departmentId): ?DateTimeImmutable { try { $openingHours = new department_time_bookings_opening_hours_o(); if (!$openingHours->selectExistingByDepartment($departmentId)) { return null; } return $openingHours->getNextOpeningStart( new DateTimeImmutable('now', new DateTimeZone('Europe/Copenhagen')) ); } catch (\Throwable) { return null; } } }