chore(order): cancel return (#7881)

This commit is contained in:
Carlos R. L. Rodrigues
2024-07-02 06:52:58 -03:00
committed by GitHub
parent b3236ff31c
commit 07715e6b50
46 changed files with 1339 additions and 121 deletions

View File

@@ -1,6 +1,5 @@
export enum ChangeActionType {
CANCEL = "CANCEL",
CANCEL_RETURN = "CANCEL_RETURN",
FULFILL_ITEM = "FULFILL_ITEM",
CANCEL_ITEM_FULFILLMENT = "CANCEL_ITEM_FULFILLMENT",
ITEM_ADD = "ITEM_ADD",
@@ -8,7 +7,10 @@ export enum ChangeActionType {
RECEIVE_DAMAGED_RETURN_ITEM = "RECEIVE_DAMAGED_RETURN_ITEM",
RECEIVE_RETURN_ITEM = "RECEIVE_RETURN_ITEM",
RETURN_ITEM = "RETURN_ITEM",
CANCEL_RETURN_ITEM = "CANCEL_RETURN_ITEM",
SHIPPING_ADD = "SHIPPING_ADD",
SHIPPING_REMOVE = "SHIPPING_REMOVE",
SHIP_ITEM = "SHIP_ITEM",
WRITE_OFF_ITEM = "WRITE_OFF_ITEM",
REINSTATE_ITEM = "REINSTATE_ITEM",
}

View File

@@ -3,7 +3,7 @@ import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
import { setActionReference } from "../set-action-reference"
OrderChangeProcessing.registerActionType(ChangeActionType.CANCEL_RETURN, {
OrderChangeProcessing.registerActionType(ChangeActionType.CANCEL_RETURN_ITEM, {
operation({ action, currentOrder }) {
const existing = currentOrder.items.find(
(item) => item.id === action.details.reference_id

View File

@@ -9,4 +9,5 @@ export * from "./receive-return-item"
export * from "./return-item"
export * from "./ship-item"
export * from "./shipping-add"
export * from "./shipping-remove"
export * from "./write-off-item"

View File

@@ -0,0 +1,64 @@
import { MathBN, MedusaError, isDefined } from "@medusajs/utils"
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
import { setActionReference } from "../set-action-reference"
OrderChangeProcessing.registerActionType(ChangeActionType.REINSTATE_ITEM, {
operation({ action, currentOrder }) {
const existing = currentOrder.items.find(
(item) => item.id === action.details.reference_id
)!
existing.detail.written_off_quantity ??= 0
existing.detail.written_off_quantity = MathBN.sub(
existing.detail.written_off_quantity,
action.details.quantity
)
setActionReference(existing, action)
},
revert({ action, currentOrder }) {
const existing = currentOrder.items.find(
(item) => item.id === action.details.reference_id
)!
existing.detail.written_off_quantity = MathBN.add(
existing.detail.written_off_quantity,
action.details.quantity
)
},
validate({ action, currentOrder }) {
const refId = action.details?.reference_id
if (!isDefined(refId)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Details reference ID is required."
)
}
const existing = currentOrder.items.find((item) => item.id === refId)
if (!existing) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Item ID "${refId}" not found.`
)
}
if (!action.details?.quantity) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity to reinstate item ${refId} is required.`
)
}
const quantityAvailable = existing!.quantity ?? 0
const greater = MathBN.gt(action.details?.quantity, quantityAvailable)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Cannot unclaim more items than what was ordered."
)
}
},
})

View File

@@ -57,7 +57,7 @@ OrderChangeProcessing.registerActionType(ChangeActionType.RETURN_ITEM, {
}
const quantityAvailable = MathBN.sub(
existing!.detail?.shipped_quantity ?? 0,
existing!.detail?.fulfilled_quantity ?? 0,
existing!.detail?.return_requested_quantity ?? 0
)
@@ -65,7 +65,7 @@ OrderChangeProcessing.registerActionType(ChangeActionType.RETURN_ITEM, {
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot request to return more items than what was shipped for item ${refId}.`
`Cannot request to return more items than what was fulfilled for item ${refId}.`
)
}
},

View File

@@ -1,6 +1,7 @@
import { MedusaError, isDefined } from "@medusajs/utils"
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
import { setActionReference } from "../set-action-reference"
OrderChangeProcessing.registerActionType(ChangeActionType.SHIPPING_ADD, {
operation({ action, currentOrder }) {
@@ -8,15 +9,20 @@ OrderChangeProcessing.registerActionType(ChangeActionType.SHIPPING_ADD, {
? currentOrder.shipping_methods
: [currentOrder.shipping_methods]
shipping.push({
shipping_method_id: action.reference_id!,
order_id: currentOrder.id,
return_id: action.return_id,
claim_id: action.claim_id,
exchange_id: action.exchange_id,
const existing = shipping.find((sh) => sh.id === action.reference_id)
price: action.amount as number,
})
if (existing) {
setActionReference(existing, action)
} else {
shipping.push({
id: action.reference_id!,
order_id: currentOrder.id,
return_id: action.return_id,
claim_id: action.claim_id,
exchange_id: action.exchange_id,
price: action.amount as number,
})
}
currentOrder.shipping_methods = shipping
},
@@ -26,7 +32,7 @@ OrderChangeProcessing.registerActionType(ChangeActionType.SHIPPING_ADD, {
: [currentOrder.shipping_methods]
const existingIndex = shipping.findIndex(
(item) => item.shipping_method_id === action.reference_id
(item) => item.id === action.reference_id
)
if (existingIndex > -1) {

View File

@@ -0,0 +1,56 @@
import { MedusaError, isDefined } from "@medusajs/utils"
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
OrderChangeProcessing.registerActionType(ChangeActionType.SHIPPING_REMOVE, {
operation({ action, currentOrder }) {
const shipping = Array.isArray(currentOrder.shipping_methods)
? currentOrder.shipping_methods
: [currentOrder.shipping_methods]
const existingIndex = shipping.findIndex(
(item) => item.id === action.reference_id
)
if (existingIndex > -1) {
shipping.splice(existingIndex, 1)
}
currentOrder.shipping_methods = shipping
},
revert({ action, currentOrder }) {
const shipping = Array.isArray(currentOrder.shipping_methods)
? currentOrder.shipping_methods
: [currentOrder.shipping_methods]
const existingIndex = shipping.findIndex(
(item) => item.id === action.reference_id
)
if (existingIndex > -1) {
shipping.push({
id: action.reference_id!,
order_id: currentOrder.id,
return_id: action.return_id,
claim_id: action.claim_id,
exchange_id: action.exchange_id,
price: action.amount as number,
})
}
},
validate({ action }) {
if (!action.reference_id) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Reference ID is required."
)
}
if (!isDefined(action.amount)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Amount is required."
)
}
},
})

View File

@@ -15,7 +15,7 @@ export function applyChangesToOrder(
actionsMap: Record<string, any[]>
) {
const itemsToUpsert: OrderItem[] = []
const shippingMethodsToInsert: OrderShippingMethod[] = []
const shippingMethodsToUpsert: OrderShippingMethod[] = []
const summariesToUpsert: any[] = []
const orderToUpdate: any[] = []
@@ -68,8 +68,9 @@ export function applyChangesToOrder(
...((shippingMethod as any).detail ?? shippingMethod),
version,
}
delete sm.id
shippingMethodsToInsert.push(sm)
shippingMethodsToUpsert.push(sm)
}
orderToUpdate.push({
@@ -85,7 +86,7 @@ export function applyChangesToOrder(
return {
itemsToUpsert,
shippingMethodsToInsert,
shippingMethodsToUpsert,
summariesToUpsert,
orderToUpdate,
}

View File

@@ -34,11 +34,18 @@ export function setFindMethods<T>(klass: Constructor<T>, entity: any) {
let orderAlias = "o0"
if (isRelatedEntity) {
if (!config.options.populate.includes("order.items")) {
config.options.populate.unshift("order.items")
}
// first relation is always order if the entity is not Order
const index = config.options.populate.findIndex((p) => p === "order")
if (index > -1) {
config.options.populate.splice(index, 1)
}
config.options.populate.unshift("order")
orderAlias = "o1"
config.options.populate.unshift("order.items")
}
let defaultVersion = knex.raw(`"${orderAlias}"."version"`)
@@ -105,7 +112,12 @@ export function setFindMethods<T>(klass: Constructor<T>, entity: any) {
let orderAlias = "o0"
if (isRelatedEntity) {
// first relation is always order if entity is not Order
// first relation is always order if the entity is not Order
const index = config.options.populate.findIndex((p) => p === "order")
if (index > -1) {
config.options.populate.splice(index, 1)
}
config.options.populate.unshift("order")
orderAlias = "o1"
}

View File

@@ -118,10 +118,10 @@ export class OrderChangeProcessing {
const amount = MathBN.mult(action.amount!, type.isDeduction ? -1 : 1)
if (action.group_id && !action.evaluationOnly) {
this.groupTotal[action.group_id] ??= 0
this.groupTotal[action.group_id] = MathBN.add(
this.groupTotal[action.group_id],
if (action.change_id && !action.evaluationOnly) {
this.groupTotal[action.change_id] ??= 0
this.groupTotal[action.change_id] = MathBN.add(
this.groupTotal[action.change_id],
amount
)
}
@@ -146,7 +146,7 @@ export class OrderChangeProcessing {
amount
)
} else {
if (!this.isEventDone(action) && !action.group_id) {
if (!this.isEventDone(action) && !action.change_id) {
summary.difference_sum = MathBN.add(summary.difference_sum, amount)
}
summary.current_order_total = MathBN.add(
@@ -243,8 +243,8 @@ export class OrderChangeProcessing {
if (action.resolve.reference_id) {
this.resolveReferences(action)
}
const groupId = action.resolve.group_id ?? "__default"
if (action.resolve.group_id) {
const groupId = action.resolve.change_id ?? "__default"
if (action.resolve.change_id) {
// resolve all actions in the same group
this.resolveGroup(action)
}
@@ -320,7 +320,7 @@ export class OrderChangeProcessing {
const type = OrderChangeProcessing.typeDefinition[actionKey]
const actions = this.actionsProcessed[actionKey]
for (const action of actions) {
if (!resolve?.group_id || action?.group_id !== resolve.group_id) {
if (!resolve?.change_id || action?.change_id !== resolve.change_id) {
continue
}

View File

@@ -5,8 +5,9 @@ import {
deduplicate,
isDefined,
} from "@medusajs/utils"
import { Order, OrderClaim, OrderExchange, Return } from "@models"
// Reshape the order object to match the OrderDTO
// This function is used to format the order object before returning to the main module methods
export function formatOrder(
order,
options: {
@@ -20,8 +21,9 @@ export function formatOrder(
orders.map((order) => {
let mainOrder = order
const isRelatedEntity = options?.entity !== Order
const isRelatedEntity = options?.entity?.name !== "Order"
// If the entity is a related entity, the original order is located in the order property
if (isRelatedEntity) {
if (!order.order) {
return order
@@ -48,11 +50,12 @@ export function formatOrder(
formatOrderReturn(order.return, mainOrder)
}
if (options.entity === OrderClaim) {
const entityName = options.entity.name
if (entityName === "OrderClaim") {
formatClaim(order)
} else if (options.entity === OrderExchange) {
} else if (entityName === "OrderExchange") {
formatExchange(order)
} else if (options.entity === Return) {
} else if (entityName === "Return") {
formatReturn(order)
}
}
@@ -85,9 +88,6 @@ export function formatOrder(
}
function formatOrderReturn(orderReturn, mainOrder) {
orderReturn.items = orderReturn.items.filter(
(item) => !item.is_additional_item
)
orderReturn.items.forEach((orderItem) => {
const item = mainOrder.items?.find((item) => item.id === orderItem.item_id)
@@ -154,7 +154,15 @@ function formatReturn(returnOrder) {
})
}
// Map the public order model to the repository model format
// As the public responses have a different shape than the repository responses, this function is used to map the public properties to the internal db entities
// e.g "items" is the relation between "line-item" and "order" + "version", The line item itself is in "items.item"
// This helper maps to the correct repository to query the DB, and the function "formatOrder" remap the response to the public shape
export function mapRepositoryToOrderModel(config, isRelatedEntity = false) {
if (isRelatedEntity) {
return mapRepositoryToRelatedEntity(config)
}
const conf = { ...config }
function replace(obj, type): string[] | undefined {
@@ -223,3 +231,36 @@ export function mapRepositoryToOrderModel(config, isRelatedEntity = false) {
return conf
}
// This function has the same purpose as "mapRepositoryToOrderModel" but for returns, claims and exchanges
function mapRepositoryToRelatedEntity(config) {
const conf = { ...config }
function replace(obj, type): string[] | undefined {
if (!isDefined(obj[type])) {
return
}
return deduplicate(
obj[type].sort().map((rel) => {
if (
rel.includes("shipping_methods") &&
!rel.includes("shipping_methods.shipping_method")
) {
obj.populate.push("shipping_methods.shipping_method")
return rel.replace(
"shipping_methods",
"shipping_methods.shipping_method"
)
}
return rel
})
)
}
conf.options.fields = replace(config.options, "fields")
conf.options.populate = replace(config.options, "populate")
return conf
}