diff --git a/integration-tests/modules/__tests__/order/order.spec.ts b/integration-tests/modules/__tests__/order/order.spec.ts index 6dd029f77f..3a9f84297c 100644 --- a/integration-tests/modules/__tests__/order/order.spec.ts +++ b/integration-tests/modules/__tests__/order/order.spec.ts @@ -155,6 +155,9 @@ medusaIntegrationTestRunner({ version: 1, display_id: 1, payment_collections: [], + payment_status: "not_paid", + fulfillments: [], + fulfillment_status: "not_fulfilled", summary: expect.objectContaining({ // TODO: add all summary fields }), diff --git a/packages/admin-next/dashboard/src/routes/orders/order-create-fulfillment/components/order-create-fulfillment-form/order-create-fulfillment-form.tsx b/packages/admin-next/dashboard/src/routes/orders/order-create-fulfillment/components/order-create-fulfillment-form/order-create-fulfillment-form.tsx index 157e847439..d3d5a9c5f3 100644 --- a/packages/admin-next/dashboard/src/routes/orders/order-create-fulfillment/components/order-create-fulfillment-form/order-create-fulfillment-form.tsx +++ b/packages/admin-next/dashboard/src/routes/orders/order-create-fulfillment/components/order-create-fulfillment-form/order-create-fulfillment-form.tsx @@ -1,27 +1,27 @@ -import React, { useEffect, useState } from "react" -import * as zod from "zod" -import { useTranslation } from "react-i18next" import { zodResolver } from "@hookform/resolvers/zod" +import { useEffect, useState } from "react" +import { useTranslation } from "react-i18next" +import * as zod from "zod" -import { useForm, useWatch } from "react-hook-form" +import { AdminOrder } from "@medusajs/types" import { Alert, Button, Select, toast } from "@medusajs/ui" -import { OrderDTO } from "@medusajs/types" +import { useForm, useWatch } from "react-hook-form" +import { Form } from "../../../../../components/common/form" import { RouteFocusModal, useRouteModal, } from "../../../../../components/route-modal" -import { CreateFulfillmentSchema } from "./constants" -import { Form } from "../../../../../components/common/form" -import { OrderCreateFulfillmentItem } from "./order-create-fulfillment-item" -import { getFulfillableQuantity } from "../../../../../lib/order-item" import { useCreateFulfillment } from "../../../../../hooks/api/fulfillment" -import { useStockLocations } from "../../../../../hooks/api/stock-locations" import { useFulfillmentProviders } from "../../../../../hooks/api/fulfillment-providers" +import { useStockLocations } from "../../../../../hooks/api/stock-locations" import { cleanNonValues, pick } from "../../../../../lib/common" +import { getFulfillableQuantity } from "../../../../../lib/order-item" +import { CreateFulfillmentSchema } from "./constants" +import { OrderCreateFulfillmentItem } from "./order-create-fulfillment-item" type OrderCreateFulfillmentFormProps = { - order: OrderDTO + order: AdminOrder } export function OrderCreateFulfillmentForm({ @@ -43,10 +43,13 @@ export function OrderCreateFulfillmentForm({ const form = useForm>({ defaultValues: { - quantity: fulfillableItems.reduce((acc, item) => { - acc[item.id] = getFulfillableQuantity(item) - return acc - }, {} as Record), + quantity: fulfillableItems.reduce( + (acc, item) => { + acc[item.id] = getFulfillableQuantity(item) + return acc + }, + {} as Record + ), // send_notification: !order.no_notification, }, resolver: zodResolver(CreateFulfillmentSchema), diff --git a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-activity-section/order-activity-section.tsx b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-activity-section/order-activity-section.tsx index 591513c10f..9574e9978a 100644 --- a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-activity-section/order-activity-section.tsx +++ b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-activity-section/order-activity-section.tsx @@ -1,11 +1,11 @@ +import { AdminOrder } from "@medusajs/types" import { Container, Heading } from "@medusajs/ui" import { useTranslation } from "react-i18next" import { OrderNoteForm } from "./order-note-form" import { OrderTimeline } from "./order-timeline" -import { OrderDTO } from "@medusajs/types" type OrderActivityProps = { - order: OrderDTO + order: AdminOrder } export const OrderActivitySection = ({ order }: OrderActivityProps) => { diff --git a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-activity-section/order-note-form.tsx b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-activity-section/order-note-form.tsx index da5af72ccf..12a718a5b7 100644 --- a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-activity-section/order-note-form.tsx +++ b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-activity-section/order-note-form.tsx @@ -5,12 +5,12 @@ import { useRef } from "react" import { useForm } from "react-hook-form" import { z } from "zod" +import { AdminOrder } from "@medusajs/types" import { useTranslation } from "react-i18next" import { Form } from "../../../../../components/common/form" -import { OrderDTO } from "@medusajs/types" type OrderNoteFormProps = { - order: OrderDTO + order: AdminOrder } const OrderNoteSchema = z.object({ diff --git a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-activity-section/order-timeline.tsx b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-activity-section/order-timeline.tsx index 0520714196..011ca62664 100644 --- a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-activity-section/order-timeline.tsx +++ b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-activity-section/order-timeline.tsx @@ -6,14 +6,13 @@ import { PropsWithChildren, ReactNode, useMemo, useState } from "react" import { Link } from "react-router-dom" import { XMarkMini } from "@medusajs/icons" +import { AdminOrder } from "@medusajs/types" import { useTranslation } from "react-i18next" import { Skeleton } from "../../../../../components/common/skeleton" import { useDate } from "../../../../../hooks/use-date" -import { getStylizedAmount } from "../../../../../lib/money-amount-helpers" -import { OrderDTO } from "@medusajs/types" type OrderTimelineProps = { - order: OrderDTO + order: AdminOrder } /** diff --git a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-fulfillment-section/order-fulfillment-section.tsx b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-fulfillment-section/order-fulfillment-section.tsx index 775a3e9b86..82714d8c71 100644 --- a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-fulfillment-section/order-fulfillment-section.tsx +++ b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-fulfillment-section/order-fulfillment-section.tsx @@ -1,7 +1,7 @@ import { Buildings, XCircle } from "@medusajs/icons" import { + AdminOrder, FulfillmentDTO, - OrderDTO, OrderLineItemDTO, ProductVariantDTO, } from "@medusajs/types" @@ -27,7 +27,7 @@ import { formatProvider } from "../../../../../lib/format-provider" import { getLocaleAmount } from "../../../../../lib/money-amount-helpers" type OrderFulfillmentSectionProps = { - order: OrderDTO & { fulfillments: FulfillmentDTO[] } + order: AdminOrder } export const OrderFulfillmentSection = ({ @@ -103,11 +103,7 @@ const UnfulfilledItem = ({ ) } -const UnfulfilledItemBreakdown = ({ - order, -}: { - order: OrderDTO & { fulfillments: FulfillmentDTO[] } -}) => { +const UnfulfilledItemBreakdown = ({ order }: { order: AdminOrder }) => { const { t } = useTranslation() // Create an array of order items that haven't been fulfilled or at least not fully fulfilled @@ -161,7 +157,7 @@ const Fulfillment = ({ index, }: { fulfillment: FulfillmentDTO - order: OrderDTO + order: AdminOrder index: number }) => { const { t } = useTranslation() diff --git a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-summary-section/order-summary-section.tsx b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-summary-section/order-summary-section.tsx index e3019aea9c..8a648fe25a 100644 --- a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-summary-section/order-summary-section.tsx +++ b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-summary-section/order-summary-section.tsx @@ -1,5 +1,8 @@ -import { Buildings, PencilSquare, ArrowUturnLeft } from "@medusajs/icons" -import { OrderDTO, OrderLineItemDTO, ReservationItemDTO } from "@medusajs/types" +import { + AdminOrder, + OrderLineItemDTO, + ReservationItemDTO, +} from "@medusajs/types" import { Container, Copy, Heading, StatusBadge, Text } from "@medusajs/ui" import { useTranslation } from "react-i18next" @@ -11,7 +14,7 @@ import { } from "../../../../../lib/money-amount-helpers" type OrderSummarySectionProps = { - order: OrderDTO + order: AdminOrder } export const OrderSummarySection = ({ order }: OrderSummarySectionProps) => { @@ -25,7 +28,7 @@ export const OrderSummarySection = ({ order }: OrderSummarySectionProps) => { ) } -const Header = ({ order }: { order: OrderDTO }) => { +const Header = ({ order }: { order: AdminOrder }) => { const { t } = useTranslation() return ( @@ -129,7 +132,7 @@ const Item = ({ ) } -const ItemBreakdown = ({ order }: { order: OrderDTO }) => { +const ItemBreakdown = ({ order }: { order: AdminOrder }) => { // const { reservations, isError, error } = useAdminReservations({ // line_item_id: order.items.map((i) => i.id), // }) @@ -184,7 +187,7 @@ const Cost = ({ ) -const CostBreakdown = ({ order }: { order: OrderDTO }) => { +const CostBreakdown = ({ order }: { order: AdminOrder }) => { const { t } = useTranslation() return ( @@ -230,7 +233,7 @@ const CostBreakdown = ({ order }: { order: OrderDTO }) => { ) } -const Total = ({ order }: { order: OrderDTO }) => { +const Total = ({ order }: { order: AdminOrder }) => { const { t } = useTranslation() return ( diff --git a/packages/core/core-flows/src/order/utils/__tests__/aggregate-status.spec.ts b/packages/core/core-flows/src/order/utils/__tests__/aggregate-status.spec.ts new file mode 100644 index 0000000000..41863b1986 --- /dev/null +++ b/packages/core/core-flows/src/order/utils/__tests__/aggregate-status.spec.ts @@ -0,0 +1,227 @@ +import { + getLastFulfillmentStatus, + getLastPaymentStatus, +} from "../aggregate-status" + +describe("Aggregate Order Status", () => { + it("should return aggregated payment collection status", () => { + expect( + getLastPaymentStatus({ + payment_collections: [], + }) + ).toEqual("not_paid") + + expect( + getLastPaymentStatus({ + payment_collections: [{ status: "not_paid" }], + }) + ).toEqual("not_paid") + + expect( + getLastPaymentStatus({ + payment_collections: [{ status: "not_paid" }, { status: "awaiting" }], + }) + ).toEqual("awaiting") + + expect( + getLastPaymentStatus({ + payment_collections: [ + { status: "requires_action" }, + { status: "refunded" }, + { status: "refunded" }, + { status: "captured" }, + { status: "captured" }, + { status: "canceled" }, + { status: "authorized" }, + ], + }) + ).toEqual("requires_action") + + expect( + getLastPaymentStatus({ + payment_collections: [ + { status: "awaiting" }, + { status: "awaiting" }, + { status: "canceled" }, + { status: "awaiting" }, + ], + }) + ).toEqual("awaiting") + + expect( + getLastPaymentStatus({ + payment_collections: [ + { status: "authorized" }, + { status: "authorized" }, + { status: "canceled" }, + ], + }) + ).toEqual("authorized") + + expect( + getLastPaymentStatus({ + payment_collections: [ + { status: "awaiting" }, + { status: "authorized" }, + { status: "canceled" }, + ], + }) + ).toEqual("partially_authorized") + + expect( + getLastPaymentStatus({ + payment_collections: [ + { status: "authorized", refunded_amount: 10, amount: 10 }, + { status: "authorized", refunded_amount: 5, amount: 10 }, + { status: "canceled" }, + ], + }) + ).toEqual("partially_refunded") + + expect( + getLastPaymentStatus({ + payment_collections: [ + { status: "authorized", captured_amount: 10, amount: 10 }, + { status: "authorized", refunded_amount: 10, amount: 10 }, + { status: "authorized", refunded_amount: 10, amount: 10 }, + { status: "authorized" }, + { status: "canceled" }, + ], + }) + ).toEqual("partially_refunded") + + expect( + getLastPaymentStatus({ + payment_collections: [ + { status: "authorized", captured_amount: 10, amount: 10 }, + { status: "authorized", captured_amount: 12, amount: 12 }, + { status: "canceled" }, + ], + }) + ).toEqual("captured") + + expect( + getLastPaymentStatus({ + payment_collections: [ + { status: "authorized", captured_amount: 10, amount: 10 }, + { status: "authorized", captured_amount: 5, amount: 10 }, + ], + }) + ).toEqual("partially_captured") + + expect( + getLastPaymentStatus({ + payment_collections: [ + { status: "authorized", captured_amount: 10, amount: 10 }, + { status: "authorized", captured_amount: 10, amount: 10 }, + { status: "authorized" }, + ], + }) + ).toEqual("partially_captured") + + expect( + getLastPaymentStatus({ + payment_collections: [ + { status: "authorized", captured_amount: 10, amount: 10 }, + { status: "authorized", captured_amount: 12, amount: 12 }, + ], + }) + ).toEqual("captured") + + expect( + getLastPaymentStatus({ + payment_collections: [{ status: "canceled" }, { status: "canceled" }], + }) + ).toEqual("canceled") + }) + + it("should return aggregated fulfillment status", () => { + expect( + getLastFulfillmentStatus({ + fulfillments: [], + }) + ).toEqual("not_fulfilled") + + expect( + getLastFulfillmentStatus({ + fulfillments: [{ created_at: new Date() }], + }) + ).toEqual("not_fulfilled") + + expect( + getLastFulfillmentStatus({ + fulfillments: [{ created_at: new Date() }, { packed_at: new Date() }], + }) + ).toEqual("partially_fulfilled") + + expect( + getLastFulfillmentStatus({ + fulfillments: [{ packed_at: new Date() }, { packed_at: new Date() }], + }) + ).toEqual("fulfilled") + + expect( + getLastFulfillmentStatus({ + fulfillments: [{ shipped_at: new Date() }, { packed_at: new Date() }], + }) + ).toEqual("partially_shipped") + + expect( + getLastFulfillmentStatus({ + fulfillments: [{ shipped_at: new Date() }, { shipped_at: new Date() }], + }) + ).toEqual("shipped") + + expect( + getLastFulfillmentStatus({ + fulfillments: [ + { shipped_at: new Date() }, + { delivered_at: new Date() }, + ], + }) + ).toEqual("partially_delivered") + + expect( + getLastFulfillmentStatus({ + fulfillments: [ + { delivered_at: new Date() }, + { delivered_at: new Date() }, + ], + }) + ).toEqual("delivered") + + expect( + getLastFulfillmentStatus({ + fulfillments: [ + { delivered_at: new Date() }, + { canceled_at: new Date() }, + ], + }) + ).toEqual("delivered") + + expect( + getLastFulfillmentStatus({ + fulfillments: [{ shipped_at: new Date() }, { canceled_at: new Date() }], + }) + ).toEqual("shipped") + + expect( + getLastFulfillmentStatus({ + fulfillments: [ + { packed_at: new Date() }, + { shipped_at: new Date() }, + { canceled_at: new Date() }, + ], + }) + ).toEqual("partially_shipped") + + expect( + getLastFulfillmentStatus({ + fulfillments: [ + { canceled_at: new Date() }, + { canceled_at: new Date() }, + ], + }) + ).toEqual("canceled") + }) +}) diff --git a/packages/core/core-flows/src/order/utils/aggregate-status.ts b/packages/core/core-flows/src/order/utils/aggregate-status.ts new file mode 100644 index 0000000000..9d0733e0f3 --- /dev/null +++ b/packages/core/core-flows/src/order/utils/aggregate-status.ts @@ -0,0 +1,170 @@ +import { OrderDetailDTO } from "@medusajs/types" +import { MathBN } from "@medusajs/utils" + +export const getLastPaymentStatus = (order: OrderDetailDTO) => { + const PaymentStatus = { + NOT_PAID: "not_paid", + AWAITING: "awaiting", + CAPTURED: "captured", + PARTIALLY_CAPTURED: "partially_captured", + PARTIALLY_REFUNDED: "partially_refunded", + REFUNDED: "refunded", + CANCELED: "canceled", + REQUIRES_ACTION: "requires_action", + AUTHORIZED: "authorized", + PARTIALLY_AUTHORIZED: "partially_authorized", + } + + let paymentStatus = {} + for (const status in PaymentStatus) { + paymentStatus[PaymentStatus[status]] = 0 + } + + for (const paymentCollection of order.payment_collections) { + if (MathBN.gt(paymentCollection.captured_amount ?? 0, 0)) { + paymentStatus[PaymentStatus.CAPTURED] += MathBN.eq( + paymentCollection.captured_amount as number, + paymentCollection.amount + ) + ? 1 + : 0.5 + } + + if (MathBN.gt(paymentCollection.refunded_amount ?? 0, 0)) { + paymentStatus[PaymentStatus.REFUNDED] += MathBN.eq( + paymentCollection.refunded_amount as number, + paymentCollection.amount + ) + ? 1 + : 0.5 + } + + paymentStatus[paymentCollection.status] += 1 + } + + const totalPayments = order.payment_collections.length + const totalPaymentExceptCanceled = + totalPayments - paymentStatus[PaymentStatus.CANCELED] + + if (paymentStatus[PaymentStatus.REQUIRES_ACTION] > 0) { + return PaymentStatus.REQUIRES_ACTION + } + + if (paymentStatus[PaymentStatus.REFUNDED] > 0) { + if (paymentStatus[PaymentStatus.REFUNDED] === totalPaymentExceptCanceled) { + return PaymentStatus.REFUNDED + } + + return PaymentStatus.PARTIALLY_REFUNDED + } + + if (paymentStatus[PaymentStatus.CAPTURED] > 0) { + if (paymentStatus[PaymentStatus.CAPTURED] === totalPaymentExceptCanceled) { + return PaymentStatus.CAPTURED + } + + return PaymentStatus.PARTIALLY_CAPTURED + } + + if (paymentStatus[PaymentStatus.AUTHORIZED] > 0) { + if ( + paymentStatus[PaymentStatus.AUTHORIZED] === totalPaymentExceptCanceled + ) { + return PaymentStatus.AUTHORIZED + } + + return PaymentStatus.PARTIALLY_AUTHORIZED + } + + if ( + paymentStatus[PaymentStatus.CANCELED] > 0 && + paymentStatus[PaymentStatus.CANCELED] === totalPayments + ) { + return PaymentStatus.CANCELED + } + + if (paymentStatus[PaymentStatus.AWAITING] > 0) { + return PaymentStatus.AWAITING + } + + return PaymentStatus.NOT_PAID +} + +export const getLastFulfillmentStatus = (order: OrderDetailDTO) => { + const FulfillmentStatus = { + NOT_FULFILLED: "not_fulfilled", + PARTIALLY_FULFILLED: "partially_fulfilled", + FULFILLED: "fulfilled", + PARTIALLY_SHIPPED: "partially_shipped", + SHIPPED: "shipped", + DELIVERED: "delivered", + PARTIALLY_DELIVERED: "partially_delivered", + CANCELED: "canceled", + } + + let fulfillmentStatus = {} + for (const status in FulfillmentStatus) { + fulfillmentStatus[FulfillmentStatus[status]] = 0 + } + + const statusMap = { + packed_at: FulfillmentStatus.FULFILLED, + shipped_at: FulfillmentStatus.SHIPPED, + delivered_at: FulfillmentStatus.DELIVERED, + canceled_at: FulfillmentStatus.CANCELED, + } + for (const fulfillmentCollection of order.fulfillments) { + for (const key in statusMap) { + if (fulfillmentCollection[key]) { + fulfillmentStatus[statusMap[key]] += 1 + break + } + } + } + + const totalFulfillments = order.fulfillments.length + const totalFulfillmentsExceptCanceled = + totalFulfillments - fulfillmentStatus[FulfillmentStatus.CANCELED] + + if (fulfillmentStatus[FulfillmentStatus.DELIVERED] > 0) { + if ( + fulfillmentStatus[FulfillmentStatus.DELIVERED] === + totalFulfillmentsExceptCanceled + ) { + return FulfillmentStatus.DELIVERED + } + + return FulfillmentStatus.PARTIALLY_DELIVERED + } + + if (fulfillmentStatus[FulfillmentStatus.SHIPPED] > 0) { + if ( + fulfillmentStatus[FulfillmentStatus.SHIPPED] === + totalFulfillmentsExceptCanceled + ) { + return FulfillmentStatus.SHIPPED + } + + return FulfillmentStatus.PARTIALLY_SHIPPED + } + + if (fulfillmentStatus[FulfillmentStatus.FULFILLED] > 0) { + if ( + fulfillmentStatus[FulfillmentStatus.FULFILLED] === + totalFulfillmentsExceptCanceled + ) { + return FulfillmentStatus.FULFILLED + } + + return FulfillmentStatus.PARTIALLY_FULFILLED + } + + if ( + fulfillmentStatus[FulfillmentStatus.CANCELED] > 0 && + fulfillmentStatus[FulfillmentStatus.CANCELED] === totalFulfillments + ) { + return FulfillmentStatus.CANCELED + } + + return FulfillmentStatus.NOT_FULFILLED +} diff --git a/packages/core/core-flows/src/order/workflows/get-order-detail.ts b/packages/core/core-flows/src/order/workflows/get-order-detail.ts new file mode 100644 index 0000000000..c3c1ab489e --- /dev/null +++ b/packages/core/core-flows/src/order/workflows/get-order-detail.ts @@ -0,0 +1,53 @@ +import { OrderDTO, OrderDetailDTO } from "@medusajs/types" +import { deduplicate } from "@medusajs/utils" +import { + WorkflowData, + createWorkflow, + transform, +} from "@medusajs/workflows-sdk" +import { useRemoteQueryStep } from "../../common" +import { + getLastFulfillmentStatus, + getLastPaymentStatus, +} from "../utils/aggregate-status" + +export const getOrderDetailWorkflowId = "get-order-detail" +export const getOrderDetailWorkflow = createWorkflow( + getOrderDetailWorkflowId, + ( + input: WorkflowData<{ fields: string[]; order_id: string }> + ): WorkflowData => { + const fields = transform(input, ({ fields }) => { + return deduplicate([ + ...fields, + "id", + "status", + "version", + "payment_collections.*", + "fulfillments.*", + ]) + }) + + const order: OrderDTO = useRemoteQueryStep({ + entry_point: "orders", + fields, + variables: { id: input.order_id }, + list: false, + throw_if_key_not_found: true, + }) + + const aggregatedOrder = transform({ order }, ({ order }) => { + const order_ = order as OrderDetailDTO + + order_.payment_status = getLastPaymentStatus( + order_ + ) as OrderDetailDTO["payment_status"] + order_.fulfillment_status = getLastFulfillmentStatus( + order_ + ) as OrderDetailDTO["fulfillment_status"] + return order_ + }) + + return aggregatedOrder + } +) diff --git a/packages/core/core-flows/src/order/workflows/index.ts b/packages/core/core-flows/src/order/workflows/index.ts index 9b0cabf445..7ed9e0d836 100644 --- a/packages/core/core-flows/src/order/workflows/index.ts +++ b/packages/core/core-flows/src/order/workflows/index.ts @@ -4,4 +4,5 @@ export * from "./create-fulfillment" export * from "./create-orders" export * from "./create-return" export * from "./create-shipment" +export * from "./get-order-detail" export * from "./update-tax-lines" diff --git a/packages/core/types/src/http/order/common.ts b/packages/core/types/src/http/order/common.ts index 9504fcf434..f3f6b1b34f 100644 --- a/packages/core/types/src/http/order/common.ts +++ b/packages/core/types/src/http/order/common.ts @@ -221,6 +221,43 @@ export interface BaseOrderTransaction { updated_at: Date | string } +export interface BaseOrderFulfillment { + id: string + location_id: string + packed_at: Date | null + shipped_at: Date | null + delivered_at: Date | null + canceled_at: Date | null + data: Record | null + provider_id: string + shipping_option_id: string | null + metadata: Record | null + created_at: Date + updated_at: Date +} + +type PaymentStatus = + | "not_paid" + | "awaiting" + | "authorized" + | "partially_authorized" + | "captured" + | "partially_captured" + | "partially_refunded" + | "refunded" + | "canceled" + | "requires_action" + +type FulfillmentStatus = + | "not_fulfilled" + | "partially_fulfilled" + | "fulfilled" + | "partially_shipped" + | "shipped" + | "partially_delivered" + | "delivered" + | "canceled" + export interface BaseOrder { id: string version: number @@ -234,7 +271,10 @@ export interface BaseOrder { billing_address?: BaseOrderAddress items: BaseOrderLineItem[] | null shipping_methods: BaseOrderShippingMethod[] | null - payment_collection?: BasePaymentCollection + payment_collections?: BasePaymentCollection[] + payment_status: PaymentStatus + fulfillments?: BaseOrderFulfillment[] + fulfillment_status: FulfillmentStatus transactions?: BaseOrderTransaction[] summary: BaseOrderSummary metadata: Record | null diff --git a/packages/core/types/src/order/common.ts b/packages/core/types/src/order/common.ts index 807d4e39d1..fb57302366 100644 --- a/packages/core/types/src/order/common.ts +++ b/packages/core/types/src/order/common.ts @@ -1,5 +1,7 @@ import { BaseFilterable } from "../dal" import { OperatorMap } from "../dal/utils" +import { FulfillmentDTO } from "../fulfillment" +import { PaymentCollectionDTO } from "../payment" import { BigNumberRawValue, BigNumberValue } from "../totals" export type ChangeActionType = @@ -1102,6 +1104,35 @@ export interface OrderDTO { raw_original_shipping_tax_total: BigNumberRawValue } +type PaymentStatus = + | "not_paid" + | "awaiting" + | "authorized" + | "partially_authorized" + | "captured" + | "partially_captured" + | "partially_refunded" + | "refunded" + | "canceled" + | "requires_action" + +type FulfillmentStatus = + | "not_fulfilled" + | "partially_fulfilled" + | "fulfilled" + | "partially_shipped" + | "shipped" + | "partially_delivered" + | "delivered" + | "canceled" + +export interface OrderDetailDTO extends OrderDTO { + payment_collections: PaymentCollectionDTO[] + payment_status: PaymentStatus + fulfillments: FulfillmentDTO[] + fulfillment_status: FulfillmentStatus +} + export interface OrderChangeDTO { /** * The ID of the order change diff --git a/packages/core/types/src/payment/common.ts b/packages/core/types/src/payment/common.ts index 0f208f12ef..86df2ee170 100644 --- a/packages/core/types/src/payment/common.ts +++ b/packages/core/types/src/payment/common.ts @@ -100,6 +100,11 @@ export interface PaymentCollectionDTO { */ refunded_amount?: BigNumberValue + /** + * The amount captured within the associated payments. + */ + captured_amount?: BigNumberValue + /** * When the payment collection was completed. */ diff --git a/packages/medusa/src/api/admin/orders/[id]/route.ts b/packages/medusa/src/api/admin/orders/[id]/route.ts index 6dec01842d..22babe009f 100644 --- a/packages/medusa/src/api/admin/orders/[id]/route.ts +++ b/packages/medusa/src/api/admin/orders/[id]/route.ts @@ -1,3 +1,4 @@ +import { getOrderDetailWorkflow } from "@medusajs/core-flows" import { ContainerRegistrationKeys, remoteQueryObjectFromString, @@ -11,18 +12,15 @@ export const GET = async ( req: AuthenticatedMedusaRequest, res: MedusaResponse ) => { - const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) - - const variables = { id: req.params.id } - - const queryObject = remoteQueryObjectFromString({ - entryPoint: "order", - variables, - fields: req.remoteQueryConfig.fields, + const worklow = getOrderDetailWorkflow(req.scope) + const { result } = await worklow.run({ + input: { + fields: req.remoteQueryConfig.fields, + order_id: req.params.id, + }, }) - const [order] = await remoteQuery(queryObject) - res.status(200).json({ order }) + res.status(200).json({ order: result }) } export const POST = async (