fix(utils,core-flows): subtotal calculation and returns location (#13497)

* fix(utils,core-flows): subtotal calculation and returns location

* changeset

* fix test

* var

* rm extra field from test

* fix original total

* fix partial refunds and pending difference

* fix test

* fix test

* test

* extract to util

* original total and update payment when receive return

* original_subtotal

* default fields

* test

* calculate pending difference

* revert claims test

* pending difference

* creadit line fix

* if
This commit is contained in:
Carlos R. L. Rodrigues
2025-09-18 17:50:40 +02:00
committed by GitHub
parent 4736c58da5
commit 9563ee446f
37 changed files with 746 additions and 204 deletions
+9
View File
@@ -0,0 +1,9 @@
---
"@medusajs/utils": patch
"@medusajs/types": patch
"@medusajs/core-flows": patch
"@medusajs/order": patch
"@medusajs/medusa": patch
---
fix(utils): subtotal calculation discounting returned items
@@ -22,8 +22,10 @@ medusaIntegrationTestRunner({
let returnReason let returnReason
let inventoryItem let inventoryItem
let inventoryItemExtra let inventoryItemExtra
let inventoryItemExtra2
let location let location
let productExtra let productExtra
let productExtra2
const shippingProviderId = "manual_test-provider" const shippingProviderId = "manual_test-provider"
beforeEach(async () => { beforeEach(async () => {
@@ -123,6 +125,31 @@ medusaIntegrationTestRunner({
) )
).data.product ).data.product
productExtra2 = (
await api.post(
"/admin/products",
{
title: "Extra product 2, same price",
shipping_profile_id: shippingProfile.id,
options: [{ title: "size", values: ["large", "small"] }],
variants: [
{
title: "my variant 2",
sku: "variant-sku-2",
options: { size: "large" },
prices: [
{
currency_code: "usd",
amount: 25,
},
],
},
],
},
adminHeaders
)
).data.product
returnReason = ( returnReason = (
await api.post( await api.post(
"/admin/return-reasons", "/admin/return-reasons",
@@ -269,6 +296,10 @@ medusaIntegrationTestRunner({
await api.get(`/admin/inventory-items?sku=variant-sku`, adminHeaders) await api.get(`/admin/inventory-items?sku=variant-sku`, adminHeaders)
).data.inventory_items[0] ).data.inventory_items[0]
inventoryItemExtra2 = (
await api.get(`/admin/inventory-items?sku=variant-sku-2`, adminHeaders)
).data.inventory_items[0]
await api.post( await api.post(
`/admin/inventory-items/${inventoryItemExtra.id}/location-levels`, `/admin/inventory-items/${inventoryItemExtra.id}/location-levels`,
{ {
@@ -278,6 +309,15 @@ medusaIntegrationTestRunner({
adminHeaders adminHeaders
) )
await api.post(
`/admin/inventory-items/${inventoryItemExtra2.id}/location-levels`,
{
location_id: location.id,
stocked_quantity: 2,
},
adminHeaders
)
const remoteLink = container.resolve( const remoteLink = container.resolve(
ContainerRegistrationKeys.REMOTE_LINK ContainerRegistrationKeys.REMOTE_LINK
) )
@@ -323,6 +363,14 @@ medusaIntegrationTestRunner({
inventory_item_id: inventoryItemExtra.id, inventory_item_id: inventoryItemExtra.id,
}, },
}, },
{
[Modules.PRODUCT]: {
variant_id: productExtra2.variants[0].id,
},
[Modules.INVENTORY]: {
inventory_item_id: inventoryItemExtra2.id,
},
},
]) ])
// create reservation for inventory item that is initially on the order // create reservation for inventory item that is initially on the order
@@ -440,6 +488,95 @@ medusaIntegrationTestRunner({
}) })
describe("Exchanges lifecycle", () => { describe("Exchanges lifecycle", () => {
it("test full exchange flow", async () => {
const orderBefore = (
await api.get(`/admin/orders/${order.id}`, adminHeaders)
).data.order
let result = await api.post(
"/admin/exchanges",
{
order_id: order.id,
description: "Test",
},
adminHeaders
)
expect(result.data.exchange.created_by).toEqual(expect.any(String))
const exchangeId = result.data.exchange.id
const item = order.items[0]
result = await api.post(
`/admin/exchanges/${exchangeId}/inbound/items`,
{
items: [
{
id: item.id,
reason_id: returnReason.id,
quantity: 2,
},
],
},
adminHeaders
)
// New Item
result = await api.post(
`/admin/exchanges/${exchangeId}/outbound/items`,
{
items: [
{
variant_id: productExtra2.variants[0].id,
quantity: 2,
},
],
},
adminHeaders
)
result = await api.post(
`/admin/exchanges/${exchangeId}/request`,
{},
adminHeaders
)
const returnId = result.data.exchange.return_id
result = (await api.get(`/admin/orders/${order.id}`, adminHeaders)).data
.order
expect(orderBefore.total).toBe(61)
expect(result.total).toBe(112)
// receive return
await api.post(`/admin/returns/${returnId}/receive`, {}, adminHeaders)
await api.post(
`/admin/returns/${returnId}/receive-items`,
{
items: [
{
id: item.id,
quantity: 2,
},
],
},
adminHeaders
)
await api.post(
`/admin/returns/${returnId}/receive/confirm`,
{},
adminHeaders
)
result = (await api.get(`/admin/orders/${order.id}`, adminHeaders)).data
.order
expect(orderBefore.total).toBe(61)
expect(result.total).toBe(62) // +1 is from taxes of the new item
})
it("Full flow with 2 orders", async () => { it("Full flow with 2 orders", async () => {
let result = await api.post( let result = await api.post(
"/admin/exchanges", "/admin/exchanges",
@@ -6,6 +6,7 @@ import {
AdminStockLocation, AdminStockLocation,
MedusaContainer, MedusaContainer,
} from "@medusajs/types" } from "@medusajs/types"
import { ContainerRegistrationKeys, Modules } from "@medusajs/utils"
import { import {
adminHeaders, adminHeaders,
generatePublishableKey, generatePublishableKey,
@@ -175,6 +176,26 @@ export async function createOrderSeeder({
adminHeaders adminHeaders
) )
const remoteLink = container.resolve(ContainerRegistrationKeys.LINK)
await remoteLink.create([
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: stockLocation.id,
},
},
{
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.INVENTORY]: {
inventory_item_id: inventoryItem.id,
},
},
])
/** /**
* Create shipping options for each shipping profile provided * Create shipping options for each shipping profile provided
*/ */
@@ -32,7 +32,35 @@ medusaIntegrationTestRunner({
adminHeaders adminHeaders
) )
await api.post(`/admin/claims/${claim.id}/request`, {}, adminHeaders) const createdClaim = await api.post(
`/admin/claims/${claim.id}/request`,
{},
adminHeaders
)
const returnOrder = createdClaim.data.return
const returnId = returnOrder.id
await api.post(`/admin/returns/${returnId}/receive`, {}, adminHeaders)
let lineItem = returnOrder.items[0].item
await api.post(
`/admin/returns/${returnId}/receive-items`,
{
items: [
{
id: lineItem.id,
quantity: returnOrder.items[0].quantity,
},
],
},
adminHeaders
)
await api.post(
`/admin/returns/${returnId}/receive/confirm`,
{},
adminHeaders
)
} }
beforeEach(async () => { beforeEach(async () => {
@@ -64,10 +92,6 @@ medusaIntegrationTestRunner({
}) })
describe("with outstanding amount due to claim", () => { describe("with outstanding amount due to claim", () => {
beforeEach(async () => {
await createClaim({ order })
})
it("should capture an authorized payment", async () => { it("should capture an authorized payment", async () => {
const payment = order.payment_collections[0].payments[0] const payment = order.payment_collections[0].payments[0]
@@ -189,6 +213,8 @@ medusaIntegrationTestRunner({
adminHeaders adminHeaders
) )
await createClaim({ order })
const refundReason = ( const refundReason = (
await api.post( await api.post(
`/admin/refund-reasons`, `/admin/refund-reasons`,
@@ -253,6 +279,8 @@ medusaIntegrationTestRunner({
) )
).data.refund_reason ).data.refund_reason
await createClaim({ order })
await api.post( await api.post(
`/admin/payments/${payment.id}/refund`, `/admin/payments/${payment.id}/refund`,
{ {
@@ -311,6 +339,8 @@ medusaIntegrationTestRunner({
adminHeaders adminHeaders
) )
await createClaim({ order })
await api.post( await api.post(
`/admin/payments/${payment.id}/refund`, `/admin/payments/${payment.id}/refund`,
{ amount: 25 }, { amount: 25 },
@@ -1,9 +1,9 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { import {
ContainerRegistrationKeys, ContainerRegistrationKeys,
Modules, Modules,
RuleOperator, RuleOperator,
} from "@medusajs/utils" } from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { import {
adminHeaders, adminHeaders,
createAdminUser, createAdminUser,
@@ -478,6 +478,16 @@ medusaIntegrationTestRunner({
adminHeaders adminHeaders
) )
expect(result.data.order_preview.summary).toEqual(
expect.objectContaining({
transaction_total: 0,
current_order_total: 61,
pending_difference: 11,
paid_total: 0,
refunded_total: 0,
})
)
expect(result.data.order_preview).toEqual( expect(result.data.order_preview).toEqual(
expect.objectContaining({ expect.objectContaining({
id: order.id, id: order.id,
@@ -1,8 +1,18 @@
import { createOrderChangeWorkflow, createOrderWorkflow, } from "@medusajs/core-flows" import {
createOrderChangeWorkflow,
createOrderWorkflow,
} from "@medusajs/core-flows"
import { medusaIntegrationTestRunner } from "@medusajs/test-utils" import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { CreateOrderLineItemDTO, IOrderModuleService, OrderDTO, } from "@medusajs/types" import {
CreateOrderLineItemDTO,
IOrderModuleService,
OrderDTO,
} from "@medusajs/types"
import { Modules, ProductStatus } from "@medusajs/utils" import { Modules, ProductStatus } from "@medusajs/utils"
import { adminHeaders, createAdminUser, } from "../../../helpers/create-admin-user" import {
adminHeaders,
createAdminUser,
} from "../../../helpers/create-admin-user"
jest.setTimeout(50000) jest.setTimeout(50000)
@@ -288,6 +298,7 @@ medusaIntegrationTestRunner({
discount_total: 1.1, discount_total: 1.1,
discount_tax_total: 0.1, discount_tax_total: 0.1,
original_total: 61, original_total: 61,
original_subtotal: 60,
original_tax_total: 1, original_tax_total: 1,
item_total: 50, item_total: 50,
item_subtotal: 50, item_subtotal: 50,
@@ -433,6 +444,7 @@ medusaIntegrationTestRunner({
subtotal: 50, subtotal: 50,
total: 50, total: 50,
original_total: 50, original_total: 50,
original_subtotal: 50,
discount_total: 0, discount_total: 0,
discount_tax_total: 0, discount_tax_total: 0,
discount_subtotal: 0, discount_subtotal: 0,
@@ -490,6 +502,10 @@ medusaIntegrationTestRunner({
precision: 20, precision: 20,
value: "0", value: "0",
}, },
raw_original_subtotal: {
precision: 20,
value: "50",
},
raw_return_dismissed_total: { raw_return_dismissed_total: {
precision: 20, precision: 20,
value: "0", value: "0",
@@ -31,24 +31,43 @@ export const addOrderTransactionStep = createStep(
return new StepResponse(null) return new StepResponse(null)
} }
const existingQuery: any[] = []
for (const trx of trxsData) { for (const trx of trxsData) {
const existing = await service.listOrderTransactions( existingQuery.push({
{ order_id: trx.order_id,
order_id: trx.order_id, reference: trx.reference,
reference: trx.reference, reference_id: trx.reference_id,
reference_id: trx.reference_id, })
}, }
{
select: ["id"],
}
)
if (existing.length) { const existing = await service.listOrderTransactions(
return new StepResponse(null) {
$or: existingQuery,
},
{
select: ["order_id", "reference", "reference_id"],
}
)
const existingSet = new Set<string>(
existing.map(
(trx) => `${trx.order_id}-${trx.reference}-${trx.reference_id}`
)
)
const selectedData: CreateOrderTransactionDTO[] = []
for (const trx of trxsData) {
if (
!existingSet.has(`${trx.order_id}-${trx.reference}-${trx.reference_id}`)
) {
selectedData.push(trx)
} }
} }
const created = await service.addOrderTransactions(trxsData) if (!selectedData.length) {
return new StepResponse(null)
}
const created = await service.addOrderTransactions(selectedData)
return new StepResponse( return new StepResponse(
(Array.isArray(data) (Array.isArray(data)
@@ -9,6 +9,7 @@ import {
MedusaError, MedusaError,
OrderStatus, OrderStatus,
arrayDifference, arrayDifference,
deepFlatMap,
isPresent, isPresent,
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
@@ -21,6 +22,58 @@ export function throwIfOrderIsCancelled({ order }: { order: OrderDTO }) {
} }
} }
export function throwIfManagedItemsNotStockedAtReturnLocation({
order,
orderReturn,
inputItems,
}: {
order: Pick<OrderDTO, "items">
orderReturn: Pick<ReturnDTO, "location_id">
inputItems: OrderWorkflow.CreateOrderFulfillmentWorkflowInput["items"]
}) {
if (!orderReturn?.location_id) {
return
}
const inputItemIds = new Set(inputItems.map((i) => i.id))
const requestedOrderItems = order.items?.filter((oi: any) =>
inputItemIds.has(oi.id)
)
const invalidManagedItems: string[] = []
for (const orderItem of requestedOrderItems ?? []) {
const variant = (orderItem as any)?.variant
if (!variant?.manage_inventory) {
continue
}
let hasStockAtLocation = false
deepFlatMap(
orderItem,
"variant.inventory_items.inventory.location_levels",
({ location_levels }) => {
if (location_levels?.location_id === orderReturn.location_id) {
hasStockAtLocation = true
}
}
)
if (!hasStockAtLocation) {
invalidManagedItems.push(orderItem.id)
}
}
if (invalidManagedItems.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot request item return at location ${
orderReturn.location_id
} for managed inventory items: ${invalidManagedItems.join(", ")}`
)
}
}
export function throwIfItemsDoesNotExistsInOrder({ export function throwIfItemsDoesNotExistsInOrder({
order, order,
inputItems, inputItems,
@@ -6,7 +6,12 @@ import {
OrderWorkflow, OrderWorkflow,
ReturnDTO, ReturnDTO,
} from "@medusajs/framework/types" } from "@medusajs/framework/types"
import { ChangeActionType, OrderChangeStatus } from "@medusajs/framework/utils" import {
ChangeActionType,
OrderChangeStatus,
deepFlatMap,
isDefined,
} from "@medusajs/framework/utils"
import { import {
WorkflowData, WorkflowData,
WorkflowResponse, WorkflowResponse,
@@ -23,6 +28,7 @@ import { updateOrderChangesStep } from "../../steps/update-order-changes"
import { import {
throwIfIsCancelled, throwIfIsCancelled,
throwIfItemsDoesNotExistsInOrder, throwIfItemsDoesNotExistsInOrder,
throwIfManagedItemsNotStockedAtReturnLocation,
throwIfOrderChangeIsNotActive, throwIfOrderChangeIsNotActive,
} from "../../utils/order-validation" } from "../../utils/order-validation"
import { createOrderChangeActionsWorkflow } from "../create-order-change-actions" import { createOrderChangeActionsWorkflow } from "../create-order-change-actions"
@@ -105,6 +111,11 @@ export const orderClaimRequestItemReturnValidationStep = createStep(
throwIfIsCancelled(orderReturn, "Return") throwIfIsCancelled(orderReturn, "Return")
throwIfOrderChangeIsNotActive({ orderChange }) throwIfOrderChangeIsNotActive({ orderChange })
throwIfItemsDoesNotExistsInOrder({ order, inputItems: items }) throwIfItemsDoesNotExistsInOrder({ order, inputItems: items })
throwIfManagedItemsNotStockedAtReturnLocation({
order,
orderReturn,
inputItems: items,
})
} }
) )
@@ -154,34 +165,23 @@ export const orderClaimRequestItemReturnWorkflow = createWorkflow(
}).then(() => { }).then(() => {
return useRemoteQueryStep({ return useRemoteQueryStep({
entry_point: "return", entry_point: "return",
fields: ["id", "status", "order_id", "canceled_at"], fields: ["id", "status", "order_id", "location_id", "canceled_at"],
variables: { id: orderClaim.return_id }, variables: { id: orderClaim.return_id },
list: false, list: false,
throw_if_key_not_found: true, throw_if_key_not_found: true,
}).config({ name: "return-query" }) as ReturnDTO }).config({ name: "return-query" }) as ReturnDTO
}) })
const createdReturn = when({ orderClaim }, ({ orderClaim }) => {
return !orderClaim.return_id
}).then(() => {
return createReturnsStep([
{
order_id: orderClaim.order_id,
claim_id: orderClaim.id,
},
])
})
const orderReturn: ReturnDTO = transform(
{ createdReturn, existingOrderReturn },
({ createdReturn, existingOrderReturn }) => {
return existingOrderReturn ?? (createdReturn?.[0] as ReturnDTO)
}
)
const order: OrderDTO = useRemoteQueryStep({ const order: OrderDTO = useRemoteQueryStep({
entry_point: "orders", entry_point: "orders",
fields: ["id", "status", "items.*"], fields: [
"id",
"status",
"items.*",
"items.variant.manage_inventory",
"items.variant.inventory_items.inventory_item_id",
"items.variant.inventory_items.inventory.location_levels.location_id",
],
variables: { id: orderClaim.order_id }, variables: { id: orderClaim.order_id },
list: false, list: false,
throw_if_key_not_found: true, throw_if_key_not_found: true,
@@ -202,6 +202,51 @@ export const orderClaimRequestItemReturnWorkflow = createWorkflow(
name: "order-change-query", name: "order-change-query",
}) })
const pickItemLocationId = transform(
{ order, input },
({ order, input }) => {
if (input.location_id) {
return input.location_id
}
// pick the first item location
const item = order?.items?.find(
(item) => item.id === input.items[0].id
) as any
let locationId: string | undefined
deepFlatMap(
item,
"variant.inventory_items.inventory.location_levels",
({ location_levels }) => {
if (!locationId && isDefined(location_levels?.location_id)) {
locationId = location_levels.location_id
}
}
)
return locationId
}
)
const createdReturn = when({ orderClaim }, ({ orderClaim }) => {
return !orderClaim.return_id
}).then(() => {
return createReturnsStep([
{
order_id: orderClaim.order_id,
claim_id: orderClaim.id,
location_id: pickItemLocationId,
},
])
})
const orderReturn: ReturnDTO = transform(
{ createdReturn, existingOrderReturn },
({ createdReturn, existingOrderReturn }) => {
return existingOrderReturn ?? (createdReturn?.[0] as ReturnDTO)
}
)
when({ createdReturn }, ({ createdReturn }) => { when({ createdReturn }, ({ createdReturn }) => {
return !!createdReturn?.length return !!createdReturn?.length
}).then(() => { }).then(() => {
@@ -13,8 +13,8 @@ import {
} from "@medusajs/framework/workflows-sdk" } from "@medusajs/framework/workflows-sdk"
import { useRemoteQueryStep } from "../../common" import { useRemoteQueryStep } from "../../common"
import { updatePaymentCollectionStep } from "../../payment-collection" import { updatePaymentCollectionStep } from "../../payment-collection"
import { createOrderPaymentCollectionWorkflow } from "./create-order-payment-collection"
import { cancelPaymentCollectionWorkflow } from "../../payment-collection/workflows/cancel-payment-collection" import { cancelPaymentCollectionWorkflow } from "../../payment-collection/workflows/cancel-payment-collection"
import { createOrderPaymentCollectionWorkflow } from "./create-order-payment-collection"
/** /**
* The details of the order payment collection to create or update. * The details of the order payment collection to create or update.
@@ -65,7 +65,7 @@ export const createOrUpdateOrderPaymentCollectionWorkflow = createWorkflow(
) => { ) => {
const order = useRemoteQueryStep({ const order = useRemoteQueryStep({
entry_point: "order", entry_point: "order",
fields: ["id", "summary", "currency_code", "region_id"], fields: ["id", "summary", "total", "currency_code", "region_id"],
variables: { id: input.order_id }, variables: { id: input.order_id },
throw_if_key_not_found: true, throw_if_key_not_found: true,
list: false, list: false,
@@ -90,7 +90,7 @@ export const createOrderCreditLinesWorkflow = createWorkflow(
) => { ) => {
const orderQuery = useQueryGraphStep({ const orderQuery = useQueryGraphStep({
entity: "orders", entity: "orders",
fields: ["id", "status", "summary"], fields: ["id", "status", "summary", "total"],
filters: { id: input.id }, filters: { id: input.id },
options: { throwIfKeyNotFound: true }, options: { throwIfKeyNotFound: true },
}).config({ name: "get-order" }) }).config({ name: "get-order" })
@@ -5,8 +5,8 @@ import {
createWorkflow, createWorkflow,
transform, transform,
} from "@medusajs/framework/workflows-sdk" } from "@medusajs/framework/workflows-sdk"
import { createRemoteLinkStep, useRemoteQueryStep } from "../../common"
import { createPaymentCollectionsStep } from "../../cart" import { createPaymentCollectionsStep } from "../../cart"
import { createRemoteLinkStep, useRemoteQueryStep } from "../../common"
/** /**
* The details of the payment collection to create. * The details of the payment collection to create.
@@ -27,10 +27,10 @@ export const createOrderPaymentCollectionWorkflowId =
/** /**
* This workflow creates a payment collection for an order. It's used by the * This workflow creates a payment collection for an order. It's used by the
* [Create Payment Collection Admin API Route](https://docs.medusajs.com/api/admin#payment-collections_postpaymentcollections). * [Create Payment Collection Admin API Route](https://docs.medusajs.com/api/admin#payment-collections_postpaymentcollections).
* *
* You can use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around * You can use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around
* creating a payment collection for an order. * creating a payment collection for an order.
* *
* @example * @example
* const { result } = await createOrderPaymentCollectionWorkflow(container) * const { result } = await createOrderPaymentCollectionWorkflow(container)
* .run({ * .run({
@@ -39,19 +39,17 @@ export const createOrderPaymentCollectionWorkflowId =
* amount: 10, * amount: 10,
* } * }
* }) * })
* *
* @summary * @summary
* *
* Create a payment collection for an order. * Create a payment collection for an order.
*/ */
export const createOrderPaymentCollectionWorkflow = createWorkflow( export const createOrderPaymentCollectionWorkflow = createWorkflow(
createOrderPaymentCollectionWorkflowId, createOrderPaymentCollectionWorkflowId,
( (input: WorkflowData<CreateOrderPaymentCollectionWorkflowInput>) => {
input: WorkflowData<CreateOrderPaymentCollectionWorkflowInput>
) => {
const order = useRemoteQueryStep({ const order = useRemoteQueryStep({
entry_point: "order", entry_point: "order",
fields: ["id", "summary", "currency_code", "region_id"], fields: ["id", "summary", "total", "currency_code", "region_id"],
variables: { id: input.order_id }, variables: { id: input.order_id },
throw_if_key_not_found: true, throw_if_key_not_found: true,
list: false, list: false,
@@ -6,14 +6,19 @@ import {
OrderWorkflow, OrderWorkflow,
ReturnDTO, ReturnDTO,
} from "@medusajs/framework/types" } from "@medusajs/framework/types"
import { ChangeActionType, OrderChangeStatus } from "@medusajs/framework/utils"
import { import {
WorkflowData, ChangeActionType,
WorkflowResponse, deepFlatMap,
isDefined,
OrderChangeStatus,
} from "@medusajs/framework/utils"
import {
createStep, createStep,
createWorkflow, createWorkflow,
transform, transform,
when, when,
WorkflowData,
WorkflowResponse,
} from "@medusajs/framework/workflows-sdk" } from "@medusajs/framework/workflows-sdk"
import { useRemoteQueryStep } from "../../../common" import { useRemoteQueryStep } from "../../../common"
import { updateOrderExchangesStep } from "../../steps/exchange/update-order-exchanges" import { updateOrderExchangesStep } from "../../steps/exchange/update-order-exchanges"
@@ -23,6 +28,7 @@ import { updateOrderChangesStep } from "../../steps/update-order-changes"
import { import {
throwIfIsCancelled, throwIfIsCancelled,
throwIfItemsDoesNotExistsInOrder, throwIfItemsDoesNotExistsInOrder,
throwIfManagedItemsNotStockedAtReturnLocation,
throwIfOrderChangeIsNotActive, throwIfOrderChangeIsNotActive,
} from "../../utils/order-validation" } from "../../utils/order-validation"
import { createOrderChangeActionsWorkflow } from "../create-order-change-actions" import { createOrderChangeActionsWorkflow } from "../create-order-change-actions"
@@ -106,6 +112,11 @@ export const exchangeRequestItemReturnValidationStep = createStep(
throwIfIsCancelled(orderReturn, "Return") throwIfIsCancelled(orderReturn, "Return")
throwIfOrderChangeIsNotActive({ orderChange }) throwIfOrderChangeIsNotActive({ orderChange })
throwIfItemsDoesNotExistsInOrder({ order, inputItems: items }) throwIfItemsDoesNotExistsInOrder({ order, inputItems: items })
throwIfManagedItemsNotStockedAtReturnLocation({
order,
orderReturn,
inputItems: items,
})
} }
) )
@@ -144,7 +155,7 @@ export const orderExchangeRequestItemReturnWorkflow = createWorkflow(
): WorkflowResponse<OrderPreviewDTO> { ): WorkflowResponse<OrderPreviewDTO> {
const orderExchange = useRemoteQueryStep({ const orderExchange = useRemoteQueryStep({
entry_point: "order_exchange", entry_point: "order_exchange",
fields: ["id", "order_id", "return_id", "canceled_at"], fields: ["id", "order_id", "return_id", "location_id", "canceled_at"],
variables: { id: input.exchange_id }, variables: { id: input.exchange_id },
list: false, list: false,
throw_if_key_not_found: true, throw_if_key_not_found: true,
@@ -162,27 +173,16 @@ export const orderExchangeRequestItemReturnWorkflow = createWorkflow(
}).config({ name: "return-query" }) as ReturnDTO }).config({ name: "return-query" }) as ReturnDTO
}) })
const createdReturn = when({ orderExchange }, ({ orderExchange }) => {
return !orderExchange.return_id
}).then(() => {
return createReturnsStep([
{
order_id: orderExchange.order_id,
exchange_id: orderExchange.id,
},
])
})
const orderReturn: ReturnDTO = transform(
{ createdReturn, existingOrderReturn, orderExchange },
({ createdReturn, existingOrderReturn, orderExchange }) => {
return existingOrderReturn ?? (createdReturn?.[0] as ReturnDTO)
}
)
const order: OrderDTO = useRemoteQueryStep({ const order: OrderDTO = useRemoteQueryStep({
entry_point: "orders", entry_point: "orders",
fields: ["id", "status", "items.*"], fields: [
"id",
"status",
"items.*",
"items.variant.manage_inventory",
"items.variant.inventory_items.inventory_item_id",
"items.variant.inventory_items.inventory.location_levels.location_id",
],
variables: { id: orderExchange.order_id }, variables: { id: orderExchange.order_id },
list: false, list: false,
throw_if_key_not_found: true, throw_if_key_not_found: true,
@@ -204,6 +204,51 @@ export const orderExchangeRequestItemReturnWorkflow = createWorkflow(
status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED],
}) })
const pickItemLocationId = transform(
{ order, input },
({ order, input }) => {
if (input.location_id) {
return input.location_id
}
// pick the first item location
const item = order?.items?.find(
(item) => item.id === input.items[0].id
) as any
let locationId: string | undefined
deepFlatMap(
item,
"variant.inventory_items.inventory.location_levels",
({ location_levels }) => {
if (!locationId && isDefined(location_levels?.location_id)) {
locationId = location_levels.location_id
}
}
)
return locationId
}
)
const createdReturn = when({ orderExchange }, ({ orderExchange }) => {
return !orderExchange.return_id
}).then(() => {
return createReturnsStep([
{
order_id: orderExchange.order_id,
location_id: pickItemLocationId,
exchange_id: orderExchange.id,
},
])
})
const orderReturn: ReturnDTO = transform(
{ createdReturn, existingOrderReturn, orderExchange },
({ createdReturn, existingOrderReturn, orderExchange }) => {
return existingOrderReturn ?? (createdReturn?.[0] as ReturnDTO)
}
)
when({ createdReturn }, ({ createdReturn }) => { when({ createdReturn }, ({ createdReturn }) => {
return !!createdReturn?.length return !!createdReturn?.length
}).then(() => { }).then(() => {
@@ -234,6 +234,7 @@ export const markOrderFulfillmentAsDeliveredWorkflow = createWorkflow(
fields: [ fields: [
"id", "id",
"summary", "summary",
"total",
"currency_code", "currency_code",
"region_id", "region_id",
"fulfillments.id", "fulfillments.id",
@@ -42,7 +42,7 @@ export const createOrderRefundCreditLinesWorkflow = createWorkflow(
) { ) {
const orderQuery = useQueryGraphStep({ const orderQuery = useQueryGraphStep({
entity: "orders", entity: "orders",
fields: ["id", "status", "summary", "payment_collections.id"], fields: ["id", "status", "summary", "total", "payment_collections.id"],
filters: { id: input.order_id }, filters: { id: input.order_id },
options: { throwIfKeyNotFound: true }, options: { throwIfKeyNotFound: true },
}).config({ name: "get-order" }) }).config({ name: "get-order" })
@@ -29,6 +29,7 @@ export const refundCapturedPaymentsWorkflow = createWorkflow(
"id", "id",
"status", "status",
"summary", "summary",
"total",
"payment_collections.payments.id", "payment_collections.payments.id",
"payment_collections.payments.amount", "payment_collections.payments.amount",
"payment_collections.payments.refunds.id", "payment_collections.payments.refunds.id",
@@ -34,6 +34,7 @@ import {
throwIfIsCancelled, throwIfIsCancelled,
throwIfOrderChangeIsNotActive, throwIfOrderChangeIsNotActive,
} from "../../utils/order-validation" } from "../../utils/order-validation"
import { createOrUpdateOrderPaymentCollectionWorkflow } from "../create-or-update-order-payment-collection"
/** /**
* The data to validate that a return receival can be confirmed. * The data to validate that a return receival can be confirmed.
@@ -56,14 +57,14 @@ export type ConfirmReceiveReturnValidationStepInput = {
/** /**
* This step validates that a return receival can be confirmed. * This step validates that a return receival can be confirmed.
* If the order or return is canceled, or the order change is not active, the step will throw an error. * If the order or return is canceled, or the order change is not active, the step will throw an error.
* *
* :::note * :::note
* *
* You can retrieve an order, return, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query), * You can retrieve an order, return, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep). * or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
* *
* ::: * :::
* *
* @example * @example
* const data = confirmReceiveReturnValidationStep({ * const data = confirmReceiveReturnValidationStep({
* order: { * order: {
@@ -182,10 +183,10 @@ export const confirmReturnReceiveWorkflowId = "confirm-return-receive"
/** /**
* This workflow confirms a return receival request. It's used by the * This workflow confirms a return receival request. It's used by the
* [Confirm Return Receival Admin API Route](https://docs.medusajs.com/api/admin#returns_postreturnsidreceiveconfirm). * [Confirm Return Receival Admin API Route](https://docs.medusajs.com/api/admin#returns_postreturnsidreceiveconfirm).
* *
* You can use this workflow within your customizations or your own custom workflows, allowing you * You can use this workflow within your customizations or your own custom workflows, allowing you
* to confirm a return receival in your custom flow. * to confirm a return receival in your custom flow.
* *
* @example * @example
* const { result } = await confirmReturnReceiveWorkflow(container) * const { result } = await confirmReturnReceiveWorkflow(container)
* .run({ * .run({
@@ -193,9 +194,9 @@ export const confirmReturnReceiveWorkflowId = "confirm-return-receive"
* return_id: "return_123", * return_id: "return_123",
* } * }
* }) * })
* *
* @summary * @summary
* *
* Confirm a return receival request. * Confirm a return receival request.
*/ */
export const confirmReturnReceiveWorkflow = createWorkflow( export const confirmReturnReceiveWorkflow = createWorkflow(
@@ -363,7 +364,15 @@ export const confirmReturnReceiveWorkflow = createWorkflow(
orderId: order.id, orderId: order.id,
confirmed_by: input.confirmed_by, confirmed_by: input.confirmed_by,
}), }),
adjustInventoryLevelsStep(inventoryAdjustment), adjustInventoryLevelsStep(inventoryAdjustment)
)
parallelize(
createOrUpdateOrderPaymentCollectionWorkflow.runAsStep({
input: {
order_id: order.id,
},
}),
emitEventStep({ emitEventStep({
eventName: OrderWorkflowEvents.RETURN_RECEIVED, eventName: OrderWorkflowEvents.RETURN_RECEIVED,
data: { data: {
@@ -1,13 +1,13 @@
import { import {
AdditionalData,
BigNumberInput, BigNumberInput,
CreateOrderShippingMethodDTO, CreateOrderShippingMethodDTO,
FulfillmentWorkflow, FulfillmentWorkflow,
OrderDTO, OrderDTO,
ReturnDTO,
OrderWorkflow, OrderWorkflow,
ReturnDTO,
ShippingOptionDTO, ShippingOptionDTO,
WithCalculatedPrice, WithCalculatedPrice,
AdditionalData,
} from "@medusajs/framework/types" } from "@medusajs/framework/types"
import { import {
MathBN, MathBN,
@@ -25,6 +25,7 @@ import {
parallelize, parallelize,
transform, transform,
} from "@medusajs/framework/workflows-sdk" } from "@medusajs/framework/workflows-sdk"
import { pricingContextResult } from "../../../cart/utils/schemas"
import { import {
createRemoteLinkStep, createRemoteLinkStep,
emitEventStep, emitEventStep,
@@ -38,7 +39,6 @@ import {
throwIfOrderIsCancelled, throwIfOrderIsCancelled,
} from "../../utils/order-validation" } from "../../utils/order-validation"
import { validateReturnReasons } from "../../utils/validate-return-reason" import { validateReturnReasons } from "../../utils/validate-return-reason"
import { pricingContextResult } from "../../../cart/utils/schemas"
function prepareShippingMethodData({ function prepareShippingMethodData({
orderId, orderId,
@@ -311,11 +311,11 @@ export const createAndCompleteReturnOrderWorkflowId =
* @summary * @summary
* *
* Create and complete a return for an order. * Create and complete a return for an order.
* *
* @property hooks.setPricingContext - This hook is executed before the return's shipping method is created. You can consume this hook to return any custom context useful for the prices retrieval of the shipping method's option. * @property hooks.setPricingContext - This hook is executed before the return's shipping method is created. You can consume this hook to return any custom context useful for the prices retrieval of the shipping method's option.
* *
* For example, assuming you have the following custom pricing rule: * For example, assuming you have the following custom pricing rule:
* *
* ```json * ```json
* { * {
* "attribute": "location_id", * "attribute": "location_id",
@@ -323,13 +323,13 @@ export const createAndCompleteReturnOrderWorkflowId =
* "value": "sloc_123", * "value": "sloc_123",
* } * }
* ``` * ```
* *
* You can consume the `setPricingContext` hook to add the `location_id` context to the prices calculation: * You can consume the `setPricingContext` hook to add the `location_id` context to the prices calculation:
* *
* ```ts * ```ts
* import { createAndCompleteReturnOrderWorkflow } from "@medusajs/medusa/core-flows"; * import { createAndCompleteReturnOrderWorkflow } from "@medusajs/medusa/core-flows";
* import { StepResponse } from "@medusajs/workflows-sdk"; * import { StepResponse } from "@medusajs/workflows-sdk";
* *
* createAndCompleteReturnOrderWorkflow.hooks.setPricingContext(( * createAndCompleteReturnOrderWorkflow.hooks.setPricingContext((
* { order, additional_data }, { container } * { order, additional_data }, { container }
* ) => { * ) => {
@@ -338,13 +338,13 @@ export const createAndCompleteReturnOrderWorkflowId =
* }); * });
* }); * });
* ``` * ```
* *
* The price of the shipping method's option will now be retrieved using the context you return. * The price of the shipping method's option will now be retrieved using the context you return.
* *
* :::note * :::note
* *
* Learn more about prices calculation context in the [Prices Calculation](https://docs.medusajs.com/resources/commerce-modules/pricing/price-calculation) documentation. * Learn more about prices calculation context in the [Prices Calculation](https://docs.medusajs.com/resources/commerce-modules/pricing/price-calculation) documentation.
* *
* ::: * :::
*/ */
export const createAndCompleteReturnOrderWorkflow = createWorkflow( export const createAndCompleteReturnOrderWorkflow = createWorkflow(
@@ -423,6 +423,7 @@ export const createAndCompleteReturnOrderWorkflow = createWorkflow(
const returnCreated = createCompleteReturnStep({ const returnCreated = createCompleteReturnStep({
order_id: input.order_id, order_id: input.order_id,
location_id: input.location_id,
items: input.items, items: input.items,
shipping_method: shippingMethodData, shipping_method: shippingMethodData,
created_by: input.created_by, created_by: input.created_by,
@@ -146,31 +146,33 @@ export const refundPaymentWorkflow = createWorkflow(
const order = useRemoteQueryStep({ const order = useRemoteQueryStep({
entry_point: "order", entry_point: "order",
fields: ["id", "summary", "currency_code", "region_id"], fields: ["id", "summary", "total", "currency_code", "region_id"],
variables: { id: orderPaymentCollection.order.id }, variables: { id: orderPaymentCollection.order.id },
throw_if_key_not_found: true, throw_if_key_not_found: true,
list: false, list: false,
}).config({ name: "order" }) }).config({ name: "order" })
validateRefundStep({ order, payment, amount: input.amount }) validateRefundStep({ order, payment, amount: input.amount })
refundPaymentStep(input) const refundPayment = refundPaymentStep(input)
when({ orderPaymentCollection }, ({ orderPaymentCollection }) => { when({ orderPaymentCollection }, ({ orderPaymentCollection }) => {
return !!orderPaymentCollection?.order?.id return !!orderPaymentCollection?.order?.id
}).then(() => { }).then(() => {
const orderTransactionData = transform( const orderTransactionData = transform(
{ input, payment, orderPaymentCollection }, { input, refundPayment, orderPaymentCollection, order },
({ input, payment, orderPaymentCollection }) => { ({ input, refundPayment, orderPaymentCollection, order }) => {
return { return refundPayment.refunds?.map((refund) => {
order_id: orderPaymentCollection.order.id, return {
amount: MathBN.mult( order_id: orderPaymentCollection.order.id,
input.amount ?? payment.raw_amount ?? payment.amount, amount: MathBN.mult(
-1 input.amount ?? refund.raw_amount ?? refund.amount,
), -1
currency_code: payment.currency_code ?? order.currency_code, ),
reference_id: payment.id, currency_code: refundPayment.currency_code ?? order.currency_code,
reference: "refund", reference_id: refund.id,
} reference: "refund",
}
})
} }
) )
+1 -1
View File
@@ -1578,7 +1578,7 @@ export interface CreateOrderReturnDTO extends BaseOrderBundledActionsDTO {
/** /**
* The ID of the location to return the items to. * The ID of the location to return the items to.
*/ */
location_id?: string location_id?: string | null
/** /**
* The items of the return. * The items of the return.
@@ -73,6 +73,10 @@ export interface OrderExchangeRequestItemReturnWorkflowInput {
* The ID of the return that's associated with the exchange. * The ID of the return that's associated with the exchange.
*/ */
return_id: string return_id: string
/**
* The ID of the location to return the items to.
*/
location_id?: string
/** /**
* The ID of the exchange to add the inbound items to. * The ID of the exchange to add the inbound items to.
*/ */
@@ -95,6 +99,10 @@ export interface OrderClaimRequestItemReturnWorkflowInput {
* The ID of the return that's associated with the claim. * The ID of the return that's associated with the claim.
*/ */
return_id: string return_id: string
/**
* The ID of the location to return the items to.
*/
location_id?: string
/** /**
* The ID of the claim to add the items to. * The ID of the claim to add the items to.
*/ */
@@ -124,11 +132,11 @@ export interface DeleteRequestItemReturnWorkflowInput {
/** /**
* The details of the received item to be removed. * The details of the received item to be removed.
* *
* @property return_id - The ID of the return to remove the item from. * @property return_id - The ID of the return to remove the item from.
* @property action_id - The ID of the action associated with the item to remove. * @property action_id - The ID of the action associated with the item to remove.
* Every item has an `actions` property, whose value is an array of actions. * Every item has an `actions` property, whose value is an array of actions.
* You can find an action with the name `RECEIVE_RETURN_ITEM` using its `action` property, * You can find an action with the name `RECEIVE_RETURN_ITEM` using its `action` property,
* and use the value of its `id` property. * and use the value of its `id` property.
*/ */
export interface DeleteRequestItemReceiveReturnWorkflowInput export interface DeleteRequestItemReceiveReturnWorkflowInput
@@ -41,6 +41,7 @@ describe("Total calculation", function () {
subtotal: 60, subtotal: 60,
total: 66, total: 66,
original_total: 66, original_total: 66,
original_subtotal: 60,
discount_total: 0, discount_total: 0,
discount_subtotal: 0, discount_subtotal: 0,
discount_tax_total: 0, discount_tax_total: 0,
@@ -60,6 +61,7 @@ describe("Total calculation", function () {
subtotal: 5, subtotal: 5,
total: 7.5, total: 7.5,
original_total: 7.5, original_total: 7.5,
original_subtotal: 5,
discount_total: 0, discount_total: 0,
discount_subtotal: 0, discount_subtotal: 0,
discount_tax_total: 0, discount_tax_total: 0,
@@ -78,6 +80,7 @@ describe("Total calculation", function () {
item_tax_total: 8.5, item_tax_total: 8.5,
item_discount_total: 0, item_discount_total: 0,
original_total: 73.5, original_total: 73.5,
original_subtotal: 65,
original_tax_total: 8.5, original_tax_total: 8.5,
original_item_subtotal: 65, original_item_subtotal: 65,
original_item_total: 73.5, original_item_total: 73.5,
@@ -132,6 +135,7 @@ describe("Total calculation", function () {
subtotal: 100, subtotal: 100,
total: 99, total: 99,
original_total: 110, original_total: 110,
original_subtotal: 100,
discount_total: 11, discount_total: 11,
discount_subtotal: 10, discount_subtotal: 10,
discount_tax_total: 1, discount_tax_total: 1,
@@ -146,6 +150,7 @@ describe("Total calculation", function () {
discount_subtotal: 10, discount_subtotal: 10,
discount_tax_total: 1, discount_tax_total: 1,
original_total: 110, original_total: 110,
original_subtotal: 100,
original_tax_total: 10, original_tax_total: 10,
item_total: 99, item_total: 99,
item_subtotal: 100, item_subtotal: 100,
@@ -253,6 +258,7 @@ describe("Total calculation", function () {
subtotal: 90, subtotal: 90,
total: 89.1, total: 89.1,
original_total: 99, original_total: 99,
original_subtotal: 90,
discount_total: 9.9, discount_total: 9.9,
discount_subtotal: 9, discount_subtotal: 9,
discount_tax_total: 0.9, discount_tax_total: 0.9,
@@ -280,6 +286,7 @@ describe("Total calculation", function () {
subtotal: 9, subtotal: 9,
total: 6.6, total: 6.6,
original_total: 9.9, original_total: 9.9,
original_subtotal: 9,
discount_total: 3.3, discount_total: 3.3,
discount_subtotal: 3, discount_subtotal: 3,
discount_tax_total: 0.3, discount_tax_total: 0.3,
@@ -308,6 +315,7 @@ describe("Total calculation", function () {
subtotal: 90, subtotal: 90,
total: 89.1, total: 89.1,
original_total: 99, original_total: 99,
original_subtotal: 90,
discount_total: 9.9, discount_total: 9.9,
discount_subtotal: 9, discount_subtotal: 9,
discount_tax_total: 0.9, discount_tax_total: 0.9,
@@ -334,6 +342,7 @@ describe("Total calculation", function () {
subtotal: 9, subtotal: 9,
total: 6.6, total: 6.6,
original_total: 9.9, original_total: 9.9,
original_subtotal: 9,
discount_total: 3.3, discount_total: 3.3,
discount_subtotal: 3, discount_subtotal: 3,
discount_tax_total: 0.3, discount_tax_total: 0.3,
@@ -348,6 +357,7 @@ describe("Total calculation", function () {
discount_subtotal: 24, discount_subtotal: 24,
discount_tax_total: 2.4, discount_tax_total: 2.4,
original_total: 217.8, original_total: 217.8,
original_subtotal: 198,
original_tax_total: 19.8, original_tax_total: 19.8,
item_total: 95.7, item_total: 95.7,
item_subtotal: 99, item_subtotal: 99,
@@ -431,6 +441,7 @@ describe("Total calculation", function () {
subtotal: 90.9090909090909, subtotal: 90.9090909090909,
total: 100, total: 100,
original_total: 100, original_total: 100,
original_subtotal: 90.9090909090909,
discount_total: 0, discount_total: 0,
discount_tax_total: 0, discount_tax_total: 0,
tax_total: 9.090909090909092, tax_total: 9.090909090909092,
@@ -444,6 +455,7 @@ describe("Total calculation", function () {
discount_total: 0, discount_total: 0,
discount_tax_total: 0, discount_tax_total: 0,
original_total: 100, original_total: 100,
original_subtotal: 90.9090909090909,
original_tax_total: 9.090909090909092, original_tax_total: 9.090909090909092,
item_total: 100, item_total: 100,
item_subtotal: 90.9090909090909, item_subtotal: 90.9090909090909,
@@ -478,6 +490,7 @@ describe("Total calculation", function () {
discount_tax_total: 0, discount_tax_total: 0,
tax_total: 10, tax_total: 10,
original_tax_total: 10, original_tax_total: 10,
original_subtotal: 100,
}, },
], ],
total: 110, total: 110,
@@ -487,6 +500,7 @@ describe("Total calculation", function () {
discount_total: 0, discount_total: 0,
discount_tax_total: 0, discount_tax_total: 0,
original_total: 110, original_total: 110,
original_subtotal: 100,
original_tax_total: 10, original_tax_total: 10,
item_total: 110, item_total: 110,
item_subtotal: 100, item_subtotal: 100,
@@ -516,6 +530,7 @@ describe("Total calculation", function () {
], ],
subtotal: 90.9090909090909, subtotal: 90.9090909090909,
total: 100, total: 100,
original_subtotal: 90.9090909090909,
original_total: 100, original_total: 100,
discount_total: 0, discount_total: 0,
discount_tax_total: 0, discount_tax_total: 0,
@@ -537,6 +552,7 @@ describe("Total calculation", function () {
subtotal: 100, subtotal: 100,
total: 110, total: 110,
original_total: 110, original_total: 110,
original_subtotal: 100,
discount_total: 0, discount_total: 0,
discount_tax_total: 0, discount_tax_total: 0,
tax_total: 10, tax_total: 10,
@@ -550,6 +566,7 @@ describe("Total calculation", function () {
discount_total: 0, discount_total: 0,
discount_tax_total: 0, discount_tax_total: 0,
original_total: 210, original_total: 210,
original_subtotal: 190.9090909090909,
original_tax_total: 19.09090909090909, original_tax_total: 19.09090909090909,
item_total: 210, item_total: 210,
item_subtotal: 190.9090909090909, item_subtotal: 190.9090909090909,
@@ -606,6 +623,7 @@ describe("Total calculation", function () {
is_tax_inclusive: true, is_tax_inclusive: true,
original_total: 120, original_total: 120,
original_subtotal: 100,
original_tax_total: 20, original_tax_total: 20,
discount_subtotal: 8.333333333333334, discount_subtotal: 8.333333333333334,
@@ -637,6 +655,7 @@ describe("Total calculation", function () {
original_item_total: 120, original_item_total: 120,
original_tax_total: 20, original_tax_total: 20,
original_total: 120, original_total: 120,
original_subtotal: 100,
discount_subtotal: 8.333333333333334, discount_subtotal: 8.333333333333334,
discount_tax_total: 1.6666666666666667, discount_tax_total: 1.6666666666666667,
@@ -712,6 +731,7 @@ describe("Total calculation", function () {
subtotal: 100, subtotal: 100,
total: 88, total: 88,
original_total: 110, original_total: 110,
original_subtotal: 100,
discount_total: 22, discount_total: 22,
discount_subtotal: 20, discount_subtotal: 20,
discount_tax_total: 2, discount_tax_total: 2,
@@ -739,6 +759,7 @@ describe("Total calculation", function () {
subtotal: 25, subtotal: 25,
total: 25.3, total: 25.3,
original_total: 27.5, original_total: 27.5,
original_subtotal: 25,
discount_total: 2.2, discount_total: 2.2,
discount_subtotal: 2, discount_subtotal: 2,
discount_tax_total: 0.2, discount_tax_total: 0.2,
@@ -753,6 +774,7 @@ describe("Total calculation", function () {
discount_subtotal: 22, discount_subtotal: 22,
discount_tax_total: 2.2, discount_tax_total: 2.2,
original_total: 137.5, original_total: 137.5,
original_subtotal: 125,
original_tax_total: 12.5, original_tax_total: 12.5,
item_total: 88, item_total: 88,
item_subtotal: 100, item_subtotal: 100,
@@ -836,8 +858,8 @@ describe("Total calculation", function () {
tax_lines: [ tax_lines: [
{ {
rate: 10, rate: 10,
total: 8, total: 0,
subtotal: 10, subtotal: 0,
}, },
], ],
adjustments: [ adjustments: [
@@ -847,14 +869,15 @@ describe("Total calculation", function () {
total: 22, total: 22,
}, },
], ],
subtotal: 100, subtotal: 0,
total: 88, total: 0,
original_total: 110, original_total: 0,
discount_total: 22, original_subtotal: 0,
discount_subtotal: 20, discount_total: 0,
discount_tax_total: 2, discount_subtotal: 0,
tax_total: 8, discount_tax_total: 0,
original_tax_total: 10, tax_total: 0,
original_tax_total: 0,
refundable_total_per_unit: 0, refundable_total_per_unit: 0,
refundable_total: 0, refundable_total: 0,
fulfilled_total: 88, fulfilled_total: 88,
@@ -865,21 +888,22 @@ describe("Total calculation", function () {
write_off_total: 44, write_off_total: 44,
}, },
], ],
total: 48, total: -40,
subtotal: 100, subtotal: 0,
tax_total: 8, tax_total: 0,
discount_total: 22, discount_total: 0,
discount_subtotal: 20, discount_subtotal: 0,
discount_tax_total: 2, discount_tax_total: 0,
original_total: 110, original_total: 0,
original_tax_total: 10, original_subtotal: 0,
item_total: 88, original_tax_total: 0,
item_subtotal: 100, item_total: 0,
item_tax_total: 8, item_subtotal: 0,
item_discount_total: 22, item_tax_total: 0,
original_item_total: 110, item_discount_total: 0,
original_item_subtotal: 100, original_item_total: 0,
original_item_tax_total: 10, original_item_subtotal: 0,
original_item_tax_total: 0,
fulfilled_total: 88, fulfilled_total: 88,
shipped_total: 88, shipped_total: 88,
return_requested_total: 0, return_requested_total: 0,
@@ -923,6 +947,7 @@ describe("Total calculation", function () {
{ {
rate: 19, rate: 19,
subtotal: 22.61, subtotal: 22.61,
total: 0,
}, },
], ],
adjustments: [ adjustments: [
@@ -935,6 +960,7 @@ describe("Total calculation", function () {
subtotal: 119, subtotal: 119,
total: 0, total: 0,
original_total: 141.61, original_total: 141.61,
original_subtotal: 119,
discount_total: 141.61, discount_total: 141.61,
discount_subtotal: 119, discount_subtotal: 119,
discount_tax_total: 22.61, discount_tax_total: 22.61,
@@ -949,6 +975,7 @@ describe("Total calculation", function () {
discount_subtotal: 119, discount_subtotal: 119,
discount_tax_total: 22.61, discount_tax_total: 22.61,
original_total: 141.61, original_total: 141.61,
original_subtotal: 119,
original_tax_total: 22.61, original_tax_total: 22.61,
item_total: 0, item_total: 0,
item_subtotal: 119, item_subtotal: 119,
@@ -1026,6 +1053,7 @@ describe("Total calculation", function () {
discount_total: 0, discount_total: 0,
is_tax_inclusive: true, is_tax_inclusive: true,
original_tax_total: 19, original_tax_total: 19,
original_subtotal: 100,
original_total: 119, original_total: 119,
quantity: 1, quantity: 1,
subtotal: 100, subtotal: 100,
@@ -1045,6 +1073,7 @@ describe("Total calculation", function () {
original_item_tax_total: 19, original_item_tax_total: 19,
original_item_total: 119, original_item_total: 119,
original_tax_total: 19, original_tax_total: 19,
original_subtotal: 100,
original_total: 119, original_total: 119,
subtotal: 100, subtotal: 100,
tax_total: 19, tax_total: 19,
@@ -1069,6 +1098,7 @@ describe("Total calculation", function () {
discount_total: 0, discount_total: 0,
is_tax_inclusive: false, is_tax_inclusive: false,
original_tax_total: 22.61, original_tax_total: 22.61,
original_subtotal: 119,
original_total: 141.61, original_total: 141.61,
quantity: 1, quantity: 1,
subtotal: 119, subtotal: 119,
@@ -1088,6 +1118,7 @@ describe("Total calculation", function () {
original_item_tax_total: 22.61, original_item_tax_total: 22.61,
original_item_total: 141.61, original_item_total: 141.61,
original_tax_total: 22.61, original_tax_total: 22.61,
original_subtotal: 119,
original_total: 141.61, original_total: 141.61,
subtotal: 119, subtotal: 119,
tax_total: 22.61, tax_total: 22.61,
@@ -1113,6 +1144,7 @@ describe("Total calculation", function () {
is_tax_inclusive: true, is_tax_inclusive: true,
original_tax_total: 19, original_tax_total: 19,
original_total: 119, original_total: 119,
original_subtotal: 100,
quantity: 1, quantity: 1,
subtotal: 100, subtotal: 100,
tax_lines: [ tax_lines: [
@@ -1132,6 +1164,7 @@ describe("Total calculation", function () {
discount_total: 0, discount_total: 0,
is_tax_inclusive: false, is_tax_inclusive: false,
original_tax_total: 22.61, original_tax_total: 22.61,
original_subtotal: 119,
original_total: 141.61, original_total: 141.61,
quantity: 1, quantity: 1,
subtotal: 119, subtotal: 119,
@@ -1150,6 +1183,7 @@ describe("Total calculation", function () {
original_item_subtotal: 219, original_item_subtotal: 219,
original_item_tax_total: 41.61, original_item_tax_total: 41.61,
original_item_total: 260.61, original_item_total: 260.61,
original_subtotal: 219,
original_tax_total: 41.61, original_tax_total: 41.61,
original_total: 260.61, original_total: 260.61,
subtotal: 219, subtotal: 219,
@@ -1206,6 +1240,7 @@ describe("Total calculation", function () {
discount_total: 119, discount_total: 119,
is_tax_inclusive: true, is_tax_inclusive: true,
original_tax_total: 19, original_tax_total: 19,
original_subtotal: 100,
original_total: 119, original_total: 119,
quantity: 1, quantity: 1,
subtotal: 100, subtotal: 100,
@@ -1213,6 +1248,7 @@ describe("Total calculation", function () {
{ {
rate: 19, rate: 19,
subtotal: 19, subtotal: 19,
total: 0,
}, },
], ],
tax_total: 0, tax_total: 0,
@@ -1224,6 +1260,7 @@ describe("Total calculation", function () {
original_item_tax_total: 19, original_item_tax_total: 19,
original_item_total: 119, original_item_total: 119,
original_tax_total: 19, original_tax_total: 19,
original_subtotal: 100,
original_total: 119, original_total: 119,
subtotal: 100, subtotal: 100,
tax_total: 0, tax_total: 0,
@@ -4,9 +4,11 @@ import { BigNumber } from "../big-number"
import { MathBN } from "../math" import { MathBN } from "../math"
export function calculateAdjustmentTotal({ export function calculateAdjustmentTotal({
item,
adjustments, adjustments,
taxRate, taxRate,
}: { }: {
item?: { quantity: BigNumberInput }
adjustments: Pick<AdjustmentLineDTO, "amount" | "is_tax_inclusive">[] adjustments: Pick<AdjustmentLineDTO, "amount" | "is_tax_inclusive">[]
taxRate?: BigNumberInput taxRate?: BigNumberInput
}) { }) {
@@ -40,9 +42,24 @@ export function calculateAdjustmentTotal({
adj["total"] = new BigNumber(adjustmentsTotal) adj["total"] = new BigNumber(adjustmentsTotal)
} }
const quantity = item?.quantity || MathBN.convert(1)
let adjustmentPerItem = MathBN.convert(0)
let adjustmentSubtotalPerItem = MathBN.convert(0)
let adjustmentTaxTotalPerItem = MathBN.convert(0)
if (!MathBN.eq(quantity, 0)) {
adjustmentPerItem = MathBN.div(adjustmentsTotal, quantity)
adjustmentSubtotalPerItem = MathBN.div(adjustmentsSubtotal, quantity)
adjustmentTaxTotalPerItem = MathBN.div(adjustmentsTaxTotal, quantity)
}
return { return {
adjustmentsTotal, adjustmentsTotal,
adjustmentsSubtotal, adjustmentsSubtotal,
adjustmentsTaxTotal, adjustmentsTaxTotal,
adjustmentPerItem,
adjustmentSubtotalPerItem,
adjustmentTaxTotalPerItem,
} }
} }
+24 -1
View File
@@ -101,7 +101,8 @@ export function decorateCartTotals(
let shippingDiscountTotal = MathBN.convert(0) let shippingDiscountTotal = MathBN.convert(0)
const cartItems = items.map((item, index) => { const cartItems = items.map((item, index) => {
const itemTotals = Object.assign(item, itemsTotals[item.id ?? index] ?? {}) const rawTotals = itemsTotals[item.id ?? index] ?? {}
const itemTotals = Object.assign(item, rawTotals)
const itemSubtotal = itemTotals.subtotal const itemSubtotal = itemTotals.subtotal
const itemTotal = MathBN.convert(itemTotals.total) const itemTotal = MathBN.convert(itemTotals.total)
@@ -208,6 +209,10 @@ export function decorateCartTotals(
// TODO: Gift Card calculations // TODO: Gift Card calculations
const originalTotal = MathBN.add(itemsOriginalTotal, shippingOriginalTotal) const originalTotal = MathBN.add(itemsOriginalTotal, shippingOriginalTotal)
const originalSubtotal = MathBN.add(
itemsOriginalSubtotal,
shippingOriginalSubtotal
)
// TODO: subtract (cart.gift_card_total + cart.gift_card_tax_total) // TODO: subtract (cart.gift_card_total + cart.gift_card_tax_total)
const tempTotal = MathBN.add(subtotal, taxTotal) const tempTotal = MathBN.add(subtotal, taxTotal)
@@ -231,6 +236,7 @@ export function decorateCartTotals(
// cart.gift_card_tax_total = giftCardTotal.tax_total || 0 // cart.gift_card_tax_total = giftCardTotal.tax_total || 0
cart.original_total = new BigNumber(originalTotal) cart.original_total = new BigNumber(originalTotal)
cart.original_subtotal = new BigNumber(originalSubtotal)
cart.original_tax_total = new BigNumber(originalTaxTotal) cart.original_tax_total = new BigNumber(originalTaxTotal)
// cart.original_gift_card_total = // cart.original_gift_card_total =
@@ -264,5 +270,22 @@ export function decorateCartTotals(
cart.original_shipping_total = new BigNumber(shippingOriginalTotal) cart.original_shipping_total = new BigNumber(shippingOriginalTotal)
} }
// Calculate pending return total
if (cart.summary) {
const pendingReturnTotal = MathBN.sum(
0,
...(cart.items?.map((item) => item.return_requested_total ?? 0) ?? [0])
)
const pendingDifference = new BigNumber(
MathBN.sub(
MathBN.sub(cart.total, pendingReturnTotal),
cart.summary?.transaction_total ?? 0
)
)
cart.summary.pending_difference = pendingDifference
}
return cart return cart
} }
@@ -33,6 +33,7 @@ export interface GetItemTotalOutput {
unit_price: BigNumber unit_price: BigNumber
subtotal: BigNumber subtotal: BigNumber
original_subtotal: BigNumber
total: BigNumber total: BigNumber
original_total: BigNumber original_total: BigNumber
@@ -76,8 +77,7 @@ export function getLineItemsTotals(
function setRefundableTotal( function setRefundableTotal(
item: GetItemTotalInput, item: GetItemTotalInput,
discountsTotal: BigNumberInput, discountsTotal: BigNumberInput,
totals: GetItemTotalOutput, totals: GetItemTotalOutput
context: GetLineItemsTotalsContext
) { ) {
const itemDetail = item.detail! const itemDetail = item.detail!
const totalReturnedQuantity = MathBN.sum( const totalReturnedQuantity = MathBN.sum(
@@ -127,54 +127,107 @@ function getLineItemTotals(
? MathBN.div(totalItemPrice, MathBN.add(1, sumTaxRate)) ? MathBN.div(totalItemPrice, MathBN.add(1, sumTaxRate))
: totalItemPrice : totalItemPrice
// Proportional discounts to current quantity and compute taxes on the current net amount
const { const {
adjustmentsTotal: discountsTotal, adjustmentsTotal: discountsTotal,
adjustmentsSubtotal: discountsSubtotal, adjustmentsSubtotal: discountsSubtotalFull,
adjustmentsTaxTotal: discountTaxTotal, adjustmentSubtotalPerItem,
} = calculateAdjustmentTotal({ } = calculateAdjustmentTotal({
item,
adjustments: item.adjustments || [], adjustments: item.adjustments || [],
taxRate: sumTaxRate, taxRate: sumTaxRate,
}) })
const itemDetail = item.detail!
const totalReturnedQuantity = MathBN.sum(
itemDetail?.return_received_quantity ?? 0,
itemDetail?.return_dismissed_quantity ?? 0
)
const currentQuantity = MathBN.sub(item.quantity, totalReturnedQuantity)
const currentTotalItemPrice = MathBN.mult(item.unit_price, currentQuantity)
const currentSubtotal = isTaxInclusive
? MathBN.div(currentTotalItemPrice, MathBN.add(1, sumTaxRate))
: currentTotalItemPrice
const currentDiscountsSubtotal = MathBN.mult(
adjustmentSubtotalPerItem ?? 0,
currentQuantity
)
const taxTotal = calculateTaxTotal({ const taxTotal = calculateTaxTotal({
taxLines: item.tax_lines || [], taxLines: item.tax_lines || [],
taxableAmount: MathBN.sub(subtotal, discountsSubtotal), taxableAmount: MathBN.sub(currentSubtotal, currentDiscountsSubtotal),
setTotalField: "total", setTotalField: "total",
}) })
const originalTaxTotal = calculateTaxTotal({ const originalTaxTotal = calculateTaxTotal({
taxLines: item.tax_lines || [], taxLines: item.tax_lines || [],
taxableAmount: subtotal, taxableAmount: currentSubtotal,
setTotalField: "subtotal", setTotalField: "subtotal",
}) })
// Compute full-quantity net total after discounts and taxes to derive per-unit totals
const fullDiscountedTaxable = MathBN.sub(subtotal, discountsSubtotalFull ?? 0)
const taxTotalFull = calculateTaxTotal({
taxLines: item.tax_lines || [],
taxableAmount: fullDiscountedTaxable,
})
const fullNetTotal = MathBN.sum(fullDiscountedTaxable, taxTotalFull)
const totals: GetItemTotalOutput = { const totals: GetItemTotalOutput = {
quantity: item.quantity, quantity: item.quantity,
unit_price: item.unit_price, unit_price: item.unit_price,
subtotal: new BigNumber(subtotal), subtotal: new BigNumber(currentSubtotal),
total: new BigNumber( total: new BigNumber(
MathBN.sum(MathBN.sub(subtotal, discountsSubtotal), taxTotal) MathBN.sum(
MathBN.sub(currentSubtotal, currentDiscountsSubtotal),
taxTotal
)
),
original_subtotal: new BigNumber(
MathBN.sub(
isTaxInclusive
? currentTotalItemPrice
: MathBN.add(currentSubtotal, originalTaxTotal),
originalTaxTotal
)
), ),
original_total: new BigNumber( original_total: new BigNumber(
isTaxInclusive ? totalItemPrice : MathBN.add(subtotal, originalTaxTotal) isTaxInclusive
? currentTotalItemPrice
: MathBN.add(currentSubtotal, originalTaxTotal)
), ),
discount_total: new BigNumber(discountsTotal), // Discount values prorated to the current quantity
discount_subtotal: new BigNumber(discountsSubtotal), discount_subtotal: new BigNumber(currentDiscountsSubtotal),
discount_tax_total: new BigNumber(discountTaxTotal), discount_tax_total: new BigNumber(MathBN.sub(originalTaxTotal, taxTotal)),
discount_total: new BigNumber(
MathBN.add(
currentDiscountsSubtotal,
MathBN.sub(originalTaxTotal, taxTotal)
)
),
tax_total: new BigNumber(taxTotal), tax_total: new BigNumber(taxTotal),
original_tax_total: new BigNumber(originalTaxTotal), original_tax_total: new BigNumber(originalTaxTotal),
} }
if (isDefined(item.detail?.return_requested_quantity)) { if (
setRefundableTotal(item, discountsTotal, totals, context) isDefined(item.detail?.return_requested_quantity) ||
isDefined(item.detail?.return_received_quantity) ||
isDefined(item.detail?.return_dismissed_quantity)
) {
setRefundableTotal(item, discountsTotal, totals)
} }
// Per-unit total should be based on full-quantity net total to support lifecycle totals consistently
const div = MathBN.eq(item.quantity, 0) ? 1 : item.quantity const div = MathBN.eq(item.quantity, 0) ? 1 : item.quantity
const totalPerUnit = MathBN.div(totals.total, div) const totalPerUnit = MathBN.div(fullNetTotal, div)
const optionalFields = { const optionalFields = {
...(context.extraQuantityFields ?? {}), ...(context.extraQuantityFields ?? {}),
@@ -183,10 +236,9 @@ function getLineItemTotals(
for (const field in optionalFields) { for (const field in optionalFields) {
const totalField = optionalFields[field] const totalField = optionalFields[field]
let target = item[totalField] let target = field.includes(".")
if (field.includes(".")) { ? pickValueFromObject(field, item)
target = pickValueFromObject(field, item) : item[field]
}
if (!isDefined(target)) { if (!isDefined(target)) {
continue continue
@@ -20,6 +20,7 @@ export interface GetShippingMethodTotalOutput {
amount: BigNumber amount: BigNumber
subtotal: BigNumber subtotal: BigNumber
original_subtotal: BigNumber
total: BigNumber total: BigNumber
original_total: BigNumber original_total: BigNumber
@@ -96,6 +97,14 @@ export function getShippingMethodTotals(
total: new BigNumber( total: new BigNumber(
MathBN.sum(MathBN.sub(subtotal, discountsSubtotal), taxTotal) MathBN.sum(MathBN.sub(subtotal, discountsSubtotal), taxTotal)
), ),
original_subtotal: new BigNumber(
MathBN.sub(
isTaxInclusive
? shippingMethodAmount
: MathBN.add(subtotal, originalTaxTotal),
originalTaxTotal
)
),
original_total: new BigNumber( original_total: new BigNumber(
isTaxInclusive isTaxInclusive
? shippingMethodAmount ? shippingMethodAmount
@@ -11,10 +11,6 @@ export function calculateTaxTotal({
taxableAmount: BigNumberInput taxableAmount: BigNumberInput
setTotalField?: string setTotalField?: string
}) { }) {
if (MathBN.lte(taxableAmount, 0)) {
return MathBN.convert(0)
}
let taxTotal = MathBN.convert(0) let taxTotal = MathBN.convert(0)
for (const taxLine of taxLines) { for (const taxLine of taxLines) {
@@ -128,6 +128,7 @@ export type AdminPostClaimsAddItemsReqSchemaType = z.infer<
> >
export const AdminPostClaimsRequestReturnItemsReqSchema = z.object({ export const AdminPostClaimsRequestReturnItemsReqSchema = z.object({
location_id: z.string().optional(),
items: z.array( items: z.array(
z.object({ z.object({
id: z.string(), id: z.string(),
@@ -7,6 +7,7 @@ export const defaultAdminListOrderFields = [
"region_id", "region_id",
"*items", "*items",
"summary", "summary",
"total",
"metadata", "metadata",
"created_at", "created_at",
"updated_at", "updated_at",
@@ -31,6 +32,7 @@ export const defaultAdminOrderFields = [
"*shipping_methods.tax_lines", "*shipping_methods.tax_lines",
"*shipping_methods.adjustments", "*shipping_methods.adjustments",
"summary", "summary",
"total",
"metadata", "metadata",
"created_at", "created_at",
"updated_at", "updated_at",
@@ -127,6 +127,7 @@ export type AdminPostExchangesAddItemsReqSchemaType = z.infer<
> >
export const AdminPostExchangesReturnRequestItemsReqSchema = z.object({ export const AdminPostExchangesReturnRequestItemsReqSchema = z.object({
location_id: z.string().optional(),
items: z.array( items: z.array(
z.object({ z.object({
id: z.string(), id: z.string(),
@@ -4,6 +4,7 @@ export const defaultAdminOrderFields = [
"status", "status",
"version", "version",
"summary", "summary",
"total",
"metadata", "metadata",
"created_at", "created_at",
"updated_at", "updated_at",
@@ -19,6 +20,7 @@ export const defaultAdminRetrieveOrderFields = [
"discount_total", "discount_total",
"discount_tax_total", "discount_tax_total",
"original_total", "original_total",
"original_subtotal",
"original_tax_total", "original_tax_total",
"item_total", "item_total",
"item_subtotal", "item_subtotal",
@@ -27,6 +27,7 @@ export const defaultStoreRetrieveOrderFields = [
"discount_subtotal", "discount_subtotal",
"discount_tax_total", "discount_tax_total",
"original_total", "original_total",
"original_subtotal",
"original_tax_total", "original_tax_total",
"item_total", "item_total",
"item_subtotal", "item_subtotal",
@@ -2934,6 +2934,7 @@ moduleIntegrationTestRunner<ICartModuleService>({
subtotal: 100, subtotal: 100,
total: 0, total: 0,
original_total: 100, original_total: 100,
original_subtotal: 100,
discount_total: 100, discount_total: 100,
discount_subtotal: 100, discount_subtotal: 100,
discount_tax_total: 0, discount_tax_total: 0,
@@ -2951,6 +2952,10 @@ moduleIntegrationTestRunner<ICartModuleService>({
value: "100", value: "100",
precision: 20, precision: 20,
}, },
raw_original_subtotal: {
value: "100",
precision: 20,
},
raw_discount_total: { raw_discount_total: {
value: "100", value: "100",
precision: 20, precision: 20,
@@ -3042,6 +3047,7 @@ moduleIntegrationTestRunner<ICartModuleService>({
subtotal: 400, subtotal: 400,
total: 200, total: 200,
original_total: 400, original_total: 400,
original_subtotal: 400,
discount_total: 200, discount_total: 200,
discount_subtotal: 200, discount_subtotal: 200,
discount_tax_total: 0, discount_tax_total: 0,
@@ -3059,6 +3065,10 @@ moduleIntegrationTestRunner<ICartModuleService>({
value: "400", value: "400",
precision: 20, precision: 20,
}, },
raw_original_subtotal: {
value: "400",
precision: 20,
},
raw_discount_total: { raw_discount_total: {
value: "200", value: "200",
precision: 20, precision: 20,
@@ -3104,6 +3114,7 @@ moduleIntegrationTestRunner<ICartModuleService>({
subtotal: 10, subtotal: 10,
total: 10, total: 10,
original_total: 10, original_total: 10,
original_subtotal: 10,
discount_total: 0, discount_total: 0,
discount_subtotal: 0, discount_subtotal: 0,
discount_tax_total: 0, discount_tax_total: 0,
@@ -3121,6 +3132,10 @@ moduleIntegrationTestRunner<ICartModuleService>({
value: "10", value: "10",
precision: 20, precision: 20,
}, },
raw_original_subtotal: {
value: "10",
precision: 20,
},
raw_discount_total: { raw_discount_total: {
value: "0", value: "0",
precision: 20, precision: 20,
@@ -3166,6 +3181,7 @@ moduleIntegrationTestRunner<ICartModuleService>({
discount_subtotal: 300, discount_subtotal: 300,
discount_tax_total: 0, discount_tax_total: 0,
original_total: 510, original_total: 510,
original_subtotal: 510,
original_tax_total: 0, original_tax_total: 0,
item_total: 200, item_total: 200,
item_subtotal: 500, item_subtotal: 500,
@@ -3217,6 +3233,10 @@ moduleIntegrationTestRunner<ICartModuleService>({
value: "510", value: "510",
precision: 20, precision: 20,
}, },
raw_original_subtotal: {
value: "510",
precision: 20,
},
raw_original_tax_total: { raw_original_tax_total: {
value: "0", value: "0",
precision: 20, precision: 20,
@@ -227,7 +227,7 @@ moduleIntegrationTestRunner<IOrderModuleService>({
const serializedOrder = JSON.parse( const serializedOrder = JSON.parse(
JSON.stringify( JSON.stringify(
await service.retrieveOrder(created.id, { await service.retrieveOrder(created.id, {
select: ["id", "summary"], select: ["id", "summary", "total"],
}) })
) )
) )
@@ -246,7 +246,7 @@ moduleIntegrationTestRunner<IOrderModuleService>({
const serializedOrder2 = JSON.parse( const serializedOrder2 = JSON.parse(
JSON.stringify( JSON.stringify(
await service.retrieveOrder(created.id, { await service.retrieveOrder(created.id, {
select: ["id", "summary"], select: ["id", "summary", "total"],
}) })
) )
) )
@@ -271,7 +271,7 @@ moduleIntegrationTestRunner<IOrderModuleService>({
const serializedOrder3 = JSON.parse( const serializedOrder3 = JSON.parse(
JSON.stringify( JSON.stringify(
await service.retrieveOrder(created.id, { await service.retrieveOrder(created.id, {
select: ["id", "summary"], select: ["id", "summary", "total"],
}) })
) )
) )
@@ -290,7 +290,7 @@ moduleIntegrationTestRunner<IOrderModuleService>({
const serializedOrder4 = JSON.parse( const serializedOrder4 = JSON.parse(
JSON.stringify( JSON.stringify(
await service.retrieveOrder(created.id, { await service.retrieveOrder(created.id, {
select: ["id", "summary"], select: ["id", "summary", "total"],
}) })
) )
) )
@@ -414,7 +414,7 @@ moduleIntegrationTestRunner<IOrderModuleService>({
}) })
const changedOrder = await service.retrieveOrder(createdOrder.id, { const changedOrder = await service.retrieveOrder(createdOrder.id, {
select: ["total", "items.detail", "summary"], select: ["total", "items.detail", "summary", "total"],
relations: ["items"], relations: ["items"],
}) })
@@ -492,7 +492,7 @@ moduleIntegrationTestRunner<IOrderModuleService>({
}) })
const modified = await service.retrieveOrder(createdOrder.id, { const modified = await service.retrieveOrder(createdOrder.id, {
select: ["total", "items.detail", "summary"], select: ["total", "items.detail", "summary", "total"],
relations: ["items"], relations: ["items"],
}) })
@@ -358,6 +358,7 @@ export default class OrderModuleService
"discount_tax_total", "discount_tax_total",
"original_total", "original_total",
"original_tax_total", "original_tax_total",
"pending_difference",
"item_total", "item_total",
"item_subtotal", "item_subtotal",
"item_tax_total", "item_tax_total",
@@ -370,9 +371,13 @@ export default class OrderModuleService
"original_shipping_tax_total", "original_shipping_tax_total",
"original_shipping_subtotal", "original_shipping_subtotal",
"original_shipping_total", "original_shipping_total",
"original_total",
"original_subtotal",
"original_tax_total",
"credit_line_total", "credit_line_total",
"credit_line_tax_total", "credit_line_tax_total",
"credit_line_subtotal", "credit_line_subtotal",
"refundable_amount",
] ]
const includeTotals = (config?.select ?? []).some((field) => const includeTotals = (config?.select ?? []).some((field) =>
@@ -224,7 +224,7 @@ export class OrderChangeProcessing {
return orderSummary return orderSummary
} }
// Calculate the order summary from a calculated order including taxes // Returns the order summary from a calculated order including taxes
public getSummaryFromOrder(order: OrderDTO): OrderSummaryDTO { public getSummaryFromOrder(order: OrderDTO): OrderSummaryDTO {
const summary_ = this.summary const summary_ = this.summary
const total = order.total const total = order.total
@@ -241,35 +241,6 @@ export class OrderChangeProcessing {
orderSummary.accounting_total = orderSummary.current_order_total orderSummary.accounting_total = orderSummary.current_order_total
orderSummary.pending_difference = MathBN.sub(
orderSummary.current_order_total,
orderSummary.transaction_total
)
// return total becomes pending difference
for (const item of order.items ?? []) {
const item_ = item as any
;[
"return_requested_total",
"return_received_total",
// TODO: revisit this when we settle on which dismissed items need to be refunded
// "return_dismissed_total",
].forEach((returnTotalKey) => {
const returnTotal = item_[returnTotalKey]
if (MathBN.gt(returnTotal, 0)) {
orderSummary.pending_difference = MathBN.sub(
orderSummary.pending_difference,
returnTotal
)
}
})
}
orderSummary.pending_difference = new BigNumber(
orderSummary.pending_difference
)
return orderSummary return orderSummary
} }