Clarify self-serve entity relationships and naming intent

This commit is contained in:
Jeppe B
2026-03-11 13:54:11 +01:00
parent 6713a67eb5
commit 3ec338457e
6 changed files with 69 additions and 24 deletions
@@ -11,10 +11,18 @@ class department_selfserve_conditions_o extends db
{
use db_object_t;
/**
* Canonical relationship model:
* - A condition is a reusable logical node.
* - `condition_id` on this entity is a parent condition id, enabling nested condition trees.
* - Questions may reference a condition as their gate.
* - Tasks may reference a question as their gate (legacy stored in tasks.condition_id).
*/
public object_property $department; // The department id
public object_property $lane; // The lane id
public object_property $product; // The product id
public object_property $condition_id; // The condition id (optional)
public object_property $condition_id; // Parent condition id for nesting/grouping (nullable)
public object_property $name; // The condition name
public object_property $description; // The task description
public object_property $created_at;
@@ -34,7 +42,7 @@ class department_selfserve_conditions_o extends db
* @param int $product The product id
* @param string $name The condition name
* @param string $description The condition description
* @param int|null $condition_id The condition id (optional)
* @param int|null $condition_id Optional parent condition id for nesting/grouping.
* @return department_selfserve_conditions_o
* @throws Exception If the object was not created successfully
*/
@@ -102,4 +110,4 @@ class department_selfserve_conditions_o extends db
'updated_at' => (string)$this->updated_at->value(),
];
}
}
}
@@ -16,7 +16,14 @@ class department_selfserve_questions_o extends db
public object_property $department; // The department id
public object_property $lane; // The lane id
public object_property $product; // The product id
public object_property $condition_id; // The question condition object id (if applicable)
/**
* Canonical relationship model:
* - Question `condition_id` references `department_selfserve_conditions.id` and means
* "show/ask this question only when this parent condition is met".
* - Tasks are linked to questions (not conditions); tasks use adapter methods in tasks object
* while persisting to legacy `department_selfserve_tasks.condition_id`.
*/
public object_property $condition_id; // Parent condition id that gates this question (nullable)
public object_property $question; // The question text
public object_property $description; // The question description
public object_property $order_priority; // The order priority of the question (lower numbers are shown first)
@@ -37,7 +44,7 @@ class department_selfserve_questions_o extends db
* @param int $product The product id
* @param string $question The question text
* @param string $description The question description
* @param int|null $condition_id The question condition_id (if applicable)
* @param int|null $condition_id Optional parent condition id that gates this question.
* @param int $order_priority The order priority of the question (lower numbers are shown first)
* @return department_selfserve_questions_o
* @throws Exception If the object was not created successfully
@@ -96,6 +103,8 @@ class department_selfserve_questions_o extends db
self::requireSelected();
global $db;
$id = (int)$this->id;
// Deleting a question detaches dependent tasks by clearing the legacy
// `department_selfserve_tasks.condition_id` link (which semantically stores question id).
$sql = "UPDATE department_selfserve_tasks SET condition_id = NULL WHERE condition_id = $id";
$db->query($sql);
$this->trait_delete();
@@ -117,4 +126,4 @@ class department_selfserve_questions_o extends db
'updated_at' => (string)$this->updated_at->value(),
];
}
}
}
@@ -13,10 +13,18 @@ class department_selfserve_tasks_o extends db
{
use db_object_t;
/**
* Canonical relationship model for self-serve entities:
* - Conditions can be nested via `department_selfserve_conditions.condition_id` (parent condition id).
* - Questions can optionally be gated by a condition via `department_selfserve_questions.condition_id` (parent condition id).
* - Tasks can optionally be gated by a question. For backward compatibility this is stored in
* `department_selfserve_tasks.condition_id`, but semantically this is a question reference.
*/
public object_property $department; // The department id
public object_property $lane; // The lane id
public object_property $product; // The product id
public object_property $condition_id; // The question id (if conditional task)
public object_property $condition_id; // Legacy column name: stores question id that gates this task (nullable)
public object_property $task; // The task
public object_property $description; // The task description
public object_property $order_priority; // The order priority of the task (lower numbers are shown first)
@@ -54,7 +62,7 @@ class department_selfserve_tasks_o extends db
* @param int $department The department id
* @param int $lane The lane id
* @param int $product The product id
* @param int|null $condition_id The question id (if conditional task)
* @param int|null $question_id Optional question id that gates this task. Persisted in legacy `condition_id` column.
* @param string $task The task text
* @param string $description The task description
* @param int $order_priority The order priority of the task (lower numbers are shown first)
@@ -64,7 +72,7 @@ class department_selfserve_tasks_o extends db
* @return department_selfserve_tasks_o
* @throws Exception If the object was not created successfully
*/
public function add(int $department, int $lane, int $product, int|null $condition_id, string $task, string $description, int $order_priority = 0, ?array $services = null, array|string|null $buttons = null, int|null $dynamic_images_vehicle_type = null): self
public function add(int $department, int $lane, int $product, int|null $question_id, string $task, string $description, int $order_priority = 0, ?array $services = null, array|string|null $buttons = null, int|null $dynamic_images_vehicle_type = null): self
{
global /** @var db $db */
$db;
@@ -72,8 +80,8 @@ class department_selfserve_tasks_o extends db
$department = (int)$department;
$lane = (int)$lane;
$product = (int)$product;
if (!is_null($condition_id)) {
$condition_id = (int)$condition_id;
if (!is_null($question_id)) {
$question_id = (int)$question_id;
}
$task = $db->escape_string($task);
$description = $db->escape_string($description);
@@ -124,7 +132,7 @@ class department_selfserve_tasks_o extends db
'department' => $department,
'lane' => $lane,
'product' => $product,
...(!is_null($condition_id) ? ['condition_id' => $condition_id] : []), // If the question is null, it will be set to null in the database
...(!is_null($question_id) ? ['condition_id' => $question_id] : []), // Legacy column name; contains the gating question id
'task' => $task,
'description' => $description,
'order_priority' => $order_priority,
@@ -181,6 +189,26 @@ class department_selfserve_tasks_o extends db
];
}
/**
* Adapter for canonical naming.
* @return int|null Question id that gates this task.
*/
public function getQuestionId(): ?int
{
$value = $this->condition_id->value();
return is_null($value) ? null : (int)$value;
}
/**
* Adapter for canonical naming while persisting to legacy `condition_id` column.
* @param int|null $question_id
* @return void
*/
public function setQuestionId(?int $question_id): void
{
$this->condition_id->set(is_null($question_id) ? null : (int)$question_id);
}
/**
* Normalize input for dynamic_images_vehicle_type into a nullable non-negative integer.
* Accepts int, string (numeric), null, or empty string (treated as null).
@@ -259,4 +287,4 @@ class department_selfserve_tasks_o extends db
$ids = array_values(array_unique($ids));
return $ids;
}
}
}
@@ -98,13 +98,13 @@ class departmentSelfserveConditionsRoute
$department = (int)$response->getRequestParameter('department');
$lane = (int)$response->getRequestParameter('lane');
$product = (int)$response->getRequestParameter('product');
$condition_id = $response->getRequestParameter('condition_id');
$condition_id = $response->getRequestParameter('condition_id'); // parent condition id for nested condition trees
$condition_id = is_null($condition_id) || $condition_id === 'null' ? null : (int)$condition_id;
$name = (string)$response->getRequestParameter('name');
$description = (string)$response->getRequestParameter('description');
if (!$department || !$lane || !$product || !$name || !$description) {
$response->error('Missing required fields', 400);
$response->error('Missing required fields: department, lane, product, name, and description', 400);
}
$authorized_department_ids = $user->getGroup()->getDepartments();
@@ -143,7 +143,7 @@ class departmentSelfserveConditionsRoute
if ($user) {
$id = (int)$response->getRequestParameter('id');
if (!$id) {
$response->error('Missing required fields', 400);
$response->error('Missing required fields: id', 400);
}
$condition_o = new department_selfserve_conditions_o();
@@ -171,7 +171,7 @@ class departmentSelfserveConditionsRoute
$condition_o->product->update((int)$response->getRequestParameter('product'));
}
if ($response->isRequestParameterSet('condition_id')) {
$condition_id = $response->getRequestParameter('condition_id');
$condition_id = $response->getRequestParameter('condition_id'); // parent condition id for nested condition trees
$condition_o->condition_id->update(is_null($condition_id) || $condition_id === 'null' ? null : (int)$condition_id);
}
if ($response->isRequestParameterSet('name')) {
@@ -200,7 +200,7 @@ class departmentSelfserveConditionsRoute
if ($user) {
$id = (int)$response->getRequestParameter('id');
if (!$id) {
$response->error('Missing required fields', 400);
$response->error('Missing required fields: id', 400);
}
$condition_o = new department_selfserve_conditions_o();
@@ -100,11 +100,11 @@ class departmentSelfserveQuestionsRoute
$product = (int)$response->getRequestParameter('product');
$question = (string)$response->getRequestParameter('question');
$description = (string)$response->getRequestParameter('description');
$condition_id = $response->isRequestParameterSet('condition_id') ? (int)$response->getRequestParameter('condition_id') : null;
$condition_id = $response->isRequestParameterSet('condition_id') ? (int)$response->getRequestParameter('condition_id') : null; // parent condition id that gates this question
$order_priority = (int)($response->getRequestParameter('order_priority') ?? 0);
if (!$department || !$lane || !$product || !$question || !$description) {
$response->error('Missing required fields', 400);
$response->error('Missing required fields: department, lane, product, question, and description', 400);
}
$authorized_department_ids = $user->getGroup()->getDepartments();
@@ -106,7 +106,7 @@ class departmentSelfserveTasksRoute
$department = (int)$response->getRequestParameter('department');
$lane = (int)$response->getRequestParameter('lane');
$product = (int)$response->getRequestParameter('product');
$condition_id = $response->isRequestParameterSet('condition_id') ? (int)$response->getRequestParameter('condition_id') : null;
$question_id = $response->isRequestParameterSet('condition_id') ? (int)$response->getRequestParameter('condition_id') : null; // legacy request field: condition_id
$task = (string)$response->getRequestParameter('task');
$description = (string)$response->getRequestParameter('description');
$order_priority = (int)($response->getRequestParameter('order_priority') ?? 0);
@@ -168,7 +168,7 @@ class departmentSelfserveTasksRoute
}
if (!$department || !$lane || !$product || !$task || !$description) {
$response->error('Missing required fields', 400);
$response->error('Missing required fields: department, lane, product, task, and description', 400);
}
$authorized_department_ids = $user->getGroup()->getDepartments();
@@ -181,7 +181,7 @@ class departmentSelfserveTasksRoute
$department,
$lane,
$product,
$condition_id,
$question_id,
$task,
$description,
$order_priority,
@@ -237,7 +237,7 @@ class departmentSelfserveTasksRoute
$task_o->product->set((int)self::getParameter('product'));
}
if (self::isParametersSet(['condition_id'])) {
$task_o->condition_id->set(self::getParameter('condition_id') === null ? null : (int)self::getParameter('condition_id'));
$task_o->setQuestionId(self::getParameter('condition_id') === null ? null : (int)self::getParameter('condition_id'));
}
if (self::isParametersSet(['task'])) {
$task_o->task->set((string)self::getParameter('task'));