rework calcCart
use swr for discounts datafetching remove discounts store refetch discount on promocode apply
This commit is contained in:
parent
6607919624
commit
10e2022035
@ -15,7 +15,7 @@
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@frontend/kitui": "^1.0.70",
|
||||
"@frontend/kitui": "^1.0.71",
|
||||
"@mui/icons-material": "^5.10.14",
|
||||
"@mui/material": "^5.10.14",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
|
@ -1,24 +1,37 @@
|
||||
import { makeRequest } from "@frontend/kitui"
|
||||
import { makeRequest } from "@frontend/kitui";
|
||||
import type { GetDiscountsResponse } from "@root/model/discount";
|
||||
import { parseAxiosError } from "@root/utils/parse-error";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { parseAxiosError } from "@root/utils/parse-error"
|
||||
|
||||
import type { GetDiscountsResponse } from "@root/model/discount"
|
||||
const apiUrl = process.env.REACT_APP_DOMAIN + "/price";
|
||||
|
||||
const apiUrl = process.env.REACT_APP_DOMAIN + "/price"
|
||||
|
||||
export async function getDiscounts(signal: AbortSignal | undefined): Promise<[GetDiscountsResponse | null, string?]> {
|
||||
export async function getDiscounts() {
|
||||
try {
|
||||
const discountsResponse = await makeRequest<never, GetDiscountsResponse>({
|
||||
url: apiUrl + "/discounts",
|
||||
method: "get",
|
||||
useToken: true,
|
||||
signal,
|
||||
})
|
||||
});
|
||||
|
||||
return [discountsResponse]
|
||||
return discountsResponse.Discounts;
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError)
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
|
||||
return [null, `Ошибка получения списка скидок. ${error}`]
|
||||
throw new Error(`Ошибка получения списка скидок. ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function useDiscounts() {
|
||||
const { data } = useSWR("discounts", getDiscounts, {
|
||||
keepPreviousData: true,
|
||||
onError: (error) => {
|
||||
if (!(error instanceof Error)) return;
|
||||
|
||||
enqueueSnackbar(error.message, { variant: "error" });
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
@ -19,8 +19,6 @@ import { enqueueSnackbar } from "notistack";
|
||||
import { updateTariffs } from "@root/stores/tariffs";
|
||||
import { useCustomTariffs } from "@root/utils/hooks/useCustomTariffs";
|
||||
import { setCustomTariffs } from "@root/stores/customTariffs";
|
||||
import { useDiscounts } from "@root/utils/hooks/useDiscounts";
|
||||
import { setDiscounts } from "@root/stores/discounts";
|
||||
import { setPrivileges } from "@root/stores/privileges";
|
||||
import { useHistoryData } from "@root/utils/hooks/useHistoryData";
|
||||
import { useSSETab } from "@root/utils/hooks/useSSETab";
|
||||
@ -79,14 +77,6 @@ export default function ProtectedLayout() {
|
||||
},
|
||||
});
|
||||
|
||||
useDiscounts({
|
||||
onNewDiscounts: setDiscounts,
|
||||
onError: (error) => {
|
||||
const message = getMessageFromFetchError(error);
|
||||
if (message) enqueueSnackbar(message);
|
||||
},
|
||||
});
|
||||
|
||||
usePrivilegeFetcher({
|
||||
onSuccess: setPrivileges,
|
||||
onError: (error) => {
|
||||
|
@ -29,6 +29,7 @@ import CollapsiblePromocodeField from "./CollapsiblePromocodeField";
|
||||
import PaymentMethodCard from "./PaymentMethodCard";
|
||||
import { SorryModal } from "./SorryModal";
|
||||
import { WarnModal } from "./WarnModal";
|
||||
import { mutate } from "swr";
|
||||
|
||||
type PaymentMethod = {
|
||||
label: string;
|
||||
@ -171,6 +172,7 @@ export default function Payment() {
|
||||
|
||||
activatePromocode(promocodeField).then(response => {
|
||||
enqueueSnackbar(response);
|
||||
mutate("discounts");
|
||||
}).catch(error => {
|
||||
enqueueSnackbar(error.message);
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { CustomPrivilege, Privilege, useThrottle } from "@frontend/kitui";
|
||||
import { CustomPrivilege, useThrottle } from "@frontend/kitui";
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useDiscounts } from "@root/api/price";
|
||||
import { CustomSlider } from "@root/components/CustomSlider";
|
||||
import NumberInputWithUnitAdornment from "@root/components/NumberInputWithUnitAdornment";
|
||||
import CalendarIcon from "@root/components/icons/CalendarIcon";
|
||||
@ -30,6 +31,7 @@ export default function TariffPrivilegeSlider({ privilege }: Props) {
|
||||
const userValue = useCustomTariffsStore((state) => state.userValuesMap[privilege.serviceKey]?.[privilege._id]) ?? sliderSettingsByType[privilege.value]?.min;
|
||||
const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.spent) ?? sliderSettingsByType[privilege.value]?.min;
|
||||
const cartTariffs = useCartTariffs();
|
||||
const discounts = useDiscounts();
|
||||
const userId = useUserStore(state => state.user?._id) ?? "";
|
||||
const [value, setValue] = useState<number>(userValue);
|
||||
const throttledValue = useThrottle(value, 200);
|
||||
@ -38,6 +40,7 @@ export default function TariffPrivilegeSlider({ privilege }: Props) {
|
||||
function setStoreValue() {
|
||||
setCustomTariffsUserValue(
|
||||
cartTariffs ?? [],
|
||||
discounts ?? [],
|
||||
privilege.serviceKey,
|
||||
privilege._id,
|
||||
throttledValue,
|
||||
@ -45,7 +48,7 @@ export default function TariffPrivilegeSlider({ privilege }: Props) {
|
||||
userId,
|
||||
);
|
||||
},
|
||||
[cartTariffs, purchasesAmount, privilege._id, privilege.serviceKey, throttledValue, userId]
|
||||
[cartTariffs, discounts, purchasesAmount, privilege._id, privilege.serviceKey, throttledValue, userId]
|
||||
);
|
||||
|
||||
function handleSliderChange(measurement: PrivilegeName) {
|
||||
|
@ -3,7 +3,6 @@ import { Tariff, getMessageFromFetchError } from "@frontend/kitui";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import NumberIcon from "@root/components/NumberIcon";
|
||||
import { useDiscountStore } from "@root/stores/discounts";
|
||||
import { useTariffStore } from "@root/stores/tariffs";
|
||||
import { addTariffToCart, useUserStore } from "@root/stores/user";
|
||||
import { calcIndividualTariffPrices } from "@root/utils/calcTariffPrices";
|
||||
@ -17,6 +16,7 @@ import { withErrorBoundary } from "react-error-boundary";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import FreeTariffCard from "./FreeTariffCard";
|
||||
import TariffCard from "./TariffCard";
|
||||
import { useDiscounts } from "@root/api/price";
|
||||
|
||||
const subPages = ["Опросник", /*"Шаблонизатор",*/ /*"Сокращатель ссылок"*/];
|
||||
|
||||
@ -35,7 +35,7 @@ function TariffPage() {
|
||||
const location = useLocation();
|
||||
const tariffs = useTariffStore((state) => state.tariffs);
|
||||
const [selectedItem, setSelectedItem] = useState<number>(0);
|
||||
const discounts = useDiscountStore((state) => state.discounts);
|
||||
const discounts = useDiscounts();
|
||||
const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.spent) ?? 0;
|
||||
const userId = useUserStore(state => state.user?._id) ?? "";
|
||||
const isUserNko = useUserStore((state) => state.userAccount?.status) === "nko";
|
||||
@ -75,7 +75,7 @@ function TariffPage() {
|
||||
.map((tariff, index) => {
|
||||
const { priceBeforeDiscounts, priceAfterDiscounts } = calcIndividualTariffPrices(
|
||||
tariff,
|
||||
discounts,
|
||||
discounts ?? [],
|
||||
purchasesAmount,
|
||||
currentTariffs ?? [],
|
||||
isUserNko,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { CustomPrivilegeWithAmount, Tariff } from "@frontend/kitui";
|
||||
import { CustomPrivilegeWithAmount, Discount, Tariff } from "@frontend/kitui";
|
||||
import { createTariff } from "@root/api/tariff";
|
||||
import { CustomTariffUserValuesMap, ServiceKeyToPriceMap } from "@root/model/customTariffs";
|
||||
import { ServiceKeyToPrivilegesMap } from "@root/model/privilege";
|
||||
@ -6,7 +6,6 @@ import { calcCustomTariffPrice } from "@root/utils/calcCart/calcCustomTariffPric
|
||||
import { produce } from "immer";
|
||||
import { create } from "zustand";
|
||||
import { devtools, persist } from "zustand/middleware";
|
||||
import { useDiscountStore } from "./discounts";
|
||||
import { useUserStore } from "./user";
|
||||
|
||||
|
||||
@ -48,6 +47,7 @@ export const setCustomTariffs = (customTariffs: ServiceKeyToPrivilegesMap) => us
|
||||
|
||||
export const setCustomTariffsUserValue = (
|
||||
cartTariffs: Tariff[],
|
||||
discounts: Discount[],
|
||||
serviceKey: string,
|
||||
privilegeId: string,
|
||||
value: number,
|
||||
@ -58,7 +58,6 @@ export const setCustomTariffsUserValue = (
|
||||
state.userValuesMap[serviceKey] ??= {};
|
||||
state.userValuesMap[serviceKey][privilegeId] = value;
|
||||
const isUserNko = useUserStore.getState().userAccount?.status === "nko";
|
||||
const discounts = useDiscountStore.getState().discounts;
|
||||
|
||||
const { priceBeforeDiscounts, priceAfterDiscounts } = calcCustomTariffPrice(
|
||||
state.userValuesMap[serviceKey],
|
||||
|
@ -1,22 +0,0 @@
|
||||
import { Discount } from "@frontend/kitui"
|
||||
import { create } from "zustand"
|
||||
import { devtools } from "zustand/middleware"
|
||||
|
||||
|
||||
interface DiscountStore {
|
||||
discounts: Discount[];
|
||||
}
|
||||
|
||||
export const useDiscountStore = create<DiscountStore>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
discounts: []
|
||||
}),
|
||||
{
|
||||
name: "Discounts",
|
||||
enabled: process.env.NODE_ENV === "development",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
export const setDiscounts = (discounts: DiscountStore["discounts"]) => useDiscountStore.setState({ discounts })
|
@ -13,7 +13,7 @@ describe("Cart calculation", () => {
|
||||
|
||||
const tariffs = testTariffs.filter((_, index) => (usedTariffsMask[index] === 1));
|
||||
|
||||
const cart = calcCart(tariffs, testDiscounts, 0, isNkoApplied, "someuserid");
|
||||
const cart = calcCart(tariffs, testDiscounts, 0, "someuserid", isNkoApplied);
|
||||
|
||||
expect(cart.priceAfterDiscounts).toBeCloseTo(cartTestResults[i][0]);
|
||||
});
|
||||
|
@ -4,30 +4,27 @@ import {
|
||||
PrivilegeCartData,
|
||||
Tariff,
|
||||
TariffCartData,
|
||||
findCartDiscount,
|
||||
findDiscountFactor,
|
||||
} from "@frontend/kitui";
|
||||
import {
|
||||
findCartDiscount,
|
||||
findLoyaltyDiscount,
|
||||
findNkoDiscount,
|
||||
findPrivilegeDiscount,
|
||||
findServiceDiscount,
|
||||
} from "@frontend/kitui";
|
||||
} from "./utils";
|
||||
|
||||
export function calcCart(tariffs: Tariff[], discounts: Discount[], purchasesAmount: number, isUserNko?: boolean, userId: string = ""): CartData {
|
||||
export function calcCart(tariffs: Tariff[], discounts: Discount[], purchasesAmount: number, userId: string, isUserNko?: boolean): CartData {
|
||||
const cartData: CartData = {
|
||||
services: [],
|
||||
priceBeforeDiscounts: 0,
|
||||
priceAfterDiscounts: 0,
|
||||
appliedCartPurchasesDiscount: null,
|
||||
appliedLoyaltyDiscount: null,
|
||||
allAppliedDiscounts: [],
|
||||
appliedDiscountsByPrivilegeId: new Map(),
|
||||
};
|
||||
const privilegeAmountById = new Map<string, number>();
|
||||
const servicePriceByKey = new Map<string, number>();
|
||||
const allAppliedDiscounts = new Set<Discount>();
|
||||
|
||||
// Формируем корзину
|
||||
|
||||
tariffs.forEach(tariff => {
|
||||
if (tariff.privileges === undefined) return;
|
||||
if (
|
||||
@ -43,7 +40,6 @@ export function calcCart(tariffs: Tariff[], discounts: Discount[], purchasesAmou
|
||||
serviceKey: tariff.isCustom ? "custom" : tariff.privileges[0]?.serviceKey,
|
||||
tariffs: [],
|
||||
price: 0,
|
||||
appliedServiceDiscount: null,
|
||||
};
|
||||
cartData.services.push(serviceData);
|
||||
}
|
||||
@ -59,7 +55,8 @@ export function calcCart(tariffs: Tariff[], discounts: Discount[], purchasesAmou
|
||||
|
||||
tariff.privileges.forEach(privilege => {
|
||||
let privilegePrice = privilege.amount * privilege.price;
|
||||
if (tariff.price) privilegePrice = tariff.price;
|
||||
if (!tariff.price) tariffCartData.price += privilegePrice;
|
||||
else privilegePrice = tariff.price;
|
||||
|
||||
const privilegeCartData: PrivilegeCartData = {
|
||||
serviceKey: privilege.serviceKey,
|
||||
@ -71,57 +68,63 @@ export function calcCart(tariffs: Tariff[], discounts: Discount[], purchasesAmou
|
||||
|
||||
privilegeAmountById.set(
|
||||
privilege.privilegeId,
|
||||
privilege.amount + (privilegeAmountById.get(privilege.privilegeId) || 0)
|
||||
privilege.amount + (privilegeAmountById.get(privilege.privilegeId) ?? 0)
|
||||
);
|
||||
servicePriceByKey.set(
|
||||
privilege.serviceKey,
|
||||
privilegePrice + (servicePriceByKey.get(privilege.serviceKey) || 0)
|
||||
privilegePrice + (servicePriceByKey.get(privilege.serviceKey) ?? 0)
|
||||
);
|
||||
cartData.appliedDiscountsByPrivilegeId.set(privilege.privilegeId, new Set());
|
||||
|
||||
tariffCartData.privileges.push(privilegeCartData);
|
||||
});
|
||||
|
||||
serviceData.price += tariffCartData.price;
|
||||
});
|
||||
|
||||
cartData.priceBeforeDiscounts = Array.from(servicePriceByKey.values()).reduce((a, b) => a + b, 0);
|
||||
cartData.priceAfterDiscounts = cartData.priceBeforeDiscounts;
|
||||
|
||||
const nkoDiscount = findNkoDiscount(discounts);
|
||||
if (isUserNko && nkoDiscount) {
|
||||
cartData.priceAfterDiscounts *= nkoDiscount.Target.Factor;
|
||||
allAppliedDiscounts.add(nkoDiscount);
|
||||
cartData.allAppliedDiscounts = [nkoDiscount];
|
||||
|
||||
cartData.services.forEach(service => {
|
||||
service.tariffs.forEach(tariff => {
|
||||
tariff.privileges.forEach(privilege => {
|
||||
const privilegeAppliedDiscounts = cartData.appliedDiscountsByPrivilegeId.get(privilege.privilegeId);
|
||||
if (!privilegeAppliedDiscounts) throw new Error(`Privilege id ${privilege.privilegeId} not found in appliedDiscountsByPrivilegeId`);
|
||||
const discountAmount = privilege.price * (1 - findDiscountFactor(nkoDiscount));
|
||||
|
||||
privilegeAppliedDiscounts.add(nkoDiscount);
|
||||
privilege.price -= discountAmount;
|
||||
tariff.price -= discountAmount;
|
||||
service.price -= discountAmount;
|
||||
cartData.priceAfterDiscounts -= discountAmount;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
applyDiscountsToCart(cartData);
|
||||
|
||||
cartData.allAppliedDiscounts = Array.from(allAppliedDiscounts);
|
||||
|
||||
return cartData;
|
||||
}
|
||||
|
||||
// Ищем и собираем скидки в appliedDiscountsByPrivilegeId
|
||||
|
||||
cartData.services.forEach(service => {
|
||||
service.tariffs.forEach(tariff => {
|
||||
tariff.privileges.forEach(privilege => {
|
||||
const privilegeDiscount = findPrivilegeDiscount(privilege.privilegeId, privilegeAmountById.get(privilege.privilegeId) || 0, discounts, userId);
|
||||
if (!privilegeDiscount) return;
|
||||
const privilegeTotalAmount = privilegeAmountById.get(privilege.privilegeId) ?? 0;
|
||||
|
||||
allAppliedDiscounts.add(privilegeDiscount);
|
||||
const discount = findPrivilegeDiscount(privilege.privilegeId, privilegeTotalAmount, discounts, userId);
|
||||
if (!discount) return;
|
||||
|
||||
const privilegeAppliedDiscounts = cartData.appliedDiscountsByPrivilegeId.get(privilege.privilegeId);
|
||||
if (!privilegeAppliedDiscounts) throw new Error(`Privilege id ${privilege.privilegeId} not found in appliedDiscountsByPrivilegeId`);
|
||||
allAppliedDiscounts.add(discount);
|
||||
|
||||
privilegeAppliedDiscounts.add(privilegeDiscount);
|
||||
const discountAmount = privilege.price * (1 - findDiscountFactor(discount));
|
||||
|
||||
privilege.price -= discountAmount;
|
||||
tariff.price -= discountAmount;
|
||||
service.price -= discountAmount;
|
||||
cartData.priceAfterDiscounts -= discountAmount;
|
||||
|
||||
const serviceTotalPrice = servicePriceByKey.get(privilege.serviceKey);
|
||||
if (!serviceTotalPrice) throw new Error(`Service key ${privilege.serviceKey} not found in servicePriceByKey`);
|
||||
|
||||
servicePriceByKey.set(privilege.serviceKey, serviceTotalPrice - discountAmount);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -129,96 +132,67 @@ export function calcCart(tariffs: Tariff[], discounts: Discount[], purchasesAmou
|
||||
cartData.services.forEach(service => {
|
||||
service.tariffs.map(tariff => {
|
||||
tariff.privileges.forEach(privilege => {
|
||||
const servicePrice = servicePriceByKey.get(privilege.serviceKey);
|
||||
if (!servicePrice) return;
|
||||
const serviceTotalPrice = servicePriceByKey.get(privilege.serviceKey);
|
||||
if (!serviceTotalPrice) throw new Error(`Service key ${privilege.serviceKey} not found in servicePriceByKey`);
|
||||
|
||||
const serviceDiscount = findServiceDiscount(privilege.serviceKey, servicePrice, discounts, userId);
|
||||
if (!serviceDiscount) return;
|
||||
const discount = findServiceDiscount(privilege.serviceKey, serviceTotalPrice, discounts, userId);
|
||||
if (!discount) return;
|
||||
|
||||
allAppliedDiscounts.add(serviceDiscount);
|
||||
service.appliedServiceDiscount = serviceDiscount;
|
||||
allAppliedDiscounts.add(discount);
|
||||
|
||||
const privilegeAppliedDiscounts = cartData.appliedDiscountsByPrivilegeId.get(privilege.privilegeId);
|
||||
if (!privilegeAppliedDiscounts) throw new Error(`Privilege id ${privilege.privilegeId} not found in appliedDiscountsByPrivilegeId`);
|
||||
const discountAmount = privilege.price * (1 - findDiscountFactor(discount));
|
||||
|
||||
privilegeAppliedDiscounts.add(serviceDiscount);
|
||||
privilege.price -= discountAmount;
|
||||
tariff.price -= discountAmount;
|
||||
service.price -= discountAmount;
|
||||
cartData.priceAfterDiscounts -= discountAmount;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const intermediateCartData = structuredClone(cartData);
|
||||
applyDiscountsToCart(intermediateCartData);
|
||||
const userDiscount = discounts.find(discount => discount.Condition.User === userId);
|
||||
|
||||
const intermediateCartPriceAfterDiscounts = intermediateCartData.priceAfterDiscounts;
|
||||
|
||||
const cartDiscount = findCartDiscount(intermediateCartPriceAfterDiscounts, discounts);
|
||||
const cartDiscount = findCartDiscount(cartData.priceAfterDiscounts, discounts);
|
||||
if (cartDiscount) {
|
||||
cartData.services.forEach(service => {
|
||||
if (service.serviceKey === userDiscount?.Condition.Group) return;
|
||||
|
||||
service.tariffs.forEach(tariff => {
|
||||
tariff.privileges.forEach(privilege => {
|
||||
const privilegeAppliedDiscounts = cartData.appliedDiscountsByPrivilegeId.get(privilege.privilegeId);
|
||||
if (!privilegeAppliedDiscounts) throw new Error(`Privilege id ${privilege.privilegeId} not found in appliedDiscountsByPrivilegeId`);
|
||||
|
||||
if (!Array.from(privilegeAppliedDiscounts)[0]?.Condition.User) privilegeAppliedDiscounts.add(cartDiscount);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
allAppliedDiscounts.add(cartDiscount);
|
||||
cartData.appliedCartPurchasesDiscount = cartDiscount;
|
||||
|
||||
const discountAmount = privilege.price * (1 - findDiscountFactor(cartDiscount));
|
||||
|
||||
privilege.price -= discountAmount;
|
||||
tariff.price -= discountAmount;
|
||||
service.price -= discountAmount;
|
||||
cartData.priceAfterDiscounts -= discountAmount;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const loyalDiscount = findLoyaltyDiscount(purchasesAmount, discounts);
|
||||
if (loyalDiscount) {
|
||||
cartData.services.forEach(service => {
|
||||
if (service.serviceKey === userDiscount?.Condition.Group) return;
|
||||
|
||||
service.tariffs.forEach(tariff => {
|
||||
tariff.privileges.forEach(privilege => {
|
||||
const privilegeAppliedDiscounts = cartData.appliedDiscountsByPrivilegeId.get(privilege.privilegeId);
|
||||
if (!privilegeAppliedDiscounts) throw new Error(`Privilege id ${privilege.privilegeId} not found in appliedDiscountsByPrivilegeId`);
|
||||
|
||||
if (!Array.from(privilegeAppliedDiscounts)[0]?.Condition.User) privilegeAppliedDiscounts.add(loyalDiscount);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
allAppliedDiscounts.add(loyalDiscount);
|
||||
cartData.appliedLoyaltyDiscount = loyalDiscount;
|
||||
|
||||
const discountAmount = privilege.price * (1 - findDiscountFactor(loyalDiscount));
|
||||
|
||||
privilege.price -= discountAmount;
|
||||
tariff.price -= discountAmount;
|
||||
service.price -= discountAmount;
|
||||
cartData.priceAfterDiscounts -= discountAmount;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Применяем скидки
|
||||
|
||||
applyDiscountsToCart(cartData);
|
||||
|
||||
cartData.allAppliedDiscounts = Array.from(allAppliedDiscounts);
|
||||
|
||||
return Object.freeze(cartData);
|
||||
}
|
||||
|
||||
function applyDiscountsToCart(cartData: CartData) {
|
||||
cartData.services.forEach(service => {
|
||||
let servicePrice = 0;
|
||||
|
||||
service.tariffs.forEach(tariff => {
|
||||
let privilegePriceSum = 0;
|
||||
let tariffDiscountFactor = 1;
|
||||
|
||||
tariff.privileges.forEach(privilege => {
|
||||
const discounts = cartData.appliedDiscountsByPrivilegeId.get(privilege.privilegeId) ?? [];
|
||||
|
||||
const discountsFactor = Array.from(discounts).reduce((factor, discount) => factor * findDiscountFactor(discount), 1);
|
||||
privilege.price *= discountsFactor;
|
||||
tariffDiscountFactor *= discountsFactor;
|
||||
|
||||
privilegePriceSum += privilege.price;
|
||||
});
|
||||
|
||||
if (tariff.price) tariff.price *= tariffDiscountFactor;
|
||||
else tariff.price = privilegePriceSum;
|
||||
|
||||
servicePrice += tariff.price;
|
||||
});
|
||||
|
||||
service.price = servicePrice;
|
||||
cartData.priceAfterDiscounts += servicePrice;
|
||||
});
|
||||
return cartData;
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ export function calcCustomTariffPrice(
|
||||
privileges: privileges,
|
||||
};
|
||||
|
||||
const cart = calcCart([...cartTariffs, customTariff], discounts, purchasesAmount, isUserNko, userId);
|
||||
const cart = calcCart([...cartTariffs, customTariff], discounts, purchasesAmount, userId, isUserNko);
|
||||
|
||||
const customService = cart.services.flatMap(service => service.tariffs).find(tariff => tariff.id === customTariff._id);
|
||||
if (!customService) throw new Error("Custom service not found in cart");
|
||||
|
119
src/utils/calcCart/utils.ts
Normal file
119
src/utils/calcCart/utils.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import { Discount } from "@frontend/kitui";
|
||||
|
||||
|
||||
export function findNkoDiscount(discounts: Discount[]): Discount | null {
|
||||
const applicableDiscounts = discounts.filter(discount => discount.Condition.UserType === "nko");
|
||||
|
||||
if (!applicableDiscounts.length) return null;
|
||||
|
||||
const maxValueDiscount = applicableDiscounts.reduce((prev, current) => {
|
||||
return Number(current.Condition.CartPurchasesAmount) > Number(prev.Condition.CartPurchasesAmount) ? current : prev;
|
||||
});
|
||||
|
||||
return maxValueDiscount;
|
||||
}
|
||||
|
||||
export function findPrivilegeDiscount(
|
||||
privilegeId: string,
|
||||
privilegeAmount: number,
|
||||
discounts: Discount[],
|
||||
userId: string,
|
||||
): Discount | null {
|
||||
const applicableDiscounts = discounts.filter(discount => {
|
||||
return (
|
||||
discount.Layer === 1
|
||||
&& privilegeId === discount.Condition.Product
|
||||
&& privilegeAmount >= Number(discount.Condition.Term)
|
||||
&& (discount.Condition.User === "" || discount.Condition.User === userId)
|
||||
);
|
||||
});
|
||||
|
||||
if (!applicableDiscounts.length) return null;
|
||||
|
||||
let maxValueDiscount: Discount = applicableDiscounts[0];
|
||||
for (const discount of applicableDiscounts) {
|
||||
if (discount.Condition.User !== "" && discount.Condition.User === userId) {
|
||||
maxValueDiscount = discount;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Number(discount.Condition.Term) > Number(maxValueDiscount.Condition.Term)) {
|
||||
maxValueDiscount = discount;
|
||||
}
|
||||
}
|
||||
|
||||
return maxValueDiscount;
|
||||
}
|
||||
|
||||
export function findServiceDiscount(
|
||||
serviceKey: string,
|
||||
currentPrice: number,
|
||||
discounts: Discount[],
|
||||
userId: string,
|
||||
): Discount | null {
|
||||
const applicableDiscounts = discounts.filter(discount => {
|
||||
return (
|
||||
discount.Layer === 2
|
||||
&& serviceKey === discount.Condition.Group
|
||||
&& currentPrice >= Number(discount.Condition.PriceFrom)
|
||||
&& (discount.Condition.User === "" || discount.Condition.User === userId)
|
||||
);
|
||||
});
|
||||
|
||||
if (!applicableDiscounts.length) return null;
|
||||
|
||||
let maxValueDiscount: Discount = applicableDiscounts[0];
|
||||
for (const discount of applicableDiscounts) {
|
||||
if (discount.Condition.User !== "" && discount.Condition.User === userId) {
|
||||
maxValueDiscount = discount;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Number(discount.Condition.PriceFrom) > Number(maxValueDiscount.Condition.PriceFrom)) {
|
||||
maxValueDiscount = discount;
|
||||
}
|
||||
}
|
||||
|
||||
return maxValueDiscount;
|
||||
}
|
||||
|
||||
export function findCartDiscount(
|
||||
cartPurchasesAmount: number,
|
||||
discounts: Discount[],
|
||||
): Discount | null {
|
||||
const applicableDiscounts = discounts.filter(discount => {
|
||||
return (
|
||||
discount.Layer === 3
|
||||
&& cartPurchasesAmount >= Number(discount.Condition.CartPurchasesAmount)
|
||||
);
|
||||
});
|
||||
|
||||
if (!applicableDiscounts.length) return null;
|
||||
|
||||
const maxValueDiscount = applicableDiscounts.reduce((prev, current) => {
|
||||
return Number(current.Condition.CartPurchasesAmount) > Number(prev.Condition.CartPurchasesAmount) ? current : prev;
|
||||
});
|
||||
|
||||
return maxValueDiscount;
|
||||
}
|
||||
|
||||
export function findLoyaltyDiscount(
|
||||
purchasesAmount: number,
|
||||
discounts: Discount[],
|
||||
): Discount | null {
|
||||
const applicableDiscounts = discounts.filter(discount => {
|
||||
return (
|
||||
discount.Layer === 4
|
||||
&& discount.Condition.UserType !== "nko"
|
||||
&& purchasesAmount >= Number(discount.Condition.PurchasesAmount)
|
||||
);
|
||||
});
|
||||
|
||||
if (!applicableDiscounts.length) return null;
|
||||
|
||||
const maxValueDiscount = applicableDiscounts.reduce((prev, current) => {
|
||||
return Number(current.Condition.PurchasesAmount) > Number(prev.Condition.PurchasesAmount) ? current : prev;
|
||||
});
|
||||
|
||||
return maxValueDiscount;
|
||||
}
|
@ -17,7 +17,7 @@ export function calcIndividualTariffPrices(
|
||||
0
|
||||
);
|
||||
|
||||
const cart = calcCart([...currentTariffs, targetTariff], discounts, purchasesAmount, isUserNko, userId);
|
||||
const cart = calcCart([...currentTariffs, targetTariff], discounts, purchasesAmount, userId, isUserNko);
|
||||
|
||||
const tariffCartData = cart.services.flatMap(service => service.tariffs).find(tariff => tariff.id === targetTariff._id);
|
||||
if (!tariffCartData) throw new Error(`Target tariff ${targetTariff._id} not found in cart`);
|
||||
|
@ -1,23 +1,21 @@
|
||||
import { useDiscountStore } from "@root/stores/discounts";
|
||||
import { CartData } from "@frontend/kitui";
|
||||
import { useUserStore } from "@root/stores/user";
|
||||
import { useDebugValue, useMemo } from "react";
|
||||
import { useMemo } from "react";
|
||||
import { calcCart } from "../calcCart/calcCart";
|
||||
import { useCartTariffs } from "./useCartTariffs";
|
||||
import { CartData } from "@frontend/kitui";
|
||||
import { useDiscounts } from "@root/api/price";
|
||||
|
||||
|
||||
export function useCart(): CartData {
|
||||
const cartTariffs = useCartTariffs();
|
||||
const discounts = useDiscountStore((state) => state.discounts);
|
||||
const discounts = useDiscounts();
|
||||
const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.spent) ?? 0;
|
||||
const isUserNko = useUserStore(state => state.userAccount?.status) === "nko";
|
||||
const userId = useUserStore(state => state.user?._id) ?? "";
|
||||
|
||||
const cart = useMemo(() => {
|
||||
return calcCart(cartTariffs ?? [], discounts, purchasesAmount, isUserNko, userId);
|
||||
}, [cartTariffs, discounts, purchasesAmount, isUserNko, userId]);
|
||||
|
||||
useDebugValue(cart);
|
||||
return calcCart(cartTariffs ?? [], discounts ?? [], purchasesAmount, userId, isUserNko);
|
||||
}, [cartTariffs, discounts, purchasesAmount, userId, isUserNko]);
|
||||
|
||||
return cart;
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
import { useEffect, useLayoutEffect, useRef } from "react"
|
||||
import { Discount, devlog } from "@frontend/kitui"
|
||||
|
||||
import { getDiscounts } from "@root/api/price"
|
||||
|
||||
export function useDiscounts({
|
||||
onNewDiscounts,
|
||||
onError,
|
||||
}: {
|
||||
url?: string;
|
||||
onNewDiscounts: (response: Discount[]) => void;
|
||||
onError: (error: Error) => void;
|
||||
}) {
|
||||
const onNewTariffsRef = useRef(onNewDiscounts)
|
||||
const onErrorRef = useRef(onError)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
onNewTariffsRef.current = onNewDiscounts
|
||||
onErrorRef.current = onError
|
||||
}, [onError, onNewDiscounts])
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController()
|
||||
|
||||
getDiscounts(controller.signal)
|
||||
.then(([discounts]) => {
|
||||
if (discounts) {
|
||||
onNewTariffsRef.current(discounts.Discounts)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
devlog("Error fetching tariffs", error)
|
||||
onErrorRef.current(error)
|
||||
})
|
||||
|
||||
return () => controller.abort()
|
||||
}, [])
|
||||
}
|
@ -1607,10 +1607,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2"
|
||||
integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==
|
||||
|
||||
"@frontend/kitui@^1.0.70":
|
||||
version "1.0.70"
|
||||
resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.70.tgz#4ff9d4ef51132bbf06d4efda561b95efdaf4e9f0"
|
||||
integrity sha1-T/nU71ETK78G1O/aVhuV79r06fA=
|
||||
"@frontend/kitui@^1.0.71":
|
||||
version "1.0.71"
|
||||
resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.71.tgz#16d20c163a1574ba98a4720c0deb21eab3e67b75"
|
||||
integrity sha1-FtIMFjoVdLqYpHIMDesh6rPme3U=
|
||||
dependencies:
|
||||
immer "^10.0.2"
|
||||
reconnecting-eventsource "^1.6.2"
|
||||
|
Loading…
Reference in New Issue
Block a user