From 8134b89fcc8a31925f14f3e1ca9a8a217223d1bc Mon Sep 17 00:00:00 2001 From: Wojciech Mista Date: Fri, 6 Sep 2024 12:14:14 +0200 Subject: [PATCH] Allow shipping cost refund (#5150) * allow shipping cost refund * add tests * extract hook to separate file * fixes --- .changeset/seven-timers-doubt.md | 5 + .../OrderFulfilledProductsCard.tsx | 2 +- .../components/OrderReturnPage/form.tsx | 49 ++------- .../useFulfillmentFormset.test.ts | 102 ++++++++++++++++++ .../OrderReturnPage/useFulfillmentFormset.ts | 46 ++++++++ .../components/OrderReturnPage/utils.tsx | 35 ++++++ 6 files changed, 199 insertions(+), 40 deletions(-) create mode 100644 .changeset/seven-timers-doubt.md create mode 100644 src/orders/components/OrderReturnPage/useFulfillmentFormset.test.ts create mode 100644 src/orders/components/OrderReturnPage/useFulfillmentFormset.ts diff --git a/.changeset/seven-timers-doubt.md b/.changeset/seven-timers-doubt.md new file mode 100644 index 00000000000..6ddd97816fd --- /dev/null +++ b/.changeset/seven-timers-doubt.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +You can now make a refund with shipping costs or custom shipping amount. This means you can make a refund without selecting line items. diff --git a/src/orders/components/OrderFulfilledProductsCard/OrderFulfilledProductsCard.tsx b/src/orders/components/OrderFulfilledProductsCard/OrderFulfilledProductsCard.tsx index 670cf7f931e..bcf344d625d 100644 --- a/src/orders/components/OrderFulfilledProductsCard/OrderFulfilledProductsCard.tsx +++ b/src/orders/components/OrderFulfilledProductsCard/OrderFulfilledProductsCard.tsx @@ -90,7 +90,7 @@ const OrderFulfilledProductsCard: React.FC = pr } /> - + diff --git a/src/orders/components/OrderReturnPage/form.tsx b/src/orders/components/OrderReturnPage/form.tsx index 6b915d9055a..20dd5aa8716 100644 --- a/src/orders/components/OrderReturnPage/form.tsx +++ b/src/orders/components/OrderReturnPage/form.tsx @@ -8,6 +8,7 @@ import { getById } from "@dashboard/misc"; import React, { useEffect } from "react"; import { OrderRefundAmountCalculationMode } from "../OrderRefundPage/form"; +import { useFulfillmentFormset } from "./useFulfillmentFormset"; import { getLineItem, getOrderUnfulfilledLines, @@ -93,41 +94,17 @@ function useOrderReturnForm( const { setExitDialogSubmitRef } = useExitFormDialog({ formId, }); - const unfulfiledItemsQuantites = useFormset( - getOrderUnfulfilledLines(order).map(getParsedLineData({ initialValue: 0 })), - ); - const getItemsFulfilled = () => { - const commonOptions = { - initialValue: 0, - isFulfillment: true, - }; - const refundedFulfilmentsItems = getParsedLineDataForFulfillmentStatus( - order, - FulfillmentStatus.REFUNDED, - { ...commonOptions, isRefunded: true }, - ); - const fulfilledFulfillmentsItems = getParsedLineDataForFulfillmentStatus( - order, - FulfillmentStatus.FULFILLED, - commonOptions, - ); - return refundedFulfilmentsItems.concat(fulfilledFulfillmentsItems); - }; - const getItemsWaiting = () => { - const commonOptions = { - initialValue: 0, - isFulfillment: true, - }; + const { + fulfiledItemsQuatities, + waitingItemsQuantities, + unfulfiledItemsQuantites, + disabled: isSaveDisabled, + } = useFulfillmentFormset({ + order, + formData, + }); - return getParsedLineDataForFulfillmentStatus( - order, - FulfillmentStatus.WAITING_FOR_APPROVAL, - commonOptions, - ); - }; - const fulfiledItemsQuatities = useFormset(getItemsFulfilled()); - const waitingItemsQuantities = useFormset(getItemsWaiting()); const getItemsToBeReplaced = () => { if (!order) { return []; @@ -225,12 +202,6 @@ function useOrderReturnForm( }; } - const hasAnyItemsSelected = - fulfiledItemsQuatities.data.some(({ value }) => !!value) || - waitingItemsQuantities.data.some(({ value }) => !!value) || - unfulfiledItemsQuantites.data.some(({ value }) => !!value); - const isSaveDisabled = !hasAnyItemsSelected; - setIsSubmitDisabled(isSaveDisabled); return { diff --git a/src/orders/components/OrderReturnPage/useFulfillmentFormset.test.ts b/src/orders/components/OrderReturnPage/useFulfillmentFormset.test.ts new file mode 100644 index 00000000000..52302ca7df1 --- /dev/null +++ b/src/orders/components/OrderReturnPage/useFulfillmentFormset.test.ts @@ -0,0 +1,102 @@ +import { renderHook } from "@testing-library/react-hooks"; + +import { useFulfillmentFormset } from "./useFulfillmentFormset"; + +describe("useFulfillmentFormset", () => { + it("should be disabled when no items are returned, no shipping and no amount", () => { + // Arrange & Act + const { result } = renderHook(() => + useFulfillmentFormset({ + order: { + fulfillments: [], + lines: [], + } as any, + formData: { + amount: 0, + refundShipmentCosts: false, + }, + }), + ); + + // Assert + expect(result.current.disabled).toBe(true); + }); + + it("should not disabled when no items are returned, but with shipping and no amount", () => { + // Arrange & Act + const { result } = renderHook(() => + useFulfillmentFormset({ + order: { + fulfillments: [], + lines: [], + } as any, + formData: { + amount: 0, + refundShipmentCosts: true, + }, + }), + ); + + // Assert + expect(result.current.disabled).toBe(false); + }); + + it("should not disabled when no items are returned, but with shipping and amount", () => { + // Arrange & Act + const { result } = renderHook(() => + useFulfillmentFormset({ + order: { + fulfillments: [], + lines: [], + } as any, + formData: { + amount: 21.37, + refundShipmentCosts: true, + }, + }), + ); + + // Assert + expect(result.current.disabled).toBe(false); + }); + + it("should not disabled when there are items selected with value, but with no shipping and no amount", () => { + // Arrange & Act + const { result } = renderHook(() => + useFulfillmentFormset({ + order: { + fulfillments: [ + { + status: "FULFILLED", + lines: [ + { + id: "id", + quantity: 1, + orderLine: { + id: "id", + }, + }, + ], + } as any, + ], + lines: [ + { + id: "id", + quantity: 1, + } as any, + ], + } as any, + formData: { + amount: 0, + refundShipmentCosts: false, + }, + }), + ); + + result.current.fulfiledItemsQuatities.change("id", 21.37); + + // Assert + expect(result.current.fulfiledItemsQuatities.data.length).toBe(1); + expect(result.current.disabled).toBe(false); + }); +}); diff --git a/src/orders/components/OrderReturnPage/useFulfillmentFormset.ts b/src/orders/components/OrderReturnPage/useFulfillmentFormset.ts new file mode 100644 index 00000000000..dc49e8ec332 --- /dev/null +++ b/src/orders/components/OrderReturnPage/useFulfillmentFormset.ts @@ -0,0 +1,46 @@ +import { OrderDetailsFragment } from "@dashboard/graphql"; +import useFormset from "@dashboard/hooks/useFormset"; + +import { LineItemData } from "./form"; +import { + getItemsFulfilled, + getItemsWaiting, + getOrderUnfulfilledLines, + getParsedLineData, + LineItem, +} from "./utils"; + +const mapWithLabel = (line: LineItem) => ({ ...line, label: line.label ?? "" }); + +export const useFulfillmentFormset = ({ + order, + formData, +}: { + order: OrderDetailsFragment; + formData: { refundShipmentCosts: boolean; amount: number }; +}) => { + const fulfiledItemsQuatities = useFormset( + getItemsFulfilled(order).map(mapWithLabel), + ); + const waitingItemsQuantities = useFormset( + getItemsWaiting(order).map(mapWithLabel), + ); + const unfulfiledItemsQuantites = useFormset( + getOrderUnfulfilledLines(order) + .map(getParsedLineData({ initialValue: 0 })) + .map(mapWithLabel), + ); + + const hasAnyItemsSelected = + fulfiledItemsQuatities.data.some(({ value }) => !!value) || + waitingItemsQuantities.data.some(({ value }) => !!value) || + unfulfiledItemsQuantites.data.some(({ value }) => !!value); + const disabled = !hasAnyItemsSelected && !formData.refundShipmentCosts && !formData.amount; + + return { + fulfiledItemsQuatities, + waitingItemsQuantities, + unfulfiledItemsQuantites, + disabled, + }; +}; diff --git a/src/orders/components/OrderReturnPage/utils.tsx b/src/orders/components/OrderReturnPage/utils.tsx index ef569fb1947..69ff95c6040 100644 --- a/src/orders/components/OrderReturnPage/utils.tsx +++ b/src/orders/components/OrderReturnPage/utils.tsx @@ -42,6 +42,9 @@ export const getAllOrderWaitingLines = (order?: OrderDetailsFragment) => [], ); +// TODO: Migrate this utils file to strict mode +export type LineItem = ReturnType>; + export function getLineItem( line: FulfillmentLine | ParsedFulfillmentLine | OrderLine, { initialValue, isFulfillment = false, isRefunded = false }: LineItemOptions, @@ -218,3 +221,35 @@ export const getReturnRefundValue = ({ .toString() ?? "" ); }; + +export const getItemsFulfilled = (order: OrderDetailsFragment) => { + const commonOptions = { + initialValue: 0, + isFulfillment: true, + }; + const refundedFulfilmentsItems = getParsedLineDataForFulfillmentStatus( + order, + FulfillmentStatus.REFUNDED, + { ...commonOptions, isRefunded: true }, + ); + const fulfilledFulfillmentsItems = getParsedLineDataForFulfillmentStatus( + order, + FulfillmentStatus.FULFILLED, + commonOptions, + ); + + return refundedFulfilmentsItems.concat(fulfilledFulfillmentsItems); +}; + +export const getItemsWaiting = (order: OrderDetailsFragment) => { + const commonOptions = { + initialValue: 0, + isFulfillment: true, + }; + + return getParsedLineDataForFulfillmentStatus( + order, + FulfillmentStatus.WAITING_FOR_APPROVAL, + commonOptions, + ); +};