chore(order): aggregate statuses (#7497)
This commit is contained in:
committed by
GitHub
parent
8e66e10995
commit
bbca54efa7
@@ -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")
|
||||
})
|
||||
})
|
||||
170
packages/core/core-flows/src/order/utils/aggregate-status.ts
Normal file
170
packages/core/core-flows/src/order/utils/aggregate-status.ts
Normal file
@@ -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
|
||||
}
|
||||
@@ -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<OrderDetailDTO> => {
|
||||
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
|
||||
}
|
||||
)
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user