Introduce scope filtering and machine type integration for Self-Serve module:

- Add scope label computation for tasks, questions, and conditions based on department, lane, product, and machine type.
- Introduce filters for machine type, lane, and department in Self-Serve pagination.
- Replace `EditableTableColumn` with scoped rendering for improved flexibility.
- Extend API and schema to support machine type integration in department redistribution.
- Optimize UI for task, question, and condition management with machine type-aware workflows.
This commit is contained in:
Jeppe Bundgaard
2026-03-16 13:43:08 +01:00
parent f6050740ec
commit 5b3e59bbb4
8 changed files with 446 additions and 303 deletions
+129 -1
View File
@@ -5721,6 +5721,33 @@ paths:
'403': { $ref: '#/components/responses/Forbidden' }
'500': { $ref: '#/components/responses/InternalServerError' }
/superuser/invoicing/period/distribution/v2/booked-department-75:
get:
tags:
- Invoices
summary: Get booked e-conomic department 75 redistribution
operationId: getInvoicingPeriodDistributionV2BookedDepartment75
parameters:
- name: dateFrom
in: query
required: true
schema: { type: string, format: date }
- name: dateTo
in: query
required: true
schema: { type: string, format: date }
responses:
'200':
description: Actual booked e-conomic department 75 net amounts redistributed to internal departments
content:
application/json:
schema:
$ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75Response'
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
'500': { $ref: '#/components/responses/InternalServerError' }
/superuser/customers/pricing-history:
get:
tags:
@@ -11207,6 +11234,105 @@ components:
InvoicingDistributionV2CustomerPricesResponse:
$ref: '#/components/schemas/InvoicingDistributionV2CategoryResponse'
InvoicingDistributionV2BookedDepartment75Group:
type: object
properties:
month:
type: string
example: '2026-01'
source_category:
type: string
enum: [fixed_pricing, wash_subscriptions, unclassified]
invoice_ids:
type: array
items:
type: integer
booked_net_amount:
type: number
department_distribution:
$ref: '#/components/schemas/EconomicV2DepartmentDistribution'
undistributed_net_amount:
type: number
required:
- month
- source_category
- invoice_ids
- booked_net_amount
- department_distribution
- undistributed_net_amount
InvoicingDistributionV2BookedDepartment75Meta:
type: object
properties:
booked_net_amount:
type: number
distributed_net_amount:
type: number
undistributed_net_amount:
type: number
department_distribution:
$ref: '#/components/schemas/EconomicV2DepartmentDistribution'
booked_groups:
type: array
items:
$ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75Group'
required:
- booked_net_amount
- distributed_net_amount
- undistributed_net_amount
- department_distribution
- booked_groups
InvoicingDistributionV2BookedDepartment75Customer:
allOf:
- $ref: '#/components/schemas/InvoicingDistributionV2Customer'
- type: object
properties:
meta:
type: object
properties:
booked_department_75:
$ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75Meta'
required:
- booked_department_75
InvoicingDistributionV2BookedDepartment75CollectiveResults:
type: object
properties:
booked_net_amount:
type: number
distributed_net_amount:
type: number
undistributed_net_amount:
type: number
department_distribution:
$ref: '#/components/schemas/EconomicV2DepartmentDistribution'
department_distribution_parsed:
type: object
additionalProperties:
type: number
required:
- booked_net_amount
- distributed_net_amount
- undistributed_net_amount
- department_distribution
- department_distribution_parsed
InvoicingDistributionV2BookedDepartment75Response:
type: object
properties:
customers:
type: array
items:
$ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75Customer'
collective_results:
$ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75CollectiveResults'
warnings:
type: array
items:
type: string
required: [customers, collective_results, warnings]
InvoicingDistributionV2AllResponse:
type: object
properties:
@@ -11216,7 +11342,9 @@ components:
$ref: '#/components/schemas/InvoicingDistributionV2WashSubscriptionsResponse'
customer_prices:
$ref: '#/components/schemas/InvoicingDistributionV2CustomerPricesResponse'
required: [fixed_pricing, wash_subscriptions, customer_prices]
booked_department_75:
$ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75Response'
required: [fixed_pricing, wash_subscriptions, customer_prices, booked_department_75]
PricingHistoryVersionEntry:
type: object
@@ -1,12 +1,11 @@
<script setup>
import { ref } from "vue";
import { ref, watch } from "vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
import { usePaginatedListInstance } from "@/components/pagination/paginatedList.vue";
const { loadList, setList } = usePaginatedListInstance();
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
const { loadList } = usePaginatedListInstance();
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import SelfServeConditionRulesModal from "@/components/displays/department/tables/SelfServeConditionRulesModal.vue";
@@ -31,6 +30,48 @@ const closeModal = () => {
selectedCondition.value = null;
};
const machineTypeNames = ref({});
const loadMachineTypes = async () => {
const machineTypes = await SessionUser.objects.self_serve_machine_types.get.all();
machineTypeNames.value = machineTypes.reduce((accumulator, entry) => {
accumulator[entry.id] = entry.name;
return accumulator;
}, {});
};
watch(() => props.objects, () => {
loadMachineTypes();
}, { immediate: true, deep: true });
const getScopeLabel = (object) => {
const machineTypeId = parseInt(object.machine_type_id || 0);
const department = parseInt(object.department || 0);
const lane = parseInt(object.lane || 0);
const product = parseInt(object.product || 0);
if (machineTypeId !== 0) {
return `Maskintype: ${machineTypeNames.value[machineTypeId] || machineTypeId}`;
}
if (department === 0 && lane === 0 && product === 0) {
return 'Fælles';
}
const parts = [];
if (department !== 0) {
parts.push(`${SessionUser.objects.self_serve_conditions.columns.department.label}: ${department}`);
}
if (lane !== 0) {
parts.push(`${t('global.lane')}: ${lane}`);
}
if (product !== 0) {
parts.push(`${SessionUser.objects.self_serve_conditions.columns.product.label}: ${SessionUser.objects.products.functions.getProductName(product)}`);
}
return parts.join(' / ');
};
</script>
<template>
@@ -41,54 +82,24 @@ const closeModal = () => {
<th>{{ SessionUser.objects.self_serve_conditions.columns.condition_id.label }}</th>
<th>{{ SessionUser.objects.self_serve_conditions.columns.name.label }}</th>
<th>{{ SessionUser.objects.self_serve_conditions.columns.description.label }}</th>
<th>{{ SessionUser.objects.self_serve_conditions.columns.lane.label }}</th>
<th>{{ SessionUser.objects.self_serve_conditions.columns.product.label }}</th>
<th>Scope</th>
<th class="has-text-right">{{ $t('tables.actions') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="object in objects" :key="object.id">
<td>{{ object.id }}</td>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="condition_id"
:edit-function="SessionUser.objects.self_serve_conditions.showEditObjectFieldForm"
:parse-function="SessionUser.objects.self_serve_conditions.columns.condition_id.parse"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="name"
:edit-function="SessionUser.objects.self_serve_conditions.showEditObjectFieldForm"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="description"
:edit-function="SessionUser.objects.self_serve_conditions.showEditObjectFieldForm"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="lane"
:edit-function="SessionUser.objects.self_serve_conditions.showEditObjectFieldForm"
:parse-function="(laneId) => $t('global.lane') + ' ' + laneId"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="product"
:edit-function="SessionUser.objects.self_serve_conditions.showEditObjectFieldForm"
:parse-function="(productId) => SessionUser.objects.products.functions.getProductName(productId)"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<td>{{ object.condition_id ? SessionUser.objects.self_serve_conditions.columns.condition_id.parse(object.condition_id) : $t('global.none') }}</td>
<td>{{ object.name }}</td>
<td>{{ object.description }}</td>
<td>{{ getScopeLabel(object) }}</td>
<!-- Actions -->
<td class="has-text-right">
<button class="button is-small is-info is-light mr-2" @click="SessionUser.objects.self_serve_conditions.showEditObjectForm(object, loadList)">
<span class="icon is-small">
<i class="fas fa-pen"></i>
</span>
</button>
<button class="button is-small is-link is-light mr-2" @click="openModal(object)">
<span class="icon is-small">
<i class="fas fa-gavel"></i>
@@ -5,7 +5,6 @@ const { t } = useI18n();
import { usePaginatedListInstance } from "@/components/pagination/paginatedList.vue";
const { loadList, loadSwitch, metaCurrentPage, metaItemsPerPage, setList } = usePaginatedListInstance();
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { ref } from "vue";
@@ -74,6 +73,29 @@ const onDrop = async (event, newIndex) => {
}
};
const getScopeLabel = (object) => {
const department = parseInt(object.department || 0);
const lane = parseInt(object.lane || 0);
const product = parseInt(object.product || 0);
if (department === 0 && lane === 0 && product === 0) {
return 'Fælles';
}
const parts = [];
if (department !== 0) {
parts.push(`${SessionUser.objects.self_serve_questions.columns.department.label}: ${department}`);
}
if (lane !== 0) {
parts.push(`${t('global.lane')}: ${lane}`);
}
if (product !== 0) {
parts.push(`${SessionUser.objects.self_serve_questions.columns.product.label}: ${SessionUser.objects.products.functions.getProductName(product)}`);
}
return parts.join(' / ');
};
</script>
<template>
@@ -82,11 +104,10 @@ const onDrop = async (event, newIndex) => {
<tr>
<th v-if="SessionUser.canAccessAdmin()" style="width: 40px;"></th>
<th>{{ SessionUser.objects.self_serve_questions.columns.order_priority.label }}</th>
<th>{{ SessionUser.objects.self_serve_questions.columns.condition_id.label }}</th>
<th>{{ SessionUser.objects.self_serve_questions.columns.question.label }}</th>
<th>{{ SessionUser.objects.self_serve_questions.columns.description.label }}</th>
<th>{{ SessionUser.objects.self_serve_questions.columns.lane.label }}</th>
<th>{{ SessionUser.objects.self_serve_questions.columns.product.label }}</th>
<th>{{ SessionUser.objects.self_serve_questions.columns.condition_id.label }}</th>
<th>Scope</th>
<th class="has-text-right">{{ $t('tables.actions') }}</th>
</tr>
</thead>
@@ -106,53 +127,18 @@ const onDrop = async (event, newIndex) => {
<i class="fas fa-grip-lines"></i>
</span>
</td>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="order_priority"
:edit-function="SessionUser.objects.self_serve_questions.showEditObjectFieldForm"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="condition_id"
:edit-function="SessionUser.objects.self_serve_questions.showEditObjectFieldForm"
:parse-function="SessionUser.objects.self_serve_questions.columns.condition_id.parse"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="question"
:edit-function="SessionUser.objects.self_serve_questions.showEditObjectFieldForm"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="description"
:edit-function="SessionUser.objects.self_serve_questions.showEditObjectFieldForm"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="lane"
:edit-function="SessionUser.objects.self_serve_questions.showEditObjectFieldForm"
:parse-function="(laneId) => $t('global.lane') + ' ' + laneId"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="product"
:edit-function="SessionUser.objects.self_serve_questions.showEditObjectFieldForm"
:parse-function="(productId) => SessionUser.objects.products.functions.getProductName(productId)"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<td>{{ object.order_priority }}</td>
<td>{{ object.question }}</td>
<td>{{ object.description }}</td>
<td>{{ object.condition_id ? SessionUser.objects.self_serve_questions.columns.condition_id.parse(object.condition_id) : $t('global.none') }}</td>
<td>{{ getScopeLabel(object) }}</td>
<!-- Actions -->
<td class="has-text-right">
<button class="button is-small is-info is-light mr-2" @click="SessionUser.objects.self_serve_questions.showEditObjectForm(object, loadList)">
<span class="icon is-small">
<i class="fas fa-pen"></i>
</span>
</button>
<button class="button is-small is-danger is-light" @click="SessionUser.objects.self_serve_questions.functions.showDeleteObjectForm(object.id, loadList)">
<span class="icon is-small">
<i class="fas fa-trash"></i>
@@ -5,9 +5,8 @@ const { t } = useI18n();
import { usePaginatedListInstance } from "@/components/pagination/paginatedList.vue";
const { loadList, loadSwitch, metaCurrentPage, metaItemsPerPage, setList } = usePaginatedListInstance();
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { ref } from "vue";
import { ref, watch } from "vue";
import SelfServeTaskAttachmentsModal from "@/components/displays/department/tables/SelfServeTaskAttachmentsModal.vue";
// Define the props
@@ -117,6 +116,48 @@ const onDrop = async (event, newIndex) => {
}
};
const machineTypeNames = ref({});
const loadMachineTypes = async () => {
const machineTypes = await SessionUser.objects.self_serve_machine_types.get.all();
machineTypeNames.value = machineTypes.reduce((accumulator, entry) => {
accumulator[entry.id] = entry.name;
return accumulator;
}, {});
};
watch(() => props.objects, () => {
loadMachineTypes();
}, { immediate: true, deep: true });
const getScopeLabel = (object) => {
const machineTypeId = parseInt(object.machine_type_id || 0);
const department = parseInt(object.department || 0);
const lane = parseInt(object.lane || 0);
const product = parseInt(object.product || 0);
if (machineTypeId !== 0) {
return `Maskintype: ${machineTypeNames.value[machineTypeId] || machineTypeId}`;
}
if (department === 0 && lane === 0 && product === 0) {
return 'Fælles';
}
const parts = [];
if (department !== 0) {
parts.push(`${SessionUser.objects.self_serve_tasks.columns.department.label}: ${department}`);
}
if (lane !== 0) {
parts.push(`${t('global.lane')}: ${lane}`);
}
if (product !== 0) {
parts.push(`${SessionUser.objects.self_serve_tasks.columns.product.label}: ${SessionUser.objects.products.functions.getProductName(product)}`);
}
return parts.join(' / ');
};
</script>
<template>
@@ -127,9 +168,8 @@ const onDrop = async (event, newIndex) => {
<th>{{ SessionUser.objects.self_serve_tasks.columns.order_priority.label }}</th>
<th>{{ SessionUser.objects.self_serve_tasks.columns.task.label }}</th>
<th>{{ SessionUser.objects.self_serve_tasks.columns.description.label }}</th>
<th>{{ SessionUser.objects.self_serve_tasks.columns.lane.label }}</th>
<th>{{ SessionUser.objects.self_serve_tasks.columns.product.label }}</th>
<th>{{ SessionUser.objects.self_serve_tasks.columns.condition_id.label }}</th>
<th>Scope</th>
<th>{{ SessionUser.objects.self_serve_tasks.columns.services.label }}</th>
<th>{{ SessionUser.objects.self_serve_tasks.columns.buttons.label }}</th>
<th>{{ SessionUser.objects.self_serve_tasks.columns.dynamic_images_vehicle_type.label }}</th>
@@ -152,78 +192,22 @@ const onDrop = async (event, newIndex) => {
<i class="fas fa-grip-lines"></i>
</span>
</td>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="order_priority"
:edit-function="SessionUser.objects.self_serve_tasks.showEditObjectFieldForm"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="task"
:edit-function="SessionUser.objects.self_serve_tasks.showEditObjectFieldForm"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="description"
:edit-function="SessionUser.objects.self_serve_tasks.showEditObjectFieldForm"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="lane"
:edit-function="SessionUser.objects.self_serve_tasks.showEditObjectFieldForm"
:parse-function="(laneId) => $t('global.lane') + ' ' + laneId"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="product"
:edit-function="SessionUser.objects.self_serve_tasks.showEditObjectFieldForm"
:parse-function="(productId) => SessionUser.objects.products.functions.getProductName(productId)"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="condition_id"
:edit-function="(id, column, value, onAfterSubmit) => SessionUser.objects.self_serve_tasks.showEditObjectFieldForm(id, column, value, onAfterSubmit, { department: object.department, product: object.product })"
:parse-function="(conditionId) => conditionId ? SessionUser.objects.self_serve_conditions.functions.getConditionName(conditionId) : $t('global.all')"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="services"
:edit-function="SessionUser.objects.self_serve_tasks.showEditObjectFieldForm"
:parse-function="SessionUser.objects.self_serve_tasks.columns.services.parse"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="buttons"
:edit-function="editButtonsFunction"
:parse-function="SessionUser.objects.self_serve_tasks.columns.buttons.parse"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="dynamic_images_vehicle_type"
:edit-function="SessionUser.objects.self_serve_tasks.showEditObjectFieldForm"
:parse-function="SessionUser.objects.self_serve_tasks.columns.dynamic_images_vehicle_type.parse"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<td>{{ object.order_priority }}</td>
<td>{{ object.task }}</td>
<td>{{ object.description }}</td>
<td>{{ object.condition_id ? SessionUser.objects.self_serve_conditions.functions.getConditionName(object.condition_id) : $t('global.all') }}</td>
<td>{{ getScopeLabel(object) }}</td>
<td>{{ SessionUser.objects.self_serve_tasks.columns.services.parse(object.services) }}</td>
<td>{{ SessionUser.objects.self_serve_tasks.columns.buttons.parse(object.buttons) }}</td>
<td>{{ SessionUser.objects.self_serve_tasks.columns.dynamic_images_vehicle_type.parse(object.dynamic_images_vehicle_type) }}</td>
<!-- Actions -->
<td class="has-text-right">
<div class="buttons is-right">
<button class="button is-small is-info is-light" @click="SessionUser.objects.self_serve_tasks.showEditObjectForm(object, loadList)">
<span class="icon is-small">
<i class="fas fa-pen"></i>
</span>
</button>
<button class="button is-small is-link is-light" @click="openAttachmentsModal(object)" :title="$t('tables.self_serve.attachments')">
<span class="icon is-small">
<i class="fas fa-paperclip"></i>
@@ -17,6 +17,16 @@ const props = defineProps({
required: false,
default: null,
},
setLaneFilter: {
type: [String, Number],
required: false,
default: null,
},
setMachineTypeFilter: {
type: [String, Number],
required: false,
default: null,
},
autoLoad: {
type: Boolean,
required: false,
@@ -41,7 +51,43 @@ const {
} = paginatedList;
const sortedList = computed(() => {
return [...list.value];
const departmentFilter = props.setDepartmentFilter ? parseInt(props.setDepartmentFilter) : null;
const laneFilter = props.setLaneFilter ? parseInt(props.setLaneFilter) : null;
const vehicleTypeFilter = props.setVehicleTypeFilter ? parseInt(props.setVehicleTypeFilter) : null;
const conditionFilter = props.setConditionFilter ? parseInt(props.setConditionFilter) : null;
const machineTypeFilter = props.setMachineTypeFilter ? parseInt(props.setMachineTypeFilter) : null;
const matchesScope = (item) => {
const itemDepartment = parseInt(item.department || 0);
const itemLane = parseInt(item.lane || 0);
const itemProduct = parseInt(item.product || 0);
const itemCondition = parseInt(item.condition_id || 0);
const itemMachineType = parseInt(item.machine_type_id || 0);
if (machineTypeFilter) {
return itemMachineType === machineTypeFilter && (!conditionFilter || itemCondition === conditionFilter);
}
if (departmentFilter && itemMachineType === 0 && itemDepartment !== 0 && itemDepartment !== departmentFilter) {
return false;
}
if (laneFilter && itemMachineType === 0 && itemLane !== 0 && itemLane !== laneFilter) {
return false;
}
if (vehicleTypeFilter && itemMachineType === 0 && itemProduct !== 0 && itemProduct !== vehicleTypeFilter) {
return false;
}
if (conditionFilter && itemCondition !== conditionFilter) {
return false;
}
return true;
};
return [...list.value].filter(matchesScope);
});
import TableLabeledPagination from "@/components/displays/pagination/TableLabeledPagination.vue";
@@ -52,47 +98,18 @@ const { t } = useI18n();
setEndpoint(SessionUser.objects.self_serve_conditions.meta.endpoint, false);
if (props.setDepartmentFilter) {
setFilter(
"department",
props.setDepartmentFilter
);
}
if (props.setVehicleTypeFilter) {
setFilter(
"product",
props.setVehicleTypeFilter
);
}
if (props.setConditionFilter) {
setFilter(
"condition_id",
props.setConditionFilter
);
}
if (props.autoLoad) {
loadList();
}
import { watch } from "vue";
watch(() => props.setVehicleTypeFilter, (newFilter) => {
if (newFilter) {
setFilter("product", newFilter);
} else {
setFilter("product", "*");
}
loadList();
});
watch(() => props.setConditionFilter, (newFilter) => {
if (newFilter) {
setFilter("condition_id", newFilter);
} else {
setFilter("condition_id", "*");
}
watch(() => [
props.setDepartmentFilter,
props.setLaneFilter,
props.setVehicleTypeFilter,
props.setConditionFilter,
props.setMachineTypeFilter,
], () => {
loadList();
});
@@ -105,13 +122,15 @@ defineExpose({
<template>
<TableLabeledPagination :label="SessionUser.objects.self_serve_conditions.meta.title">
<template #buttons="{ loadList }">
<slot name="buttons" :loadList="loadList" :totalItems="metaTotalItems">
<slot name="buttons" :loadList="loadList" :totalItems="sortedList.length">
<button
class="button is-primary is-small"
@click="SessionUser.objects.self_serve_conditions.showCreateObjectForm({
department: props.setDepartmentFilter,
lane: props.setLaneFilter,
product: props.setVehicleTypeFilter,
condition_id: props.setConditionFilter
condition_id: props.setConditionFilter,
machine_type_id: props.setMachineTypeFilter,
}, loadList)"
>
<span class="icon is-small">
@@ -47,7 +47,33 @@ const {
} = paginatedList;
const sortedList = computed(() => {
return [...list.value].sort((a, b) => (a.order_priority || 0) - (b.order_priority || 0));
const departmentFilter = props.setDepartmentFilter ? parseInt(props.setDepartmentFilter) : null;
const laneFilter = props.setLaneFilter ? parseInt(props.setLaneFilter) : null;
const vehicleTypeFilter = props.setVehicleTypeFilter ? parseInt(props.setVehicleTypeFilter) : null;
const matchesScope = (item) => {
const itemDepartment = parseInt(item.department || 0);
const itemLane = parseInt(item.lane || 0);
const itemProduct = parseInt(item.product || 0);
if (departmentFilter && itemDepartment !== 0 && itemDepartment !== departmentFilter) {
return false;
}
if (laneFilter && itemLane !== 0 && itemLane !== laneFilter) {
return false;
}
if (vehicleTypeFilter && itemProduct !== 0 && itemProduct !== vehicleTypeFilter) {
return false;
}
return true;
};
return [...list.value]
.filter(matchesScope)
.sort((a, b) => (a.order_priority || 0) - (b.order_priority || 0));
});
import TableLabeledPagination from "@/components/displays/pagination/TableLabeledPagination.vue";
@@ -64,38 +90,12 @@ if (props.hideSearch) {
setHideSearchField(true);
}
if (props.setDepartmentFilter) {
setFilter(
"department",
props.setDepartmentFilter
);
}
if (props.setLaneFilter) {
setFilter(
"lane",
props.setLaneFilter
);
}
if (props.setVehicleTypeFilter) {
setFilter(
"product",
props.setVehicleTypeFilter
);
}
if (props.autoLoad) {
loadList();
}
import { watch } from "vue";
watch(() => props.setVehicleTypeFilter, (newFilter) => {
if (newFilter) {
setFilter("product", newFilter);
} else {
setFilter("product", "*");
}
watch(() => [props.setDepartmentFilter, props.setLaneFilter, props.setVehicleTypeFilter], () => {
loadList();
});
@@ -108,11 +108,11 @@ defineExpose({
<template>
<TableLabeledPagination :label="SessionUser.objects.self_serve_questions.meta.title">
<template #buttons="{ loadList }">
<slot name="buttons" :loadList="loadList" :totalItems="metaTotalItems">
<slot name="buttons" :loadList="loadList" :totalItems="sortedList.length">
<button
class="button is-primary is-small"
@click="SessionUser.objects.self_serve_questions.showCreateObjectForm({
order_priority: metaTotalItems + 1
order_priority: sortedList.length + 1
}, loadList)"
>
<span class="icon is-small">
@@ -22,6 +22,11 @@ const props = defineProps({
required: false,
default: null,
},
setMachineTypeFilter: {
type: [String, Number],
required: false,
default: null,
},
hideSearch: {
type: Boolean,
required: false,
@@ -52,7 +57,45 @@ const {
} = paginatedList;
const sortedList = computed(() => {
return [...list.value].sort((a, b) => (a.order_priority || 0) - (b.order_priority || 0));
const departmentFilter = props.setDepartmentFilter ? parseInt(props.setDepartmentFilter) : null;
const laneFilter = props.setLaneFilter ? parseInt(props.setLaneFilter) : null;
const vehicleTypeFilter = props.setVehicleTypeFilter ? parseInt(props.setVehicleTypeFilter) : null;
const conditionFilter = props.setConditionFilter ? parseInt(props.setConditionFilter) : null;
const machineTypeFilter = props.setMachineTypeFilter ? parseInt(props.setMachineTypeFilter) : null;
const matchesScope = (item) => {
const itemDepartment = parseInt(item.department || 0);
const itemLane = parseInt(item.lane || 0);
const itemProduct = parseInt(item.product || 0);
const itemCondition = parseInt(item.condition_id || 0);
const itemMachineType = parseInt(item.machine_type_id || 0);
if (machineTypeFilter) {
return itemMachineType === machineTypeFilter && (!conditionFilter || itemCondition === conditionFilter);
}
if (departmentFilter && itemMachineType === 0 && itemDepartment !== 0 && itemDepartment !== departmentFilter) {
return false;
}
if (laneFilter && itemMachineType === 0 && itemLane !== 0 && itemLane !== laneFilter) {
return false;
}
if (vehicleTypeFilter && itemMachineType === 0 && itemProduct !== 0 && itemProduct !== vehicleTypeFilter) {
return false;
}
if (conditionFilter && itemCondition !== conditionFilter) {
return false;
}
return true;
};
return [...list.value]
.filter(matchesScope)
.sort((a, b) => (a.order_priority || 0) - (b.order_priority || 0));
});
import TableLabeledPagination from "@/components/displays/pagination/TableLabeledPagination.vue";
@@ -69,54 +112,18 @@ if (props.hideSearch) {
setHideSearchField(true);
}
if (props.setDepartmentFilter) {
setFilter(
"department",
props.setDepartmentFilter
);
}
if (props.setLaneFilter) {
setFilter(
"lane",
props.setLaneFilter
);
}
if (props.setVehicleTypeFilter) {
setFilter(
"product",
props.setVehicleTypeFilter
);
}
if (props.setConditionFilter) {
setFilter(
"condition_id",
props.setConditionFilter
);
}
if (props.autoLoad) {
loadList();
}
import { watch } from "vue";
watch(() => props.setVehicleTypeFilter, (newFilter) => {
if (newFilter) {
setFilter("product", newFilter);
} else {
setFilter("product", "*");
}
loadList();
});
watch(() => props.setConditionFilter, (newFilter) => {
if (newFilter) {
setFilter("condition_id", newFilter);
} else {
setFilter("condition_id", "*");
}
watch(() => [
props.setDepartmentFilter,
props.setLaneFilter,
props.setVehicleTypeFilter,
props.setConditionFilter,
props.setMachineTypeFilter,
], () => {
loadList();
});
@@ -129,11 +136,16 @@ defineExpose({
<template>
<TableLabeledPagination :label="SessionUser.objects.self_serve_tasks.meta.title">
<template #buttons="{ loadList }">
<slot name="buttons" :loadList="loadList" :totalItems="metaTotalItems">
<slot name="buttons" :loadList="loadList" :totalItems="sortedList.length">
<button
class="button is-primary is-small"
@click="SessionUser.objects.self_serve_tasks.showCreateObjectForm({
order_priority: metaTotalItems + 1
department: props.setDepartmentFilter,
lane: props.setLaneFilter,
product: props.setVehicleTypeFilter,
machine_type_id: props.setMachineTypeFilter,
condition_id: props.setConditionFilter,
order_priority: sortedList.length + 1
}, loadList)"
>
<span class="icon is-small">
@@ -23,6 +23,7 @@ const selectedVehicleType = ref(null);
const selfServeQuestionsPagination = ref(null);
const selfServeTasksPagination = ref(null);
const showTryModal = ref(false);
const machineTypeName = ref(null);
const selectVehicleType = (vehicleType) => {
selectedVehicleType.value = vehicleType;
@@ -32,6 +33,9 @@ const loadLane = async () => {
isLoading.value = true;
try {
lane.value = await SessionUser.objects.department_lanes.get.single(laneId);
machineTypeName.value = lane.value?.machine_type_id
? await SessionUser.objects.self_serve_machine_types.functions.getMachineTypeName(lane.value.machine_type_id)
: null;
} catch (error) {
console.error("Failed to load lane:", error);
} finally {
@@ -85,6 +89,10 @@ onMounted(() => {
{{ lane.status }}
</td>
</tr>
<tr>
<th>{{ SessionUser.objects.department_lanes.columns.machine_type_id.label }}</th>
<td>{{ machineTypeName || SessionUser.objects.global.language.none }}</td>
</tr>
</tbody>
</table>
</div>
@@ -147,6 +155,9 @@ onMounted(() => {
<div class="columns">
<div class="column is-12">
<div v-if="lane.machine_type_id" class="notification is-info is-light">
Runtime bruger maskintype-først for denne bane: <strong>{{ machineTypeName }}</strong>.
</div>
<div class="box">
<h3 class="title is-5">Vælg køretøjstype</h3>
<MyWashStartVehicleTypeSelection @selected="selectVehicleType" :selectedVehicleTypeId="selectedVehicleType?.id" />
@@ -154,8 +165,9 @@ onMounted(() => {
<div class="box">
<SelfServeConditionsPagination
:set-department-filter="departmentId"
:set-lane-filter="laneId"
:set-vehicle-type-filter="selectedVehicleType?.id"
:set-lane-filter="lane.machine_type_id ? null : laneId"
:set-vehicle-type-filter="lane.machine_type_id ? null : selectedVehicleType?.id"
:set-machine-type-filter="lane.machine_type_id || null"
:auto-load="true"
>
<template #buttons="{ loadList }">
@@ -164,11 +176,10 @@ onMounted(() => {
class="button is-primary"
@click="SessionUser.objects.self_serve_conditions.showCreateObjectForm({
department: departmentId,
lane: laneId,
product: selectedVehicleType?.id
lane: lane.machine_type_id ? 0 : laneId,
product: lane.machine_type_id ? 0 : (selectedVehicleType?.id || 0),
machine_type_id: lane.machine_type_id || null
}, loadList)"
:disabled="!selectedVehicleType"
:title="!selectedVehicleType ? t('admin.self_serve.select_vehicle_type_first') : ''"
>
<span class="icon">
<i class="fas fa-plus"></i>
@@ -178,8 +189,6 @@ onMounted(() => {
<button
class="button is-warning is-light"
@click="showTryModal = true"
:disabled="!selectedVehicleType"
:title="!selectedVehicleType ? t('admin.self_serve.select_vehicle_type_first') : ''"
>
<span class="icon">
<i class="fas fa-play"></i>
@@ -205,11 +214,9 @@ onMounted(() => {
@click="SessionUser.objects.self_serve_questions.showCreateObjectForm({
department: departmentId,
lane: laneId,
product: selectedVehicleType?.id,
product: selectedVehicleType?.id || 0,
order_priority: totalItems + 1
}, loadList)"
:disabled="!selectedVehicleType"
:title="!selectedVehicleType ? t('admin.self_serve.select_vehicle_type_first') : ''"
>
<span class="icon">
<i class="fas fa-plus"></i>
@@ -219,8 +226,6 @@ onMounted(() => {
<button
class="button is-warning is-light"
@click="showTryModal = true"
:disabled="!selectedVehicleType"
:title="!selectedVehicleType ? t('admin.self_serve.select_vehicle_type_first') : ''"
>
<span class="icon">
<i class="fas fa-play"></i>
@@ -235,8 +240,9 @@ onMounted(() => {
<SelfServeTasksPagination
ref="selfServeTasksPagination"
:set-department-filter="departmentId"
:set-lane-filter="laneId"
:set-vehicle-type-filter="selectedVehicleType?.id"
:set-lane-filter="lane.machine_type_id ? null : laneId"
:set-vehicle-type-filter="lane.machine_type_id ? null : selectedVehicleType?.id"
:set-machine-type-filter="lane.machine_type_id || null"
:auto-load="true"
>
<template #buttons="{ loadList, totalItems }">
@@ -245,12 +251,11 @@ onMounted(() => {
class="button is-primary"
@click="SessionUser.objects.self_serve_tasks.showCreateObjectForm({
department: departmentId,
lane: laneId,
product: selectedVehicleType?.id,
lane: lane.machine_type_id ? 0 : laneId,
product: lane.machine_type_id ? 0 : (selectedVehicleType?.id || 0),
machine_type_id: lane.machine_type_id || null,
order_priority: totalItems + 1
}, loadList)"
:disabled="!selectedVehicleType"
:title="!selectedVehicleType ? t('admin.self_serve.select_vehicle_type_first') : ''"
>
<span class="icon">
<i class="fas fa-plus"></i>
@@ -266,8 +271,6 @@ onMounted(() => {
<button
class="button is-warning is-light"
@click="showTryModal = true"
:disabled="!selectedVehicleType"
:title="!selectedVehicleType ? t('admin.self_serve.select_vehicle_type_first') : ''"
>
<span class="icon">
<i class="fas fa-play"></i>
@@ -294,7 +297,7 @@ onMounted(() => {
v-if="showTryModal"
:departmentId="departmentId"
:laneId="laneId"
:vehicleTypeId="selectedVehicleType?.id"
:vehicleTypeId="selectedVehicleType?.id || null"
@closeModal="showTryModal = false"
/>
</NotFoundFallBackPageWrapper>