feat(medusa): Migrate Return service to ts (#1926)

This commit is contained in:
Adrien de Peretti authored and GitHub committed 2022-08-19 17:26:12 +02:00
1 parent d617e21bf5
commit 844d7d1f5f
7 files changed
+338 -263

No files matched your search

@@ -1,8 +1,3 @@
import {
EventBusService,
OrderService,
ReturnService,
} from "../../../../services"
import { import {
IsArray, IsArray,
IsBoolean, IsBoolean,
@@ -12,13 +7,19 @@ import {
ValidateNested, ValidateNested,
} from "class-validator" } from "class-validator"
import { defaultAdminOrdersFields, defaultAdminOrdersRelations } from "." import { defaultAdminOrdersFields, defaultAdminOrdersRelations } from "."
import {
EventBusService,
OrderService,
ReturnService,
} from "../../../../services"
import { MedusaError } from "medusa-core-utils"
import { OrdersReturnItem } from "../../../../types/orders"
import { Type } from "class-transformer" import { Type } from "class-transformer"
import { validator } from "../../../../utils/validator" import { MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { Order, Return } from "../../../../models"
import { OrdersReturnItem } from "../../../../types/orders"
import { isDefined } from "../../../../utils" import { isDefined } from "../../../../utils"
import { validator } from "../../../../utils/validator"
/** /**
* @oas [post] /orders/{id}/return * @oas [post] /orders/{id}/return
@@ -197,7 +198,7 @@ export default async (req, res) => {
const { key, error } = await idempotencyKeyService const { key, error } = await idempotencyKeyService
.withTransaction(transactionManager) .withTransaction(transactionManager)
.workStage(idempotencyKey.idempotency_key, async (manager) => { .workStage(idempotencyKey.idempotency_key, async (manager) => {
let order = await orderService let order: Order | Return = await orderService
.withTransaction(manager) .withTransaction(manager)
.retrieve(id, { relations: ["returns"] }) .retrieve(id, { relations: ["returns"] })
@@ -206,22 +207,24 @@ export default async (req, res) => {
* and register it as received. * and register it as received.
*/ */
if (value.receive_now) { if (value.receive_now) {
let ret = await returnService.withTransaction(manager).list({ const returns = await returnService
idempotency_key: idempotencyKey.idempotency_key, .withTransaction(manager)
}) .list({
idempotency_key: idempotencyKey.idempotency_key,
})
if (!ret.length) { if (!returns.length) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
`Return not found` `Return not found`
) )
} }
ret = ret[0] const returnOrder = returns[0]
order = await returnService order = await returnService
.withTransaction(manager) .withTransaction(manager)
.receive(ret.id, value.items, value.refund) .receive(returnOrder.id, value.items, value.refund)
} }
order = await orderService order = await orderService
@@ -278,7 +281,7 @@ export default async (req, res) => {
} }
type ReturnObj = { type ReturnObj = {
order_id?: string order_id: string
idempotency_key?: string idempotency_key?: string
items?: OrdersReturnItem[] items?: OrdersReturnItem[]
shipping_method?: ReturnShipping shipping_method?: ReturnShipping
@@ -3,6 +3,8 @@ import { IsNumber, IsOptional } from "class-validator"
import { ReturnService } from "../../../../services" import { ReturnService } from "../../../../services"
import { Type } from "class-transformer" import { Type } from "class-transformer"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
import { FindConfig } from "../../../../types/common"
import { Return } from "../../../../models"
/** /**
* @oas [get] /returns * @oas [get] /returns
@@ -47,7 +49,7 @@ export default async (req, res) => {
skip: validated.offset, skip: validated.offset,
take: validated.limit, take: validated.limit,
order: { created_at: "DESC" }, order: { created_at: "DESC" },
} } as FindConfig<Return>
const returns = await returnService.list(selector, { ...listConfig }) const returns = await returnService.list(selector, { ...listConfig })
@@ -8,14 +8,14 @@ import {
ValidateNested, ValidateNested,
} from "class-validator" } from "class-validator"
import { Type } from "class-transformer"
import { MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm"
import EventBusService from "../../../../services/event-bus" import EventBusService from "../../../../services/event-bus"
import IdempotencyKeyService from "../../../../services/idempotency-key" import IdempotencyKeyService from "../../../../services/idempotency-key"
import { MedusaError } from "medusa-core-utils"
import OrderService from "../../../../services/order" import OrderService from "../../../../services/order"
import ReturnService from "../../../../services/return" import ReturnService from "../../../../services/return"
import { Type } from "class-transformer"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
import { EntityManager } from "typeorm";
/** /**
* @oas [post] /returns * @oas [post] /returns
@@ -87,12 +87,9 @@ export default async (req, res) => {
let idempotencyKey let idempotencyKey
try { try {
await manager.transaction(async (transactionManager) => { await manager.transaction(async (transactionManager) => {
idempotencyKey = await idempotencyKeyService.withTransaction(transactionManager).initializeRequest( idempotencyKey = await idempotencyKeyService
headerKey, .withTransaction(transactionManager)
req.method, .initializeRequest(headerKey, req.method, req.params, req.path)
req.params,
req.path
)
}) })
} catch (error) { } catch (error) {
res.status(409).send("Failed to create idempotency key") res.status(409).send("Failed to create idempotency key")
@@ -116,48 +113,45 @@ export default async (req, res) => {
await manager.transaction(async (transactionManager) => { await manager.transaction(async (transactionManager) => {
const { key, error } = await idempotencyKeyService const { key, error } = await idempotencyKeyService
.withTransaction(transactionManager) .withTransaction(transactionManager)
.workStage( .workStage(idempotencyKey.idempotency_key, async (manager) => {
idempotencyKey.idempotency_key, const order = await orderService
async (manager) => { .withTransaction(manager)
const order = await orderService .retrieve(returnDto.order_id, {
.withTransaction(manager) select: ["refunded_total", "total"],
.retrieve(returnDto.order_id, { relations: ["items"],
select: ["refunded_total", "total"], })
relations: ["items"],
})
const returnObj: any = { const returnObj: any = {
order_id: returnDto.order_id, order_id: returnDto.order_id,
idempotency_key: idempotencyKey.idempotency_key, idempotency_key: idempotencyKey.idempotency_key,
items: returnDto.items, items: returnDto.items,
}
if (returnDto.return_shipping) {
returnObj.shipping_method = returnDto.return_shipping
}
const createdReturn = await returnService
.withTransaction(manager)
.create(returnObj)
if (returnDto.return_shipping) {
await returnService
.withTransaction(manager)
.fulfill(createdReturn.id)
}
await eventBus
.withTransaction(manager)
.emit("order.return_requested", {
id: returnDto.order_id,
return_id: createdReturn.id,
})
return {
recovery_point: "return_requested",
}
} }
)
if (returnDto.return_shipping) {
returnObj.shipping_method = returnDto.return_shipping
}
const createdReturn = await returnService
.withTransaction(manager)
.create(returnObj)
if (returnDto.return_shipping) {
await returnService
.withTransaction(manager)
.fulfill(createdReturn.id)
}
await eventBus
.withTransaction(manager)
.emit("order.return_requested", {
id: returnDto.order_id,
return_id: createdReturn.id,
})
return {
recovery_point: "return_requested",
}
})
if (error) { if (error) {
inProgress = false inProgress = false
@@ -173,10 +167,10 @@ export default async (req, res) => {
await manager.transaction(async (transactionManager) => { await manager.transaction(async (transactionManager) => {
const { key, error } = await idempotencyKeyService const { key, error } = await idempotencyKeyService
.withTransaction(transactionManager) .withTransaction(transactionManager)
.workStage( .workStage(idempotencyKey.idempotency_key, async (manager) => {
idempotencyKey.idempotency_key, const returnOrders = await returnService
async (manager) => { .withTransaction(manager)
let ret = await returnService.withTransaction(manager).list( .list(
{ {
idempotency_key: idempotencyKey.idempotency_key, idempotency_key: idempotencyKey.idempotency_key,
}, },
@@ -184,20 +178,19 @@ export default async (req, res) => {
relations: ["items", "items.reason"], relations: ["items", "items.reason"],
} }
) )
if (!ret.length) { if (!returnOrders.length) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
`Return not found` `Return not found`
) )
}
ret = ret[0]
return {
response_code: 200,
response_body: { return: ret },
}
} }
) const returnOrder = returnOrders[0]
return {
response_code: 200,
response_body: { return: returnOrder },
}
})
if (error) { if (error) {
inProgress = false inProgress = false
@@ -218,14 +211,11 @@ export default async (req, res) => {
await manager.transaction(async (transactionManager) => { await manager.transaction(async (transactionManager) => {
idempotencyKey = await idempotencyKeyService idempotencyKey = await idempotencyKeyService
.withTransaction(transactionManager) .withTransaction(transactionManager)
.update( .update(idempotencyKey.idempotency_key, {
idempotencyKey.idempotency_key, recovery_point: "finished",
{ response_code: 500,
recovery_point: "finished", response_body: { message: "Unknown recovery point" },
response_code: 500, })
response_body: { message: "Unknown recovery point" },
}
)
}) })
break break
} }
+9 -5
View File
@@ -18,6 +18,7 @@ import {
ClaimType, ClaimType,
FulfillmentItem, FulfillmentItem,
LineItem, LineItem,
ReturnItem,
} from "../models" } from "../models"
import { ClaimRepository } from "../repositories/claim" import { ClaimRepository } from "../repositories/claim"
import { DeepPartial, EntityManager } from "typeorm" import { DeepPartial, EntityManager } from "typeorm"
@@ -425,11 +426,14 @@ export default class ClaimService extends TransactionBaseService {
await this.returnService_.withTransaction(transactionManager).create({ await this.returnService_.withTransaction(transactionManager).create({
order_id: order.id, order_id: order.id,
claim_order_id: result.id, claim_order_id: result.id,
items: claim_items.map((ci) => ({ items: claim_items.map(
item_id: ci.item_id, (ci) =>
quantity: ci.quantity, ({
metadata: (ci as any).metadata, item_id: ci.item_id,
})), quantity: ci.quantity,
metadata: (ci as any).metadata,
} as ReturnItem)
),
shipping_method: return_shipping, shipping_method: return_shipping,
no_notification: evaluatedNoNotification, no_notification: evaluatedNoNotification,
}) })
+6 -1
View File
@@ -14,6 +14,7 @@ import { LineItem } from "../models/line-item"
import LineItemAdjustmentService from "./line-item-adjustment" import LineItemAdjustmentService from "./line-item-adjustment"
import { Cart } from "../models/cart" import { Cart } from "../models/cart"
import { LineItemAdjustment } from "../models/line-item-adjustment" import { LineItemAdjustment } from "../models/line-item-adjustment"
import { FindConfig } from "../types/common"
type InjectedDependencies = { type InjectedDependencies = {
manager: EntityManager manager: EntityManager
@@ -89,7 +90,11 @@ class LineItemService extends BaseService {
async list( async list(
selector, selector,
config = { skip: 0, take: 50, order: { created_at: "DESC" } } config: FindConfig<LineItem> = {
skip: 0,
take: 50,
order: { created_at: "DESC" },
}
): Promise<LineItem[]> { ): Promise<LineItem[]> {
const manager = this.manager_ const manager = this.manager_
const lineItemRepo = manager.getCustomRepository(this.lineItemRepository_) const lineItemRepo = manager.getCustomRepository(this.lineItemRepository_)
@@ -1,12 +1,66 @@
import { isDefined } from "class-validator"
import { MedusaError } from "medusa-core-utils" import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces" import { DeepPartial, EntityManager } from "typeorm"
import { isDefined } from "../utils" import { TransactionBaseService } from "../interfaces"
import {
FulfillmentStatus,
LineItem,
Order,
PaymentStatus,
Return,
ReturnItem,
ReturnStatus,
} from "../models"
import { ReturnRepository } from "../repositories/return"
import { ReturnItemRepository } from "../repositories/return-item"
import { FindConfig, Selector } from "../types/common"
import { OrdersReturnItem } from "../types/orders"
import { CreateReturnInput, UpdateReturnInput } from "../types/return"
import { buildQuery, setMetadata } from "../utils"
import FulfillmentProviderService from "./fulfillment-provider"
import InventoryService from "./inventory"
import LineItemService from "./line-item"
import OrderService from "./order"
import ReturnReasonService from "./return-reason"
import ShippingOptionService from "./shipping-option"
import TaxProviderService from "./tax-provider"
import TotalsService from "./totals"
type InjectedDependencies = {
manager: EntityManager
totalsService: TotalsService
lineItemService: LineItemService
returnRepository: typeof ReturnRepository
returnItemRepository: typeof ReturnItemRepository
shippingOptionService: ShippingOptionService
returnReasonService: ReturnReasonService
taxProviderService: TaxProviderService
fulfillmentProviderService: FulfillmentProviderService
inventoryService: InventoryService
orderService: OrderService
}
type Transformer = (
item?: LineItem,
quantity?: number,
additional?: OrdersReturnItem
) => Promise<DeepPartial<LineItem>> | DeepPartial<LineItem>
class ReturnService extends TransactionBaseService {
protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined
protected readonly totalsService_: TotalsService
protected readonly returnRepository_: typeof ReturnRepository
protected readonly returnItemRepository_: typeof ReturnItemRepository
protected readonly lineItemService_: LineItemService
protected readonly taxProviderService_: TaxProviderService
protected readonly shippingOptionService_: ShippingOptionService
protected readonly fulfillmentProviderService_: FulfillmentProviderService
protected readonly returnReasonService_: ReturnReasonService
protected readonly inventoryService_: InventoryService
protected readonly orderService_: OrderService
/**
* Handles Returns
* @extends BaseService
*/
class ReturnService extends BaseService {
constructor({ constructor({
manager, manager,
totalsService, totalsService,
@@ -19,75 +73,54 @@ class ReturnService extends BaseService {
fulfillmentProviderService, fulfillmentProviderService,
inventoryService, inventoryService,
orderService, orderService,
}) { }: InjectedDependencies) {
super() super({
manager,
/** @private @const {EntityManager} */ totalsService,
this.manager_ = manager lineItemService,
returnRepository,
/** @private @const {TotalsService} */ returnItemRepository,
this.totalsService_ = totalsService shippingOptionService,
returnReasonService,
/** @private @const {ReturnRepository} */ taxProviderService,
this.returnRepository_ = returnRepository fulfillmentProviderService,
inventoryService,
/** @private @const {ReturnItemRepository} */ orderService,
this.returnItemRepository_ = returnItemRepository
/** @private @const {ReturnItemRepository} */
this.lineItemService_ = lineItemService
this.taxProviderService_ = taxProviderService
/** @private @const {ShippingOptionService} */
this.shippingOptionService_ = shippingOptionService
/** @private @const {FulfillmentProviderService} */
this.fulfillmentProviderService_ = fulfillmentProviderService
this.returnReasonService_ = returnReasonService
this.inventoryService_ = inventoryService
/** @private @const {OrderService} */
this.orderService_ = orderService
}
withTransaction(transactionManager) {
if (!transactionManager) {
return this
}
const cloned = new ReturnService({
manager: transactionManager,
totalsService: this.totalsService_,
lineItemService: this.lineItemService_,
returnRepository: this.returnRepository_,
taxProviderService: this.taxProviderService_,
returnItemRepository: this.returnItemRepository_,
shippingOptionService: this.shippingOptionService_,
fulfillmentProviderService: this.fulfillmentProviderService_,
returnReasonService: this.returnReasonService_,
inventoryService: this.inventoryService_,
orderService: this.orderService_,
}) })
cloned.transactionManager_ = transactionManager this.manager_ = manager
this.totalsService_ = totalsService
return cloned this.returnRepository_ = returnRepository
this.returnItemRepository_ = returnItemRepository
this.lineItemService_ = lineItemService
this.taxProviderService_ = taxProviderService
this.shippingOptionService_ = shippingOptionService
this.fulfillmentProviderService_ = fulfillmentProviderService
this.returnReasonService_ = returnReasonService
this.inventoryService_ = inventoryService
this.orderService_ = orderService
} }
/** /**
* Retrieves the order line items, given an array of items * Retrieves the order line items, given an array of items
* @param {Order} order - the order to get line items from * @param order - the order to get line items from
* @param {{ item_id: string, quantity: number }} items - the items to get * @param items - the items to get
* @param {function} transformer - a function to apply to each of the items * @param transformer - a function to apply to each of the items
* retrieved from the order, should return a line item. If the transformer * retrieved from the order, should return a line item. If the transformer
* returns an undefined value the line item will be filtered from the * returns an undefined value the line item will be filtered from the
* returned array. * returned array.
* @return {Promise<Array<LineItem>>} the line items generated by the transformer. * @return the line items generated by the transformer.
*/ */
async getFulfillmentItems_(order, items, transformer) { protected async getFulfillmentItems(
order: Order,
items: OrdersReturnItem[],
transformer: Transformer
): Promise<
(LineItem & {
reason_id?: string
note?: string
})[]
> {
let merged = [...order.items] let merged = [...order.items]
// merge items from order with items from order swaps // merge items from order with items from order swaps
@@ -110,33 +143,37 @@ class ReturnService extends BaseService {
}) })
) )
return toReturn.filter((i) => !!i) return toReturn.filter((i) => !!i) as (LineItem & OrdersReturnItem)[]
} }
/** /**
* @param {Object} selector - the query object for find * @param selector - the query object for find
* @param {object} config - the config object for find * @param config - the config object for find
* @return {Promise} the result of the find operation * @return the result of the find operation
*/ */
list( list(
selector, selector: Selector<Return>,
config = { skip: 0, take: 50, order: { created_at: "DESC" } } config: FindConfig<Return> = {
) { skip: 0,
take: 50,
order: { created_at: "DESC" },
}
): Promise<Return[]> {
const returnRepo = this.manager_.getCustomRepository(this.returnRepository_) const returnRepo = this.manager_.getCustomRepository(this.returnRepository_)
const query = this.buildQuery_(selector, config) const query = buildQuery(selector, config)
return returnRepo.find(query) return returnRepo.find(query)
} }
/** /**
* Cancels a return if possible. Returns can be canceled if it has not been received. * Cancels a return if possible. Returns can be canceled if it has not been received.
* @param {string} returnId - the id of the return to cancel. * @param returnId - the id of the return to cancel.
* @return {Promise<Return>} the updated Return * @return the updated Return
*/ */
async cancel(returnId) { async cancel(returnId: string): Promise<Return | never> {
return this.atomicPhase_(async (manager) => { return await this.atomicPhase_(async (manager) => {
const ret = await this.retrieve(returnId) const ret = await this.retrieve(returnId)
if (ret.status === "received") { if (ret.status === ReturnStatus.RECEIVED) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_ALLOWED, MedusaError.Types.NOT_ALLOWED,
"Can't cancel a return which has been returned" "Can't cancel a return which has been returned"
@@ -145,10 +182,9 @@ class ReturnService extends BaseService {
const retRepo = manager.getCustomRepository(this.returnRepository_) const retRepo = manager.getCustomRepository(this.returnRepository_)
ret.status = "canceled" ret.status = ReturnStatus.CANCELED
const result = retRepo.save(ret) return await retRepo.save(ret)
return result
}) })
} }
@@ -156,13 +192,13 @@ class ReturnService extends BaseService {
* Checks that an order has the statuses necessary to complete a return. * Checks that an order has the statuses necessary to complete a return.
* fulfillment_status cannot be not_fulfilled or returned. * fulfillment_status cannot be not_fulfilled or returned.
* payment_status must be captured. * payment_status must be captured.
* @param {Order} order - the order to check statuses on * @param order - the order to check statuses on
* @throws when statuses are not sufficient for returns. * @throws when statuses are not sufficient for returns.
*/ */
validateReturnStatuses_(order) { protected validateReturnStatuses(order: Order): void | never {
if ( if (
order.fulfillment_status === "not_fulfilled" || order.fulfillment_status === FulfillmentStatus.NOT_FULFILLED ||
order.fulfillment_status === "returned" order.fulfillment_status === FulfillmentStatus.RETURNED
) { ) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_ALLOWED, MedusaError.Types.NOT_ALLOWED,
@@ -170,7 +206,7 @@ class ReturnService extends BaseService {
) )
} }
if (order.payment_status !== "captured") { if (order.payment_status !== PaymentStatus.CAPTURED) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_ALLOWED, MedusaError.Types.NOT_ALLOWED,
"Can't return an order with payment unprocessed" "Can't return an order with payment unprocessed"
@@ -182,14 +218,18 @@ class ReturnService extends BaseService {
* Checks that a given quantity of a line item can be returned. Fails if the * Checks that a given quantity of a line item can be returned. Fails if the
* item is undefined or if the returnable quantity of the item is lower, than * item is undefined or if the returnable quantity of the item is lower, than
* the quantity that is requested to be returned. * the quantity that is requested to be returned.
* @param {LineItem?} item - the line item to check has sufficient returnable * @param item - the line item to check has sufficient returnable
* quantity. * quantity.
* @param {number} quantity - the quantity that is requested to be returned. * @param quantity - the quantity that is requested to be returned.
* @param {object} additional - the quantity that is requested to be returned. * @param additional - the quantity that is requested to be returned.
* @return {LineItem} a line item where the quantity is set to the requested * @return a line item where the quantity is set to the requested
* return quantity. * return quantity.
*/ */
validateReturnLineItem_(item, quantity, additional) { protected validateReturnLineItem(
item?: LineItem,
quantity = 0,
additional: { reason_id?: string; note?: string } = {}
): DeepPartial<LineItem> {
if (!item) { if (!item) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
@@ -205,13 +245,13 @@ class ReturnService extends BaseService {
) )
} }
const toReturn = { const toReturn: DeepPartial<ReturnItem> = {
...item, ...item,
quantity, quantity,
} }
if ("reason_id" in additional) { if ("reason_id" in additional) {
toReturn.reason_id = additional.reason_id toReturn.reason_id = additional.reason_id as string
} }
if ("note" in additional) { if ("note" in additional) {
@@ -223,17 +263,19 @@ class ReturnService extends BaseService {
/** /**
* Retrieves a return by its id. * Retrieves a return by its id.
* @param {string} id - the id of the return to retrieve * @param id - the id of the return to retrieve
* @param {object} config - the config object * @param config - the config object
* @return {Return} the return * @return the return
*/ */
async retrieve(id, config = {}) { async retrieve(
id: string,
config: FindConfig<Return> = {}
): Promise<Return | never> {
const returnRepository = this.manager_.getCustomRepository( const returnRepository = this.manager_.getCustomRepository(
this.returnRepository_ this.returnRepository_
) )
const validatedId = this.validateId_(id) const query = buildQuery({ id }, config)
const query = this.buildQuery_({ id: validatedId }, config)
const returnObj = await returnRepository.findOne(query) const returnObj = await returnRepository.findOne(query)
@@ -246,16 +288,17 @@ class ReturnService extends BaseService {
return returnObj return returnObj
} }
async retrieveBySwap(swapId, relations = []) { async retrieveBySwap(
swapId: string,
relations: string[] = []
): Promise<Return | never> {
const returnRepository = this.manager_.getCustomRepository( const returnRepository = this.manager_.getCustomRepository(
this.returnRepository_ this.returnRepository_
) )
const validatedId = this.validateId_(swapId)
const returnObj = await returnRepository.findOne({ const returnObj = await returnRepository.findOne({
where: { where: {
swap_id: validatedId, swap_id: swapId,
}, },
relations, relations,
}) })
@@ -266,11 +309,12 @@ class ReturnService extends BaseService {
`Return with swa_id: ${swapId} was not found` `Return with swa_id: ${swapId} was not found`
) )
} }
return returnObj return returnObj
} }
async update(returnId, update) { async update(returnId: string, update: UpdateReturnInput): Promise<Return> {
return this.atomicPhase_(async (manager) => { return await this.atomicPhase_(async (manager) => {
const ret = await this.retrieve(returnId) const ret = await this.retrieve(returnId)
if (ret.status === "canceled") { if (ret.status === "canceled") {
@@ -283,7 +327,7 @@ class ReturnService extends BaseService {
const { metadata, ...rest } = update const { metadata, ...rest } = update
if (metadata) { if (metadata) {
ret.metadata = this.setMetadata_(ret, metadata) ret.metadata = setMetadata(ret, metadata)
} }
for (const [key, value] of Object.entries(rest)) { for (const [key, value] of Object.entries(rest)) {
@@ -291,8 +335,7 @@ class ReturnService extends BaseService {
} }
const retRepo = manager.getCustomRepository(this.returnRepository_) const retRepo = manager.getCustomRepository(this.returnRepository_)
const result = await retRepo.save(ret) return await retRepo.save(ret)
return result
}) })
} }
@@ -300,26 +343,27 @@ class ReturnService extends BaseService {
* Creates a return request for an order, with given items, and a shipping * Creates a return request for an order, with given items, and a shipping
* method. If no refund amount is provided the refund amount is calculated from * method. If no refund amount is provided the refund amount is calculated from
* the return lines and the shipping cost. * the return lines and the shipping cost.
* @param {object} data - data to use for the return e.g. shipping_method, * @param data - data to use for the return e.g. shipping_method,
* items or refund_amount * items or refund_amount
* @param {object} orderLike - order object * @return the created return
* @return {Promise<Return>} the created return
*/ */
async create(data) { async create(data: CreateReturnInput): Promise<Return | never> {
return this.atomicPhase_(async (manager) => { return await this.atomicPhase_(async (manager) => {
const returnRepository = manager.getCustomRepository( const returnRepository = manager.getCustomRepository(
this.returnRepository_ this.returnRepository_
) )
const orderId = data.order_id const orderId = data.order_id
if (data.swap_id) { if (data.swap_id) {
delete data.order_id delete (data as Partial<CreateReturnInput>).order_id
} }
for (const item of data.items) { for (const item of data.items ?? []) {
const line = await this.lineItemService_.retrieve(item.item_id, { const line = await this.lineItemService_
relations: ["order", "swap", "claim_order"], .withTransaction(manager)
}) .retrieve(item.item_id, {
relations: ["order", "swap", "claim_order"],
})
if ( if (
line.order?.canceled_at || line.order?.canceled_at ||
@@ -351,10 +395,10 @@ class ReturnService extends BaseService {
], ],
}) })
const returnLines = await this.getFulfillmentItems_( const returnLines = await this.getFulfillmentItems(
order, order,
data.items, data.items ?? [],
this.validateReturnLineItem_ this.validateReturnLineItem
) )
let toRefund = data.refund_amount let toRefund = data.refund_amount
@@ -363,7 +407,7 @@ class ReturnService extends BaseService {
// refundable // refundable
const refundable = order.refundable_amount const refundable = order.refundable_amount
if (toRefund > refundable) { if (toRefund! > refundable) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
"Cannot refund more than the original payment" "Cannot refund more than the original payment"
@@ -371,7 +415,7 @@ class ReturnService extends BaseService {
} }
} else { } else {
// Merchant hasn't specified refund amount so we calculate it // Merchant hasn't specified refund amount so we calculate it
toRefund = await this.totalsService_.getRefundTotal(order, returnLines) toRefund = this.totalsService_.getRefundTotal(order, returnLines)
} }
const method = data.shipping_method const method = data.shipping_method
@@ -379,14 +423,16 @@ class ReturnService extends BaseService {
const returnObject = { const returnObject = {
...data, ...data,
status: "requested", status: ReturnStatus.REQUESTED,
refund_amount: Math.floor(toRefund), refund_amount: Math.floor(toRefund!),
} }
const returnReasons = await this.returnReasonService_.list( const returnReasons = await this.returnReasonService_
{ id: [...returnLines.map((rl) => rl.reason_id)] }, .withTransaction(manager)
{ relations: ["return_reason_children"] } .list(
) { id: [...returnLines.map((rl) => rl.reason_id as string)] },
{ relations: ["return_reason_children"] }
)
if (returnReasons.some((rr) => rr.return_reason_children?.length > 0)) { if (returnReasons.some((rr) => rr.return_reason_children?.length > 0)) {
throw new MedusaError( throw new MedusaError(
@@ -404,14 +450,13 @@ class ReturnService extends BaseService {
reason_id: i.reason_id, reason_id: i.reason_id,
note: i.note, note: i.note,
metadata: i.metadata, metadata: i.metadata,
no_notification: data.no_notification,
}) })
) )
const created = await returnRepository.create(returnObject) const created = (await returnRepository.create(returnObject)) as Return
const result = await returnRepository.save(created) const result = await returnRepository.save(created)
if (method) { if (method && method.option_id) {
const shippingMethod = await this.shippingOptionService_ const shippingMethod = await this.shippingOptionService_
.withTransaction(manager) .withTransaction(manager)
.createShippingMethod( .createShippingMethod(
@@ -439,7 +484,7 @@ class ReturnService extends BaseService {
) )
if (typeof data.refund_amount === "undefined") { if (typeof data.refund_amount === "undefined") {
result.refund_amount = toRefund - shippingTotal result.refund_amount = toRefund! - shippingTotal
return await returnRepository.save(result) return await returnRepository.save(result)
} }
} }
@@ -448,8 +493,8 @@ class ReturnService extends BaseService {
}) })
} }
fulfill(returnId) { async fulfill(returnId: string): Promise<Return | never> {
return this.atomicPhase_(async (manager) => { return await this.atomicPhase_(async (manager) => {
const returnOrder = await this.retrieve(returnId, { const returnOrder = await this.retrieve(returnId, {
relations: [ relations: [
"items", "items",
@@ -470,7 +515,7 @@ class ReturnService extends BaseService {
const returnData = { ...returnOrder } const returnData = { ...returnOrder }
const items = await this.lineItemService_.list( const items = await this.lineItemService_.withTransaction(manager).list(
{ {
id: returnOrder.items.map(({ item_id }) => item_id), id: returnOrder.items.map(({ item_id }) => item_id),
}, },
@@ -482,7 +527,7 @@ class ReturnService extends BaseService {
return { return {
...item, ...item,
item: found, item: found,
} } as ReturnItem
}) })
if (returnOrder.shipping_data) { if (returnOrder.shipping_data) {
@@ -496,14 +541,11 @@ class ReturnService extends BaseService {
return returnOrder return returnOrder
} }
const fulfillmentData = returnOrder.shipping_data =
await this.fulfillmentProviderService_.createReturn(returnData) await this.fulfillmentProviderService_.createReturn(returnData)
returnOrder.shipping_data = fulfillmentData
const returnRepo = manager.getCustomRepository(this.returnRepository_) const returnRepo = manager.getCustomRepository(this.returnRepository_)
const result = await returnRepo.save(returnOrder) return await returnRepo.save(returnOrder)
return result
}) })
} }
@@ -515,29 +557,29 @@ class ReturnService extends BaseService {
* retuned items are not matching the requested items. Setting the * retuned items are not matching the requested items. Setting the
* allowMismatch argument to true, will process the return, ignoring any * allowMismatch argument to true, will process the return, ignoring any
* mismatches. * mismatches.
* @param {string} return_id - the orderId to return to * @param returnId - the orderId to return to
* @param {Item[]} received_items - the items received after return. * @param receivedItems - the items received after return.
* @param {number | undefined} refund_amount - the amount to return * @param refundAmount - the amount to return
* @param {bool} allow_mismatch - whether to ignore return/received * @param allowMismatch - whether to ignore return/received
* product mismatch * product mismatch
* @return {Promise} the result of the update operation * @return the result of the update operation
*/ */
async receive( async receive(
return_id, returnId: string,
received_items, receivedItems: OrdersReturnItem[],
refund_amount, refundAmount?: number,
allow_mismatch = false allowMismatch = false
) { ): Promise<Return | never> {
return this.atomicPhase_(async (manager) => { return await this.atomicPhase_(async (manager) => {
const returnRepository = manager.getCustomRepository( const returnRepository = manager.getCustomRepository(
this.returnRepository_ this.returnRepository_
) )
const returnObj = await this.retrieve(return_id, { const returnObj = await this.retrieve(returnId, {
relations: ["items", "swap", "swap.additional_items"], relations: ["items", "swap", "swap.additional_items"],
}) })
if (returnObj.status === "canceled") { if (returnObj.status === ReturnStatus.CANCELED) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_ALLOWED, MedusaError.Types.NOT_ALLOWED,
"Cannot receive a canceled return" "Cannot receive a canceled return"
@@ -569,17 +611,17 @@ class ReturnService extends BaseService {
], ],
}) })
if (returnObj.status === "received") { if (returnObj.status === ReturnStatus.RECEIVED) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_ALLOWED, MedusaError.Types.NOT_ALLOWED,
`Return with id ${return_id} has already been received` `Return with id ${returnId} has already been received`
) )
} }
const returnLines = await this.getFulfillmentItems_( const returnLines = await this.getFulfillmentItems(
order, order,
received_items, receivedItems,
this.validateReturnLineItem_ this.validateReturnLineItem
) )
const newLines = returnLines.map((l) => { const newLines = returnLines.map((l) => {
@@ -604,15 +646,14 @@ class ReturnService extends BaseService {
} }
}) })
let returnStatus = "received" let returnStatus = ReturnStatus.RECEIVED
const isMatching = newLines.every((l) => l.is_requested) const isMatching = newLines.every((l) => l.is_requested)
if (!isMatching && !allow_mismatch) { if (!isMatching && !allowMismatch) {
// Should update status returnStatus = ReturnStatus.REQUIRES_ACTION
returnStatus = "requires_action"
} }
const totalRefundableAmount = refund_amount || returnObj.refund_amount const totalRefundableAmount = refundAmount || returnObj.refund_amount
const now = new Date() const now = new Date()
const updateObj = { const updateObj = {
+30
View File
@@ -0,0 +1,30 @@
type OrdersReturnItem = {
item_id: string
quantity: number
reason_id?: string
note?: string
}
export type CreateReturnInput = {
order_id: string
swap_id?: string
claim_order_id?: string
items?: OrdersReturnItem[]
shipping_method?: {
option_id?: string
price?: number
}
no_notification?: boolean
metadata?: Record<string, unknown>
refund_amount?: number
}
export type UpdateReturnInput = {
items?: OrdersReturnItem[]
shipping_method?: {
option_id: string
price?: number
}
no_notification?: boolean
metadata?: Record<string, unknown>
}