From 48aa09be234697d4a8690249170b26268a312639 Mon Sep 17 00:00:00 2001 From: "Carlos R. L. Rodrigues" <37986729+carlos-r-l-rodrigues@users.noreply.github.com> Date: Sun, 19 May 2024 14:51:08 -0300 Subject: [PATCH] Chore(order): endpoints - Part 1:N (#7360) --- packages/core/core-flows/src/index.ts | 3 +- .../src/order/steps/archive-orders.ts | 39 ++++ .../src/order/steps/complete-orders.ts | 39 ++++ .../core/core-flows/src/order/steps/index.ts | 2 + .../core/core-flows/src/order/utils/fields.ts | 23 ++ .../src/order/workflows/archive-orders.ts | 17 ++ .../src/order/workflows/complete-orders.ts | 20 ++ .../src/order/workflows/create-orders.ts | 201 +++++++----------- .../core-flows/src/order/workflows/index.ts | 4 +- packages/core/types/src/order/common.ts | 12 ++ packages/core/types/src/order/service.ts | 6 + .../api-v2/admin/orders/[id]/archive/route.ts | 34 +++ .../api-v2/admin/orders/[id]/cancel/route.ts | 25 +++ .../admin/orders/[id]/complete/route.ts | 34 +++ .../api-v2/admin/orders/[id]/refund/route.ts | 19 ++ .../src/api-v2/admin/orders/[id]/route.ts | 30 ++- .../[fulfillment_id]/cancel/route.ts | 25 +++ .../api-v2/admin/orders/fulfillments/route.ts | 25 +++ .../src/api-v2/admin/orders/middlewares.ts | 64 +++++- .../medusa/src/api-v2/admin/orders/route.ts | 10 +- .../src/api-v2/admin/orders/validators.ts | 10 + .../api-v2/admin/return-reasons/[id]/route.ts | 84 ++++++++ .../admin/return-reasons/middlewares.ts | 53 +++++ .../admin/return-reasons/query-config.ts | 34 +++ .../src/api-v2/admin/return-reasons/route.ts | 71 +++++++ .../api-v2/admin/return-reasons/validators.ts | 71 +++++++ .../api-v2/admin/returns/[id]/cancel/route.ts | 35 +++ .../admin/returns/[id]/receive/route.ts | 36 ++++ .../src/api-v2/admin/returns/[id]/route.ts | 35 +++ .../src/api-v2/admin/returns/middlewares.ts | 49 +++++ .../src/api-v2/admin/returns/query-config.ts | 62 ++++++ .../medusa/src/api-v2/admin/returns/route.ts | 56 +++++ .../src/api-v2/admin/returns/validators.ts | 63 ++++++ .../[id]/fulfillment-sets/route.ts | 9 +- .../admin/stock-locations/[id]/route.ts | 16 +- .../src/api-v2/admin/stock-locations/route.ts | 11 +- packages/medusa/src/api-v2/middlewares.ts | 16 +- .../src/api-v2/store/orders/[id]/route.ts | 6 +- .../api-v2/store/return-reasons/[id]/route.ts | 25 +++ .../store/return-reasons/middlewares.ts | 27 +++ .../store/return-reasons/query-config.ts | 23 ++ .../src/api-v2/store/return-reasons/route.ts | 29 +++ .../api-v2/store/return-reasons/validators.ts | 12 ++ .../medusa/src/api-v2/store/return/route.ts | 22 +- .../src/api-v2/store/return/validators.ts | 12 +- .../src/services/order-module-service.ts | 109 ++++++++++ 46 files changed, 1455 insertions(+), 153 deletions(-) create mode 100644 packages/core/core-flows/src/order/steps/archive-orders.ts create mode 100644 packages/core/core-flows/src/order/steps/complete-orders.ts create mode 100644 packages/core/core-flows/src/order/utils/fields.ts create mode 100644 packages/core/core-flows/src/order/workflows/archive-orders.ts create mode 100644 packages/core/core-flows/src/order/workflows/complete-orders.ts create mode 100644 packages/medusa/src/api-v2/admin/orders/[id]/archive/route.ts create mode 100644 packages/medusa/src/api-v2/admin/orders/[id]/cancel/route.ts create mode 100644 packages/medusa/src/api-v2/admin/orders/[id]/complete/route.ts create mode 100644 packages/medusa/src/api-v2/admin/orders/[id]/refund/route.ts create mode 100644 packages/medusa/src/api-v2/admin/orders/fulfillments/[fulfillment_id]/cancel/route.ts create mode 100644 packages/medusa/src/api-v2/admin/orders/fulfillments/route.ts create mode 100644 packages/medusa/src/api-v2/admin/return-reasons/[id]/route.ts create mode 100644 packages/medusa/src/api-v2/admin/return-reasons/middlewares.ts create mode 100644 packages/medusa/src/api-v2/admin/return-reasons/query-config.ts create mode 100644 packages/medusa/src/api-v2/admin/return-reasons/route.ts create mode 100644 packages/medusa/src/api-v2/admin/return-reasons/validators.ts create mode 100644 packages/medusa/src/api-v2/admin/returns/[id]/cancel/route.ts create mode 100644 packages/medusa/src/api-v2/admin/returns/[id]/receive/route.ts create mode 100644 packages/medusa/src/api-v2/admin/returns/[id]/route.ts create mode 100644 packages/medusa/src/api-v2/admin/returns/middlewares.ts create mode 100644 packages/medusa/src/api-v2/admin/returns/query-config.ts create mode 100644 packages/medusa/src/api-v2/admin/returns/route.ts create mode 100644 packages/medusa/src/api-v2/admin/returns/validators.ts create mode 100644 packages/medusa/src/api-v2/store/return-reasons/[id]/route.ts create mode 100644 packages/medusa/src/api-v2/store/return-reasons/middlewares.ts create mode 100644 packages/medusa/src/api-v2/store/return-reasons/query-config.ts create mode 100644 packages/medusa/src/api-v2/store/return-reasons/route.ts create mode 100644 packages/medusa/src/api-v2/store/return-reasons/validators.ts diff --git a/packages/core/core-flows/src/index.ts b/packages/core/core-flows/src/index.ts index 9496cd1307..a07bb32664 100644 --- a/packages/core/core-flows/src/index.ts +++ b/packages/core/core-flows/src/index.ts @@ -18,8 +18,9 @@ export * from "./pricing" export * from "./product" export * from "./product-category" export * from "./promotion" -export * from "./reservation" export * from "./region" +export * from "./reservation" +export * from "./return-reason" export * from "./sales-channel" export * from "./shipping-options" export * from "./shipping-profile" diff --git a/packages/core/core-flows/src/order/steps/archive-orders.ts b/packages/core/core-flows/src/order/steps/archive-orders.ts new file mode 100644 index 0000000000..a400d484b4 --- /dev/null +++ b/packages/core/core-flows/src/order/steps/archive-orders.ts @@ -0,0 +1,39 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { IOrderModuleService } from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +type CompleteOrdersStepInput = { + order_ids: string[] +} + +export const archiveOrdersStepId = "archive-orders" +export const archiveOrdersStep = createStep( + archiveOrdersStepId, + async (data: CompleteOrdersStepInput, { container }) => { + const service = container.resolve( + ModuleRegistrationName.ORDER + ) + + const archived = await service.archive(data.order_ids) + return new StepResponse( + archived, + archived.map((store) => { + return { + id: store.id, + status: store.status, + } + }) + ) + }, + async (archived, { container }) => { + if (!archived?.length) { + return + } + + const service = container.resolve( + ModuleRegistrationName.ORDER + ) + + await service.update(archived) + } +) diff --git a/packages/core/core-flows/src/order/steps/complete-orders.ts b/packages/core/core-flows/src/order/steps/complete-orders.ts new file mode 100644 index 0000000000..ad49f14df7 --- /dev/null +++ b/packages/core/core-flows/src/order/steps/complete-orders.ts @@ -0,0 +1,39 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { IOrderModuleService } from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +type CompleteOrdersStepInput = { + order_ids: string[] +} + +export const completeOrdersStepId = "complete-orders" +export const completeOrdersStep = createStep( + completeOrdersStepId, + async (data: CompleteOrdersStepInput, { container }) => { + const service = container.resolve( + ModuleRegistrationName.ORDER + ) + + const completed = await service.completeOrder(data.order_ids) + return new StepResponse( + completed, + completed.map((store) => { + return { + id: store.id, + status: store.status, + } + }) + ) + }, + async (completed, { container }) => { + if (!completed?.length) { + return + } + + const service = container.resolve( + ModuleRegistrationName.ORDER + ) + + await service.update(completed) + } +) diff --git a/packages/core/core-flows/src/order/steps/index.ts b/packages/core/core-flows/src/order/steps/index.ts index dd30b3c0ca..77bb6c9e78 100644 --- a/packages/core/core-flows/src/order/steps/index.ts +++ b/packages/core/core-flows/src/order/steps/index.ts @@ -1,3 +1,5 @@ +export * from "./archive-orders" +export * from "./complete-orders" export * from "./create-orders" export * from "./get-item-tax-lines" export * from "./link-order-payment-collection" diff --git a/packages/core/core-flows/src/order/utils/fields.ts b/packages/core/core-flows/src/order/utils/fields.ts new file mode 100644 index 0000000000..5dfdc29b17 --- /dev/null +++ b/packages/core/core-flows/src/order/utils/fields.ts @@ -0,0 +1,23 @@ +export const productVariantsFields = [ + "id", + "title", + "sku", + "manage_inventory", + "allow_backorder", + "barcode", + "product.id", + "product.title", + "product.description", + "product.subtitle", + "product.thumbnail", + "product.type", + "product.collection", + "product.handle", + "calculated_price.calculated_amount", + "inventory_items.inventory_item_id", + "inventory_items.required_quantity", + "inventory_items.inventory.location_levels.stock_locations.id", + "inventory_items.inventory.location_levels.stock_locations.name", + "inventory_items.inventory.location_levels.stock_locations.sales_channels.id", + "inventory_items.inventory.location_levels.stock_locations.sales_channels.name", +] diff --git a/packages/core/core-flows/src/order/workflows/archive-orders.ts b/packages/core/core-flows/src/order/workflows/archive-orders.ts new file mode 100644 index 0000000000..9ffac0bfcf --- /dev/null +++ b/packages/core/core-flows/src/order/workflows/archive-orders.ts @@ -0,0 +1,17 @@ +import { OrderDTO } from "@medusajs/types" +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { archiveOrdersStep } from "../steps" + +type ArchiveOrdersStepInput = { + order_ids: string[] +} + +export const archiveOrderWorkflowId = "archive-order-workflow" +export const archiveOrderWorkflow = createWorkflow( + archiveOrderWorkflowId, + (input: WorkflowData): WorkflowData => { + const archivedOrders = archiveOrdersStep(input) + + return archivedOrders + } +) diff --git a/packages/core/core-flows/src/order/workflows/complete-orders.ts b/packages/core/core-flows/src/order/workflows/complete-orders.ts new file mode 100644 index 0000000000..c20e0027bc --- /dev/null +++ b/packages/core/core-flows/src/order/workflows/complete-orders.ts @@ -0,0 +1,20 @@ +import { OrderDTO } from "@medusajs/types" +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { completeOrdersStep } from "../steps" + +type CompleteOrdersStepInput = { + order_ids: string[] +} + +export const completeOrderWorkflowId = "complete-order-workflow" +export const completeOrderWorkflow = createWorkflow( + completeOrderWorkflowId, + (input: WorkflowData): WorkflowData => { + const completedOrders = completeOrdersStep(input) + + // TODO: Emit event "OrderModuleService.Order.COMPLETED" + // id + no_notification + + return completedOrders + } +) diff --git a/packages/core/core-flows/src/order/workflows/create-orders.ts b/packages/core/core-flows/src/order/workflows/create-orders.ts index 4e343e126b..d0bbb1e237 100644 --- a/packages/core/core-flows/src/order/workflows/create-orders.ts +++ b/packages/core/core-flows/src/order/workflows/create-orders.ts @@ -1,5 +1,5 @@ import { CreateOrderDTO, OrderDTO } from "@medusajs/types" -import { MathBN, MedusaError } from "@medusajs/utils" +import { MathBN } from "@medusajs/utils" import { WorkflowData, createWorkflow, @@ -7,17 +7,67 @@ import { transform, } from "@medusajs/workflows-sdk" import { useRemoteQueryStep } from "../../common" -import { confirmInventoryStep } from "../../definition/cart/steps/confirm-inventory" import { findOneOrAnyRegionStep } from "../../definition/cart/steps/find-one-or-any-region" import { findOrCreateCustomerStep } from "../../definition/cart/steps/find-or-create-customer" import { findSalesChannelStep } from "../../definition/cart/steps/find-sales-channel" import { getVariantPriceSetsStep } from "../../definition/cart/steps/get-variant-price-sets" -import { getVariantsStep } from "../../definition/cart/steps/get-variants" -import { prepareConfirmInventoryInput } from "../../definition/cart/utils/prepare-confirm-inventory-input" +import { validateVariantPricesStep } from "../../definition/cart/steps/validate-variant-prices" import { prepareLineItemData } from "../../definition/cart/utils/prepare-line-item-data" +import { confirmVariantInventoryWorkflow } from "../../definition/cart/workflows/confirm-variant-inventory" import { createOrdersStep, updateOrderTaxLinesStep } from "../steps" +import { productVariantsFields } from "../utils/fields" import { prepareCustomLineItemData } from "../utils/prepare-custom-line-item-data" +function prepareLineItems(data) { + const items = (data.input.items ?? []).map((item) => { + const variant = data.variants.find((v) => v.id === item.variant_id)! + + if (!variant) { + return prepareCustomLineItemData({ + variant: { + ...item, + }, + unitPrice: MathBN.max(0, item.unit_price), + quantity: item.quantity as number, + metadata: item?.metadata ?? {}, + }) + } + + return prepareLineItemData({ + variant: variant, + unitPrice: MathBN.max( + 0, + item.unit_price ?? data.priceSets[item.variant_id!]?.calculated_amount + ), + quantity: item.quantity as number, + metadata: item?.metadata ?? {}, + taxLines: item.tax_lines || [], + adjustments: item.adjustments || [], + }) + }) + + return items +} + +function getOrderInput(data) { + const data_ = { + ...data.input, + currency_code: data.input.currency_code ?? data.region.currency_code, + region_id: data.region.id, + } + + if (data.customerData.customer?.id) { + data_.customer_id = data.customerData.customer.id + data_.email = data.input?.email ?? data.customerData.customer.email + } + + if (data.salesChannel?.id) { + data_.sales_channel_id = data.salesChannel.id + } + + return data_ +} + export const createOrdersWorkflowId = "create-orders" export const createOrdersWorkflow = createWorkflow( createOrdersWorkflowId, @@ -39,75 +89,8 @@ export const createOrdersWorkflow = createWorkflow( customerId: input.customer_id, email: input.email, }) - // validateVariantsExistStep({ variantIds }) ) - const variants = getVariantsStep({ - filter: { id: variantIds }, - config: { - select: [ - "id", - "title", - "sku", - "manage_inventory", - "barcode", - "product.id", - "product.title", - "product.description", - "product.subtitle", - "product.thumbnail", - "product.type", - "product.collection", - "product.handle", - ], - relations: ["product"], - }, - }) - - const salesChannelLocations = useRemoteQueryStep({ - entry_point: "sales_channels", - fields: ["id", "name", "stock_locations.id", "stock_locations.name"], - variables: { id: salesChannel.id }, - }) - - const productVariantInventoryItems = useRemoteQueryStep({ - entry_point: "product_variant_inventory_items", - fields: ["variant_id", "inventory_item_id", "required_quantity"], - variables: { variant_id: variantIds }, - }).config({ name: "inventory-items" }) - - const confirmInventoryInput = transform( - { productVariantInventoryItems, salesChannelLocations, input, variants }, - (data) => { - if (!data.input.items) { - return { items: [] } - } - - if (!data.salesChannelLocations.length) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - `Sales channel ${data.input.sales_channel_id} is not associated with any stock locations.` - ) - } - - const items = prepareConfirmInventoryInput({ - product_variant_inventory_items: data.productVariantInventoryItems, - location_ids: data.salesChannelLocations[0].stock_locations.map( - (l) => l.id - ), - items: data.input.items!, - variants: data.variants.map((v) => ({ - id: v.id, - manage_inventory: v.manage_inventory, - })), - }) - - return { items } - } - ) - - confirmInventoryStep(confirmInventoryInput) - // TODO: This is on par with the context used in v1.*, but we can be more flexible. const pricingContext = transform( { input, region, customerData }, @@ -120,6 +103,28 @@ export const createOrdersWorkflow = createWorkflow( } ) + const variants = useRemoteQueryStep({ + entry_point: "variants", + fields: productVariantsFields, + variables: { + id: variantIds, + calculated_price: { + context: pricingContext, + }, + }, + throw_if_key_not_found: true, + }) + + validateVariantPricesStep({ variants }) + + confirmVariantInventoryWorkflow.runAsStep({ + input: { + sales_channel_id: salesChannel.id, + variants, + items: input.items!, + }, + }) + const priceSets = getVariantPriceSetsStep({ variantIds, context: pricingContext, @@ -127,57 +132,13 @@ export const createOrdersWorkflow = createWorkflow( const orderInput = transform( { input, region, customerData, salesChannel }, - (data) => { - const data_ = { - ...data.input, - currency_code: data.input.currency_code ?? data.region.currency_code, - region_id: data.region.id, - } - - if (data.customerData.customer?.id) { - data_.customer_id = data.customerData.customer.id - data_.email = data.input?.email ?? data.customerData.customer.email - } - - if (data.salesChannel?.id) { - data_.sales_channel_id = data.salesChannel.id - } - - return data_ - } + getOrderInput ) - const lineItems = transform({ priceSets, input, variants }, (data) => { - const items = (data.input.items ?? []).map((item) => { - const variant = data.variants.find((v) => v.id === item.variant_id)! - - if (!variant) { - return prepareCustomLineItemData({ - variant: { - ...item, - }, - unitPrice: MathBN.max(0, item.unit_price), - quantity: item.quantity as number, - metadata: item?.metadata ?? {}, - }) - } - - return prepareLineItemData({ - variant: variant, - unitPrice: MathBN.max( - 0, - item.unit_price ?? - data.priceSets[item.variant_id!]?.calculated_amount - ), - quantity: item.quantity as number, - metadata: item?.metadata ?? {}, - taxLines: item.tax_lines || [], - adjustments: item.adjustments || [], - }) - }) - - return items - }) + const lineItems = transform( + { priceSets, input, variants }, + prepareLineItems + ) const orderToCreate = transform({ lineItems, orderInput }, (data) => { return { diff --git a/packages/core/core-flows/src/order/workflows/index.ts b/packages/core/core-flows/src/order/workflows/index.ts index cd814fee4c..3a848ef72f 100644 --- a/packages/core/core-flows/src/order/workflows/index.ts +++ b/packages/core/core-flows/src/order/workflows/index.ts @@ -1,3 +1,5 @@ +export * from "./archive-orders" +export * from "./complete-orders" export * from "./create-orders" -export * from "./update-tax-lines" export * from "./create-return" +export * from "./update-tax-lines" diff --git a/packages/core/types/src/order/common.ts b/packages/core/types/src/order/common.ts index 61b4685c38..038aaa2573 100644 --- a/packages/core/types/src/order/common.ts +++ b/packages/core/types/src/order/common.ts @@ -789,6 +789,14 @@ export interface OrderItemDTO { updated_at: Date } +type OrderStatus = + | "pending" + | "completed" + | "draft" + | "archived" + | "canceled" + | "requires_action" + export interface OrderDTO { /** * The ID of the order. @@ -798,6 +806,10 @@ export interface OrderDTO { * The version of the order. */ version: number + /** + * The status of the order. + */ + status: OrderStatus /** * The ID of the region the order belongs to. */ diff --git a/packages/core/types/src/order/service.ts b/packages/core/types/src/order/service.ts index 5a6556946f..97a7e5d2ee 100644 --- a/packages/core/types/src/order/service.ts +++ b/packages/core/types/src/order/service.ts @@ -1506,6 +1506,12 @@ export interface IOrderModuleService extends IModuleService { sharedContext?: Context ): Promise | void> + archive(orderId: string[], sharedContext?: Context): Promise + archive(orderId: string, sharedContext?: Context): Promise + + completeOrder(orderId: string[], sharedContext?: Context): Promise + completeOrder(orderId: string, sharedContext?: Context): Promise + // Bundled flows registerFulfillment( data: RegisterOrderFulfillmentDTO, diff --git a/packages/medusa/src/api-v2/admin/orders/[id]/archive/route.ts b/packages/medusa/src/api-v2/admin/orders/[id]/archive/route.ts new file mode 100644 index 0000000000..e2dad32b1e --- /dev/null +++ b/packages/medusa/src/api-v2/admin/orders/[id]/archive/route.ts @@ -0,0 +1,34 @@ +import { archiveOrderWorkflow } from "@medusajs/core-flows" +import { remoteQueryObjectFromString } from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../types/routing" +import { AdminArchiveOrderType } from "../../validators" + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve("remoteQuery") + const { id } = req.params + + const { errors } = await archiveOrderWorkflow(req.scope).run({ + input: { order_ids: [req.validatedBody.order_id] }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "order", + variables: { id }, + fields: req.remoteQueryConfig.fields, + }) + + const [order] = await remoteQuery(queryObject) + + res.status(200).json({ order }) +} diff --git a/packages/medusa/src/api-v2/admin/orders/[id]/cancel/route.ts b/packages/medusa/src/api-v2/admin/orders/[id]/cancel/route.ts new file mode 100644 index 0000000000..2fee833355 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/orders/[id]/cancel/route.ts @@ -0,0 +1,25 @@ +import { remoteQueryObjectFromString } from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../types/routing" + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve("remoteQuery") + + const variables = { id: req.params.id } + + // TODO: cancel order - v1.x - packages/medusa/src/api/routes/admin/orders/cancel-order.ts + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "order", + variables, + fields: req.remoteQueryConfig.fields, + }) + + const [order] = await remoteQuery(queryObject) + res.status(200).json({ order }) +} diff --git a/packages/medusa/src/api-v2/admin/orders/[id]/complete/route.ts b/packages/medusa/src/api-v2/admin/orders/[id]/complete/route.ts new file mode 100644 index 0000000000..48846d6090 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/orders/[id]/complete/route.ts @@ -0,0 +1,34 @@ +import { completeOrderWorkflow } from "@medusajs/core-flows" +import { remoteQueryObjectFromString } from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../types/routing" +import { AdminCompleteOrderType } from "../../validators" + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve("remoteQuery") + const { id } = req.params + + const { errors } = await completeOrderWorkflow(req.scope).run({ + input: { order_ids: [req.validatedBody.order_id] }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "order", + variables: { id }, + fields: req.remoteQueryConfig.fields, + }) + + const [order] = await remoteQuery(queryObject) + + res.status(200).json({ order }) +} diff --git a/packages/medusa/src/api-v2/admin/orders/[id]/refund/route.ts b/packages/medusa/src/api-v2/admin/orders/[id]/refund/route.ts new file mode 100644 index 0000000000..56fed6a12b --- /dev/null +++ b/packages/medusa/src/api-v2/admin/orders/[id]/refund/route.ts @@ -0,0 +1,19 @@ +import { remoteQueryObjectFromString } from "@medusajs/utils" +import { MedusaRequest, MedusaResponse } from "../../../../../types/routing" + +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const remoteQuery = req.scope.resolve("remoteQuery") + + const variables = { id: req.params.id } + + // TODO: refund payment - v1.x - packages/medusa/src/api/routes/admin/orders/refund-payment.ts + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "order", + variables, + fields: req.remoteQueryConfig.fields, + }) + + const [order] = await remoteQuery(queryObject) + res.status(200).json({ order }) +} diff --git a/packages/medusa/src/api-v2/admin/orders/[id]/route.ts b/packages/medusa/src/api-v2/admin/orders/[id]/route.ts index 9dee2407d3..5a02fdea48 100644 --- a/packages/medusa/src/api-v2/admin/orders/[id]/route.ts +++ b/packages/medusa/src/api-v2/admin/orders/[id]/route.ts @@ -1,7 +1,13 @@ import { remoteQueryObjectFromString } from "@medusajs/utils" -import { MedusaRequest, MedusaResponse } from "../../../../types/routing" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../types/routing" -export const GET = async (req: MedusaRequest, res: MedusaResponse) => { +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { const remoteQuery = req.scope.resolve("remoteQuery") const variables = { id: req.params.id } @@ -15,3 +21,23 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => { const [order] = await remoteQuery(queryObject) res.status(200).json({ order }) } + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve("remoteQuery") + + const variables = { id: req.params.id } + + // TODO: update order + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "order", + variables, + fields: req.remoteQueryConfig.fields, + }) + + const [order] = await remoteQuery(queryObject) + res.status(200).json({ order }) +} diff --git a/packages/medusa/src/api-v2/admin/orders/fulfillments/[fulfillment_id]/cancel/route.ts b/packages/medusa/src/api-v2/admin/orders/fulfillments/[fulfillment_id]/cancel/route.ts new file mode 100644 index 0000000000..2070238e48 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/orders/fulfillments/[fulfillment_id]/cancel/route.ts @@ -0,0 +1,25 @@ +import { remoteQueryObjectFromString } from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../../types/routing" + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve("remoteQuery") + + const variables = { id: req.params.id } + + // TODO: Workflow to cancel fulfillment + adjust inventory + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "order", + variables, + fields: req.remoteQueryConfig.fields, + }) + + const [order] = await remoteQuery(queryObject) + res.status(200).json({ order }) +} diff --git a/packages/medusa/src/api-v2/admin/orders/fulfillments/route.ts b/packages/medusa/src/api-v2/admin/orders/fulfillments/route.ts new file mode 100644 index 0000000000..b49d0f0870 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/orders/fulfillments/route.ts @@ -0,0 +1,25 @@ +import { remoteQueryObjectFromString } from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../types/routing" + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve("remoteQuery") + + const variables = { id: req.params.id } + + // TODO: Workflow fulfill items, create fulfillments and adjust inventory - v1.x - packages/medusa/src/api/routes/admin/orders/create-fulfillment.ts + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "order", + variables, + fields: req.remoteQueryConfig.fields, + }) + + const [order] = await remoteQuery(queryObject) + res.status(200).json({ order }) +} diff --git a/packages/medusa/src/api-v2/admin/orders/middlewares.ts b/packages/medusa/src/api-v2/admin/orders/middlewares.ts index 9208d9fdbd..f599dcca00 100644 --- a/packages/medusa/src/api-v2/admin/orders/middlewares.ts +++ b/packages/medusa/src/api-v2/admin/orders/middlewares.ts @@ -1,8 +1,14 @@ import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" import { authenticate } from "../../../utils/middlewares/authenticate-middleware" +import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" -import { AdminGetOrdersOrderParams, AdminGetOrdersParams } from "./validators" +import { + AdminArchiveOrder, + AdminCompleteOrder, + AdminGetOrdersOrderParams, + AdminGetOrdersParams, +} from "./validators" export const adminOrderRoutesMiddlewares: MiddlewareRoute[] = [ { @@ -30,4 +36,60 @@ export const adminOrderRoutesMiddlewares: MiddlewareRoute[] = [ ), ], }, + { + method: ["POST"], + matcher: "/admin/orders/:id/archive", + middlewares: [ + validateAndTransformBody(AdminArchiveOrder), + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + { + method: ["POST"], + matcher: "/admin/orders/:id/cancel", + middlewares: [ + // validateAndTransformBody(), + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + { + method: ["POST"], + matcher: "/admin/orders/:id/complete", + middlewares: [ + validateAndTransformBody(AdminCompleteOrder), + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + + { + method: ["POST"], + matcher: "/admin/orders/:id/fulfillments", + middlewares: [ + // validateAndTransformBody(), + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + { + method: ["POST"], + matcher: "/admin/orders/:id/fulfillments/cancel", + middlewares: [ + // validateAndTransformBody(), + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, ] diff --git a/packages/medusa/src/api-v2/admin/orders/route.ts b/packages/medusa/src/api-v2/admin/orders/route.ts index d2691fac5b..b89d207248 100644 --- a/packages/medusa/src/api-v2/admin/orders/route.ts +++ b/packages/medusa/src/api-v2/admin/orders/route.ts @@ -2,9 +2,15 @@ import { ContainerRegistrationKeys, remoteQueryObjectFromString, } from "@medusajs/utils" -import { MedusaRequest, MedusaResponse } from "../../../types/routing" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../types/routing" -export const GET = async (req: MedusaRequest, res: MedusaResponse) => { +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const queryObject = remoteQueryObjectFromString({ diff --git a/packages/medusa/src/api-v2/admin/orders/validators.ts b/packages/medusa/src/api-v2/admin/orders/validators.ts index 8c2fed6abc..ad7cb489a1 100644 --- a/packages/medusa/src/api-v2/admin/orders/validators.ts +++ b/packages/medusa/src/api-v2/admin/orders/validators.ts @@ -36,3 +36,13 @@ export const AdminGetOrdersParams = createFindParams({ ) export type AdminGetOrdersParamsType = z.infer + +export const AdminArchiveOrder = z.object({ + order_id: z.string(), +}) +export type AdminArchiveOrderType = z.infer + +export const AdminCompleteOrder = z.object({ + order_id: z.string(), +}) +export type AdminCompleteOrderType = z.infer diff --git a/packages/medusa/src/api-v2/admin/return-reasons/[id]/route.ts b/packages/medusa/src/api-v2/admin/return-reasons/[id]/route.ts new file mode 100644 index 0000000000..779a4fbab3 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/return-reasons/[id]/route.ts @@ -0,0 +1,84 @@ +import { + deleteReturnReasonsWorkflow, + updateReturnReasonsWorkflow, +} from "@medusajs/core-flows" +import { UpdateOrderReturnReasonDTO } from "@medusajs/types" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../types/routing" + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const variables = { id: req.params.id } + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "return_reason", + variables, + fields: req.remoteQueryConfig.fields, + }) + + const [return_reason] = await remoteQuery(queryObject) + + res.json({ return_reason }) +} + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const workflow = updateReturnReasonsWorkflow(req.scope) + + const { id } = req.params + const input = { + selector: { id }, + update: req.validatedBody, + } + + const { result, errors } = await workflow.run({ input }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + const variables = { id: result[0].id } + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "return_reason", + variables, + fields: req.remoteQueryConfig.fields, + }) + + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + const [return_reason] = await remoteQuery(queryObject) + + res.json({ return_reason }) +} + +export const DELETE = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const { id } = req.params + + const workflow = deleteReturnReasonsWorkflow(req.scope) + + const input = { + ids: [id], + } + await workflow.run({ input }) + + res.json({ + id, + object: "return_reason", + deleted: true, + }) +} diff --git a/packages/medusa/src/api-v2/admin/return-reasons/middlewares.ts b/packages/medusa/src/api-v2/admin/return-reasons/middlewares.ts new file mode 100644 index 0000000000..4e0eb73e76 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/return-reasons/middlewares.ts @@ -0,0 +1,53 @@ +import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { authenticate } from "../../../utils/middlewares/authenticate-middleware" +import { validateAndTransformBody } from "../../utils/validate-body" +import { validateAndTransformQuery } from "../../utils/validate-query" +import * as QueryConfig from "./query-config" +import { + AdminCreateReturnReason, + AdminGetReturnReasonsParams, + AdminGetReturnReasonsReturnReasonParams, +} from "./validators" + +export const adminReturnReasonRoutesMiddlewares: MiddlewareRoute[] = [ + { + method: ["ALL"], + matcher: "/admin/return-reasons*", + middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + }, + { + method: ["GET"], + matcher: "/admin/return-reasons", + middlewares: [ + validateAndTransformQuery( + AdminGetReturnReasonsParams, + QueryConfig.listTransformQueryConfig + ), + ], + }, + { + method: ["GET"], + matcher: "/admin/return-reasons/:id", + middlewares: [ + validateAndTransformQuery( + AdminGetReturnReasonsReturnReasonParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + { + method: ["POST"], + matcher: "/admin/return-reasons", + middlewares: [ + validateAndTransformBody(AdminCreateReturnReason), + validateAndTransformQuery( + AdminGetReturnReasonsReturnReasonParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + { + method: ["DELETE"], + matcher: "/admin/return-reasons/:id", + }, +] diff --git a/packages/medusa/src/api-v2/admin/return-reasons/query-config.ts b/packages/medusa/src/api-v2/admin/return-reasons/query-config.ts new file mode 100644 index 0000000000..e1ca20a982 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/return-reasons/query-config.ts @@ -0,0 +1,34 @@ +export const defaultAdminReturnReasonFields = [ + "id", + "display_id", + "status", + "version", + "summary", + "metadata", + "created_at", + "updated_at", +] + +export const defaultAdminRetrieveReturnReasonFields = [ + "id", + "value", + "label", + "parent_return_reason_id", + "description", + "created_at", + "updated_at", + "deleted_at", + "*.parent_return_reason", + "*.return_reason_children", +] + +export const retrieveTransformQueryConfig = { + defaultFields: defaultAdminRetrieveReturnReasonFields, + isList: false, +} + +export const listTransformQueryConfig = { + defaults: defaultAdminReturnReasonFields, + defaultLimit: 20, + isList: true, +} diff --git a/packages/medusa/src/api-v2/admin/return-reasons/route.ts b/packages/medusa/src/api-v2/admin/return-reasons/route.ts new file mode 100644 index 0000000000..502a3ceafd --- /dev/null +++ b/packages/medusa/src/api-v2/admin/return-reasons/route.ts @@ -0,0 +1,71 @@ +import { createReturnReasonsWorkflow } from "@medusajs/core-flows" +import { CreateOrderReturnReasonDTO } from "@medusajs/types" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../types/routing" + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "return_reason", + variables: { + filters: { + ...req.filterableFields, + }, + ...req.remoteQueryConfig.pagination, + }, + fields: req.remoteQueryConfig.fields, + }) + + const { rows: return_reasons, metadata } = await remoteQuery(queryObject) + + res.json({ + return_reasons, + count: metadata.count, + offset: metadata.skip, + limit: metadata.take, + }) +} + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const workflow = createReturnReasonsWorkflow(req.scope) + + const input = { + data: [ + { + ...req.validatedBody, + }, + ], + } + + const { result, errors } = await workflow.run({ input }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + const variables = { id: result[0].id } + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "return_reason", + variables, + fields: req.remoteQueryConfig.fields, + }) + + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + const [return_reason] = await remoteQuery(queryObject) + + res.json({ return_reason }) +} diff --git a/packages/medusa/src/api-v2/admin/return-reasons/validators.ts b/packages/medusa/src/api-v2/admin/return-reasons/validators.ts new file mode 100644 index 0000000000..d0c94dc9fa --- /dev/null +++ b/packages/medusa/src/api-v2/admin/return-reasons/validators.ts @@ -0,0 +1,71 @@ +import { z } from "zod" +import { + createFindParams, + createOperatorMap, + createSelectParams, +} from "../../utils/validators" + +export const AdminGetReturnReasonsReturnReasonParams = + createSelectParams().merge( + z.object({ + id: z.union([z.string(), z.array(z.string())]).optional(), + value: z.union([z.string(), z.array(z.string())]).optional(), + label: z.union([z.string(), z.array(z.string())]).optional(), + description: z.union([z.string(), z.array(z.string())]).optional(), + parent_return_reason_id: z + .union([z.string(), z.array(z.string())]) + .optional(), + created_at: createOperatorMap().optional(), + updated_at: createOperatorMap().optional(), + deleted_at: createOperatorMap().optional(), + }) + ) + +export type AdminGetReturnReasonsReturnReasonParamsType = z.infer< + typeof AdminGetReturnReasonsReturnReasonParams +> + +/** + * Parameters used to filter and configure the pagination of the retrieved order. + */ +export const AdminGetReturnReasonsParams = createFindParams({ + limit: 15, + offset: 0, +}).merge( + z.object({ + id: z.union([z.string(), z.array(z.string())]).optional(), + value: z.union([z.string(), z.array(z.string())]).optional(), + label: z.union([z.string(), z.array(z.string())]).optional(), + description: z.union([z.string(), z.array(z.string())]).optional(), + parent_return_reason_id: z + .union([z.string(), z.array(z.string())]) + .optional(), + created_at: createOperatorMap().optional(), + updated_at: createOperatorMap().optional(), + deleted_at: createOperatorMap().optional(), + }) +) +export type AdminGetReturnReasonsParamsType = z.infer< + typeof AdminGetReturnReasonsParams +> + +export const AdminCreateReturnReason = z.object({ + value: z.string(), + label: z.string(), + descripton: z.string().optional(), + parent_return_reason_id: z.string().optional(), + metadata: z.record(z.unknown()).optional(), +}) +export type AdminCreateReturnReasonType = z.infer< + typeof AdminCreateReturnReason +> + +export const AdminUpdateReturnReason = z.object({ + value: z.string(), + label: z.string(), + descripton: z.string().optional(), + metadata: z.record(z.unknown()).optional(), +}) +export type AdminUpdateReturnReasonType = z.infer< + typeof AdminUpdateReturnReason +> diff --git a/packages/medusa/src/api-v2/admin/returns/[id]/cancel/route.ts b/packages/medusa/src/api-v2/admin/returns/[id]/cancel/route.ts new file mode 100644 index 0000000000..27e7705b7e --- /dev/null +++ b/packages/medusa/src/api-v2/admin/returns/[id]/cancel/route.ts @@ -0,0 +1,35 @@ +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../types/routing" + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "order", + variables: { + filters: { + ...req.filterableFields, + }, + ...req.remoteQueryConfig.pagination, + }, + fields: req.remoteQueryConfig.fields, + }) + + const { rows: orders, metadata } = await remoteQuery(queryObject) + + res.json({ + orders, + count: metadata.count, + offset: metadata.skip, + limit: metadata.take, + }) +} diff --git a/packages/medusa/src/api-v2/admin/returns/[id]/receive/route.ts b/packages/medusa/src/api-v2/admin/returns/[id]/receive/route.ts new file mode 100644 index 0000000000..a3983a15aa --- /dev/null +++ b/packages/medusa/src/api-v2/admin/returns/[id]/receive/route.ts @@ -0,0 +1,36 @@ +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../types/routing" + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "order", + variables: { + filters: { + ...req.filterableFields, + is_draft_order: false, + }, + ...req.remoteQueryConfig.pagination, + }, + fields: req.remoteQueryConfig.fields, + }) + + const { rows: orders, metadata } = await remoteQuery(queryObject) + + res.json({ + orders, + count: metadata.count, + offset: metadata.skip, + limit: metadata.take, + }) +} diff --git a/packages/medusa/src/api-v2/admin/returns/[id]/route.ts b/packages/medusa/src/api-v2/admin/returns/[id]/route.ts new file mode 100644 index 0000000000..77f18351fe --- /dev/null +++ b/packages/medusa/src/api-v2/admin/returns/[id]/route.ts @@ -0,0 +1,35 @@ +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../types/routing" + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "order", + variables: { + filters: { + ...req.filterableFields, + }, + ...req.remoteQueryConfig.pagination, + }, + fields: req.remoteQueryConfig.fields, + }) + + const { rows: orders, metadata } = await remoteQuery(queryObject) + + res.json({ + orders, + count: metadata.count, + offset: metadata.skip, + limit: metadata.take, + }) +} diff --git a/packages/medusa/src/api-v2/admin/returns/middlewares.ts b/packages/medusa/src/api-v2/admin/returns/middlewares.ts new file mode 100644 index 0000000000..c0ce6fa8c6 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/returns/middlewares.ts @@ -0,0 +1,49 @@ +import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { authenticate } from "../../../utils/middlewares/authenticate-middleware" +import { validateAndTransformBody } from "../../utils/validate-body" +import { validateAndTransformQuery } from "../../utils/validate-query" +import * as QueryConfig from "./query-config" +import { + AdminGetOrdersOrderParams, + AdminGetOrdersParams, + AdminPostReturnsReqSchema, +} from "./validators" + +export const adminOrderRoutesMiddlewares: MiddlewareRoute[] = [ + { + method: ["ALL"], + matcher: "/admin/returns*", + middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + }, + { + method: ["GET"], + matcher: "/admin/returns", + middlewares: [ + validateAndTransformQuery( + AdminGetOrdersParams, + QueryConfig.listTransformQueryConfig + ), + ], + }, + { + method: ["GET"], + matcher: "/admin/returns/:id", + middlewares: [ + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + { + method: ["POST"], + matcher: "/admin/returns/create-return", + middlewares: [ + validateAndTransformBody(AdminPostReturnsReqSchema), + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, +] diff --git a/packages/medusa/src/api-v2/admin/returns/query-config.ts b/packages/medusa/src/api-v2/admin/returns/query-config.ts new file mode 100644 index 0000000000..df08f01b4f --- /dev/null +++ b/packages/medusa/src/api-v2/admin/returns/query-config.ts @@ -0,0 +1,62 @@ +export const defaultAdminOrderFields = [ + "id", + "display_id", + "status", + "version", + "summary", + "metadata", + "created_at", + "updated_at", +] + +export const defaultAdminRetrieveOrderFields = [ + "id", + "display_id", + "status", + "version", + "summary", + "total", + "subtotal", + "tax_total", + "discount_total", + "discount_tax_total", + "original_total", + "original_tax_total", + "item_total", + "item_subtotal", + "item_tax_total", + "original_item_total", + "original_item_subtotal", + "original_item_tax_total", + "shipping_total", + "shipping_subtotal", + "shipping_tax_total", + "original_shipping_tax_total", + "original_shipping_tax_subtotal", + "original_shipping_total", + "created_at", + "updated_at", + "*items", + "*items.tax_lines", + "*items.adjustments", + "*items.variant", + "*items.variant.product", + "*items.detail", + "*shipping_address", + "*billing_address", + "*shipping_methods", + "*shipping_methods.tax_lines", + "*shipping_methods.adjustments", + "*payment_collections", +] + +export const retrieveTransformQueryConfig = { + defaultFields: defaultAdminRetrieveOrderFields, + isList: false, +} + +export const listTransformQueryConfig = { + defaults: defaultAdminOrderFields, + defaultLimit: 20, + isList: true, +} diff --git a/packages/medusa/src/api-v2/admin/returns/route.ts b/packages/medusa/src/api-v2/admin/returns/route.ts new file mode 100644 index 0000000000..6fdcd14314 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/returns/route.ts @@ -0,0 +1,56 @@ +import { createReturnOrderWorkflow } from "@medusajs/core-flows" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../types/routing" +import { AdminPostReturnsReqSchemaType } from "./validators" + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "order", + variables: { + filters: { + ...req.filterableFields, + }, + ...req.remoteQueryConfig.pagination, + }, + fields: req.remoteQueryConfig.fields, + }) + + const { rows: orders, metadata } = await remoteQuery(queryObject) + + res.json({ + orders, + count: metadata.count, + offset: metadata.skip, + limit: metadata.take, + }) +} + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const input = req.validatedBody as AdminPostReturnsReqSchemaType + + const workflow = createReturnOrderWorkflow(req.scope) + const { result, errors } = await workflow.run({ + input, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + res.status(200).json({ return: result }) +} diff --git a/packages/medusa/src/api-v2/admin/returns/validators.ts b/packages/medusa/src/api-v2/admin/returns/validators.ts new file mode 100644 index 0000000000..887ab702a2 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/returns/validators.ts @@ -0,0 +1,63 @@ +import { z } from "zod" +import { + createFindParams, + createOperatorMap, + createSelectParams, +} from "../../utils/validators" + +export const AdminGetOrdersOrderParams = createSelectParams().merge( + z.object({ + id: z.union([z.string(), z.array(z.string())]).optional(), + status: z.union([z.string(), z.array(z.string())]).optional(), + created_at: createOperatorMap().optional(), + updated_at: createOperatorMap().optional(), + deleted_at: createOperatorMap().optional(), + }) +) + +export type AdminGetOrdersOrderParamsType = z.infer< + typeof AdminGetOrdersOrderParams +> + +/** + * Parameters used to filter and configure the pagination of the retrieved order. + */ +export const AdminGetOrdersParams = createFindParams({ + limit: 15, + offset: 0, +}).merge( + z.object({ + id: z.union([z.string(), z.array(z.string())]).optional(), + status: z.union([z.string(), z.array(z.string())]).optional(), + created_at: createOperatorMap().optional(), + updated_at: createOperatorMap().optional(), + deleted_at: createOperatorMap().optional(), + }) +) + +export type AdminGetOrdersParamsType = z.infer + +const ReturnShippingSchema = z.object({ + option_id: z.string(), + price: z.number().optional(), +}) + +const ItemSchema = z.object({ + id: z.string(), + quantity: z.number().min(1), + reason_id: z.string().optional(), + note: z.string().optional(), +}) + +export const AdminPostReturnsReqSchema = z.object({ + order_id: z.string(), + items: z.array(ItemSchema), + return_shipping: ReturnShippingSchema, + note: z.string().optional(), + receive_now: z.boolean().optional(), + refund_amount: z.number().optional(), + location_id: z.string().optional(), +}) +export type AdminPostReturnsReqSchemaType = z.infer< + typeof AdminPostReturnsReqSchema +> diff --git a/packages/medusa/src/api-v2/admin/stock-locations/[id]/fulfillment-sets/route.ts b/packages/medusa/src/api-v2/admin/stock-locations/[id]/fulfillment-sets/route.ts index b32f1f9bea..b726fcef7d 100644 --- a/packages/medusa/src/api-v2/admin/stock-locations/[id]/fulfillment-sets/route.ts +++ b/packages/medusa/src/api-v2/admin/stock-locations/[id]/fulfillment-sets/route.ts @@ -1,10 +1,13 @@ import { createLocationFulfillmentSetWorkflow } from "@medusajs/core-flows" -import { MedusaRequest, MedusaResponse } from "../../../../../types/routing" -import { AdminCreateStockLocationFulfillmentSetType } from "../../validators" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../types/routing" import { refetchStockLocation } from "../../helpers" +import { AdminCreateStockLocationFulfillmentSetType } from "../../validators" export const POST = async ( - req: MedusaRequest, + req: AuthenticatedMedusaRequest, res: MedusaResponse ) => { const { errors } = await createLocationFulfillmentSetWorkflow(req.scope).run({ diff --git a/packages/medusa/src/api-v2/admin/stock-locations/[id]/route.ts b/packages/medusa/src/api-v2/admin/stock-locations/[id]/route.ts index 9e0721a903..224b5b5a30 100644 --- a/packages/medusa/src/api-v2/admin/stock-locations/[id]/route.ts +++ b/packages/medusa/src/api-v2/admin/stock-locations/[id]/route.ts @@ -1,18 +1,21 @@ -import { MedusaRequest, MedusaResponse } from "../../../../types/routing" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../types/routing" import { deleteStockLocationsWorkflow, updateStockLocationsWorkflow, } from "@medusajs/core-flows" import { MedusaError } from "@medusajs/utils" +import { refetchStockLocation } from "../helpers" import { AdminGetStockLocationParamsType, AdminUpdateStockLocationType, } from "../validators" -import { refetchStockLocation } from "../helpers" export const POST = async ( - req: MedusaRequest, + req: AuthenticatedMedusaRequest, res: MedusaResponse ) => { const { id } = req.params @@ -35,7 +38,7 @@ export const POST = async ( } export const GET = async ( - req: MedusaRequest, + req: AuthenticatedMedusaRequest, res: MedusaResponse ) => { const { id } = req.params @@ -56,7 +59,10 @@ export const GET = async ( res.status(200).json({ stock_location: stockLocation }) } -export const DELETE = async (req: MedusaRequest, res: MedusaResponse) => { +export const DELETE = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { const { id } = req.params const { errors } = await deleteStockLocationsWorkflow(req.scope).run({ diff --git a/packages/medusa/src/api-v2/admin/stock-locations/route.ts b/packages/medusa/src/api-v2/admin/stock-locations/route.ts index 1c5063e3ed..a674a242a7 100644 --- a/packages/medusa/src/api-v2/admin/stock-locations/route.ts +++ b/packages/medusa/src/api-v2/admin/stock-locations/route.ts @@ -2,18 +2,21 @@ import { ContainerRegistrationKeys, remoteQueryObjectFromString, } from "@medusajs/utils" -import { MedusaRequest, MedusaResponse } from "../../../types/routing" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../types/routing" import { createStockLocationsWorkflow } from "@medusajs/core-flows" +import { refetchStockLocation } from "./helpers" import { AdminCreateStockLocationType, AdminGetStockLocationsParamsType, } from "./validators" -import { refetchStockLocation } from "./helpers" // Create stock location export const POST = async ( - req: MedusaRequest, + req: AuthenticatedMedusaRequest, res: MedusaResponse ) => { const { result } = await createStockLocationsWorkflow(req.scope).run({ @@ -30,7 +33,7 @@ export const POST = async ( } export const GET = async ( - req: MedusaRequest, + req: AuthenticatedMedusaRequest, res: MedusaResponse ) => { const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) diff --git a/packages/medusa/src/api-v2/middlewares.ts b/packages/medusa/src/api-v2/middlewares.ts index 21598a2ada..4f5ca171d2 100644 --- a/packages/medusa/src/api-v2/middlewares.ts +++ b/packages/medusa/src/api-v2/middlewares.ts @@ -21,6 +21,7 @@ import { adminProductRoutesMiddlewares } from "./admin/products/middlewares" import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares" import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares" import { adminReservationRoutesMiddlewares } from "./admin/reservations/middlewares" +import { adminReturnReasonRoutesMiddlewares } from "./admin/return-reasons/middlewares" import { adminSalesChannelRoutesMiddlewares } from "./admin/sales-channels/middlewares" import { adminShippingOptionRoutesMiddlewares } from "./admin/shipping-options/middlewares" import { adminShippingProfilesMiddlewares } from "./admin/shipping-profiles/middlewares" @@ -37,13 +38,14 @@ import { storeCartRoutesMiddlewares } from "./store/carts/middlewares" import { storeCollectionRoutesMiddlewares } from "./store/collections/middlewares" import { storeCurrencyRoutesMiddlewares } from "./store/currencies/middlewares" import { storeCustomerRoutesMiddlewares } from "./store/customers/middlewares" -import { storeProductRoutesMiddlewares } from "./store/products/middlewares" -import { storeProductCategoryRoutesMiddlewares } from "./store/product-categories/middlewares" -import { storeRegionRoutesMiddlewares } from "./store/regions/middlewares" -import { storePaymentProvidersMiddlewares } from "./store/payment-providers/middlewares" -import { storePaymentCollectionsMiddlewares } from "./store/payment-collections/middlewares" -import { storeShippingOptionRoutesMiddlewares } from "./store/shipping-options/middlewares" import { storeOrderRoutesMiddlewares } from "./store/orders/middlewares" +import { storePaymentCollectionsMiddlewares } from "./store/payment-collections/middlewares" +import { storePaymentProvidersMiddlewares } from "./store/payment-providers/middlewares" +import { storeProductCategoryRoutesMiddlewares } from "./store/product-categories/middlewares" +import { storeProductRoutesMiddlewares } from "./store/products/middlewares" +import { storeRegionRoutesMiddlewares } from "./store/regions/middlewares" +import { storeReturnReasonRoutesMiddlewares } from "./store/return-reasons/middlewares" +import { storeShippingOptionRoutesMiddlewares } from "./store/shipping-options/middlewares" export const config: MiddlewaresConfig = { routes: [ @@ -94,5 +96,7 @@ export const config: MiddlewaresConfig = { ...adminFulfillmentsRoutesMiddlewares, ...adminFulfillmentProvidersRoutesMiddlewares, ...storeProductRoutesMiddlewares, + ...storeReturnReasonRoutesMiddlewares, + ...adminReturnReasonRoutesMiddlewares, ], } diff --git a/packages/medusa/src/api-v2/store/orders/[id]/route.ts b/packages/medusa/src/api-v2/store/orders/[id]/route.ts index edb1690e1d..217e87cbea 100644 --- a/packages/medusa/src/api-v2/store/orders/[id]/route.ts +++ b/packages/medusa/src/api-v2/store/orders/[id]/route.ts @@ -1,8 +1,12 @@ import { MedusaRequest, MedusaResponse } from "../../../../types/routing" import { refetchOrder } from "../helpers" +import { StoreGetOrdersParamsType } from "../validators" // TODO: Do we want to apply some sort of authentication here? My suggestion is that we do -export const GET = async (req: MedusaRequest, res: MedusaResponse) => { +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { const order = await refetchOrder( req.params.id, req.scope, diff --git a/packages/medusa/src/api-v2/store/return-reasons/[id]/route.ts b/packages/medusa/src/api-v2/store/return-reasons/[id]/route.ts new file mode 100644 index 0000000000..c76a56dbb0 --- /dev/null +++ b/packages/medusa/src/api-v2/store/return-reasons/[id]/route.ts @@ -0,0 +1,25 @@ +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { MedusaRequest, MedusaResponse } from "../../../../types/routing" +import { StoreReturnReasonParamsType } from "../validators" + +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const variables = { id: req.params.id } + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "return_reason", + variables, + fields: req.remoteQueryConfig.fields, + }) + + const [return_reason] = await remoteQuery(queryObject) + + res.json({ return_reason }) +} diff --git a/packages/medusa/src/api-v2/store/return-reasons/middlewares.ts b/packages/medusa/src/api-v2/store/return-reasons/middlewares.ts new file mode 100644 index 0000000000..3e36280529 --- /dev/null +++ b/packages/medusa/src/api-v2/store/return-reasons/middlewares.ts @@ -0,0 +1,27 @@ +import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { validateAndTransformQuery } from "../../utils/validate-query" +import * as QueryConfig from "./query-config" +import { StoreReturnReasonParams } from "./validators" + +export const storeReturnReasonRoutesMiddlewares: MiddlewareRoute[] = [ + { + method: ["GET"], + matcher: "/admin/return-reasons", + middlewares: [ + validateAndTransformQuery( + StoreReturnReasonParams, + QueryConfig.listTransformQueryConfig + ), + ], + }, + { + method: ["GET"], + matcher: "/admin/return-reasons/:id", + middlewares: [ + validateAndTransformQuery( + StoreReturnReasonParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, +] diff --git a/packages/medusa/src/api-v2/store/return-reasons/query-config.ts b/packages/medusa/src/api-v2/store/return-reasons/query-config.ts new file mode 100644 index 0000000000..622142b499 --- /dev/null +++ b/packages/medusa/src/api-v2/store/return-reasons/query-config.ts @@ -0,0 +1,23 @@ +export const defaultStoreRetrieveReturnReasonFields = [ + "id", + "value", + "label", + "parent_return_reason_id", + "description", + "created_at", + "updated_at", + "deleted_at", + "*.parent_return_reason", + "*.return_reason_children", +] + +export const retrieveTransformQueryConfig = { + defaultFields: defaultStoreRetrieveReturnReasonFields, + isList: false, +} + +export const listTransformQueryConfig = { + defaults: defaultStoreRetrieveReturnReasonFields, + defaultLimit: 20, + isList: true, +} diff --git a/packages/medusa/src/api-v2/store/return-reasons/route.ts b/packages/medusa/src/api-v2/store/return-reasons/route.ts new file mode 100644 index 0000000000..4f507c94ad --- /dev/null +++ b/packages/medusa/src/api-v2/store/return-reasons/route.ts @@ -0,0 +1,29 @@ +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { MedusaRequest, MedusaResponse } from "../../../types/routing" + +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "return_reason", + variables: { + filters: { + ...req.filterableFields, + }, + ...req.remoteQueryConfig.pagination, + }, + fields: req.remoteQueryConfig.fields, + }) + + const { rows: return_reasons, metadata } = await remoteQuery(queryObject) + + res.json({ + return_reasons, + count: metadata.count, + offset: metadata.skip, + limit: metadata.take, + }) +} diff --git a/packages/medusa/src/api-v2/store/return-reasons/validators.ts b/packages/medusa/src/api-v2/store/return-reasons/validators.ts new file mode 100644 index 0000000000..6751c3fe2a --- /dev/null +++ b/packages/medusa/src/api-v2/store/return-reasons/validators.ts @@ -0,0 +1,12 @@ +import { z } from "zod" +import { createFindParams, createSelectParams } from "../../utils/validators" + +export type StoreReturnReasonParamsType = z.infer< + typeof StoreReturnReasonParams +> +export const StoreReturnReasonParams = createSelectParams() + +export type StoreReturnReasonsParamsType = z.infer< + typeof StoreReturnReasonsParams +> +export const StoreReturnReasonsParams = createFindParams() diff --git a/packages/medusa/src/api-v2/store/return/route.ts b/packages/medusa/src/api-v2/store/return/route.ts index bde95a2d30..587ed3b848 100644 --- a/packages/medusa/src/api-v2/store/return/route.ts +++ b/packages/medusa/src/api-v2/store/return/route.ts @@ -1,10 +1,22 @@ -import { CreateOrderReturnDTO } from "@medusajs/types" +import { createReturnOrderWorkflow } from "@medusajs/core-flows" import { MedusaRequest, MedusaResponse } from "../../../types/routing" +import { StorePostReturnsReqSchemaType } from "./validators" -export const POST = async (req: MedusaRequest, res: MedusaResponse) => { - const input = [req.validatedBody as CreateOrderReturnDTO] +export const POST = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + const input = req.validatedBody as StorePostReturnsReqSchemaType - // TODO: create return workflow + const workflow = createReturnOrderWorkflow(req.scope) + const { result, errors } = await workflow.run({ + input, + throwOnError: false, + }) - res.status(200).json({ return: {} }) + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + res.status(200).json({ return: result }) } diff --git a/packages/medusa/src/api-v2/store/return/validators.ts b/packages/medusa/src/api-v2/store/return/validators.ts index 343110ff16..bcef0d9e3a 100644 --- a/packages/medusa/src/api-v2/store/return/validators.ts +++ b/packages/medusa/src/api-v2/store/return/validators.ts @@ -16,10 +16,11 @@ export const ReturnsParams = createFindParams().merge( const ReturnShippingSchema = z.object({ option_id: z.string(), + price: z.number().optional(), }) const ItemSchema = z.object({ - item_id: z.string(), + id: z.string(), quantity: z.number().min(1), reason_id: z.string().optional(), note: z.string().optional(), @@ -28,5 +29,12 @@ const ItemSchema = z.object({ export const StorePostReturnsReqSchema = z.object({ order_id: z.string(), items: z.array(ItemSchema), - return_shipping: ReturnShippingSchema.optional(), + return_shipping: ReturnShippingSchema, + note: z.string().optional(), + receive_now: z.boolean().optional(), + refund_amount: z.number().optional(), + location_id: z.string().optional(), }) +export type StorePostReturnsReqSchemaType = z.infer< + typeof StorePostReturnsReqSchema +> diff --git a/packages/modules/order/src/services/order-module-service.ts b/packages/modules/order/src/services/order-module-service.ts index 70f2fe89ab..522ab9f834 100644 --- a/packages/modules/order/src/services/order-module-service.ts +++ b/packages/modules/order/src/services/order-module-service.ts @@ -26,6 +26,7 @@ import { MedusaError, ModulesSdkUtils, OrderChangeStatus, + OrderStatus, promiseAll, } from "@medusajs/utils" import { @@ -2545,6 +2546,7 @@ export default class OrderModuleService< return await this.returnReasonService_.update(toUpdate, sharedContext) } + @InjectTransactionManager("baseRepository_") public async receiveReturn( data: OrderTypes.ReceiveOrderReturnDTO, sharedContext?: Context @@ -2577,4 +2579,111 @@ export default class OrderModuleService< await this.confirmOrderChange(change[0].id, sharedContext) } + + async archive( + orderId: string, + sharedContext?: Context + ): Promise + + async archive( + orderId: string[], + sharedContext?: Context + ): Promise + + @InjectTransactionManager("baseRepository_") + async archive( + orderId: string | string[], + sharedContext?: Context + ): Promise { + const orderIds = Array.isArray(orderId) ? orderId : [orderId] + const orders = await this.list( + { + id: orderIds, + }, + {}, + sharedContext + ) + + const notAllowed: string[] = [] + for (const order of orders) { + if ( + ![ + OrderStatus.COMPLETED, + OrderStatus.CANCELED, + OrderStatus.DRAFT, + ].includes(order.status as any) + ) { + notAllowed.push(order.id) + } + + order.status = OrderStatus.ARCHIVED + } + + if (notAllowed.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Pending Order cannot be archived: ${notAllowed.join(", ")}.` + ) + } + + await this.orderService_.update( + { + id: orderIds, + status: OrderStatus.ARCHIVED, + }, + sharedContext + ) + + return Array.isArray(orderId) ? orders : orders[0] + } + + async completeOrder( + orderId: string, + sharedContext?: Context + ): Promise + async completeOrder( + orderId: string[], + sharedContext?: Context + ): Promise + + @InjectTransactionManager("baseRepository_") + async completeOrder( + orderId: string | string[], + sharedContext?: Context + ): Promise { + const orderIds = Array.isArray(orderId) ? orderId : [orderId] + const orders = await this.list( + { + id: orderIds, + }, + {}, + sharedContext + ) + + const notAllowed: string[] = [] + for (const order of orders) { + if ([OrderStatus.CANCELED].includes(order.status as any)) { + notAllowed.push(order.id) + } + + order.status = OrderStatus.COMPLETED + } + + if (notAllowed.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Canceled Order cannot be completed: ${notAllowed.join(", ")}.` + ) + } + + await this.orderService_.update( + { + id: orderIds, + status: OrderStatus.COMPLETED, + }, + sharedContext + ) + + return Array.isArray(orderId) ? orders : orders[0] + } }