feat(dashboard,core-flows,js-sdk,types,medusa): Add exchange UI + fixes (#8606)

what:

- adds exchange UI
- fixes bugs on core-flows and endpoints
- random set of tiny fixes


https://github.com/user-attachments/assets/b163b9c1-4475-4936-ae98-20795760cc55
This commit is contained in:
Riqwan Thamir authored and GitHub committed 2024-08-15 17:54:51 +00:00
1 parent ba34c53151
commit 82c147b91e
36 files changed
+3666 -67

No files matched your search

@@ -6,7 +6,12 @@ import {
OrderExchangeDTO,
OrderPreviewDTO,
} from "@medusajs/types"
import { ChangeActionType, Modules, OrderChangeStatus } from "@medusajs/utils"
import {
ChangeActionType,
Modules,
OrderChangeStatus,
ReturnStatus,
} from "@medusajs/utils"
import {
WorkflowResponse,
createStep,
@@ -19,7 +24,7 @@ import { createRemoteLinkStep, useRemoteQueryStep } from "../../../common"
import { reserveInventoryStep } from "../../../definition/cart/steps/reserve-inventory"
import { prepareConfirmInventoryInput } from "../../../definition/cart/utils/prepare-confirm-inventory-input"
import { createReturnFulfillmentWorkflow } from "../../../fulfillment/workflows/create-return-fulfillment"
import { previewOrderChangeStep } from "../../steps"
import { previewOrderChangeStep, updateReturnsStep } from "../../steps"
import { confirmOrderChanges } from "../../steps/confirm-order-changes"
import { createOrderExchangeItemsFromActionsStep } from "../../steps/exchange/create-exchange-items-from-actions"
import { createReturnItemsFromActionsStep } from "../../steps/return/create-return-items-from-actions"
@@ -57,7 +62,6 @@ function prepareFulfillmentData({
items,
shippingOption,
deliveryAddress,
isReturn,
}: {
order: OrderDTO
items: any[]
@@ -74,17 +78,15 @@ function prepareFulfillmentData({
}
}
deliveryAddress?: Record<string, any>
isReturn?: boolean
}) {
const orderItemsMap = new Map<string, Required<OrderDTO>["items"][0]>(
order.items!.map((i) => [i.id, i])
)
const fulfillmentItems = items.map((i) => {
const orderItem = orderItemsMap.get(i.item_id) ?? i.item
const orderItem = orderItemsMap.get(i.id) ?? i.item
return {
line_item_id: i.item_id,
quantity: !isReturn ? i.quantity : undefined,
return_quantity: isReturn ? i.quantity : undefined,
quantity: i.quantity,
title: orderItem.variant_title ?? orderItem.title,
sku: orderItem.variant_sku || "",
barcode: orderItem.variant_barcode || "",
@@ -191,11 +193,8 @@ export const confirmExchangeRequestWorkflow = createWorkflow(
"id",
"version",
"canceled_at",
"items.id",
"items.title",
"items.variant_title",
"items.variant_sku",
"items.variant_barcode",
"items.*",
"items.item.id",
"shipping_address.*",
],
variables: { id: orderExchange.order_id },
@@ -248,6 +247,18 @@ export const confirmExchangeRequestWorkflow = createWorkflow(
}
)
when({ returnId }, ({ returnId }) => {
return !!returnId
}).then(() => {
updateReturnsStep([
{
id: returnId,
status: ReturnStatus.REQUESTED,
requested_at: new Date(),
},
])
})
const exchangeId = transform(
{ createExchangeItems },
({ createExchangeItems }) => {
@@ -320,9 +331,12 @@ export const confirmExchangeRequestWorkflow = createWorkflow(
reserveInventoryStep(formatedInventoryItems)
})
when({ returnShippingMethod }, ({ returnShippingMethod }) => {
return !!returnShippingMethod
}).then(() => {
when(
{ returnShippingMethod, returnId },
({ returnShippingMethod, returnId }) => {
return !!returnShippingMethod && !!returnId
}
).then(() => {
const returnShippingOption = useRemoteQueryStep({
entry_point: "shipping_options",
fields: [
@@ -343,7 +357,6 @@ export const confirmExchangeRequestWorkflow = createWorkflow(
order,
items: order.items!,
shippingOption: returnShippingOption,
isReturn: true,
},
prepareFulfillmentData
)
@@ -19,6 +19,7 @@ import { useRemoteQueryStep } from "../../../common"
import { updateOrderExchangesStep } from "../../steps/exchange/update-order-exchanges"
import { previewOrderChangeStep } from "../../steps/preview-order-change"
import { createReturnsStep } from "../../steps/return/create-returns"
import { updateOrderChangesStep } from "../../steps/update-order-changes"
import {
throwIfIsCancelled,
throwIfItemsDoesNotExistsInOrder,
@@ -124,6 +125,17 @@ export const orderExchangeRequestItemReturnWorkflow = createWorkflow(
status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED],
})
when({ createdReturn }, ({ createdReturn }) => {
return !!createdReturn?.length
}).then(() => {
updateOrderChangesStep([
{
id: orderChange.id,
return_id: createdReturn?.[0]?.id,
},
])
})
exchangeRequestItemReturnValidationStep({
order,
items: input.items,
+371
View File
@@ -0,0 +1,371 @@
import { HttpTypes } from "@medusajs/types"
import { Client } from "../client"
import { ClientHeaders } from "../types"
export class Exchange {
private client: Client
constructor(client: Client) {
this.client = client
}
async list(
query?: HttpTypes.AdminExchangeListParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeListResponse>(
`/admin/exchanges`,
{
query,
headers,
}
)
}
async retrieve(
id: string,
query?: HttpTypes.AdminExchangeParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}`,
{
query,
headers,
}
)
}
async create(
body: HttpTypes.AdminCreateExchange,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges`,
{
method: "POST",
headers,
body,
query,
}
)
}
async cancel(
id: string,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/cancel`,
{
method: "POST",
headers,
query,
}
)
}
async delete(
id: string,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeDeleteResponse>(
`/admin/exchanges/${id}`,
{
method: "DELETE",
headers,
query,
}
)
}
async addItems(
id: string,
body: HttpTypes.AdminAddExchangeItems,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/exchange-items`,
{
method: "POST",
headers,
body,
query,
}
)
}
async updateItem(
id: string,
actionId: string,
body: HttpTypes.AdminUpdateExchangeItem,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/exchange-items/${actionId}`,
{
method: "POST",
headers,
body,
query,
}
)
}
async removeItem(
id: string,
actionId: string,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminReturnResponse>(
`/admin/exchanges/${id}/exchange-items/${actionId}`,
{
method: "DELETE",
headers,
query,
}
)
}
async addInboundItems(
id: string,
body: HttpTypes.AdminAddExchangeInboundItems,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/inbound/items`,
{
method: "POST",
headers,
body,
query,
}
)
}
async updateInboundItem(
id: string,
actionId: string,
body: HttpTypes.AdminUpdateExchangeInboundItem,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/inbound/items/${actionId}`,
{
method: "POST",
headers,
body,
query,
}
)
}
async removeInboundItem(
id: string,
actionId: string,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/inbound/items/${actionId}`,
{
method: "DELETE",
headers,
query,
}
)
}
async addInboundShipping(
id: string,
body: HttpTypes.AdminExchangeAddInboundShipping,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/inbound/shipping-method`,
{
method: "POST",
headers,
body,
query,
}
)
}
async updateInboundShipping(
id: string,
actionId: string,
body: HttpTypes.AdminExchangeUpdateInboundShipping,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/inbound/shipping-method/${actionId}`,
{
method: "POST",
headers,
body,
query,
}
)
}
async deleteInboundShipping(
id: string,
actionId: string,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/inbound/shipping-method/${actionId}`,
{
method: "DELETE",
headers,
query,
}
)
}
async addOutboundItems(
id: string,
body: HttpTypes.AdminAddExchangeOutboundItems,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/outbound/items`,
{
method: "POST",
headers,
body,
query,
}
)
}
async updateOutboundItem(
id: string,
actionId: string,
body: HttpTypes.AdminUpdateExchangeOutboundItem,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/outbound/items/${actionId}`,
{
method: "POST",
headers,
body,
query,
}
)
}
async removeOutboundItem(
id: string,
actionId: string,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/outbound/items/${actionId}`,
{
method: "DELETE",
headers,
query,
}
)
}
async addOutboundShipping(
id: string,
body: HttpTypes.AdminExchangeAddOutboundShipping,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/outbound/shipping-method`,
{
method: "POST",
headers,
body,
query,
}
)
}
async updateOutboundShipping(
id: string,
actionId: string,
body: HttpTypes.AdminExchangeUpdateOutboundShipping,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/outbound/shipping-method/${actionId}`,
{
method: "POST",
headers,
body,
query,
}
)
}
async deleteOutboundShipping(
id: string,
actionId: string,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/outbound/shipping-method/${actionId}`,
{
method: "DELETE",
headers,
query,
}
)
}
async request(
id: string,
body: HttpTypes.AdminRequestExchange,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/request`,
{
method: "POST",
headers,
body,
query,
}
)
}
async cancelRequest(
id: string,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminExchangeResponse>(
`/admin/exchanges/${id}/request`,
{
method: "DELETE",
headers,
query,
}
)
}
}
+3
View File
@@ -2,6 +2,7 @@ import { Client } from "../client"
import { Claim } from "./claim"
import { Currency } from "./currency"
import { Customer } from "./customer"
import { Exchange } from "./exchange"
import { Fulfillment } from "./fulfillment"
import { FulfillmentProvider } from "./fulfillment-provider"
import { FulfillmentSet } from "./fulfillment-set"
@@ -56,6 +57,7 @@ export class Admin {
public order: Order
public return: Return
public claim: Claim
public exchange: Exchange
public taxRate: TaxRate
public taxRegion: TaxRegion
public store: Store
@@ -99,5 +101,6 @@ export class Admin {
this.payment = new Payment(client)
this.productVariant = new ProductVariant(client)
this.refundReason = new RefundReason(client)
this.exchange = new Exchange(client)
}
}
@@ -1,2 +1,4 @@
export * from "./entities"
export * from "./responses"
export * from "./payloads"
export * from "./queries"
export * from "./responses"
@@ -0,0 +1,74 @@
enum ExchangeReason {
MISSING_ITEM = "missing_item",
WRONG_ITEM = "wrong_item",
PRODUCTION_FAILURE = "production_failure",
OTHER = "other",
}
interface AdminExchangeAddItems {
items: {
id: string
quantity: number
reason?: ExchangeReason
description?: string
internal_note?: string
}[]
}
interface AdminExchangeUpdateItem {
quantity?: number
reason_id?: string | null
description?: string
internal_note?: string | null
}
interface AdminExchangeAddShippingMethod {
shipping_option_id: string
custom_price?: number
description?: string
internal_note?: string
metadata?: Record<string, unknown> | null
}
interface AdminExchangeUpdateShippingMethod {
custom_price?: number | null
internal_note?: string
metadata?: Record<string, unknown> | null
}
export interface AdminCreateExchange {
type: "refund" | "replace"
order_id: string
description?: string
internal_note?: string
metadata?: Record<string, unknown> | null
}
export interface AdminAddExchangeItems extends AdminExchangeAddItems {}
export interface AdminUpdateExchangeItem extends AdminExchangeUpdateItem {}
export interface AdminAddExchangeInboundItems extends AdminExchangeAddItems {}
export interface AdminUpdateExchangeInboundItem
extends AdminExchangeUpdateItem {}
export interface AdminAddExchangeOutboundItems extends AdminExchangeAddItems {}
export interface AdminUpdateExchangeOutboundItem
extends AdminExchangeUpdateItem {}
export interface AdminExchangeAddInboundShipping
extends AdminExchangeAddShippingMethod {}
export interface AdminExchangeUpdateInboundShipping
extends AdminExchangeUpdateShippingMethod {}
export interface AdminExchangeAddOutboundShipping
extends AdminExchangeAddShippingMethod {}
export interface AdminExchangeUpdateOutboundShipping
extends AdminExchangeUpdateShippingMethod {}
export interface AdminRequestExchange {
no_notification?: boolean
}
export interface AdminCancelExchange {
no_notification?: boolean
}
@@ -0,0 +1,17 @@
import { BaseFilterable, OperatorMap } from "../../../dal"
import { SelectParams } from "../../common"
import { BaseExchangeListParams } from "../common"
export interface AdminExchangeListParams
extends BaseExchangeListParams,
BaseFilterable<AdminExchangeListParams> {
deleted_at?: OperatorMap<string>
}
export interface AdminExchangeParams extends SelectParams {
id?: string | string[]
status?: string | string[]
created_at?: OperatorMap<string>
updated_at?: OperatorMap<string>
deleted_at?: OperatorMap<string>
}
@@ -1,5 +1,5 @@
import { OrderDTO, OrderPreviewDTO } from "../../../order"
import { PaginatedResponse } from "../../common"
import { DeleteResponse, PaginatedResponse } from "../../common"
import { AdminReturn } from "../../return"
import { AdminExchange } from "./entities"
@@ -8,7 +8,7 @@ export interface AdminExchangeResponse {
}
export type AdminExchangeListResponse = PaginatedResponse<{
exchanges: AdminExchange
exchanges: AdminExchange[]
}>
export interface AdminExchangeOrderResponse {
@@ -30,3 +30,6 @@ export interface AdminExchangeReturnResponse {
order_preview: OrderPreviewDTO
return: AdminReturn
}
export interface AdminExchangeDeleteResponse
extends DeleteResponse<"exchange"> {}
@@ -1,5 +1,7 @@
import { BaseOrder } from "../order/common";
import { AdminReturnItem, AdminReturn } from "../return";
import { OperatorMap } from "../../dal"
import { FindParams } from "../common"
import { BaseOrder } from "../order/common"
import { AdminReturn, AdminReturnItem } from "../return"
export interface BaseExchangeItem {
id: string
@@ -12,7 +14,8 @@ export interface BaseExchangeItem {
updated_at: string | null
}
export interface BaseExchange extends Omit<BaseOrder, "status" | "version" | "items"> {
export interface BaseExchange
extends Omit<BaseOrder, "status" | "version" | "items"> {
order_id: string
return_items: AdminReturnItem[]
additional_items: BaseExchangeItem[]
@@ -20,4 +23,14 @@ export interface BaseExchange extends Omit<BaseOrder, "status" | "version" | "it
difference_due?: number
return?: AdminReturn
return_id?: string
}
}
export interface BaseExchangeListParams extends FindParams {
q?: string
id?: string | string[]
order_id?: string | string[]
status?: string | string[]
created_at?: OperatorMap<string>
updated_at?: OperatorMap<string>
deleted_at?: OperatorMap<string>
}