From d4c5f6593d8cbeb8890a35b029235a0ec4aff671 Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 8 Aug 2024 16:24:13 +0200 Subject: [PATCH] Feat: admin return reason list (#8482) * feat: add missing endpoints to return reason sdk * add return reason list route * add return reason query hooks * fix update return reasons in order modules service * fix store/return-reasons middleware * add missing tests for /return-resasons/:id --- .../http/__tests__/returns/returns.spec.ts | 63 +++++++++ .../src/extensions/widgets/constants.ts | 6 + .../settings-layout/settings-layout.tsx | 4 + .../src/hooks/api/return-reasons.tsx | 115 +++++++++++++++- .../src/hooks/table/columns/index.ts | 1 + .../use-return-reason-table-columns.tsx | 25 ++++ .../dashboard/src/hooks/table/query/index.ts | 1 + .../query/use-return-reason-table-query.tsx | 32 +++++ .../dashboard/src/i18n/translations/en.json | 29 +++- .../providers/router-provider/route-map.tsx | 14 ++ .../hooks/use-delete-return-reason-action.tsx | 40 ++++++ .../return-reason-list-table/index.ts | 1 + .../return-reason-list-table.tsx | 127 ++++++++++++++++++ .../return-reason-list/index.ts | 2 + .../return-reason-list/loader.ts | 21 +++ .../return-reason-list/return-reason-list.tsx | 21 +++ .../dashboard/src/routes/settings/index.ts | 2 +- .../src/return-reason/steps/index.ts | 2 +- ...urn-resons.ts => update-return-reasons.ts} | 0 .../core/js-sdk/src/admin/return-reason.ts | 68 +++++++++- packages/core/types/src/http/index.ts | 3 +- .../types/src/http/return-reason/admin.ts | 28 ---- .../src/http/return-reason/admin/entities.ts | 3 + .../src/http/return-reason/admin/index.ts | 4 + .../src/http/return-reason/admin/payloads.ts | 14 ++ .../src/http/return-reason/admin/queries.ts | 11 ++ .../src/http/return-reason/admin/responses.ts | 14 ++ .../types/src/http/return-reason/common.ts | 14 ++ packages/core/types/src/order/service.ts | 33 +---- .../api/admin/return-reasons/[id]/route.ts | 31 +++-- .../api/admin/return-reasons/middlewares.ts | 12 ++ .../src/api/admin/return-reasons/route.ts | 4 +- .../api/admin/return-reasons/validators.ts | 6 +- .../api/store/return-reasons/middlewares.ts | 4 +- .../src/services/order-module-service.ts | 54 ++++++++ .../modules/order/src/types/return-reason.ts | 5 + 36 files changed, 724 insertions(+), 90 deletions(-) create mode 100644 packages/admin-next/dashboard/src/hooks/table/columns/use-return-reason-table-columns.tsx create mode 100644 packages/admin-next/dashboard/src/hooks/table/query/use-return-reason-table-query.tsx create mode 100644 packages/admin-next/dashboard/src/routes/return-reasons/common/hooks/use-delete-return-reason-action.tsx create mode 100644 packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-list-table/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-list-table/return-reason-list-table.tsx create mode 100644 packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/loader.ts create mode 100644 packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/return-reason-list.tsx rename packages/core/core-flows/src/return-reason/steps/{update-return-resons.ts => update-return-reasons.ts} (100%) delete mode 100644 packages/core/types/src/http/return-reason/admin.ts create mode 100644 packages/core/types/src/http/return-reason/admin/entities.ts create mode 100644 packages/core/types/src/http/return-reason/admin/index.ts create mode 100644 packages/core/types/src/http/return-reason/admin/payloads.ts create mode 100644 packages/core/types/src/http/return-reason/admin/queries.ts create mode 100644 packages/core/types/src/http/return-reason/admin/responses.ts create mode 100644 packages/modules/order/src/types/return-reason.ts diff --git a/integration-tests/http/__tests__/returns/returns.spec.ts b/integration-tests/http/__tests__/returns/returns.spec.ts index 9443e7ff33..a2a2ef5610 100644 --- a/integration-tests/http/__tests__/returns/returns.spec.ts +++ b/integration-tests/http/__tests__/returns/returns.spec.ts @@ -1215,6 +1215,69 @@ medusaIntegrationTestRunner({ ) }) }) + + describe("/admin/return-reasons/:id", () => { + it("gets a return reason", async () => { + let response = await api.get( + `/admin/return-reasons/${returnReason.id}`, + adminHeaders + ) + const result = response.data.return_reason + const keysInResponse = Object.keys(result) + + expect(response.status).toEqual(200) + expect(keysInResponse).toEqual( + expect.arrayContaining([ + "id", + "created_at", + "updated_at", + "value", + "description", + "label", + ]) + ) + expect(result).toEqual( + expect.objectContaining({ + id: returnReason.id, + value: "return-reason-test", + label: "Test return reason", + description: "This is the reason description!!!", + }) + ) + }) + it("updates a return reason", async () => { + let response = await api.post( + `/admin/return-reasons/${returnReason.id}`, + { + value: "new-return-reason", + label: "New return reason", + }, + adminHeaders + ) + expect(response.status).toEqual(200) + expect(response.data.return_reason).toEqual( + expect.objectContaining({ + id: returnReason.id, + value: "new-return-reason", + label: "New return reason", + description: "This is the reason description!!!", + }) + ) + }) + it("deletes a return reason", async () => { + let response = await api.delete( + `/admin/return-reasons/${returnReason.id}`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data).toEqual({ + id: returnReason.id, + object: "return_reason", + deleted: true, + }) + }) + }) }) }, }) diff --git a/packages/admin-next/admin-shared/src/extensions/widgets/constants.ts b/packages/admin-next/admin-shared/src/extensions/widgets/constants.ts index f372e96c00..73e83f6758 100644 --- a/packages/admin-next/admin-shared/src/extensions/widgets/constants.ts +++ b/packages/admin-next/admin-shared/src/extensions/widgets/constants.ts @@ -184,6 +184,11 @@ const TAX_INJECTION_ZONES = [ "tax.list.after", ] as const +const RETURN_REASON_INJECTION_ZONES = [ + "return_reason.list.before", + "return_reason.list.after", +] as const + /** * All valid injection zones in the admin panel. An injection zone is a specific place * in the admin panel where a plugin can inject custom widgets. @@ -214,4 +219,5 @@ export const INJECTION_ZONES = [ ...TAX_INJECTION_ZONES, ...PRODUCT_TYPE_INJECTION_ZONES, ...PRODUCT_TAG_INJECTION_ZONES, + ...RETURN_REASON_INJECTION_ZONES, ] as const diff --git a/packages/admin-next/dashboard/src/components/layout/settings-layout/settings-layout.tsx b/packages/admin-next/dashboard/src/components/layout/settings-layout/settings-layout.tsx index 500576b0b0..5036391065 100644 --- a/packages/admin-next/dashboard/src/components/layout/settings-layout/settings-layout.tsx +++ b/packages/admin-next/dashboard/src/components/layout/settings-layout/settings-layout.tsx @@ -42,6 +42,10 @@ const useSettingRoutes = (): NavItemProps[] => { label: t("taxRegions.domain"), to: "/settings/tax-regions", }, + { + label: t("returnReasons.domain"), + to: "/settings/return-reasons", + }, { label: t("salesChannels.domain"), to: "/settings/sales-channels", diff --git a/packages/admin-next/dashboard/src/hooks/api/return-reasons.tsx b/packages/admin-next/dashboard/src/hooks/api/return-reasons.tsx index df47080ff6..3db0c454fc 100644 --- a/packages/admin-next/dashboard/src/hooks/api/return-reasons.tsx +++ b/packages/admin-next/dashboard/src/hooks/api/return-reasons.tsx @@ -1,19 +1,27 @@ import { HttpTypes } from "@medusajs/types" -import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query" +import { + QueryKey, + UseMutationOptions, + UseQueryOptions, + useMutation, + useQuery, +} from "@tanstack/react-query" +import { FetchError } from "@medusajs/js-sdk" import { sdk } from "../../lib/client" +import { queryClient } from "../../lib/query-client" import { queryKeysFactory } from "../../lib/query-key-factory" const RETURN_REASONS_QUERY_KEY = "return_reasons" as const -export const returnReasonQueryKeys = queryKeysFactory(RETURN_REASONS_QUERY_KEY) +export const returnReasonsQueryKeys = queryKeysFactory(RETURN_REASONS_QUERY_KEY) export const useReturnReasons = ( query?: HttpTypes.AdminReturnReasonListParams, options?: Omit< UseQueryOptions< - HttpTypes.AdminReturnReasonsResponse, - Error, - HttpTypes.AdminReturnReasonsResponse, + HttpTypes.AdminReturnReasonListResponse, + FetchError, + HttpTypes.AdminReturnReasonListResponse, QueryKey >, "queryFn" | "queryKey" @@ -21,9 +29,104 @@ export const useReturnReasons = ( ) => { const { data, ...rest } = useQuery({ queryFn: () => sdk.admin.returnReason.list(query), - queryKey: returnReasonQueryKeys.list(query), + queryKey: returnReasonsQueryKeys.list(query), ...options, }) return { ...data, ...rest } } + +export const useReturnReason = ( + id: string, + query?: HttpTypes.AdminReturnReasonParams, + options?: Omit< + UseQueryOptions< + HttpTypes.AdminReturnReasonResponse, + FetchError, + HttpTypes.AdminReturnReasonResponse, + QueryKey + >, + "queryFn" | "queryKey" + > +) => { + const { data, ...rest } = useQuery({ + queryFn: () => sdk.admin.returnReason.retrieve(id, query), + queryKey: returnReasonsQueryKeys.detail(id), + ...options, + }) + + return { ...data, ...rest } +} + +export const useCreateReturnReason = ( + query?: HttpTypes.AdminReturnReasonParams, + options?: UseMutationOptions< + HttpTypes.AdminReturnReasonResponse, + FetchError, + HttpTypes.AdminCreateReturnReason + > +) => { + return useMutation({ + mutationFn: async (data) => sdk.admin.returnReason.create(data, query), + onSuccess: (data, variables, context) => { + queryClient.invalidateQueries({ + queryKey: returnReasonsQueryKeys.lists(), + }) + + options?.onSuccess?.(data, variables, context) + }, + ...options, + }) +} +export const useUpdateReturnReason = ( + id: string, + query?: HttpTypes.AdminReturnReasonParams, + options?: UseMutationOptions< + HttpTypes.AdminReturnReasonResponse, + FetchError, + HttpTypes.AdminUpdateReturnReason + > +) => { + return useMutation({ + mutationFn: async (data) => sdk.admin.returnReason.update(id, data, query), + onSuccess: (data, variables, context) => { + queryClient.invalidateQueries({ + queryKey: returnReasonsQueryKeys.lists(), + }) + queryClient.invalidateQueries({ + queryKey: returnReasonsQueryKeys.detail(data.return_reason.id, query), + }) + + options?.onSuccess?.(data, variables, context) + }, + ...options, + }) +} + +export const useDeleteReturnReason = ( + id: string, + options?: UseMutationOptions< + HttpTypes.AdminReturnReasonDeleteResponse, + Error, + void + > +) => { + return useMutation({ + mutationFn: () => sdk.admin.returnReason.delete(id), + onSuccess: (data, variables, context) => { + queryClient.invalidateQueries({ + queryKey: returnReasonsQueryKeys.lists(), + }) + queryClient.invalidateQueries({ + queryKey: returnReasonsQueryKeys.detail(id), + }) + + queryClient.invalidateQueries({ + queryKey: returnReasonsQueryKeys.details(), + }) + + options?.onSuccess?.(data, variables, context) + }, + ...options, + }) +} diff --git a/packages/admin-next/dashboard/src/hooks/table/columns/index.ts b/packages/admin-next/dashboard/src/hooks/table/columns/index.ts index b8ef3befc2..869b6e3850 100644 --- a/packages/admin-next/dashboard/src/hooks/table/columns/index.ts +++ b/packages/admin-next/dashboard/src/hooks/table/columns/index.ts @@ -7,6 +7,7 @@ export * from "./use-product-table-columns" export * from "./use-product-tag-table-columns" export * from "./use-product-type-table-columns" export * from "./use-region-table-columns" +export * from "./use-return-reason-table-columns" export * from "./use-sales-channel-table-columns" export * from "./use-shipping-option-table-columns" export * from "./use-tax-rates-table-columns" diff --git a/packages/admin-next/dashboard/src/hooks/table/columns/use-return-reason-table-columns.tsx b/packages/admin-next/dashboard/src/hooks/table/columns/use-return-reason-table-columns.tsx new file mode 100644 index 0000000000..d27b870159 --- /dev/null +++ b/packages/admin-next/dashboard/src/hooks/table/columns/use-return-reason-table-columns.tsx @@ -0,0 +1,25 @@ +import { HttpTypes } from "@medusajs/types" +import { createColumnHelper } from "@tanstack/react-table" +import { useMemo } from "react" +import { useTranslation } from "react-i18next" +import { TextCell } from "../../../components/table/table-cells/common/text-cell" + +const columnHelper = createColumnHelper() + +export const useReturnReasonTableColumns = () => { + const { t } = useTranslation() + + return useMemo( + () => [ + columnHelper.accessor("value", { + header: () => t("fields.value"), + cell: ({ getValue }) => , + }), + columnHelper.accessor("label", { + header: () => t("fields.createdAt"), + cell: ({ getValue }) => , + }), + ], + [t] + ) +} diff --git a/packages/admin-next/dashboard/src/hooks/table/query/index.ts b/packages/admin-next/dashboard/src/hooks/table/query/index.ts index 1ced60c78f..2c6bd485e1 100644 --- a/packages/admin-next/dashboard/src/hooks/table/query/index.ts +++ b/packages/admin-next/dashboard/src/hooks/table/query/index.ts @@ -7,6 +7,7 @@ export * from "./use-product-table-query" export * from "./use-product-tag-table-query" export * from "./use-product-type-table-query" export * from "./use-region-table-query" +export * from "./use-return-reason-table-query" export * from "./use-sales-channel-table-query" export * from "./use-shipping-option-table-query" export * from "./use-tax-rate-table-query" diff --git a/packages/admin-next/dashboard/src/hooks/table/query/use-return-reason-table-query.tsx b/packages/admin-next/dashboard/src/hooks/table/query/use-return-reason-table-query.tsx new file mode 100644 index 0000000000..5aaabaf3a3 --- /dev/null +++ b/packages/admin-next/dashboard/src/hooks/table/query/use-return-reason-table-query.tsx @@ -0,0 +1,32 @@ +import { HttpTypes } from "@medusajs/types" +import { useQueryParams } from "../../use-query-params" + +type UseReturnReasonTableQueryProps = { + prefix?: string + pageSize?: number +} + +export const useReturnReasonTableQuery = ({ + prefix, + pageSize = 20, +}: UseReturnReasonTableQueryProps) => { + const queryObject = useQueryParams( + ["offset", "q", "order", "created_at", "updated_at"], + prefix + ) + + const { offset, q, order, created_at, updated_at } = queryObject + const searchParams: HttpTypes.AdminReturnReasonListParams = { + limit: pageSize, + offset: offset ? Number(offset) : 0, + order, + created_at: created_at ? JSON.parse(created_at) : undefined, + updated_at: updated_at ? JSON.parse(updated_at) : undefined, + q, + } + + return { + searchParams, + raw: queryObject, + } +} diff --git a/packages/admin-next/dashboard/src/i18n/translations/en.json b/packages/admin-next/dashboard/src/i18n/translations/en.json index 3424a762c8..c92b3c1dd0 100644 --- a/packages/admin-next/dashboard/src/i18n/translations/en.json +++ b/packages/admin-next/dashboard/src/i18n/translations/en.json @@ -2102,11 +2102,32 @@ "returnReasons": { "domain": "Return Reasons", "calloutHint": "Manage the reasons to categorize returns.", - "deleteReasonWarning": "You are about to delete the return reason {{label}}. This action cannot be undone.", - "createReason": "Create Return Reason", - "createReasonHint": "Create a new return reason to categorize returns.", "editReason": "Edit Return Reason", - "valueTooltip": "The value should be a unique identifier for the return reason." + "create": { + "header": "Add Return Reason", + "subtitle": "Specify the most common reasons for returns.", + "hint": "Create a new return reason to categorize returns.", + "successToast": "Return reason {{label}} was successfully created." + }, + "edit": { + "successToast": "Return reason {{label}} was successfully updated." + }, + "delete": { + "confirmation": "You are about to delete the return reason {{label}}. This action cannot be undone.", + "successToast": "Return reason {{label}} was successfully deleted." + }, + "fields": { + "value": { + "label": "Value", + "placeholder": "wrong_size", + "tooltip": "The value should be a unique identifier for the return reason." + }, + "label": { "label": "Label", "placeholder": "Wrong size" }, + "description": { + "label": "Descriptions", + "placeholder": "Customer received the wrong size" + } + } }, "login": { "forgotPassword": "Forgot password? - <0>Reset", diff --git a/packages/admin-next/dashboard/src/providers/router-provider/route-map.tsx b/packages/admin-next/dashboard/src/providers/router-provider/route-map.tsx index 5313daf3d6..22388fcac0 100644 --- a/packages/admin-next/dashboard/src/providers/router-provider/route-map.tsx +++ b/packages/admin-next/dashboard/src/providers/router-provider/route-map.tsx @@ -1293,6 +1293,20 @@ export const RouteMap: RouteObject[] = [ }, ], }, + { + path: "return-reasons", + element: , + handle: { + crumb: () => "Return Reasons", + }, + children: [ + { + path: "", + lazy: () => + import("../../routes/return-reasons/return-reason-list"), + }, + ], + }, ...SettingsExtensions, ], }, diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/common/hooks/use-delete-return-reason-action.tsx b/packages/admin-next/dashboard/src/routes/return-reasons/common/hooks/use-delete-return-reason-action.tsx new file mode 100644 index 0000000000..9b027b4bad --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/common/hooks/use-delete-return-reason-action.tsx @@ -0,0 +1,40 @@ +import { AdminReturnReason } from "@medusajs/types" +import { toast, usePrompt } from "@medusajs/ui" +import { useTranslation } from "react-i18next" +import { useDeleteReturnReason } from "../../../../hooks/api/return-reasons" + +export const useDeleteReturnReasonAction = ({ + id, + label, +}: AdminReturnReason) => { + const { t } = useTranslation() + const prompt = usePrompt() + + const { mutateAsync } = useDeleteReturnReason(id) + + const handleDelete = async () => { + const result = await prompt({ + title: t("general.areYouSure"), + description: t("returnReasons.delete.confirmation", { + label, + }), + confirmText: t("actions.delete"), + cancelText: t("actions.cancel"), + }) + + if (!result) { + return + } + + await mutateAsync(undefined, { + onSuccess: () => { + toast.success(t("returnReasons.delete.successToast", { label })) + }, + onError: (e) => { + toast.error(e.message) + }, + }) + } + + return handleDelete +} diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-list-table/index.ts b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-list-table/index.ts new file mode 100644 index 0000000000..1bb431e0e1 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-list-table/index.ts @@ -0,0 +1 @@ +export * from "./return-reason-list-table" diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-list-table/return-reason-list-table.tsx b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-list-table/return-reason-list-table.tsx new file mode 100644 index 0000000000..751aca961c --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-list-table/return-reason-list-table.tsx @@ -0,0 +1,127 @@ +import { Trash } from "@medusajs/icons" +import { HttpTypes } from "@medusajs/types" +import { Button, Container, Heading } from "@medusajs/ui" +import { keepPreviousData } from "@tanstack/react-query" +import { createColumnHelper } from "@tanstack/react-table" +import { useMemo } from "react" +import { useTranslation } from "react-i18next" +import { Link, useLoaderData } from "react-router-dom" + +import { ActionMenu } from "../../../../../components/common/action-menu" +import { DataTable } from "../../../../../components/table/data-table" +import { useReturnReasons } from "../../../../../hooks/api/return-reasons" +import { useReturnReasonTableColumns } from "../../../../../hooks/table/columns" +import { useReturnReasonTableQuery } from "../../../../../hooks/table/query" +import { useDataTable } from "../../../../../hooks/use-data-table" +import { useDeleteReturnReasonAction } from "../../../common/hooks/use-delete-return-reason-action" +import { returnReasonListLoader } from "../../loader" + +const PAGE_SIZE = 20 + +export const ReturnReasonListTable = () => { + const { t } = useTranslation() + const { searchParams, raw } = useReturnReasonTableQuery({ + pageSize: PAGE_SIZE, + }) + + const initialData = useLoaderData() as Awaited< + ReturnType + > + + const { return_reasons, count, isPending, isError, error } = useReturnReasons( + searchParams, + { + initialData, + placeholderData: keepPreviousData, + } + ) + + const columns = useColumns() + + const { table } = useDataTable({ + data: return_reasons, + columns, + count, + getRowId: (row) => row.id, + pageSize: PAGE_SIZE, + }) + + if (isError) { + throw error + } + + return ( + +
+ {t("returnReasons.domain")} + +
+ +
+ ) +} + +type ReturnReasonRowActionsProps = { + returnReason: HttpTypes.AdminReturnReason +} + +const ReturnReasonRowActions = ({ + returnReason, +}: ReturnReasonRowActionsProps) => { + const { t } = useTranslation() + const handleDelete = useDeleteReturnReasonAction(returnReason) + + return ( + , + label: t("actions.edit"), + to: `${returnReason.id}/edit`, + }, + ], + }, */ + { + actions: [ + { + icon: , + label: t("actions.delete"), + onClick: handleDelete, + }, + ], + }, + ]} + /> + ) +} + +const columnHelper = createColumnHelper() + +const useColumns = () => { + const base = useReturnReasonTableColumns() + + return useMemo( + () => [ + ...base, + columnHelper.display({ + id: "actions", + cell: ({ row }) => ( + + ), + }), + ], + [base] + ) +} diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/index.ts b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/index.ts new file mode 100644 index 0000000000..fd34343e18 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/index.ts @@ -0,0 +1,2 @@ +export { returnReasonListLoader as loader } from "./loader" +export { ReturnReasonList as Component } from "./return-reason-list" diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/loader.ts b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/loader.ts new file mode 100644 index 0000000000..8caf38e1f8 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/loader.ts @@ -0,0 +1,21 @@ +import { + AdminReturnReasonListParams, + AdminReturnReasonListResponse, +} from "@medusajs/types" + +import { returnReasonsQueryKeys } from "../../../hooks/api/return-reasons" +import { sdk } from "../../../lib/client" +import { queryClient } from "../../../lib/query-client" + +const returnReasonListQuery = (query?: AdminReturnReasonListParams) => ({ + queryKey: returnReasonsQueryKeys.list(query), + queryFn: async () => sdk.admin.returnReason.list(query), +}) + +export const returnReasonListLoader = async () => { + const query = returnReasonListQuery() + return ( + queryClient.getQueryData(query.queryKey) ?? + (await queryClient.fetchQuery(query)) + ) +} diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/return-reason-list.tsx b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/return-reason-list.tsx new file mode 100644 index 0000000000..8575493849 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/return-reason-list.tsx @@ -0,0 +1,21 @@ +import { SingleColumnPage } from "../../../components/layout/pages" +import { ReturnReasonListTable } from "./components/return-reason-list-table" + +import after from "virtual:medusa/widgets/return_reason/list/after" +import before from "virtual:medusa/widgets/return_reason/list/before" + +export const ReturnReasonList = () => { + return ( + + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/settings/index.ts b/packages/admin-next/dashboard/src/routes/settings/index.ts index af259dd1e4..3170af4244 100644 --- a/packages/admin-next/dashboard/src/routes/settings/index.ts +++ b/packages/admin-next/dashboard/src/routes/settings/index.ts @@ -1 +1 @@ -export { Settings as Component } from "./settings"; +export { Settings as Component } from "./settings" diff --git a/packages/core/core-flows/src/return-reason/steps/index.ts b/packages/core/core-flows/src/return-reason/steps/index.ts index 60df761267..20a5455e9a 100644 --- a/packages/core/core-flows/src/return-reason/steps/index.ts +++ b/packages/core/core-flows/src/return-reason/steps/index.ts @@ -1,3 +1,3 @@ export * from "./create-return-reasons" export * from "./delete-return-reasons" -export * from "./update-return-resons" +export * from "./update-return-reasons" diff --git a/packages/core/core-flows/src/return-reason/steps/update-return-resons.ts b/packages/core/core-flows/src/return-reason/steps/update-return-reasons.ts similarity index 100% rename from packages/core/core-flows/src/return-reason/steps/update-return-resons.ts rename to packages/core/core-flows/src/return-reason/steps/update-return-reasons.ts diff --git a/packages/core/js-sdk/src/admin/return-reason.ts b/packages/core/js-sdk/src/admin/return-reason.ts index dde719cbc4..4140960387 100644 --- a/packages/core/js-sdk/src/admin/return-reason.ts +++ b/packages/core/js-sdk/src/admin/return-reason.ts @@ -10,14 +10,76 @@ export class ReturnReason { } async list( - queryParams?: HttpTypes.AdminReturnReasonListParams, + query?: HttpTypes.AdminReturnReasonListParams, headers?: ClientHeaders ) { - return await this.client.fetch( + return await this.client.fetch( "/admin/return-reasons", { headers, - query: queryParams, + query, + } + ) + } + + async retrieve( + id: string, + query?: HttpTypes.AdminReturnReasonParams, + headers?: ClientHeaders + ) { + return await this.client.fetch( + `/admin/return-reasons/${id}`, + { + query, + headers, + } + ) + } + + async create( + body: HttpTypes.AdminCreateReturnReason, + query?: HttpTypes.AdminReturnReasonParams, + headers?: ClientHeaders + ) { + return this.client.fetch( + `/admin/return-reasons`, + { + method: "POST", + headers, + body, + query, + } + ) + } + + async update( + id: string, + body: HttpTypes.AdminUpdateReturnReason, + query?: HttpTypes.AdminReturnReasonParams, + headers?: ClientHeaders + ) { + return this.client.fetch( + `/admin/return-reasons/${id}`, + { + method: "POST", + headers, + body, + query, + } + ) + } + + async delete( + id: string, + query?: HttpTypes.AdminReturnReasonParams, + headers?: ClientHeaders + ) { + return await this.client.fetch( + `/admin/return-reasons/${id}`, + { + method: "DELETE", + headers, + query, } ) } diff --git a/packages/core/types/src/http/index.ts b/packages/core/types/src/http/index.ts index c581083a5f..738dfbaf28 100644 --- a/packages/core/types/src/http/index.ts +++ b/packages/core/types/src/http/index.ts @@ -24,10 +24,9 @@ export * from "./product-tag" export * from "./product-type" export * from "./promotion" export * from "./region" -export * from "./return" -export * from "./return-reason" export * from "./reservation" export * from "./return" +export * from "./return-reason" export * from "./sales-channel" export * from "./shipping-option" export * from "./shipping-profile" diff --git a/packages/core/types/src/http/return-reason/admin.ts b/packages/core/types/src/http/return-reason/admin.ts deleted file mode 100644 index cc57eaca90..0000000000 --- a/packages/core/types/src/http/return-reason/admin.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { BaseReturnReason } from "./common" -import { FindParams } from "../common" -import { BaseFilterable, OperatorMap } from "../../dal" - -export interface AdminReturnReason extends BaseReturnReason {} - -export interface AdminCreateReturnReason { - // TODO: - value: string - label: string - description?: string -} - -export interface AdminReturnReasonsResponse { - return_reasons: AdminReturnReason[] -} - -export interface AdminReturnReasonListParams - extends FindParams, - BaseFilterable { - id?: string[] | string | OperatorMap - value?: string | OperatorMap - label?: string | OperatorMap - description?: string | OperatorMap - parent_return_reason_id?: string | OperatorMap - created_at?: OperatorMap - updated_at?: OperatorMap -} diff --git a/packages/core/types/src/http/return-reason/admin/entities.ts b/packages/core/types/src/http/return-reason/admin/entities.ts new file mode 100644 index 0000000000..f62cf85a6c --- /dev/null +++ b/packages/core/types/src/http/return-reason/admin/entities.ts @@ -0,0 +1,3 @@ +import { BaseReturnReason } from "../common" + +export interface AdminReturnReason extends BaseReturnReason {} diff --git a/packages/core/types/src/http/return-reason/admin/index.ts b/packages/core/types/src/http/return-reason/admin/index.ts new file mode 100644 index 0000000000..1f82a2ead5 --- /dev/null +++ b/packages/core/types/src/http/return-reason/admin/index.ts @@ -0,0 +1,4 @@ +export * from "./entities" +export * from "./payloads" +export * from "./queries" +export * from "./responses" diff --git a/packages/core/types/src/http/return-reason/admin/payloads.ts b/packages/core/types/src/http/return-reason/admin/payloads.ts new file mode 100644 index 0000000000..3646c3edc2 --- /dev/null +++ b/packages/core/types/src/http/return-reason/admin/payloads.ts @@ -0,0 +1,14 @@ +import { AdminReturnReason } from "./entities" + +type AdminBaseReturnReasonPayload = Pick< + AdminReturnReason, + "value" | "label" | "description" +> + +export interface AdminCreateReturnReason extends AdminBaseReturnReasonPayload { + metadata?: Record | null +} + +export interface AdminUpdateReturnReason extends AdminBaseReturnReasonPayload { + metadata?: Record | null +} diff --git a/packages/core/types/src/http/return-reason/admin/queries.ts b/packages/core/types/src/http/return-reason/admin/queries.ts new file mode 100644 index 0000000000..d7683017b8 --- /dev/null +++ b/packages/core/types/src/http/return-reason/admin/queries.ts @@ -0,0 +1,11 @@ +import { BaseFilterable, OperatorMap } from "../../../dal" +import { SelectParams } from "../../common" +import { BaseReturnReasonListParams } from "../common" + +export interface AdminReturnReasonListParams + extends BaseReturnReasonListParams, + BaseFilterable { + deleted_at?: OperatorMap +} + +export interface AdminReturnReasonParams extends SelectParams {} diff --git a/packages/core/types/src/http/return-reason/admin/responses.ts b/packages/core/types/src/http/return-reason/admin/responses.ts new file mode 100644 index 0000000000..a5196499ce --- /dev/null +++ b/packages/core/types/src/http/return-reason/admin/responses.ts @@ -0,0 +1,14 @@ +import { DeleteResponse, PaginatedResponse } from "../../common" +import { AdminReturnReason } from "../admin" + +export interface AdminReturnReasonResponse { + return_reason: AdminReturnReason +} + +export interface AdminReturnReasonListResponse + extends PaginatedResponse<{ + return_reasons: AdminReturnReason[] + }> {} + +export interface AdminReturnReasonDeleteResponse + extends DeleteResponse<"return_reason"> {} diff --git a/packages/core/types/src/http/return-reason/common.ts b/packages/core/types/src/http/return-reason/common.ts index 07f8544a36..5d4e253de2 100644 --- a/packages/core/types/src/http/return-reason/common.ts +++ b/packages/core/types/src/http/return-reason/common.ts @@ -1,3 +1,6 @@ +import { OperatorMap } from "../../dal" +import { FindParams } from "../common" + export interface BaseReturnReason { id: string value: string @@ -7,3 +10,14 @@ export interface BaseReturnReason { created_at: string updated_at: string } + +export interface BaseReturnReasonListParams extends FindParams { + q?: string + id?: string | string[] + value?: string | OperatorMap + label?: string | OperatorMap + description?: string | OperatorMap + parent_return_reason_id?: string | OperatorMap + created_at?: OperatorMap + updated_at?: OperatorMap +} diff --git a/packages/core/types/src/order/service.ts b/packages/core/types/src/order/service.ts index 5b9b40d5e3..f7d67e0d68 100644 --- a/packages/core/types/src/order/service.ts +++ b/packages/core/types/src/order/service.ts @@ -81,7 +81,6 @@ import { UpdateOrderLineItemTaxLineDTO, UpdateOrderLineItemWithSelectorDTO, UpdateOrderReturnReasonDTO, - UpdateOrderReturnReasonWithSelectorDTO, UpdateOrderReturnWithSelectorDTO, UpdateOrderShippingMethodAdjustmentDTO, UpdateOrderShippingMethodDTO, @@ -3142,7 +3141,7 @@ export interface IOrderModuleService extends IModuleService { * ]) */ softDeleteLineItemAdjustments< - TReturnableLinkableKeys extends string = string, + TReturnableLinkableKeys extends string = string >( ids: string[], config?: SoftDeleteReturn, @@ -3191,7 +3190,7 @@ export interface IOrderModuleService extends IModuleService { * ]) */ softDeleteShippingMethodAdjustments< - TReturnableLinkableKeys extends string = string, + TReturnableLinkableKeys extends string = string >( ids: string[], config?: SoftDeleteReturn, @@ -3217,7 +3216,7 @@ export interface IOrderModuleService extends IModuleService { * ]) */ restoreShippingMethodAdjustments< - TReturnableLinkableKeys extends string = string, + TReturnableLinkableKeys extends string = string >( ids: string[], config?: RestoreReturn, @@ -3289,7 +3288,7 @@ export interface IOrderModuleService extends IModuleService { * ]) */ softDeleteShippingMethodTaxLines< - TReturnableLinkableKeys extends string = string, + TReturnableLinkableKeys extends string = string >( ids: string[], config?: SoftDeleteReturn, @@ -3315,7 +3314,7 @@ export interface IOrderModuleService extends IModuleService { * ]) */ restoreShippingMethodTaxLines< - TReturnableLinkableKeys extends string = string, + TReturnableLinkableKeys extends string = string >( ids: string[], config?: RestoreReturn, @@ -3603,28 +3602,6 @@ export interface IOrderModuleService extends IModuleService { sharedContext?: Context ): Promise - /** - * This method updates existing return reasons. - * - * @param {UpdateOrderReturnReasonWithSelectorDTO[]} data - The filters that specifies which - * return reasons to update, and the data to update in them. - * @returns {Promise} The updated return reasons. - * - * @example - * const returnReasons = await orderModuleService.updateReturnReasons([{ - * selector: { - * id: "13" - * }, - * data: { - * label: "Damaged" - * } - * }]) - */ - updateReturnReasons( - data: UpdateOrderReturnReasonWithSelectorDTO[], - sharedContext?: Context - ): Promise - /** * This method updates existing return reasons matching the specified filters. * diff --git a/packages/medusa/src/api/admin/return-reasons/[id]/route.ts b/packages/medusa/src/api/admin/return-reasons/[id]/route.ts index e8be4d7713..f748bc8041 100644 --- a/packages/medusa/src/api/admin/return-reasons/[id]/route.ts +++ b/packages/medusa/src/api/admin/return-reasons/[id]/route.ts @@ -2,31 +2,38 @@ import { deleteReturnReasonsWorkflow, updateReturnReasonsWorkflow, } from "@medusajs/core-flows" -import { UpdateOrderReturnReasonDTO } from "@medusajs/types" +import { + AdminReturnReasonResponse, + UpdateOrderReturnReasonDTO, +} from "@medusajs/types" import { ContainerRegistrationKeys, + MedusaError, remoteQueryObjectFromString, } from "@medusajs/utils" import { AuthenticatedMedusaRequest, MedusaResponse, } from "../../../../types/routing" +import { refetchEntity } from "../../../utils/refetch-entity" export const GET = async ( req: AuthenticatedMedusaRequest, - res: MedusaResponse + res: MedusaResponse ) => { - const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + const return_reason = await refetchEntity( + "return_reason", + req.params.id, + req.scope, + req.remoteQueryConfig.fields + ) - const variables = { id: req.params.id } - - const queryObject = remoteQueryObjectFromString({ - entryPoint: "return_reason", - variables, - fields: req.remoteQueryConfig.fields, - }) - - const [return_reason] = await remoteQuery(queryObject) + if (!return_reason) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `Return reason with id: ${req.params.id} was not found` + ) + } res.json({ return_reason }) } diff --git a/packages/medusa/src/api/admin/return-reasons/middlewares.ts b/packages/medusa/src/api/admin/return-reasons/middlewares.ts index 7e9efd5065..b2fa71c62d 100644 --- a/packages/medusa/src/api/admin/return-reasons/middlewares.ts +++ b/packages/medusa/src/api/admin/return-reasons/middlewares.ts @@ -6,6 +6,7 @@ import { AdminCreateReturnReason, AdminGetReturnReasonsParams, AdminGetReturnReasonsReturnReasonParams, + AdminUpdateReturnReason, } from "./validators" export const adminReturnReasonRoutesMiddlewares: MiddlewareRoute[] = [ @@ -40,6 +41,17 @@ export const adminReturnReasonRoutesMiddlewares: MiddlewareRoute[] = [ ), ], }, + { + method: ["POST"], + matcher: "/admin/return-reasons/:id", + middlewares: [ + validateAndTransformBody(AdminUpdateReturnReason), + validateAndTransformQuery( + AdminGetReturnReasonsReturnReasonParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, { method: ["DELETE"], matcher: "/admin/return-reasons/:id", diff --git a/packages/medusa/src/api/admin/return-reasons/route.ts b/packages/medusa/src/api/admin/return-reasons/route.ts index 497c537043..9ca43def46 100644 --- a/packages/medusa/src/api/admin/return-reasons/route.ts +++ b/packages/medusa/src/api/admin/return-reasons/route.ts @@ -8,13 +8,13 @@ import { AuthenticatedMedusaRequest, MedusaResponse, } from "../../../types/routing" +import { AdminGetReturnReasonsParamsType } from "./validators" export const GET = async ( - req: AuthenticatedMedusaRequest, + req: AuthenticatedMedusaRequest, res: MedusaResponse ) => { const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) - const queryObject = remoteQueryObjectFromString({ entryPoint: "return_reason", variables: { diff --git a/packages/medusa/src/api/admin/return-reasons/validators.ts b/packages/medusa/src/api/admin/return-reasons/validators.ts index 1fc5130e75..dc0fd9d84b 100644 --- a/packages/medusa/src/api/admin/return-reasons/validators.ts +++ b/packages/medusa/src/api/admin/return-reasons/validators.ts @@ -29,10 +29,11 @@ export type AdminGetReturnReasonsReturnReasonParamsType = z.infer< * Parameters used to filter and configure the pagination of the retrieved order. */ export const AdminGetReturnReasonsParams = createFindParams({ - limit: 15, offset: 0, + limit: 20, }).merge( z.object({ + q: z.string().optional(), id: z.union([z.string(), z.array(z.string())]).optional(), value: z.union([z.string(), z.array(z.string())]).optional(), label: z.union([z.string(), z.array(z.string())]).optional(), @@ -43,8 +44,11 @@ export const AdminGetReturnReasonsParams = createFindParams({ created_at: createOperatorMap().optional(), updated_at: createOperatorMap().optional(), deleted_at: createOperatorMap().optional(), + $and: z.lazy(() => AdminGetReturnReasonsParams.array()).optional(), + $or: z.lazy(() => AdminGetReturnReasonsParams.array()).optional(), }) ) + export type AdminGetReturnReasonsParamsType = z.infer< typeof AdminGetReturnReasonsParams > diff --git a/packages/medusa/src/api/store/return-reasons/middlewares.ts b/packages/medusa/src/api/store/return-reasons/middlewares.ts index 4d7be59fe2..e1bf95479d 100644 --- a/packages/medusa/src/api/store/return-reasons/middlewares.ts +++ b/packages/medusa/src/api/store/return-reasons/middlewares.ts @@ -6,7 +6,7 @@ import { StoreReturnReasonParams } from "./validators" export const storeReturnReasonRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["GET"], - matcher: "/admin/return-reasons", + matcher: "/store/return-reasons", middlewares: [ validateAndTransformQuery( StoreReturnReasonParams, @@ -16,7 +16,7 @@ export const storeReturnReasonRoutesMiddlewares: MiddlewareRoute[] = [ }, { method: ["GET"], - matcher: "/admin/return-reasons/:id", + matcher: "/store/return-reasons/:id", middlewares: [ validateAndTransformQuery( StoreReturnReasonParams, diff --git a/packages/modules/order/src/services/order-module-service.ts b/packages/modules/order/src/services/order-module-service.ts index 8b97b84ad3..e6cea56fdf 100644 --- a/packages/modules/order/src/services/order-module-service.ts +++ b/packages/modules/order/src/services/order-module-service.ts @@ -2,15 +2,18 @@ import { BigNumberInput, Context, DAL, + FilterableOrderReturnReasonProps, FindConfig, InternalModuleDeclaration, IOrderModuleService, ModulesSdkTypes, OrderDTO, + OrderReturnReasonDTO, OrderTypes, RestoreReturn, SoftDeleteReturn, UpdateOrderItemWithSelectorDTO, + UpdateOrderReturnReasonDTO, } from "@medusajs/types" import { BigNumber, @@ -68,6 +71,7 @@ import { UpdateOrderLineItemTaxLineDTO, UpdateOrderShippingMethodTaxLineDTO, } from "@types" +import { UpdateReturnReasonDTO } from "src/types/return-reason" import { applyChangesToOrder, ApplyOrderChangeDTO, @@ -3250,6 +3254,56 @@ export default class OrderModuleService< ) } + // @ts-ignore + updateReturnReasons( + id: string, + data: UpdateOrderReturnReasonDTO, + sharedContext?: Context + ): Promise + updateReturnReasons( + selector: FilterableOrderReturnReasonProps, + data: Partial, + sharedContext?: Context + ): Promise + + @InjectManager("baseRepository_") + async updateReturnReasons( + idOrSelector: string | FilterableOrderReturnReasonProps, + data: UpdateOrderReturnReasonDTO, + @MedusaContext() sharedContext: Context = {} + ): Promise { + let normalizedInput: UpdateReturnReasonDTO[] = [] + if (isString(idOrSelector)) { + // Check if the return reason exists in the first place + await this.returnReasonService_.retrieve(idOrSelector, {}, sharedContext) + normalizedInput = [{ id: idOrSelector, ...data }] + } else { + const reasons = await this.returnReasonService_.list( + idOrSelector, + {}, + sharedContext + ) + + normalizedInput = reasons.map((reason) => ({ + id: reason.id, + ...data, + })) + } + + const reasons = await this.returnReasonService_.update( + normalizedInput, + sharedContext + ) + + const updatedReturnReasons = await this.baseRepository_.serialize< + OrderReturnReasonDTO[] + >(reasons) + + return isString(idOrSelector) + ? updatedReturnReasons[0] + : updatedReturnReasons + } + @InjectTransactionManager("baseRepository_") async createExchange_( data: OrderTypes.CreateOrderExchangeDTO, diff --git a/packages/modules/order/src/types/return-reason.ts b/packages/modules/order/src/types/return-reason.ts new file mode 100644 index 0000000000..d3ad589436 --- /dev/null +++ b/packages/modules/order/src/types/return-reason.ts @@ -0,0 +1,5 @@ +import { UpdateOrderReturnReasonDTO } from "@medusajs/types" + +export type UpdateReturnReasonDTO = UpdateOrderReturnReasonDTO & { + id: string +}