fix(self-serve): restrict Dognvask machine to single vehicle type (traekker) (TRU-84)
The Dognvask machine is a single-vehicle-type wash machine. When the current lane resolves to a Dognvask machine type, the path editor and scope guide now: - restrict the vehicle type filter to the 'traekker' (tractor unit) entry in both the main toolbar and the scope guide - show a visible 'Dognvask: trækker only' hint badge so operators can see why the options are limited - auto-correct the vehicle type filter when the user switches to or from a Dognvask-configured lane (clearing the filter if no traekker type is configured, otherwise snapping to the only allowed option) Detection compares the resolved selfserve_machine_types row name against 'dognvask' (case-insensitive). The allowed vehicle type is matched by label/name against 'traekker' / 'tractor' / 'tractor unit' to tolerate the various Danish/English renderings used across the system. This follows the same pattern introduced in TRU-91 (path editor machine filter), which scoped machine-button options to the current lane's machine type.
This commit is contained in:
+85
-4
@@ -1434,6 +1434,42 @@ const pathEditorCurrentMachineType = computed(() => {
|
||||
|
||||
const pathEditorMachineIsAvailable = computed(() => Boolean(pathEditorCurrentMachineType.value));
|
||||
|
||||
// TRU-84: Dognvask machine is a single-vehicle-type machine. When the current
|
||||
// lane is configured to use the Dognvask machine, the vehicle type toolbar and
|
||||
// the path editor are restricted to the "traekker" (tractor unit) vehicle type.
|
||||
const pathEditorCurrentMachineTypeName = computed(() => {
|
||||
const machineType = pathEditorCurrentMachineType.value;
|
||||
if (!machineType) {
|
||||
return "";
|
||||
}
|
||||
return String(machineType.name || machineType.label || "").trim().toLowerCase();
|
||||
});
|
||||
|
||||
const pathEditorIsDognvaskMachine = computed(
|
||||
() => pathEditorCurrentMachineTypeName.value === "dognvask"
|
||||
);
|
||||
|
||||
const isTraekkerVehicleType = (vehicleType) => {
|
||||
if (!vehicleType) {
|
||||
return false;
|
||||
}
|
||||
const candidate = String(
|
||||
vehicleType.label || vehicleType.name || vehicleType.vehicleTypeId || ""
|
||||
)
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
return candidate === "traekker" || candidate === "tractor" || candidate === "tractor unit";
|
||||
};
|
||||
|
||||
const pathEditorAllowedVehicleTypes = computed(() => {
|
||||
const rows = lookupRows("vehicle_types");
|
||||
if (!pathEditorIsDognvaskMachine.value) {
|
||||
return rows;
|
||||
}
|
||||
const traekker = rows.filter((row) => isTraekkerVehicleType(row));
|
||||
return traekker;
|
||||
});
|
||||
|
||||
const pathEditorMachineButtonOptions = computed(() => {
|
||||
if (!pathEditorMachineIsAvailable.value) {
|
||||
return [];
|
||||
@@ -5477,6 +5513,35 @@ watch(
|
||||
scheduleSimulatorRun("scope", 150);
|
||||
}
|
||||
);
|
||||
// TRU-84: Dognvask machines are single-vehicle-type machines. When the
|
||||
// selected lane resolves to a Dognvask machine, force the vehicle type filter
|
||||
// to the traekker option and keep it in sync when the user switches lanes.
|
||||
watch(
|
||||
() => [filters.value.lane_id, pathEditorIsDognvaskMachine.value, pathEditorAllowedVehicleTypes.value],
|
||||
() => {
|
||||
if (!pathEditorIsDognvaskMachine.value) {
|
||||
return;
|
||||
}
|
||||
const allowed = pathEditorAllowedVehicleTypes.value;
|
||||
if (allowed.length === 0) {
|
||||
if (filters.value.vehicle_type_id !== "") {
|
||||
filters.value = { ...filters.value, vehicle_type_id: "" };
|
||||
}
|
||||
return;
|
||||
}
|
||||
const allowedIds = new Set(
|
||||
allowed.map((row) => String(vehicleTypeFilterValue(row)))
|
||||
);
|
||||
const current = String(filters.value.vehicle_type_id ?? "");
|
||||
if (!allowedIds.has(current)) {
|
||||
filters.value = {
|
||||
...filters.value,
|
||||
vehicle_type_id: String(vehicleTypeFilterValue(allowed[0])),
|
||||
};
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
watch(simulatorDynamicImageUrl, () => {
|
||||
simulatorDynamicImageHidden.value = false;
|
||||
simulatorDynamicImageError.value = "";
|
||||
@@ -5682,10 +5747,19 @@ onBeforeUnmount(() => {
|
||||
|
||||
<section class="studio-scope-guide-column">
|
||||
<span class="studio-scope-guide-step">2</span>
|
||||
<h3>Vehicle type</h3>
|
||||
<h3>
|
||||
Vehicle type
|
||||
<span
|
||||
v-if="pathEditorIsDognvaskMachine"
|
||||
class="tag is-info is-light is-small studio-scope-guide-constraint"
|
||||
data-testid="studio-scope-guide-dognvask-hint"
|
||||
>
|
||||
Dognvask: trækker only
|
||||
</span>
|
||||
</h3>
|
||||
<div class="studio-scope-option-list">
|
||||
<button
|
||||
v-for="vehicleType in lookupRows('vehicle_types')"
|
||||
v-for="vehicleType in pathEditorAllowedVehicleTypes"
|
||||
:key="vehicleType.id"
|
||||
type="button"
|
||||
class="studio-scope-option"
|
||||
@@ -5702,7 +5776,7 @@ onBeforeUnmount(() => {
|
||||
<small>#{{ vehicleTypeFilterValue(vehicleType) }}</small>
|
||||
</span>
|
||||
</button>
|
||||
<p v-if="lookupRows('vehicle_types').length === 0" class="studio-empty-inline">
|
||||
<p v-if="pathEditorAllowedVehicleTypes.length === 0" class="studio-empty-inline">
|
||||
No vehicle types are configured.
|
||||
</p>
|
||||
</div>
|
||||
@@ -7194,13 +7268,20 @@ onBeforeUnmount(() => {
|
||||
<select v-model="filters.vehicle_type_id" data-testid="studio-filter-vehicle-type">
|
||||
<option value="" disabled>Select vehicle type</option>
|
||||
<option
|
||||
v-for="vehicleType in lookupRows('vehicle_types')"
|
||||
v-for="vehicleType in pathEditorAllowedVehicleTypes"
|
||||
:key="vehicleType.id"
|
||||
:value="vehicleTypeFilterValue(vehicleType)"
|
||||
>
|
||||
{{ vehicleType.label }}
|
||||
</option>
|
||||
</select>
|
||||
<span
|
||||
v-if="pathEditorIsDognvaskMachine"
|
||||
class="tag is-info is-light is-small studio-scope-dognvask-hint"
|
||||
data-testid="studio-filter-dognvask-hint"
|
||||
>
|
||||
Dognvask: trækker only
|
||||
</span>
|
||||
</div>
|
||||
<div class="select is-small">
|
||||
<select v-model="filters.machine_type_id" data-testid="studio-filter-machine-type">
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const readSource = (relativePath) => readFileSync(join(process.cwd(), relativePath), "utf8");
|
||||
|
||||
describe("self-serve studio Dognvask single vehicle type (TRU-84)", () => {
|
||||
const studioSource = () =>
|
||||
readSource("src/views/dashboards/departmentDashboard/modules/self-serve/DepartmentSelfServeStudio.vue");
|
||||
|
||||
it("defines a Dognvask detection helper tied to the current lane machine type", () => {
|
||||
const source = studioSource();
|
||||
|
||||
expect(source).toContain("pathEditorCurrentMachineTypeName");
|
||||
expect(source).toContain("pathEditorIsDognvaskMachine");
|
||||
expect(source).toContain('pathEditorCurrentMachineTypeName.value === "dognvask"');
|
||||
});
|
||||
|
||||
it("provides a path editor allowed vehicle types list that filters to traekker for Dognvask", () => {
|
||||
const source = studioSource();
|
||||
|
||||
expect(source).toContain("pathEditorAllowedVehicleTypes");
|
||||
expect(source).toContain("isTraekkerVehicleType");
|
||||
expect(source).toMatch(
|
||||
/pathEditorAllowedVehicleTypes[\s\S]{0,400}if\s*\(\s*!pathEditorIsDognvaskMachine\.value\s*\)\s*\{\s*return\s+rows;\s*\}/
|
||||
);
|
||||
expect(source).toMatch(
|
||||
/pathEditorAllowedVehicleTypes[\s\S]{0,800}const\s+traekker\s*=\s*rows\.filter\(\(row\)\s*=>\s*isTraekkerVehicleType\(row\)\);/
|
||||
);
|
||||
});
|
||||
|
||||
it("recognises traekker/tractor/tractor unit as the Dognvask vehicle type", () => {
|
||||
const source = studioSource();
|
||||
|
||||
expect(source).toMatch(
|
||||
/isTraekkerVehicleType[\s\S]{0,400}return\s+candidate\s*===\s*"traekker"\s*\|\|\s*candidate\s*===\s*"tractor"\s*\|\|\s*candidate\s*===\s*"tractor unit";/
|
||||
);
|
||||
});
|
||||
|
||||
it("uses the allowed vehicle types list in the toolbar filter", () => {
|
||||
const source = studioSource();
|
||||
|
||||
// Toolbar select sits above the v-for option; assert the data-testid and the v-for coexist.
|
||||
const toolbarIndex = source.indexOf('data-testid="studio-filter-vehicle-type"');
|
||||
expect(toolbarIndex).toBeGreaterThanOrEqual(0);
|
||||
const toolbarSection = source.slice(toolbarIndex, toolbarIndex + 400);
|
||||
expect(toolbarSection).toContain("v-for=\"vehicleType in pathEditorAllowedVehicleTypes\"");
|
||||
});
|
||||
|
||||
it("uses the allowed vehicle types list in the scope guide", () => {
|
||||
const source = studioSource();
|
||||
|
||||
// Scope guide uses :data-testid="`studio-scope-option-vehicle-${...}`" with v-for before it.
|
||||
const scopeGuideIndex = source.indexOf("studio-scope-option-vehicle-${");
|
||||
expect(scopeGuideIndex).toBeGreaterThanOrEqual(0);
|
||||
const scopeGuideSection = source.slice(Math.max(0, scopeGuideIndex - 800), scopeGuideIndex);
|
||||
expect(scopeGuideSection).toContain("v-for=\"vehicleType in pathEditorAllowedVehicleTypes\"");
|
||||
});
|
||||
|
||||
it("auto-corrects the vehicle type filter when the current machine is Dognvask", () => {
|
||||
const source = studioSource();
|
||||
|
||||
expect(source).toContain(
|
||||
"() => [filters.value.lane_id, pathEditorIsDognvaskMachine.value, pathEditorAllowedVehicleTypes.value]"
|
||||
);
|
||||
expect(source).toMatch(
|
||||
/if\s*\(\s*!pathEditorIsDognvaskMachine\.value\s*\)\s*\{\s*return;\s*\}/
|
||||
);
|
||||
expect(source).toContain('filters.value = { ...filters.value, vehicle_type_id: "" }');
|
||||
expect(source).toMatch(
|
||||
/filters\.value\s*=\s*\{[\s\S]{0,200}vehicle_type_id:\s*String\(vehicleTypeFilterValue\(allowed\[0\]\)\)/
|
||||
);
|
||||
});
|
||||
|
||||
it("shows a visible Dognvask hint badge in the toolbar and scope guide", () => {
|
||||
const source = studioSource();
|
||||
|
||||
expect(source).toContain('data-testid="studio-filter-dognvask-hint"');
|
||||
expect(source).toContain('data-testid="studio-scope-guide-dognvask-hint"');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user