Compare commits

...
2 changed files with 57 additions and 30 deletions
@@ -34,20 +34,42 @@ let nextTemporaryCollectionId = -1;
const canView = computed(canViewCustomerRuleConfiguration);
const canManage = computed(canManageCustomerRuleConfiguration);
const productRuleDefinitions = computed(() => (
const productRuleDefinitions = computed(() =>
getCustomerRuleDefinitions().filter((definition) => definition.productImpact)
));
);
const productRuleTranslations = computed(() => ({
restrictAdditionalServices: {
label: t("customer_rules.attributes.restrictAdditionalServices.label"),
description: t("customer_rules.attributes.restrictAdditionalServices.description"),
},
restrictTankCleaning: {
label: t("customer_rules.attributes.restrictTankCleaning.label"),
description: t("customer_rules.attributes.restrictTankCleaning.description"),
},
restrictSpotFree: {
label: t("customer_rules.attributes.restrictSpotFree.label"),
description: t("customer_rules.attributes.restrictSpotFree.description"),
},
restrictInteriorCleaning: {
label: t("customer_rules.attributes.restrictInteriorCleaning.label"),
description: t("customer_rules.attributes.restrictInteriorCleaning.description"),
},
onlyTankCleaning: {
label: t("customer_rules.attributes.onlyTankCleaning.label"),
description: t("customer_rules.attributes.onlyTankCleaning.description"),
},
}));
const productRuleTranslation = (attribute, field) => productRuleTranslations.value[attribute]?.[field] ?? attribute;
const normalizedSnapshot = (rule) => JSON.stringify(serializeCustomerRuleProductRestriction(rule));
const isSaving = (attribute) => savingAttributes.value.has(attribute);
const hasConflict = (attribute) => conflictAttributes.value.has(attribute);
const isDirty = (rule) => savedSnapshots.value.get(rule.attribute) !== normalizedSnapshot(rule);
const effectiveDisabledProductIds = (rule) => (
[...new Set(rule.collections.flatMap((collection) => collection.product_ids))]
);
const productIsArchived = (product) => (
product?.deleted_at != null || product?.active === false || product?.is_active === false
);
const effectiveDisabledProductIds = (rule) => [
...new Set(rule.collections.flatMap((collection) => collection.product_ids)),
];
const productIsArchived = (product) =>
product?.deleted_at != null || product?.active === false || product?.is_active === false;
const productLabel = (product) => {
const category = String(product?.category_name ?? product?.category?.name ?? "").trim();
return category ? `${product.name} · ${category}` : product.name;
@@ -56,21 +78,19 @@ const productLabel = (product) => {
const normalizeLoadedRules = (response) => {
const normalized = normalizeCustomerRuleProductRestrictionResponse(response);
const byAttribute = new Map(normalized.rules.map((rule) => [rule.attribute, rule]));
const nextDrafts = productRuleDefinitions.value.map((definition) => (
byAttribute.get(definition.attribute)
?? normalizeCustomerRuleProductRestriction({ attribute: definition.attribute, version: 0, collections: [] })
));
const nextDrafts = productRuleDefinitions.value.map(
(definition) =>
byAttribute.get(definition.attribute) ??
normalizeCustomerRuleProductRestriction({ attribute: definition.attribute, version: 0, collections: [] })
);
drafts.value = nextDrafts;
products.value = normalized.products.sort((left, right) => left.name.localeCompare(right.name));
savedSnapshots.value = new Map(nextDrafts.map((rule) => [rule.attribute, normalizedSnapshot(rule)]));
};
const parseError = (error, fallbackKey) => (
error?.response?.data?.data?.message
|| error?.response?.data?.message
|| t(fallbackKey)
);
const parseError = (error, fallbackMessage) =>
error?.response?.data?.data?.message || error?.response?.data?.message || fallbackMessage;
const load = async () => {
if (!canView.value) {
@@ -87,7 +107,7 @@ const load = async () => {
conflictAttributes.value = new Set();
} catch (error) {
console.error("Failed to load customer rule product restrictions", error);
loadError.value = parseError(error, "customer_rules.configuration.errors.load");
loadError.value = parseError(error, t("customer_rules.configuration.errors.load"));
} finally {
isLoading.value = false;
}
@@ -107,10 +127,9 @@ const visibleProducts = (rule, collection) => {
return products.value;
}
return products.value.filter((product) => (
productLabel(product).toLocaleLowerCase().includes(search)
|| String(product.id).includes(search)
));
return products.value.filter(
(product) => productLabel(product).toLocaleLowerCase().includes(search) || String(product.id).includes(search)
);
};
const isProductSelected = (collection, productId) => collection.product_ids.includes(productId);
@@ -222,7 +241,7 @@ const save = async (rule) => {
} else {
saveErrors.value = {
...saveErrors.value,
[rule.attribute]: parseError(error, "customer_rules.configuration.errors.save"),
[rule.attribute]: parseError(error, t("customer_rules.configuration.errors.save")),
};
}
} finally {
@@ -239,10 +258,13 @@ const cancel = (rule) => {
}
const saved = JSON.parse(snapshot);
replaceSavedRule(rule.attribute, normalizeCustomerRuleProductRestriction({
attribute: rule.attribute,
...saved,
}));
replaceSavedRule(
rule.attribute,
normalizeCustomerRuleProductRestriction({
attribute: rule.attribute,
...saved,
})
);
saveErrors.value = { ...saveErrors.value, [rule.attribute]: "" };
};
@@ -288,8 +310,10 @@ watch(
>
<div class="customer-rule-configuration__rule-header">
<div>
<h2 class="title is-5 mb-1">{{ t(`customer_rules.attributes.${rule.attribute}.label`) }}</h2>
<p class="is-size-7 has-text-grey">{{ t(`customer_rules.attributes.${rule.attribute}.description`) }}</p>
<h2 class="title is-5 mb-1">{{ productRuleTranslation(rule.attribute, "label") }}</h2>
<p class="is-size-7 has-text-grey">
{{ productRuleTranslation(rule.attribute, "description") }}
</p>
</div>
<div class="tags">
<span class="tag is-light">{{ t("customer_rules.configuration.version", { version: rule.version }) }}</span>
+4 -1
View File
@@ -361,7 +361,10 @@ test.describe("admin POS order filters", () => {
});
test("preserves the department and draft customer across filter changes", async ({ page }, testInfo) => {
test.skip(testInfo.project.name.includes("mobile"), "Desktop draft filter coverage");
test.skip(
testInfo.project.name.includes("mobile") || testInfo.project.name.includes("tablet"),
"Desktop draft filter coverage"
);
await page.clock.setFixedTime(new Date("2026-07-07T10:00:00.000Z"));
const requests = await setupAdminOrderFilters(page);