setTable('department_lanes'); } public function getLaneStatus(): \modules\selfserve\helpers\selfserve_lane_status { self::requireSelected(); $selfserve = new selfserve(); $lane = $selfserve->lane((int)$this->id); return $lane->getLaneStatus(); } /** * Add a department lane * @param int $department The department id * @param string $name The lane name * @param string|null $relay_in_id The Shelly relay for the entrance port (if applicable) * @param string|null $relay_out_id The Shelly relay for the exit port (if applicable) * @param string|null $relay_machine_id The Shelly relay for the machine (if applicable) * @param int|null $dynamic_image_id The dynamic image id for the lane (if applicable) * @return department_lanes_o * @throws Exception If the object was not created successfully */ public function add(int $department, string $name, string $relay_in_id = null, string $relay_out_id = null, string $relay_machine_id = null, string $relay_machine_program_picker_id = null, string $relay_machine_cleaner_id = null, int $dynamic_image_id = null, ?int $machine_type_id = null, bool $selfserve_enabled = true): department_lanes_o { global /** @var db $db */ $db; // Sanitize the input $department = (int)$department; $name = $db->escape_string($name); if (!is_null($relay_in_id)) { $relay_in_id = $db->escape_string($relay_in_id); } if (!is_null($relay_out_id)) { $relay_out_id = $db->escape_string($relay_out_id); } if (!is_null($relay_machine_id)) { $relay_machine_id = $db->escape_string($relay_machine_id); } if (!is_null($relay_machine_program_picker_id)) { $relay_machine_program_picker_id = $db->escape_string($relay_machine_program_picker_id); } if (!is_null($relay_machine_cleaner_id)) { $relay_machine_cleaner_id = $db->escape_string($relay_machine_cleaner_id); } if (!is_null($dynamic_image_id)) { $dynamic_image_id = (int)$dynamic_image_id; if ($dynamic_image_id <= 0) { throw new Exception('dynamic_image_id must be a positive integer'); } } if (!is_null($machine_type_id)) { $machine_type_id = (int)$machine_type_id; if ($machine_type_id <= 0) { throw new Exception('machine_type_id must be a positive integer'); } } // Add the object $tmp_id = self::add_object([ 'department' => $department, 'name' => $name, ...(!is_null($relay_in_id) ? ['relay_in_id' => $relay_in_id] : []), // If the relay_in_id is null, it will be set to null in the database ...(!is_null($relay_out_id) ? ['relay_out_id' => $relay_out_id] : []), // If the relay_out_id is null, it will be set to null in the database ...(!is_null($relay_machine_id) ? ['relay_machine_id' => $relay_machine_id] : []), // If the relay_machine_id is null, it will be set to null in the database ...(!is_null($relay_machine_program_picker_id) ? ['relay_machine_program_picker_id' => $relay_machine_program_picker_id] : []), ...(!is_null($relay_machine_cleaner_id) ? ['relay_machine_cleaner_id' => $relay_machine_cleaner_id] : []), ...(!is_null($dynamic_image_id) ? ['dynamic_image_id' => $dynamic_image_id] : []), // If the dynamic_image_id is null, it will be set to null in the database ...(!is_null($machine_type_id) ? ['machine_type_id' => $machine_type_id] : []), 'selfserve_enabled' => $selfserve_enabled ? 1 : 0, ]); $this->id = $tmp_id; self::getObjectProperties(); self::objectChanged(); return $this; } public function getObjectProperties(): void { $this->department = new object_property($this->table, $this->id, 'department', 'int', false); $this->name = new object_property($this->table, $this->id, 'name', 'string', false); $this->relay_in_id = new object_property($this->table, $this->id, 'relay_in_id', 'string', false); $this->relay_out_id = new object_property($this->table, $this->id, 'relay_out_id', 'string', false); $this->relay_machine_id = new object_property($this->table, $this->id, 'relay_machine_id', 'string', false); $this->relay_machine_program_picker_id = new object_property($this->table, $this->id, 'relay_machine_program_picker_id', 'string', false); $this->relay_machine_cleaner_id = new object_property($this->table, $this->id, 'relay_machine_cleaner_id', 'string', false); $this->dynamic_image_id = new object_property($this->table, $this->id, 'dynamic_image_id', 'int', false); $this->machine_type_id = new object_property($this->table, $this->id, 'machine_type_id', 'int', false); $this->selfserve_enabled = new object_property($this->table, $this->id, 'selfserve_enabled', 'bool', false, true); $this->created_at = new object_property($this->table, $this->id, 'created_at', 'timestamp', false); $this->updated_at = new object_property($this->table, $this->id, 'updated_at', 'timestamp', false); $this->deleted_at = new object_property($this->table, $this->id, 'deleted_at', 'timestamp', false); } public function objectChanged(): void { //TODO: Add cache invalidation } public function asArray(): array { $status = (string)$this->getLaneStatus()->name; $machine_status_audit = $this->getMachineStatusAudit(); $selfserve_configuration_warnings = $this->getSelfServeConfigurationWarnings(); return [ 'id' => (int)$this->id, 'department' => (int)$this->department->value(), 'name' => (string)$this->name->value(), 'relay_in_id' => (string)$this->relay_in_id->value(), 'relay_out_id' => (string)$this->relay_out_id->value(), 'relay_machine_id' => (string)$this->relay_machine_id->value(), 'relay_machine_program_picker_id' => (string)$this->relay_machine_program_picker_id->value(), 'relay_machine_cleaner_id' => (string)$this->relay_machine_cleaner_id->value(), 'dynamic_image_id' => (function($v){ return $v === null ? null : (int)$v; })($this->dynamic_image_id->value()), 'machine_type_id' => (function($v){ return $v === null ? null : (int)$v; })($this->machine_type_id->value()), 'selfserve_enabled' => $this->isSelfServeEnabled(), // Status of the lane 'status' => $status, 'machine_status_enabled' => self::isOperationalStatusName($status), 'machine_status_audit' => $machine_status_audit, 'machine_status_modified_at' => $machine_status_audit['modified_at'] ?? null, 'machine_status_modified_by' => $machine_status_audit['modified_by_name'] ?? null, 'machine_status_modified_by_user_id' => $machine_status_audit['modified_by_user_id'] ?? null, 'selfserve_configured' => $selfserve_configuration_warnings === [], 'dognvask_configured' => $selfserve_configuration_warnings === [], 'dognvask_configuration_warnings' => $selfserve_configuration_warnings, // Timestamps 'created_at' => (string)$this->created_at->value(), 'updated_at' => (string)$this->updated_at->value(), ]; } private function getMachineStatusAudit(): ?array { try { $lane = (new selfserve())->lane((int)$this->id); if (!method_exists($lane, 'getLaneStatusAudit')) { return null; } $audit = $lane->getLaneStatusAudit(); return is_array($audit) ? $audit : null; } catch (\Throwable) { return null; } } public function getSelfServeConfigurationWarnings(): array { self::requireSelected(); $required_fields = [ 'relay_in_id' => 'Indgangsrelæ', 'relay_out_id' => 'Udgangsrelæ', 'relay_machine_id' => 'Maskinrelæ', 'relay_machine_program_picker_id' => 'Programvælgerrelæ', 'relay_machine_cleaner_id' => 'Vaskerelæ', 'dynamic_image_id' => 'Maskinstatusbillede', 'machine_type_id' => 'Maskintype', ]; $warnings = []; foreach ($required_fields as $field => $label) { if ($this->hasConfiguredFieldValue($field)) { continue; } $warnings[] = [ 'field' => $field, 'label' => $label, 'message' => $label . ' mangler', ]; } return $warnings; } public function isSelfServeConfigured(): bool { return $this->getSelfServeConfigurationWarnings() === []; } public static function isOperationalStatusName(string $status): bool { return in_array(strtoupper(trim($status)), ['AVAILABLE', 'OCCUPIED', 'RESERVED'], true); } public function isSelfServeEnabled(): bool { self::requireSelected(); try { $value = $this->selfserve_enabled->value(); } catch (\Throwable) { return true; } if ($value === null || $value === '') { return true; } if (is_bool($value)) { return $value; } if (is_numeric($value)) { return (int)$value === 1; } return in_array(strtolower(trim((string)$value)), ['1', 'true', 'yes', 'on'], true); } public static function normalizeSelfServeEnabledValue(mixed $value): bool { if (is_bool($value)) { return $value; } if (is_numeric($value)) { return (int)$value === 1; } return in_array(strtolower(trim((string)$value)), ['1', 'true', 'yes', 'on'], true); } private function hasConfiguredFieldValue(string $field): bool { if (!isset($this->{$field}) || !is_object($this->{$field}) || !method_exists($this->{$field}, 'value')) { return false; } $value = $this->{$field}->value(); if ($value === null) { return false; } if (is_string($value)) { $value = trim($value); return $value !== '' && $value !== '0' && strtolower($value) !== 'null'; } if (is_numeric($value)) { return (int)$value > 0; } return (bool)$value; } public static function disableSelfServeRelaysBestEffort(int $lane_id): void { if ($lane_id <= 0) { return; } try { $lane = (new selfserve())->lane($lane_id); } catch (\Throwable) { return; } self::setLaneRelayOffIfConfigured($lane, 'relay_machine_program_picker_id', static function () use ($lane): void { $lane->setMachineProgramPickerRelayStatusHard(false); }); self::setLaneRelayOffIfConfigured($lane, 'relay_machine_cleaner_id', static function () use ($lane): void { $lane->setMachineCleanerRelayStatusHard(false); }); self::setLaneRelayOffIfConfigured($lane, 'relay_machine_id', static function () use ($lane): void { $lane->setMachineRelayStatusHard(false); }); } private static function setLaneRelayOffIfConfigured(object $lane, string $relay_property, callable $callback): void { if ( empty($lane->department_lane) || !isset($lane->department_lane->{$relay_property}) || !is_object($lane->department_lane->{$relay_property}) || !method_exists($lane->department_lane->{$relay_property}, 'value') || trim((string)$lane->department_lane->{$relay_property}->value()) === '' ) { return; } try { $callback(); } catch (\Throwable) { // Best effort only; toggling lane self-serve should not fail on relay I/O. } } /** * Get the self-serve lane products available for this lane * @return array An array of product ids available for this lane * @throws Exception */ public function getSelfServeLaneProducts(): array { self::requireSelected(); $publishedProducts = $this->getPublishedSelfServeLaneProducts(); if ($publishedProducts !== []) { return $publishedProducts; } return department_selfserve_tasks_o::getLaneProducts((int)$this->id); } /** * @return int[] */ private function getPublishedSelfServeLaneProducts(): array { try { $published = (new selfserve_config_versioning())->getPublishedV2Config((int)$this->department->value()); } catch (\Throwable) { return []; } $config = is_array($published['config'] ?? null) ? $published['config'] : null; if ($config === null) { return []; } $laneId = (int)$this->id; $products = []; foreach ((array)($config['tasks'] ?? []) as $task) { if (!is_array($task)) { continue; } $taskLane = (int)($task['lane'] ?? 0); if ($taskLane !== 0 && $taskLane !== $laneId) { continue; } $productId = (int)($task['product'] ?? 0); if ($productId > 0 && !in_array($productId, $products, true)) { $products[] = $productId; } } sort($products, SORT_NUMERIC); return $products; } /** * @throws Exception * @return department_lanes_o[] An array of department lane objects for the specified department */ public function getDepartmentLanes(int $department_id): array { $lanes = []; $department_lanes = self::getFieldsWhere( [ 'department' => $department_id, 'deleted_at' => null, ], [ 'id', ], ); foreach ( $department_lanes as $department_lane ) { $lanes[] = (new department_lanes_o())->select((int)$department_lane['id']); } return $lanes; } }