Filter restricted products and add-ons based on customer attributes in POS components

- Introduced logic to filter out restricted products and add-ons using new utility methods (`isProductRestricted`, `isAddonRestricted`, `canBuyAdditionalServices`) based on customer attributes.
- Updated relevant POS components to replace unfiltered lists with computed properties for restricted items.
- Enhanced maintainability by centralizing restriction checks in `POSDepartmentProcess.vue`.
This commit is contained in:
Jeppe Bundgaard
2026-02-17 17:21:19 +01:00
parent 382d9e6f6b
commit a978675899
4 changed files with 92 additions and 11 deletions
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { setTransparency, setBackgroundColor, backgroundColors } from "@/components/viewport/page/headers/ViewportHeaderSettings.vue";
import {onMounted, onUnmounted, ref, watch} from "vue";
import {onMounted, onUnmounted, ref, watch, computed} from "vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import PosDepartmentStepMobile2Product
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2Product.vue";
@@ -19,7 +19,7 @@ import PosDepartmentStep2MobileVehicleSelection
import PosDepartmentStepMobileButtonNextStep
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobileButtonNextStep.vue";
import { primaryItem } from "./objects/PosDepartmentStepMobileFlow.vue";
import { order_id, order_notes, department_id, customer_id, getCustomerEmail, customer_name } from "@/components/shop/POSDepartmentProcess.vue";
import { order_id, order_notes, department_id, customer_id, getCustomerEmail, customer_name, isAddonRestricted, canBuyAdditionalServices } from "@/components/shop/POSDepartmentProcess.vue";
import { getOrderItems } from "@/components/shop/OrdersItems.vue";
import { PosProduct } from "@/components/displays/department/pos/steps/mobile/objects/PosProduct.vue";
import PosDepartmentStepMobileButtonClearAll
@@ -499,7 +499,16 @@ watch(() => vehicles.vehicle_1.value.reference, (newValue, oldValue) => {
}
});
// Computed property to filter out restricted addons based on customer attributes
const filteredAddons = computed(() => {
const addons = transactionItems.primaryItem.value?.addons || [];
// If additional services are restricted, return empty array
if (!canBuyAdditionalServices()) {
return [];
}
// Filter out individually restricted addons
return addons.filter((addon: any) => !isAddonRestricted(addon));
});
</script>
@@ -545,8 +554,8 @@ watch(() => vehicles.vehicle_1.value.reference, (newValue, oldValue) => {
v-show="lastOrders.get(1)"
@click="lastOrders.select(1)"
:classes="layout.classes" :label="'ordered on...'" :subtitle="'Subtitle'" :lastOrder="lastOrders.get(1)"/>
<!-- Addons -->
<PosDepartmentStepMobile2Addons :addons="transactionItems.primaryItem.value?.addons || []" :label="'Add-ons'" :compact="false"/>
<!-- Addons (filtered based on customer restrictions) -->
<PosDepartmentStepMobile2Addons :addons="filteredAddons" :label="'Add-ons'" :compact="false"/>
<!-- Notes -->
<ControlField>
<ControlFieldInputLabel label="Notes" :classes="layout.classes" :optional="true"/>
@@ -22,6 +22,7 @@ import PosDepartmentStep2MobileVehicleSelection
import GenericButton from "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
import PosDepartmentStepMobile2FloatingCart
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2FloatingCart.vue";
import { isAddonRestricted, canBuyAdditionalServices } from "@/components/shop/POSDepartmentProcess.vue";
const props = defineProps({
label: {
@@ -117,6 +118,16 @@ const getAvailableAdditionalItems = () => {
}));
}
const availableAdditionalItems = ref<Addon[]>(getAvailableAdditionalItems());
// Computed property to filter out restricted additional items based on customer attributes
const filteredAdditionalItems = computed(() => {
// If additional services are restricted, return empty array
if (!canBuyAdditionalServices()) {
return [];
}
// Filter out individually restricted addons
return availableAdditionalItems.value.filter((addon: Addon) => !isAddonRestricted(addon));
});
const onClickAddOtherProduct = () => {
pos.views.additionalItemSelection.value = !pos.views.additionalItemSelection.value;
}
@@ -184,7 +195,7 @@ const onClickAddProduct = async (product: PosProduct) => {
<div v-if="!availableAdditionalItems || availableAdditionalItems.length === 0">
<p>{{ SessionUser.objects.global.language.no_additional_items }}</p>
</div>
<PosDepartmentStepMobile2Addons :label="''" :addons="availableAdditionalItems" />
<PosDepartmentStepMobile2Addons :label="''" :addons="filteredAdditionalItems" />
</div>
</template>
<!-- Footer -->
@@ -1,12 +1,12 @@
<script setup lang="ts">
import {watch, defineEmits, defineProps, ref, defineSlots} from "vue";
import {watch, defineEmits, defineProps, ref, defineSlots, computed} from "vue";
import { pos } from "../objects/PosDepartmentStepMobileFlow.vue";
import PosDepartmentStepMobile2CategoryProduct
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2CategoryProduct.vue";
import {PosCategory} from "@/components/displays/department/pos/steps/mobile/objects/PosCategory.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import {PosProduct} from "@/components/displays/department/pos/steps/mobile/objects/PosProduct.vue";
import { department_id, customer_id } from "@/components/shop/POSDepartmentProcess.vue";
import { department_id, customer_id, isProductRestricted } from "@/components/shop/POSDepartmentProcess.vue";
import PosDepartmentStepMobile2Addons
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2Addons.vue";
import {Addon} from "@/components/displays/department/pos/steps/mobile/objects/PosAddon.vue";
@@ -79,8 +79,12 @@ const mergedAddons = ref<Addon[]>([]);
// Function to merge the count of addons with the products
const mergeCountAddons = (products: PosProduct[], addons: Addon[] | undefined) => {
const tmpAddons = <Addon[]>[]; // Temporary array to hold the merged addons
// Convert all products to addons
// Convert all products to addons (only non-restricted products)
for (let product of products) {
// Skip restricted products
if (isProductRestricted(product)) {
continue;
}
let tmpAddon = pos.transactionItems.convertProductToAddon(product); // Convert product to addon (count = 0)
tmpAddons.push(tmpAddon);
}
@@ -100,13 +104,18 @@ const mergeCountAddons = (products: PosProduct[], addons: Addon[] | undefined) =
watch(() => props.addons, (newAddons) => {
mergeCountAddons(pos.productList.get(), newAddons);
}, { immediate: true });
// Computed property to filter out restricted products
const filteredProducts = computed(() => {
return pos.productList.get().filter(product => !isProductRestricted(product));
});
</script>
<template>
<template v-if="pos.categories.isSelected()">
<template v-if="!props.asAddons">
<!-- Display of products, without a "basket" -->
<template v-for="product in pos.productList.get()" :key="product.id">
<!-- Display of products, without a "basket" (filtered for customer restrictions) -->
<template v-for="product in filteredProducts" :key="product.id">
<PosDepartmentStepMobile2CategoryProduct
@addProduct="emits('addProduct', product)"
:price="product.price"
@@ -671,6 +671,58 @@ export const canBuyTankCleaning = () => {
return !hasAttribute('restrictTankCleaning');
};
/** Check if the customer is allowed to buy additional services */
export const canBuyAdditionalServices = () => {
// Check if the customer has the attribute restrictAdditionalServices
return !hasAttribute('restrictAdditionalServices');
};
/** Check if the customer has the onlyTankCleaning attribute */
export const hasOnlyTankCleaning = () => {
return hasAttribute('onlyTankCleaning');
};
/** Check if a product is restricted based on customer attributes */
export const isProductRestricted = (product) => {
if (!product) return false;
const productName = (product.name || '').toLowerCase();
// Check if the product has "Spot Free" in the name
if (!canBuySpotFree() && productName.includes('spot free')) {
return true;
}
// Check if the product has "Indvendig vask" in the name
if (!canBuyInteriorCleaning() && productName.includes('indvendig vask')) {
return true;
}
// Check if the product category is 5 (Tank Cleaning)
if (product.category === 5) {
if (!canBuyTankCleaning()) {
return true;
}
if (!hasOnlyTankCleaning()) {
return true;
}
}
return false;
};
/** Check if an addon is restricted based on customer attributes */
export const isAddonRestricted = (addon) => {
if (!addon) return false;
const addonName = (addon.name || '').toLowerCase();
// Check if the addon has "Spot Free" in the name
if (!canBuySpotFree() && addonName.includes('spot free')) {
return true;
}
// Check if the addon has "Indvendig vask" in the name
if (!canBuyInteriorCleaning() && addonName.includes('indvendig vask')) {
return true;
}
return false;
};
export function loadOrderDetails(onAfterSuccess = null) {
getOrderDetails().then((response) => {
console.log(response);