fix: datagrid does not save prices without decimal prices (#2955)

This commit is contained in:
poulch 2023-01-11 10:49:21 +01:00 committed by GitHub
parent 5bc8e39eb7
commit e619866338
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 168 additions and 28 deletions

View file

@ -6,7 +6,6 @@ import {
} from "@glideapps/glide-data-grid";
import React from "react";
import { Locale } from "../Locale";
import { usePriceField } from "../PriceField/usePriceField";
interface MoneyCellProps {
@ -46,33 +45,13 @@ const MoneyCellEdit: ReturnType<ProvideEditorCallback<MoneyCell>> = ({
);
};
const getFractionDigits = (locale: Locale, currency: string) => {
try {
const numberFormat = new Intl.NumberFormat(locale, {
style: "currency",
currency,
});
return numberFormat.resolvedOptions().maximumFractionDigits;
} catch (e) {
return 2;
}
};
export const moneyCellRenderer = (
locale: Locale,
): CustomCellRenderer<MoneyCell> => ({
export const moneyCellRenderer = (): CustomCellRenderer<MoneyCell> => ({
isMatch: (c): c is MoneyCell => (c.data as any).kind === "money-cell",
draw: (args, cell) => {
const { ctx, theme, rect } = args;
const { currency, value } = cell.data;
const hasValue = value === 0 ? true : !!value;
const currencyFractionDigits = getFractionDigits(locale, currency);
const formatted =
value?.toLocaleString(locale, {
maximumFractionDigits: currencyFractionDigits,
minimumFractionDigits: currencyFractionDigits,
}) ?? "-";
const formatted = value?.toString() ?? "-";
ctx.fillStyle = theme.textDark;
ctx.textAlign = "right";

View file

@ -10,7 +10,7 @@ function useCells() {
const { locale } = useLocale();
const value = useMemo(
() => [
moneyCellRenderer(locale),
moneyCellRenderer(),
numberCellRenderer(locale),
dropdownCellRenderer,
],

View file

@ -23,6 +23,7 @@ import { ProductFragment } from "@saleor/graphql";
import useForm from "@saleor/hooks/useForm";
import useFormset from "@saleor/hooks/useFormset";
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
import useLocale from "@saleor/hooks/useLocale";
import {
getAttributeInputFromProduct,
getProductUpdatePageFormData,
@ -46,6 +47,7 @@ import {
UseProductUpdateFormOpts,
UseProductUpdateFormOutput,
} from "./types";
import { prepareVariantChangeData } from "./utils";
function useProductUpdateForm(
product: ProductFragment,
@ -71,6 +73,7 @@ function useProductUpdateForm(
data: formData,
setIsSubmitDisabled,
} = form;
const { locale } = useLocale();
const datagrid = useDatagridChangeState();
const variants = useRef<DatagridChangeOpts>({
@ -78,10 +81,14 @@ function useProductUpdateForm(
removed: [],
updates: [],
});
const handleVariantChange = React.useCallback((data: DatagridChangeOpts) => {
variants.current = data;
triggerChange();
}, []);
const handleVariantChange = React.useCallback(
(data: DatagridChangeOpts) => {
variants.current = prepareVariantChangeData(data, locale, product);
triggerChange();
},
[locale, product, triggerChange],
);
const attributes = useFormset(getAttributeInputFromProduct(product));
const {

View file

@ -0,0 +1,67 @@
import { Locale } from "@saleor/components/Locale";
import { parseCurrency } from "./utils";
describe("parseCurrency", () => {
it("rounds down to 3 decimals in 3 digit currency - EN locale (dot)", () => {
// Arrange
const value = "5.6777";
const currency = "BHD";
const locale = Locale.EN;
// Act
const parsed = parseCurrency(value, locale, currency);
// Assert
expect(parsed).toBe(5.677);
});
it("rounds down to 0 decimals in 0 digit currency - PL locale (comma)", () => {
// Arrange
const value = "12,90679";
const currency = "JPY";
const locale = Locale.PL;
// Act
const parsed = parseCurrency(value, locale, currency);
// Assert
expect(parsed).toBe(12);
});
it("rounds down to 2 decimals in 2 digit currency - EN locale (dot)", () => {
// Arrange
const value = "244.98721";
const currency = "PLN";
const locale = Locale.EN;
// Act
const parsed = parseCurrency(value, locale, currency);
// Assert
expect(parsed).toBe(244.98);
});
it("rounds down to 3 decimals in 3 digit currency - PL locale (comma)", () => {
// Arrange
const value = "0,27386";
const currency = "TND";
const locale = Locale.PL;
// Act
const parsed = parseCurrency(value, locale, currency);
// Assert
expect(parsed).toBe(0.273);
});
it("rounds down correctly (floating point difficult case)", () => {
// Arrange
const value = "2.07";
const currency = "EUR";
const locale = Locale.PL;
// Act
const parsed = parseCurrency(value, locale, currency);
// Assert
expect(parsed).toBe(2.07);
});
});

View file

@ -0,0 +1,87 @@
import {
DatagridChange,
DatagridChangeOpts,
} from "@saleor/components/Datagrid/useDatagridChange";
import { Locale } from "@saleor/components/Locale";
import { ProductFragment } from "@saleor/graphql";
const getFractionDigits = (locale: Locale, currency: string) => {
try {
const numberFormat = new Intl.NumberFormat(locale, {
style: "currency",
currency,
});
return numberFormat.resolvedOptions().maximumFractionDigits;
} catch (e) {
return 2;
}
};
export const parseCurrency = (
value: string,
locale: Locale,
currency: string,
): number => {
// Thousand seperators are not allowedd
const number = value.replace(/,/, ".");
const fractionDigits = getFractionDigits(locale, currency);
const trimmedNumber = number.slice(
0,
number.lastIndexOf(".") + 1 + fractionDigits,
);
return parseFloat(trimmedNumber);
};
export const prepareVariantChangeData = (
data: DatagridChangeOpts,
locale: Locale,
product: ProductFragment,
): DatagridChangeOpts => {
data.updates = data.updates.map(update => {
if (isColumnChannelPrice(update.column)) {
return updateVaraintWithPriceFormat(update, locale, product);
}
return update;
});
return data;
};
function updateVaraintWithPriceFormat(
dataChange: DatagridChange,
locale: Locale,
product: ProductFragment,
) {
const channelId = dataChange.column.split(":")[1];
const currencyCode = getChannelCurrencyCodeById(
channelId,
product.channelListings,
);
dataChange.data.value = parseCurrency(
`${dataChange.data.value}`,
locale,
currencyCode,
);
return dataChange;
}
function isColumnChannelPrice(name: string) {
return name.includes("channel:");
}
function getChannelCurrencyCodeById(
channelId: string,
channelList: ProductFragment["channelListings"],
): string {
const channel = channelList.find(({ channel }) => channel.id === channelId);
if (channel) {
return channel.channel.currencyCode;
}
return "";
}