Add new table and enhance studio layout logic
Introduce `department_selfserve_studio_layouts` table for department-specific layouts and implement advanced auto-layout functionality in the DepartmentSelfServeStudio module. Added custom node definitions, updated styling, and integrated new logics for sorting and visualizing nodes in the Vue Flow interface.
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
app_require('modules/selfserve/classes/selfserve_studio_graph.php');
|
||||
|
||||
use modules\selfserve\classes\selfserve_studio_graph;
|
||||
|
||||
function selfserve_studio_graph_without_constructor(): selfserve_studio_graph
|
||||
{
|
||||
$reflection = new ReflectionClass(selfserve_studio_graph::class);
|
||||
return $reflection->newInstanceWithoutConstructor();
|
||||
}
|
||||
|
||||
it('serializes questions, conditions, tasks, scopes, and gateways into one graph', function (): void {
|
||||
$service = selfserve_studio_graph_without_constructor();
|
||||
|
||||
$graph = $service->buildGraphFromConfig([
|
||||
'questions' => [
|
||||
['id' => 1, 'question' => 'Are mirrors folded?', 'condition_id' => 10, 'lane' => 7, 'product' => 3, 'department' => 2, 'order_priority' => 1],
|
||||
],
|
||||
'conditions' => [
|
||||
['id' => 10, 'name' => 'Trailer present', 'condition_id' => null, 'lane' => 7, 'product' => 3, 'department' => 2],
|
||||
],
|
||||
'rules' => [
|
||||
['id' => 20, 'condition_id' => 10, 'type' => 'IS_TRUE', 'object_type' => 'question', 'object_id' => 1, 'name' => 'Mirror answer'],
|
||||
],
|
||||
'tasks' => [
|
||||
['id' => 30, 'task' => 'Fold mirrors', 'gate_type' => 'QUESTION', 'gate_ref_id' => 1, 'lane' => 7, 'product' => 3, 'department' => 2, 'order_priority' => 1],
|
||||
],
|
||||
], [
|
||||
'lookups' => [
|
||||
'departments' => [['id' => 2, 'label' => 'Roskilde']],
|
||||
'lanes' => [['id' => 7, 'label' => 'Lane 7']],
|
||||
'products' => [['id' => 3, 'label' => 'Forvogn']],
|
||||
'machine_types' => [],
|
||||
'vehicle_types' => [['id' => 3, 'product' => 3, 'label' => 'Forvogn', 'source' => 'products']],
|
||||
'labels' => [
|
||||
'departments' => ['2' => 'Roskilde'],
|
||||
'lanes' => ['7' => 'Lane 7'],
|
||||
'products' => ['3' => 'Forvogn'],
|
||||
'vehicle_types' => ['3' => 'Forvogn'],
|
||||
'questions' => ['1' => 'Are mirrors folded?'],
|
||||
'conditions' => ['10' => 'Trailer present'],
|
||||
'tasks' => ['30' => 'Fold mirrors'],
|
||||
],
|
||||
],
|
||||
'gateway_workspace' => [
|
||||
'gateways' => [
|
||||
[
|
||||
'id' => 50,
|
||||
'label' => 'Gateway A',
|
||||
'status' => 'ONLINE',
|
||||
'bindings' => [
|
||||
['relay_id' => 'relay-1', 'label' => 'Machine relay'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'relays' => [
|
||||
['relay_id' => 'relay-1', 'name' => 'Machine relay'],
|
||||
],
|
||||
'lanes' => [
|
||||
[
|
||||
'id' => 7,
|
||||
'relay_slots' => [
|
||||
['relay_id' => 'relay-1', 'slot' => 'MACHINE'],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$nodeIds = array_column($graph['nodes'], 'id');
|
||||
$edgeIds = array_column($graph['edges'], 'id');
|
||||
|
||||
expect($nodeIds)->toContain('question:1');
|
||||
expect($nodeIds)->toContain('condition:10');
|
||||
expect($nodeIds)->toContain('rule:20');
|
||||
expect($nodeIds)->toContain('task:30');
|
||||
expect($nodeIds)->toContain('vehicle_type:3');
|
||||
expect($nodeIds)->toContain('gateway:50');
|
||||
expect($nodeIds)->toContain('relay:relay-1');
|
||||
expect($edgeIds)->toContain('question-gate:10:1');
|
||||
expect($edgeIds)->toContain('task-gate:question:1:30');
|
||||
expect($edgeIds)->toContain('scope:vehicle_type:3:question:1');
|
||||
expect($edgeIds)->toContain('scope:vehicle_type:3:condition:10');
|
||||
expect($edgeIds)->toContain('scope:vehicle_type:3:task:30');
|
||||
expect($edgeIds)->toContain('gateway-binding:50:relay-1:0');
|
||||
expect($edgeIds)->toContain('relay-lane:relay-1:7:MACHINE');
|
||||
});
|
||||
|
||||
it('derives studio vehicle type lookup rows from selectable wash products', function (): void {
|
||||
$service = selfserve_studio_graph_without_constructor();
|
||||
$method = new ReflectionMethod(selfserve_studio_graph::class, 'vehicleTypeRowsFromProducts');
|
||||
|
||||
$vehicleTypes = $method->invoke($service, [
|
||||
['id' => 3, 'name' => 'Forvogn', 'description' => 'Front vehicle', 'is_wash' => 1, 'subscription_allowed' => 1, 'order_priority' => 10],
|
||||
['id' => 4, 'name' => 'Trækker', 'description' => 'Tractor unit', 'is_wash' => '1', 'subscription_allowed' => '1', 'order_priority' => 20],
|
||||
['id' => 5, 'name' => 'Addon', 'description' => '', 'is_wash' => 0, 'subscription_allowed' => 1, 'order_priority' => 30],
|
||||
['id' => 6, 'name' => 'Internal wash', 'description' => '', 'is_wash' => 1, 'subscription_allowed' => 0, 'order_priority' => 40],
|
||||
]);
|
||||
|
||||
expect(array_column($vehicleTypes, 'id'))->toBe([3, 4]);
|
||||
expect(array_column($vehicleTypes, 'label'))->toBe(['Forvogn', 'Trækker']);
|
||||
expect($vehicleTypes[0]['product'])->toBe(3);
|
||||
expect($vehicleTypes[0]['product_id'])->toBe(3);
|
||||
expect($vehicleTypes[0]['source'])->toBe('products');
|
||||
});
|
||||
|
||||
it('applies saved layout without changing graph semantics', function (): void {
|
||||
$service = selfserve_studio_graph_without_constructor();
|
||||
|
||||
$graph = $service->buildGraphFromConfig([
|
||||
'questions' => [
|
||||
['id' => 1, 'question' => 'Question', 'order_priority' => 1],
|
||||
],
|
||||
'conditions' => [],
|
||||
'rules' => [],
|
||||
'tasks' => [],
|
||||
], [
|
||||
'lookups' => [
|
||||
'labels' => [],
|
||||
],
|
||||
], [
|
||||
'nodes' => [
|
||||
'question:1' => ['x' => 123, 'y' => 456],
|
||||
],
|
||||
]);
|
||||
|
||||
$questionNode = array_values(array_filter(
|
||||
$graph['nodes'],
|
||||
static fn(array $node): bool => ($node['id'] ?? null) === 'question:1'
|
||||
))[0] ?? null;
|
||||
|
||||
expect($questionNode)->not->toBeNull();
|
||||
expect($questionNode['position'])->toBe(['x' => 123.0, 'y' => 456.0]);
|
||||
});
|
||||
|
||||
it('keeps layout loading compatible with native PDO named placeholders', function (): void {
|
||||
$method = new ReflectionMethod(selfserve_studio_graph::class, 'loadLayout');
|
||||
$source = implode('', array_slice(
|
||||
file((string)$method->getFileName()) ?: [],
|
||||
$method->getStartLine() - 1,
|
||||
$method->getEndLine() - $method->getStartLine() + 1
|
||||
));
|
||||
|
||||
expect($source)->not->toContain('user_id = :user_id OR user_id IS NULL');
|
||||
expect($source)->not->toContain('user_id = :user_id THEN 0 ELSE 1');
|
||||
expect($source)->toContain(':user_id_filter');
|
||||
expect($source)->toContain(':user_id_sort');
|
||||
});
|
||||
Reference in New Issue
Block a user