From 844d7d1f5f42a850ced5e70431b7590eafa390ac Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Fri, 19 Aug 2022 17:26:12 +0200 Subject: [PATCH] feat(medusa): Migrate Return service to ts (#1926) --- .../api/routes/admin/orders/request-return.ts | 35 +- .../api/routes/admin/returns/list-returns.ts | 4 +- .../api/routes/store/returns/create-return.ts | 138 +++---- packages/medusa/src/services/claim.ts | 14 +- packages/medusa/src/services/line-item.ts | 7 +- .../src/services/{return.js => return.ts} | 373 ++++++++++-------- packages/medusa/src/types/return.ts | 30 ++ 7 files changed, 338 insertions(+), 263 deletions(-) rename packages/medusa/src/services/{return.js => return.ts} (62%) create mode 100644 packages/medusa/src/types/return.ts diff --git a/packages/medusa/src/api/routes/admin/orders/request-return.ts b/packages/medusa/src/api/routes/admin/orders/request-return.ts index afae0127d6..4fa67a96fc 100644 --- a/packages/medusa/src/api/routes/admin/orders/request-return.ts +++ b/packages/medusa/src/api/routes/admin/orders/request-return.ts @@ -1,8 +1,3 @@ -import { - EventBusService, - OrderService, - ReturnService, -} from "../../../../services" import { IsArray, IsBoolean, @@ -12,13 +7,19 @@ import { ValidateNested, } from "class-validator" 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 { validator } from "../../../../utils/validator" +import { MedusaError } from "medusa-core-utils" import { EntityManager } from "typeorm" +import { Order, Return } from "../../../../models" +import { OrdersReturnItem } from "../../../../types/orders" import { isDefined } from "../../../../utils" +import { validator } from "../../../../utils/validator" /** * @oas [post] /orders/{id}/return @@ -197,7 +198,7 @@ export default async (req, res) => { const { key, error } = await idempotencyKeyService .withTransaction(transactionManager) .workStage(idempotencyKey.idempotency_key, async (manager) => { - let order = await orderService + let order: Order | Return = await orderService .withTransaction(manager) .retrieve(id, { relations: ["returns"] }) @@ -206,22 +207,24 @@ export default async (req, res) => { * and register it as received. */ if (value.receive_now) { - let ret = await returnService.withTransaction(manager).list({ - idempotency_key: idempotencyKey.idempotency_key, - }) + const returns = await returnService + .withTransaction(manager) + .list({ + idempotency_key: idempotencyKey.idempotency_key, + }) - if (!ret.length) { + if (!returns.length) { throw new MedusaError( MedusaError.Types.INVALID_DATA, `Return not found` ) } - ret = ret[0] + const returnOrder = returns[0] order = await returnService .withTransaction(manager) - .receive(ret.id, value.items, value.refund) + .receive(returnOrder.id, value.items, value.refund) } order = await orderService @@ -278,7 +281,7 @@ export default async (req, res) => { } type ReturnObj = { - order_id?: string + order_id: string idempotency_key?: string items?: OrdersReturnItem[] shipping_method?: ReturnShipping diff --git a/packages/medusa/src/api/routes/admin/returns/list-returns.ts b/packages/medusa/src/api/routes/admin/returns/list-returns.ts index bd2f709b40..3f0d3124ab 100644 --- a/packages/medusa/src/api/routes/admin/returns/list-returns.ts +++ b/packages/medusa/src/api/routes/admin/returns/list-returns.ts @@ -3,6 +3,8 @@ import { IsNumber, IsOptional } from "class-validator" import { ReturnService } from "../../../../services" import { Type } from "class-transformer" import { validator } from "../../../../utils/validator" +import { FindConfig } from "../../../../types/common" +import { Return } from "../../../../models" /** * @oas [get] /returns @@ -47,7 +49,7 @@ export default async (req, res) => { skip: validated.offset, take: validated.limit, order: { created_at: "DESC" }, - } + } as FindConfig const returns = await returnService.list(selector, { ...listConfig }) diff --git a/packages/medusa/src/api/routes/store/returns/create-return.ts b/packages/medusa/src/api/routes/store/returns/create-return.ts index 50229128d9..e93538dcfc 100644 --- a/packages/medusa/src/api/routes/store/returns/create-return.ts +++ b/packages/medusa/src/api/routes/store/returns/create-return.ts @@ -8,14 +8,14 @@ import { ValidateNested, } 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 IdempotencyKeyService from "../../../../services/idempotency-key" -import { MedusaError } from "medusa-core-utils" import OrderService from "../../../../services/order" import ReturnService from "../../../../services/return" -import { Type } from "class-transformer" import { validator } from "../../../../utils/validator" -import { EntityManager } from "typeorm"; /** * @oas [post] /returns @@ -87,12 +87,9 @@ export default async (req, res) => { let idempotencyKey try { await manager.transaction(async (transactionManager) => { - idempotencyKey = await idempotencyKeyService.withTransaction(transactionManager).initializeRequest( - headerKey, - req.method, - req.params, - req.path - ) + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .initializeRequest(headerKey, req.method, req.params, req.path) }) } catch (error) { res.status(409).send("Failed to create idempotency key") @@ -116,48 +113,45 @@ export default async (req, res) => { await manager.transaction(async (transactionManager) => { const { key, error } = await idempotencyKeyService .withTransaction(transactionManager) - .workStage( - idempotencyKey.idempotency_key, - async (manager) => { - const order = await orderService - .withTransaction(manager) - .retrieve(returnDto.order_id, { - select: ["refunded_total", "total"], - relations: ["items"], - }) + .workStage(idempotencyKey.idempotency_key, async (manager) => { + const order = await orderService + .withTransaction(manager) + .retrieve(returnDto.order_id, { + select: ["refunded_total", "total"], + relations: ["items"], + }) - const returnObj: any = { - order_id: returnDto.order_id, - idempotency_key: idempotencyKey.idempotency_key, - 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", - } + const returnObj: any = { + order_id: returnDto.order_id, + idempotency_key: idempotencyKey.idempotency_key, + 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 (error) { inProgress = false @@ -173,10 +167,10 @@ export default async (req, res) => { await manager.transaction(async (transactionManager) => { const { key, error } = await idempotencyKeyService .withTransaction(transactionManager) - .workStage( - idempotencyKey.idempotency_key, - async (manager) => { - let ret = await returnService.withTransaction(manager).list( + .workStage(idempotencyKey.idempotency_key, async (manager) => { + const returnOrders = await returnService + .withTransaction(manager) + .list( { idempotency_key: idempotencyKey.idempotency_key, }, @@ -184,20 +178,19 @@ export default async (req, res) => { relations: ["items", "items.reason"], } ) - if (!ret.length) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - `Return not found` - ) - } - ret = ret[0] - - return { - response_code: 200, - response_body: { return: ret }, - } + if (!returnOrders.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Return not found` + ) } - ) + const returnOrder = returnOrders[0] + + return { + response_code: 200, + response_body: { return: returnOrder }, + } + }) if (error) { inProgress = false @@ -218,14 +211,11 @@ export default async (req, res) => { await manager.transaction(async (transactionManager) => { idempotencyKey = await idempotencyKeyService .withTransaction(transactionManager) - .update( - idempotencyKey.idempotency_key, - { - recovery_point: "finished", - response_code: 500, - response_body: { message: "Unknown recovery point" }, - } - ) + .update(idempotencyKey.idempotency_key, { + recovery_point: "finished", + response_code: 500, + response_body: { message: "Unknown recovery point" }, + }) }) break } diff --git a/packages/medusa/src/services/claim.ts b/packages/medusa/src/services/claim.ts index b911896f98..12089add51 100644 --- a/packages/medusa/src/services/claim.ts +++ b/packages/medusa/src/services/claim.ts @@ -18,6 +18,7 @@ import { ClaimType, FulfillmentItem, LineItem, + ReturnItem, } from "../models" import { ClaimRepository } from "../repositories/claim" import { DeepPartial, EntityManager } from "typeorm" @@ -425,11 +426,14 @@ export default class ClaimService extends TransactionBaseService { await this.returnService_.withTransaction(transactionManager).create({ order_id: order.id, claim_order_id: result.id, - items: claim_items.map((ci) => ({ - item_id: ci.item_id, - quantity: ci.quantity, - metadata: (ci as any).metadata, - })), + items: claim_items.map( + (ci) => + ({ + item_id: ci.item_id, + quantity: ci.quantity, + metadata: (ci as any).metadata, + } as ReturnItem) + ), shipping_method: return_shipping, no_notification: evaluatedNoNotification, }) diff --git a/packages/medusa/src/services/line-item.ts b/packages/medusa/src/services/line-item.ts index 9f59ebf638..ad2f18459d 100644 --- a/packages/medusa/src/services/line-item.ts +++ b/packages/medusa/src/services/line-item.ts @@ -14,6 +14,7 @@ import { LineItem } from "../models/line-item" import LineItemAdjustmentService from "./line-item-adjustment" import { Cart } from "../models/cart" import { LineItemAdjustment } from "../models/line-item-adjustment" +import { FindConfig } from "../types/common" type InjectedDependencies = { manager: EntityManager @@ -89,7 +90,11 @@ class LineItemService extends BaseService { async list( selector, - config = { skip: 0, take: 50, order: { created_at: "DESC" } } + config: FindConfig = { + skip: 0, + take: 50, + order: { created_at: "DESC" }, + } ): Promise { const manager = this.manager_ const lineItemRepo = manager.getCustomRepository(this.lineItemRepository_) diff --git a/packages/medusa/src/services/return.js b/packages/medusa/src/services/return.ts similarity index 62% rename from packages/medusa/src/services/return.js rename to packages/medusa/src/services/return.ts index 24ca142212..30308c9e8a 100644 --- a/packages/medusa/src/services/return.js +++ b/packages/medusa/src/services/return.ts @@ -1,12 +1,66 @@ +import { isDefined } from "class-validator" import { MedusaError } from "medusa-core-utils" -import { BaseService } from "medusa-interfaces" -import { isDefined } from "../utils" +import { DeepPartial, EntityManager } from "typeorm" +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 + +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({ manager, totalsService, @@ -19,75 +73,54 @@ class ReturnService extends BaseService { fulfillmentProviderService, inventoryService, orderService, - }) { - super() - - /** @private @const {EntityManager} */ - this.manager_ = manager - - /** @private @const {TotalsService} */ - this.totalsService_ = totalsService - - /** @private @const {ReturnRepository} */ - this.returnRepository_ = returnRepository - - /** @private @const {ReturnItemRepository} */ - 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_, + }: InjectedDependencies) { + super({ + manager, + totalsService, + lineItemService, + returnRepository, + returnItemRepository, + shippingOptionService, + returnReasonService, + taxProviderService, + fulfillmentProviderService, + inventoryService, + orderService, }) - cloned.transactionManager_ = transactionManager - - return cloned + this.manager_ = manager + this.totalsService_ = totalsService + 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 - * @param {Order} order - the order to get line items from - * @param {{ item_id: string, quantity: number }} items - the items to get - * @param {function} transformer - a function to apply to each of the items + * @param order - the order to get line items from + * @param items - the items to get + * @param transformer - a function to apply to each of the items * retrieved from the order, should return a line item. If the transformer * returns an undefined value the line item will be filtered from the * returned array. - * @return {Promise>} 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] // 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 {object} config - the config object for find - * @return {Promise} the result of the find operation + * @param selector - the query object for find + * @param config - the config object for find + * @return the result of the find operation */ list( - selector, - config = { skip: 0, take: 50, order: { created_at: "DESC" } } - ) { + selector: Selector, + config: FindConfig = { + skip: 0, + take: 50, + order: { created_at: "DESC" }, + } + ): Promise { const returnRepo = this.manager_.getCustomRepository(this.returnRepository_) - const query = this.buildQuery_(selector, config) + const query = buildQuery(selector, config) return returnRepo.find(query) } /** * 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. - * @return {Promise} the updated Return + * @param returnId - the id of the return to cancel. + * @return the updated Return */ - async cancel(returnId) { - return this.atomicPhase_(async (manager) => { + async cancel(returnId: string): Promise { + return await this.atomicPhase_(async (manager) => { const ret = await this.retrieve(returnId) - if (ret.status === "received") { + if (ret.status === ReturnStatus.RECEIVED) { throw new MedusaError( MedusaError.Types.NOT_ALLOWED, "Can't cancel a return which has been returned" @@ -145,10 +182,9 @@ class ReturnService extends BaseService { const retRepo = manager.getCustomRepository(this.returnRepository_) - ret.status = "canceled" + ret.status = ReturnStatus.CANCELED - const result = retRepo.save(ret) - return result + return await retRepo.save(ret) }) } @@ -156,13 +192,13 @@ class ReturnService extends BaseService { * Checks that an order has the statuses necessary to complete a return. * fulfillment_status cannot be not_fulfilled or returned. * 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. */ - validateReturnStatuses_(order) { + protected validateReturnStatuses(order: Order): void | never { if ( - order.fulfillment_status === "not_fulfilled" || - order.fulfillment_status === "returned" + order.fulfillment_status === FulfillmentStatus.NOT_FULFILLED || + order.fulfillment_status === FulfillmentStatus.RETURNED ) { throw new MedusaError( 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( MedusaError.Types.NOT_ALLOWED, "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 * item is undefined or if the returnable quantity of the item is lower, than * 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. - * @param {number} quantity - the quantity that is requested to be returned. - * @param {object} additional - the quantity that is requested to be returned. - * @return {LineItem} a line item where the quantity is set to the requested + * @param quantity - the quantity that is requested to be returned. + * @param additional - the quantity that is requested to be returned. + * @return a line item where the quantity is set to the requested * return quantity. */ - validateReturnLineItem_(item, quantity, additional) { + protected validateReturnLineItem( + item?: LineItem, + quantity = 0, + additional: { reason_id?: string; note?: string } = {} + ): DeepPartial { if (!item) { throw new MedusaError( MedusaError.Types.INVALID_DATA, @@ -205,13 +245,13 @@ class ReturnService extends BaseService { ) } - const toReturn = { + const toReturn: DeepPartial = { ...item, quantity, } if ("reason_id" in additional) { - toReturn.reason_id = additional.reason_id + toReturn.reason_id = additional.reason_id as string } if ("note" in additional) { @@ -223,17 +263,19 @@ class ReturnService extends BaseService { /** * Retrieves a return by its id. - * @param {string} id - the id of the return to retrieve - * @param {object} config - the config object - * @return {Return} the return + * @param id - the id of the return to retrieve + * @param config - the config object + * @return the return */ - async retrieve(id, config = {}) { + async retrieve( + id: string, + config: FindConfig = {} + ): Promise { const returnRepository = this.manager_.getCustomRepository( this.returnRepository_ ) - const validatedId = this.validateId_(id) - const query = this.buildQuery_({ id: validatedId }, config) + const query = buildQuery({ id }, config) const returnObj = await returnRepository.findOne(query) @@ -246,16 +288,17 @@ class ReturnService extends BaseService { return returnObj } - async retrieveBySwap(swapId, relations = []) { + async retrieveBySwap( + swapId: string, + relations: string[] = [] + ): Promise { const returnRepository = this.manager_.getCustomRepository( this.returnRepository_ ) - const validatedId = this.validateId_(swapId) - const returnObj = await returnRepository.findOne({ where: { - swap_id: validatedId, + swap_id: swapId, }, relations, }) @@ -266,11 +309,12 @@ class ReturnService extends BaseService { `Return with swa_id: ${swapId} was not found` ) } + return returnObj } - async update(returnId, update) { - return this.atomicPhase_(async (manager) => { + async update(returnId: string, update: UpdateReturnInput): Promise { + return await this.atomicPhase_(async (manager) => { const ret = await this.retrieve(returnId) if (ret.status === "canceled") { @@ -283,7 +327,7 @@ class ReturnService extends BaseService { const { metadata, ...rest } = update if (metadata) { - ret.metadata = this.setMetadata_(ret, metadata) + ret.metadata = setMetadata(ret, metadata) } for (const [key, value] of Object.entries(rest)) { @@ -291,8 +335,7 @@ class ReturnService extends BaseService { } const retRepo = manager.getCustomRepository(this.returnRepository_) - const result = await retRepo.save(ret) - return result + return await retRepo.save(ret) }) } @@ -300,26 +343,27 @@ class ReturnService extends BaseService { * 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 * 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 - * @param {object} orderLike - order object - * @return {Promise} the created return + * @return the created return */ - async create(data) { - return this.atomicPhase_(async (manager) => { + async create(data: CreateReturnInput): Promise { + return await this.atomicPhase_(async (manager) => { const returnRepository = manager.getCustomRepository( this.returnRepository_ ) const orderId = data.order_id if (data.swap_id) { - delete data.order_id + delete (data as Partial).order_id } - for (const item of data.items) { - const line = await this.lineItemService_.retrieve(item.item_id, { - relations: ["order", "swap", "claim_order"], - }) + for (const item of data.items ?? []) { + const line = await this.lineItemService_ + .withTransaction(manager) + .retrieve(item.item_id, { + relations: ["order", "swap", "claim_order"], + }) if ( line.order?.canceled_at || @@ -351,10 +395,10 @@ class ReturnService extends BaseService { ], }) - const returnLines = await this.getFulfillmentItems_( + const returnLines = await this.getFulfillmentItems( order, - data.items, - this.validateReturnLineItem_ + data.items ?? [], + this.validateReturnLineItem ) let toRefund = data.refund_amount @@ -363,7 +407,7 @@ class ReturnService extends BaseService { // refundable const refundable = order.refundable_amount - if (toRefund > refundable) { + if (toRefund! > refundable) { throw new MedusaError( MedusaError.Types.INVALID_DATA, "Cannot refund more than the original payment" @@ -371,7 +415,7 @@ class ReturnService extends BaseService { } } else { // 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 @@ -379,14 +423,16 @@ class ReturnService extends BaseService { const returnObject = { ...data, - status: "requested", - refund_amount: Math.floor(toRefund), + status: ReturnStatus.REQUESTED, + refund_amount: Math.floor(toRefund!), } - const returnReasons = await this.returnReasonService_.list( - { id: [...returnLines.map((rl) => rl.reason_id)] }, - { relations: ["return_reason_children"] } - ) + const returnReasons = await this.returnReasonService_ + .withTransaction(manager) + .list( + { id: [...returnLines.map((rl) => rl.reason_id as string)] }, + { relations: ["return_reason_children"] } + ) if (returnReasons.some((rr) => rr.return_reason_children?.length > 0)) { throw new MedusaError( @@ -404,14 +450,13 @@ class ReturnService extends BaseService { reason_id: i.reason_id, note: i.note, 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) - if (method) { + if (method && method.option_id) { const shippingMethod = await this.shippingOptionService_ .withTransaction(manager) .createShippingMethod( @@ -439,7 +484,7 @@ class ReturnService extends BaseService { ) if (typeof data.refund_amount === "undefined") { - result.refund_amount = toRefund - shippingTotal + result.refund_amount = toRefund! - shippingTotal return await returnRepository.save(result) } } @@ -448,8 +493,8 @@ class ReturnService extends BaseService { }) } - fulfill(returnId) { - return this.atomicPhase_(async (manager) => { + async fulfill(returnId: string): Promise { + return await this.atomicPhase_(async (manager) => { const returnOrder = await this.retrieve(returnId, { relations: [ "items", @@ -470,7 +515,7 @@ class ReturnService extends BaseService { 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), }, @@ -482,7 +527,7 @@ class ReturnService extends BaseService { return { ...item, item: found, - } + } as ReturnItem }) if (returnOrder.shipping_data) { @@ -496,14 +541,11 @@ class ReturnService extends BaseService { return returnOrder } - const fulfillmentData = + returnOrder.shipping_data = await this.fulfillmentProviderService_.createReturn(returnData) - returnOrder.shipping_data = fulfillmentData - const returnRepo = manager.getCustomRepository(this.returnRepository_) - const result = await returnRepo.save(returnOrder) - return result + return await returnRepo.save(returnOrder) }) } @@ -515,29 +557,29 @@ class ReturnService extends BaseService { * retuned items are not matching the requested items. Setting the * allowMismatch argument to true, will process the return, ignoring any * mismatches. - * @param {string} return_id - the orderId to return to - * @param {Item[]} received_items - the items received after return. - * @param {number | undefined} refund_amount - the amount to return - * @param {bool} allow_mismatch - whether to ignore return/received + * @param returnId - the orderId to return to + * @param receivedItems - the items received after return. + * @param refundAmount - the amount to return + * @param allowMismatch - whether to ignore return/received * product mismatch - * @return {Promise} the result of the update operation + * @return the result of the update operation */ async receive( - return_id, - received_items, - refund_amount, - allow_mismatch = false - ) { - return this.atomicPhase_(async (manager) => { + returnId: string, + receivedItems: OrdersReturnItem[], + refundAmount?: number, + allowMismatch = false + ): Promise { + return await this.atomicPhase_(async (manager) => { const returnRepository = manager.getCustomRepository( this.returnRepository_ ) - const returnObj = await this.retrieve(return_id, { + const returnObj = await this.retrieve(returnId, { relations: ["items", "swap", "swap.additional_items"], }) - if (returnObj.status === "canceled") { + if (returnObj.status === ReturnStatus.CANCELED) { throw new MedusaError( MedusaError.Types.NOT_ALLOWED, "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( 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, - received_items, - this.validateReturnLineItem_ + receivedItems, + this.validateReturnLineItem ) 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) - if (!isMatching && !allow_mismatch) { - // Should update status - returnStatus = "requires_action" + if (!isMatching && !allowMismatch) { + returnStatus = ReturnStatus.REQUIRES_ACTION } - const totalRefundableAmount = refund_amount || returnObj.refund_amount + const totalRefundableAmount = refundAmount || returnObj.refund_amount const now = new Date() const updateObj = { diff --git a/packages/medusa/src/types/return.ts b/packages/medusa/src/types/return.ts new file mode 100644 index 0000000000..be5485607e --- /dev/null +++ b/packages/medusa/src/types/return.ts @@ -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 + refund_amount?: number +} + +export type UpdateReturnInput = { + items?: OrdersReturnItem[] + shipping_method?: { + option_id: string + price?: number + } + no_notification?: boolean + metadata?: Record +}