|
|
|
@@ -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>
|
|
|
|
|