diff --git a/packages/core/core-flows/src/customer-group/steps/update-customer-groups.ts b/packages/core/core-flows/src/customer-group/steps/update-customer-groups.ts index 7acced28eb..3147d2dd7b 100644 --- a/packages/core/core-flows/src/customer-group/steps/update-customer-groups.ts +++ b/packages/core/core-flows/src/customer-group/steps/update-customer-groups.ts @@ -8,7 +8,7 @@ import { getSelectsAndRelationsFromObjectArray, promiseAll, } from "@medusajs/utils" -import { createStep, StepResponse } from "@medusajs/workflows-sdk" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" type UpdateCustomerGroupStepInput = { selector: FilterableCustomerGroupProps @@ -51,6 +51,7 @@ export const updateCustomerGroupsStep = createStep( prevCustomerGroups.map((c) => service.updateCustomerGroups(c.id, { name: c.name, + metadata: c.metadata, }) ) ) diff --git a/packages/core/core-flows/src/return-reason/index.ts b/packages/core/core-flows/src/return-reason/index.ts new file mode 100644 index 0000000000..e84516860c --- /dev/null +++ b/packages/core/core-flows/src/return-reason/index.ts @@ -0,0 +1,2 @@ +export * from "./workflows" +export * from "./steps" diff --git a/packages/core/core-flows/src/return-reason/steps/create-return-reasons.ts b/packages/core/core-flows/src/return-reason/steps/create-return-reasons.ts new file mode 100644 index 0000000000..f73594be1d --- /dev/null +++ b/packages/core/core-flows/src/return-reason/steps/create-return-reasons.ts @@ -0,0 +1,36 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { + CreateOrderReturnReasonDTO, + IOrderModuleService, +} from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +export const createReturnReasonsStepId = "create-return-reasons" +export const createReturnReasonsStep = createStep( + createReturnReasonsStepId, + async (data: CreateOrderReturnReasonDTO[], { container }) => { + const service = container.resolve( + ModuleRegistrationName.ORDER + ) + + const createdReturnReasons = await service.createReturnReasons(data) + + return new StepResponse( + createdReturnReasons, + createdReturnReasons.map( + (createdReturnReasons) => createdReturnReasons.id + ) + ) + }, + async (createdReturnReasonIds, { container }) => { + if (!createdReturnReasonIds?.length) { + return + } + + const service = container.resolve( + ModuleRegistrationName.ORDER + ) + + await service.deleteReturnReasons(createdReturnReasonIds) + } +) diff --git a/packages/core/core-flows/src/return-reason/steps/delete-return-reasons.ts b/packages/core/core-flows/src/return-reason/steps/delete-return-reasons.ts new file mode 100644 index 0000000000..f41ad3a2cb --- /dev/null +++ b/packages/core/core-flows/src/return-reason/steps/delete-return-reasons.ts @@ -0,0 +1,30 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { IOrderModuleService } from "@medusajs/types" +import { createStep, StepResponse } from "@medusajs/workflows-sdk" + +type DeleteReturnReasonStepInput = string[] + +export const deleteReturnReasonStepId = "delete-return-reasons" +export const deleteReturnReasonStep = createStep( + deleteReturnReasonStepId, + async (ids: DeleteReturnReasonStepInput, { container }) => { + const service = container.resolve( + ModuleRegistrationName.ORDER + ) + + await service.softDeleteReturnReasons(ids) + + return new StepResponse(void 0, ids) + }, + async (prevReturnReasons, { container }) => { + if (!prevReturnReasons) { + return + } + + const service = container.resolve( + ModuleRegistrationName.ORDER + ) + + await service.restoreReturnReasons(prevReturnReasons) + } +) diff --git a/packages/core/core-flows/src/return-reason/steps/index.ts b/packages/core/core-flows/src/return-reason/steps/index.ts new file mode 100644 index 0000000000..60df761267 --- /dev/null +++ b/packages/core/core-flows/src/return-reason/steps/index.ts @@ -0,0 +1,3 @@ +export * from "./create-return-reasons" +export * from "./delete-return-reasons" +export * from "./update-return-resons" diff --git a/packages/core/core-flows/src/return-reason/steps/update-return-resons.ts b/packages/core/core-flows/src/return-reason/steps/update-return-resons.ts new file mode 100644 index 0000000000..498c203fdc --- /dev/null +++ b/packages/core/core-flows/src/return-reason/steps/update-return-resons.ts @@ -0,0 +1,61 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { + FilterableOrderReturnReasonProps, + IOrderModuleService, + ReturnReasonUpdatableFields, +} from "@medusajs/types" +import { + getSelectsAndRelationsFromObjectArray, + promiseAll, +} from "@medusajs/utils" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +type UpdateReturnReasonStepInput = { + selector: FilterableOrderReturnReasonProps + update: ReturnReasonUpdatableFields +} + +export const updateReturnReasonStepId = "update-return-reasons" +export const updateReturnReasonsStep = createStep( + updateReturnReasonStepId, + async (data: UpdateReturnReasonStepInput, { container }) => { + const service = container.resolve( + ModuleRegistrationName.ORDER + ) + + const { selects, relations } = getSelectsAndRelationsFromObjectArray([ + data.update, + ]) + const prevReturnReasons = await service.listReturnReasons(data.selector, { + select: selects, + relations, + }) + + const reasons = await service.updateReturnReasons( + data.selector, + data.update + ) + + return new StepResponse(reasons, prevReturnReasons) + }, + async (prevReturnReasons, { container }) => { + if (!prevReturnReasons) { + return + } + + const service = container.resolve( + ModuleRegistrationName.ORDER + ) + + await promiseAll( + prevReturnReasons.map((c) => + service.updateReturnReasons(c.id, { + value: c.value, + label: c.label, + description: c.description, + metadata: c.metadata, + }) + ) + ) + } +) diff --git a/packages/core/core-flows/src/return-reason/workflows/create-return-reasons.ts b/packages/core/core-flows/src/return-reason/workflows/create-return-reasons.ts new file mode 100644 index 0000000000..dea0b291d9 --- /dev/null +++ b/packages/core/core-flows/src/return-reason/workflows/create-return-reasons.ts @@ -0,0 +1,18 @@ +import { + CreateOrderReturnReasonDTO, + OrderReturnReasonDTO, +} from "@medusajs/types" +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { createReturnReasonsStep } from "../steps" + +type WorkflowInput = { data: CreateOrderReturnReasonDTO[] } + +export const createReturnReasonsWorkflowId = "create-return-reasons" +export const createReturnReasonsWorkflow = createWorkflow( + createReturnReasonsWorkflowId, + ( + input: WorkflowData + ): WorkflowData => { + return createReturnReasonsStep(input.data) + } +) diff --git a/packages/core/core-flows/src/return-reason/workflows/delete-return-reasons.ts b/packages/core/core-flows/src/return-reason/workflows/delete-return-reasons.ts new file mode 100644 index 0000000000..b4cb1f490f --- /dev/null +++ b/packages/core/core-flows/src/return-reason/workflows/delete-return-reasons.ts @@ -0,0 +1,12 @@ +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { deleteReturnReasonStep } from "../steps" + +type WorkflowInput = { ids: string[] } + +export const deleteReturnReasonsWorkflowId = "delete-return-reasons" +export const deleteReturnReasonsWorkflow = createWorkflow( + deleteReturnReasonsWorkflowId, + (input: WorkflowData): WorkflowData => { + return deleteReturnReasonStep(input.ids) + } +) diff --git a/packages/core/core-flows/src/return-reason/workflows/index.ts b/packages/core/core-flows/src/return-reason/workflows/index.ts new file mode 100644 index 0000000000..20a5455e9a --- /dev/null +++ b/packages/core/core-flows/src/return-reason/workflows/index.ts @@ -0,0 +1,3 @@ +export * from "./create-return-reasons" +export * from "./delete-return-reasons" +export * from "./update-return-reasons" diff --git a/packages/core/core-flows/src/return-reason/workflows/update-return-reasons.ts b/packages/core/core-flows/src/return-reason/workflows/update-return-reasons.ts new file mode 100644 index 0000000000..7c06920232 --- /dev/null +++ b/packages/core/core-flows/src/return-reason/workflows/update-return-reasons.ts @@ -0,0 +1,22 @@ +import { + FilterableOrderReturnReasonProps, + OrderReturnReasonDTO, + ReturnReasonUpdatableFields, +} from "@medusajs/types" +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { updateReturnReasonsStep } from "../steps" + +type WorkflowInput = { + selector: FilterableOrderReturnReasonProps + update: ReturnReasonUpdatableFields +} + +export const updateReturnReasonsWorkflowId = "update-return-reasons" +export const updateReturnReasonsWorkflow = createWorkflow( + updateReturnReasonsWorkflowId, + ( + input: WorkflowData + ): WorkflowData => { + return updateReturnReasonsStep(input) + } +) diff --git a/packages/core/types/src/order/common.ts b/packages/core/types/src/order/common.ts index 2ee1aa1a9f..9342d3517d 100644 --- a/packages/core/types/src/order/common.ts +++ b/packages/core/types/src/order/common.ts @@ -1255,6 +1255,95 @@ export interface OrderTransactionDTO { updated_at: Date | string } +export interface OrderTransactionDTO { + /** + * The ID of the transaction + */ + id: string + /** + * The ID of the associated order + */ + order_id: string + /** + * The associated order + * + * @expandable + */ + order: OrderDTO + /** + * The amount of the transaction + */ + amount: BigNumberValue + /** + * The raw amount of the transaction + */ + raw_amount: BigNumberRawValue + /** + * The currency code of the transaction + */ + currency_code: string + /** + * The reference of the transaction + */ + reference: string + /** + * The ID of the reference + */ + reference_id: string + /** + * The metadata of the transaction + */ + metadata: Record | null + /** + * When the transaction was created + */ + created_at: Date | string + /** + * When the transaction was updated + */ + updated_at: Date | string +} + +export interface OrderReturnReasonDTO { + /** + * The ID of the return reason + */ + id: string + /** + * The unique value of the return reason + */ + value: string + /** + * The label of the return reason + */ + label: string + /** + * The description of the return reason + */ + description?: string + /** + * The parent return reason ID + */ + parent_return_reason_id?: string + + parent_return_reason?: OrderReturnReasonDTO + + return_reason_children?: OrderReturnReasonDTO[] + + /** + * The metadata of the return reason + */ + metadata: Record | null + /** + * When the return reason was created + */ + created_at: Date | string + /** + * When the return reason was updated + */ + updated_at: Date | string +} + export interface FilterableOrderProps extends BaseFilterable { id?: string | string[] @@ -1369,3 +1458,10 @@ export interface FilterableOrderItemProps version?: string | string[] | OperatorMap item_id?: string | string[] | OperatorMap } + +export interface FilterableOrderReturnReasonProps { + id?: string | string[] + value?: string | string[] + label?: string + description?: string +} diff --git a/packages/core/types/src/order/mutations.ts b/packages/core/types/src/order/mutations.ts index 61ef62c21c..0bd2350d50 100644 --- a/packages/core/types/src/order/mutations.ts +++ b/packages/core/types/src/order/mutations.ts @@ -1,5 +1,10 @@ import { BigNumberInput } from "../totals" -import { OrderItemDTO, OrderLineItemDTO } from "./common" +import { + OrderItemDTO, + OrderLineItemDTO, + OrderReturnReasonDTO, + OrderTransactionDTO, +} from "./common" /** ADDRESS START */ export interface UpsertOrderAddressDTO { @@ -310,10 +315,13 @@ export interface UpdateOrderChangeActionDTO { export interface CreateOrderTransactionDTO { order_id: string + description?: string + reference_type?: string + reference_id?: string + internal_note?: string + created_by?: string amount: BigNumberInput currency_code: string - reference?: string - reference_id?: string metadata?: Record | null } @@ -321,11 +329,18 @@ export interface UpdateOrderTransactionDTO { id: string amount?: BigNumberInput currency_code?: string + description?: string + internal_note?: string reference?: string reference_id?: string metadata?: Record | null } +export interface UpdateOrderTransactionWithSelectorDTO { + selector: Partial + data: Partial +} + /** ORDER TRANSACTION END */ /** ORDER DETAIL START */ @@ -401,3 +416,31 @@ export interface CreateOrderReturnDTO { } /** ORDER bundled action flows */ + +export interface CreateOrderReturnReasonDTO { + value: string + label: string + description?: string + parent_return_reason_id?: string + metadata?: Record | null +} + +export interface UpdateOrderReturnReasonDTO { + id?: string + label?: string + value?: string + description?: string + metadata?: Record | null +} + +export interface ReturnReasonUpdatableFields { + value?: string + label?: string + description?: string + metadata?: Record | null +} + +export interface UpdateOrderReturnReasonWithSelectorDTO { + selector: Partial + data: Partial +} diff --git a/packages/core/types/src/order/service.ts b/packages/core/types/src/order/service.ts index c4200e308e..8e2051cd36 100644 --- a/packages/core/types/src/order/service.ts +++ b/packages/core/types/src/order/service.ts @@ -8,9 +8,11 @@ import { FilterableOrderLineItemProps, FilterableOrderLineItemTaxLineProps, FilterableOrderProps, + FilterableOrderReturnReasonProps, FilterableOrderShippingMethodAdjustmentProps, FilterableOrderShippingMethodProps, FilterableOrderShippingMethodTaxLineProps, + FilterableOrderTransactionProps, OrderAddressDTO, OrderChangeActionDTO, OrderChangeDTO, @@ -19,9 +21,11 @@ import { OrderLineItemAdjustmentDTO, OrderLineItemDTO, OrderLineItemTaxLineDTO, + OrderReturnReasonDTO, OrderShippingMethodAdjustmentDTO, OrderShippingMethodDTO, OrderShippingMethodTaxLineDTO, + OrderTransactionDTO, } from "./common" import { CancelOrderChangeDTO, @@ -35,9 +39,11 @@ import { CreateOrderLineItemForOrderDTO, CreateOrderLineItemTaxLineDTO, CreateOrderReturnDTO, + CreateOrderReturnReasonDTO, CreateOrderShippingMethodAdjustmentDTO, CreateOrderShippingMethodDTO, CreateOrderShippingMethodTaxLineDTO, + CreateOrderTransactionDTO, DeclineOrderChangeDTO, RegisterOrderFulfillmentDTO, RegisterOrderShipmentDTO, @@ -48,8 +54,12 @@ import { UpdateOrderLineItemDTO, UpdateOrderLineItemTaxLineDTO, UpdateOrderLineItemWithSelectorDTO, + UpdateOrderReturnReasonDTO, + UpdateOrderReturnReasonWithSelectorDTO, UpdateOrderShippingMethodAdjustmentDTO, UpdateOrderShippingMethodTaxLineDTO, + UpdateOrderTransactionDTO, + UpdateOrderTransactionWithSelectorDTO, UpsertOrderLineItemAdjustmentDTO, } from "./mutations" @@ -1389,6 +1399,112 @@ export interface IOrderModuleService extends IModuleService { revertLastVersion(orderId: string, sharedContext?: Context): Promise + retrieveTransaction( + id: string, + config?: FindConfig, + sharedContext?: Context + ): Promise + + listTransactions( + filters: FilterableOrderTransactionProps, + config?: FindConfig, + sharedContext?: Context + ): Promise + + addTransactions( + data: CreateOrderTransactionDTO, + sharedContext?: Context + ): Promise + + addTransactions( + data: CreateOrderTransactionDTO[], + sharedContext?: Context + ): Promise + + updateTransactions( + data: UpdateOrderTransactionWithSelectorDTO[] + ): Promise + updateTransactions( + selector: Partial, + data: Partial, + sharedContext?: Context + ): Promise + updateTransactions( + id: string, + data: Partial, + sharedContext?: Context + ): Promise + + deleteTransactions( + returnReasonIds: string[], + sharedContext?: Context + ): Promise + + softDeleteTransactions( + storeIds: string[], + config?: SoftDeleteReturn, + sharedContext?: Context + ): Promise | void> + + restoreTransactions( + storeIds: string[], + config?: RestoreReturn, + sharedContext?: Context + ): Promise | void> + + retrieveReturnReason( + reasonId: string, + config?: FindConfig, + sharedContext?: Context + ): Promise + + listReturnReasons( + filters: FilterableOrderReturnReasonProps, + config?: FindConfig, + sharedContext?: Context + ): Promise + + createReturnReasons( + returnReasonData: CreateOrderReturnReasonDTO, + sharedContext?: Context + ): Promise + + createReturnReasons( + returnReasonData: CreateOrderReturnReasonDTO[], + sharedContext?: Context + ): Promise + + updateReturnReasons( + data: UpdateOrderReturnReasonWithSelectorDTO[] + ): Promise + updateReturnReasons( + selector: Partial, + data: Partial, + sharedContext?: Context + ): Promise + updateReturnReasons( + id: string, + data: Partial, + sharedContext?: Context + ): Promise + + deleteReturnReasons( + returnReasonIds: string[], + sharedContext?: Context + ): Promise + + softDeleteReturnReasons( + storeIds: string[], + config?: SoftDeleteReturn, + sharedContext?: Context + ): Promise | void> + + restoreReturnReasons( + storeIds: string[], + config?: RestoreReturn, + sharedContext?: Context + ): Promise | void> + // Bundled flows registerFulfillment( data: RegisterOrderFulfillmentDTO, diff --git a/packages/medusa/src/api-v2/store/return/middlewares.ts b/packages/medusa/src/api-v2/store/return/middlewares.ts new file mode 100644 index 0000000000..299a12319a --- /dev/null +++ b/packages/medusa/src/api-v2/store/return/middlewares.ts @@ -0,0 +1,19 @@ +import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { validateAndTransformBody } from "../../utils/validate-body" +import { validateAndTransformQuery } from "../../utils/validate-query" +import * as QueryConfig from "./query-config" +import { ReturnsParams, StorePostReturnsReqSchema } from "./validators" + +export const storeRegionRoutesMiddlewares: MiddlewareRoute[] = [ + { + method: ["POST"], + matcher: "/store/returns/create-return", + middlewares: [ + validateAndTransformBody(StorePostReturnsReqSchema), + validateAndTransformQuery( + ReturnsParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, +] diff --git a/packages/medusa/src/api-v2/store/return/query-config.ts b/packages/medusa/src/api-v2/store/return/query-config.ts new file mode 100644 index 0000000000..f700e109e1 --- /dev/null +++ b/packages/medusa/src/api-v2/store/return/query-config.ts @@ -0,0 +1,13 @@ +export const defaultReturnFields = [ + "id", + "order_id", + "created_at", + "updated_at", + "deleted_at", + "metadata", +] + +export const retrieveTransformQueryConfig = { + defaults: defaultReturnFields, + isList: false, +} diff --git a/packages/medusa/src/api-v2/store/return/route.ts b/packages/medusa/src/api-v2/store/return/route.ts new file mode 100644 index 0000000000..bde95a2d30 --- /dev/null +++ b/packages/medusa/src/api-v2/store/return/route.ts @@ -0,0 +1,10 @@ +import { CreateOrderReturnDTO } from "@medusajs/types" +import { MedusaRequest, MedusaResponse } from "../../../types/routing" + +export const POST = async (req: MedusaRequest, res: MedusaResponse) => { + const input = [req.validatedBody as CreateOrderReturnDTO] + + // TODO: create return workflow + + res.status(200).json({ return: {} }) +} diff --git a/packages/medusa/src/api-v2/store/return/validators.ts b/packages/medusa/src/api-v2/store/return/validators.ts new file mode 100644 index 0000000000..343110ff16 --- /dev/null +++ b/packages/medusa/src/api-v2/store/return/validators.ts @@ -0,0 +1,32 @@ +import { z } from "zod" +import { createFindParams, createSelectParams } from "../../utils/validators" + +export type ReturnParamsType = z.infer +export const ReturnParams = createSelectParams() + +export type ReturnsParamsType = z.infer +export const ReturnsParams = createFindParams().merge( + z.object({ + id: z.union([z.string(), z.array(z.string())]).optional(), + order_id: z.union([z.string(), z.array(z.string())]).optional(), + $and: z.lazy(() => ReturnsParams.array()).optional(), + $or: z.lazy(() => ReturnsParams.array()).optional(), + }) +) + +const ReturnShippingSchema = z.object({ + option_id: z.string(), +}) + +const ItemSchema = z.object({ + item_id: z.string(), + quantity: z.number().min(1), + reason_id: z.string().optional(), + note: z.string().optional(), +}) + +export const StorePostReturnsReqSchema = z.object({ + order_id: z.string(), + items: z.array(ItemSchema), + return_shipping: ReturnShippingSchema.optional(), +}) diff --git a/packages/modules/order/integration-tests/__tests__/returns.ts b/packages/modules/order/integration-tests/__tests__/returns.ts new file mode 100644 index 0000000000..a78113e578 --- /dev/null +++ b/packages/modules/order/integration-tests/__tests__/returns.ts @@ -0,0 +1,94 @@ +import { Modules } from "@medusajs/modules-sdk" +import { IOrderModuleService } from "@medusajs/types" +import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils" + +jest.setTimeout(100000) + +moduleIntegrationTestRunner({ + moduleName: Modules.ORDER, + testSuite: ({ service }: SuiteOptions) => { + describe("Order Module Service - Returns", () => { + it("should create return reasons", async function () { + const reason = await service.createReturnReasons({ + value: "test", + label: "label test", + description: "description test", + }) + + expect(reason).toEqual({ + id: expect.any(String), + value: "test", + label: "label test", + description: "description test", + return_reason_children: [], + metadata: null, + deleted_at: null, + }) + }) + + it("should create return reasons with parent", async function () { + const reason = await service.createReturnReasons({ + value: "test", + label: "label test", + description: "description test", + }) + + const reason2 = await service.createReturnReasons({ + value: "test 2.0", + label: "child", + parent_return_reason_id: reason.id, + }) + const reason3 = await service.createReturnReasons({ + value: "test 3.0", + label: "child 3", + parent_return_reason_id: reason.id, + }) + + const getChild = await service.retrieveReturnReason(reason2.id, { + relations: ["parent_return_reason"], + }) + expect(getChild).toEqual( + expect.objectContaining({ + id: reason2.id, + value: "test 2.0", + label: "child", + parent_return_reason_id: reason.id, + parent_return_reason: expect.objectContaining({ + id: reason.id, + value: "test", + label: "label test", + description: "description test", + parent_return_reason_id: null, + }), + }) + ) + + const getParent = await service.retrieveReturnReason(reason.id, { + relations: ["return_reason_children"], + }) + expect(getParent).toEqual( + expect.objectContaining({ + id: reason.id, + value: "test", + label: "label test", + description: "description test", + return_reason_children: [ + expect.objectContaining({ + id: reason2.id, + value: "test 2.0", + label: "child", + parent_return_reason_id: reason.id, + }), + expect.objectContaining({ + id: reason3.id, + value: "test 3.0", + label: "child 3", + parent_return_reason_id: reason.id, + }), + ], + }) + ) + }) + }) + }, +}) diff --git a/packages/modules/order/src/joiner-config.ts b/packages/modules/order/src/joiner-config.ts index ed28ce8f01..3d9a455163 100644 --- a/packages/modules/order/src/joiner-config.ts +++ b/packages/modules/order/src/joiner-config.ts @@ -1,12 +1,14 @@ import { Modules } from "@medusajs/modules-sdk" import { ModuleJoinerConfig } from "@medusajs/types" import { MapToConfig } from "@medusajs/utils" -import { LineItem } from "@models" +import { LineItem, ReturnReason } from "@models" import Order from "./models/order" export const LinkableKeys: Record = { order_id: Order.name, order_item_id: LineItem.name, + return_reason_id: ReturnReason.name, + return_reason_value: ReturnReason.name, } const entityLinkableKeysMap: MapToConfig = {} @@ -31,5 +33,11 @@ export const joinerConfig: ModuleJoinerConfig = { entity: Order.name, }, }, + { + name: ["return_reason", "return_reasons"], + args: { + entity: ReturnReason.name, + }, + }, ], } as ModuleJoinerConfig diff --git a/packages/modules/order/src/migrations/Migration20240219102530.ts b/packages/modules/order/src/migrations/Migration20240219102530.ts index fe23b9e2d4..f6a5a0c851 100644 --- a/packages/modules/order/src/migrations/Migration20240219102530.ts +++ b/packages/modules/order/src/migrations/Migration20240219102530.ts @@ -466,6 +466,28 @@ export class Migration20240219102530 extends Migration { CREATE INDEX IF NOT EXISTS "IDX_order_transaction_reference_id" ON "order_transaction" ( reference_id ); + + CREATE TABLE IF NOT EXISTS "return_reason" + ( + id character varying NOT NULL, + value character varying NOT NULL, + label character varying NOT NULL, + description character varying, + metadata JSONB NULL, + parent_return_reason_id character varying, + created_at timestamp with time zone NOT NULL DEFAULT now(), + updated_at timestamp with time zone NOT NULL DEFAULT now(), + deleted_at timestamp with time zone, + CONSTRAINT "return_reason_pkey" PRIMARY KEY (id), + CONSTRAINT "return_reason_parent_return_reason_id_foreign" FOREIGN KEY (parent_return_reason_id) + REFERENCES "return_reason" (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE NO ACTION + ); + + CREATE UNIQUE INDEX IF NOT EXISTS "IDX_return_reason_value" ON "return_reason" USING btree (value ASC NULLS LAST) + WHERE deleted_at IS NOT NULL; + ALTER TABLE if exists "order" ADD CONSTRAINT "order_shipping_address_id_foreign" FOREIGN KEY ("shipping_address_id") REFERENCES "order_address" ("id") ON diff --git a/packages/modules/order/src/models/index.ts b/packages/modules/order/src/models/index.ts index 72fb64f390..e0d0688638 100644 --- a/packages/modules/order/src/models/index.ts +++ b/packages/modules/order/src/models/index.ts @@ -8,6 +8,7 @@ export { default as OrderChangeAction } from "./order-change-action" export { default as OrderItem } from "./order-item" export { default as OrderShippingMethod } from "./order-shipping-method" export { default as OrderSummary } from "./order-summary" +export { default as ReturnReason } from "./return-reason" export { default as ShippingMethod } from "./shipping-method" export { default as ShippingMethodAdjustment } from "./shipping-method-adjustment" export { default as ShippingMethodTaxLine } from "./shipping-method-tax-line" diff --git a/packages/modules/order/src/models/return-reason.ts b/packages/modules/order/src/models/return-reason.ts new file mode 100644 index 0000000000..68e668f422 --- /dev/null +++ b/packages/modules/order/src/models/return-reason.ts @@ -0,0 +1,105 @@ +import { DAL } from "@medusajs/types" +import { + createPsqlIndexStatementHelper, + generateEntityId, +} from "@medusajs/utils" +import { + BeforeCreate, + Cascade, + Entity, + ManyToOne, + OnInit, + OneToMany, + OptionalProps, + PrimaryKey, + Property, +} from "@mikro-orm/core" + +const DeletedAtIndex = createPsqlIndexStatementHelper({ + tableName: "return_reason", + columns: "deleted_at", + where: "deleted_at IS NOT NULL", +}) + +const ValueIndex = createPsqlIndexStatementHelper({ + tableName: "return_reason", + columns: "value", + where: "deleted_at IS NOT NULL", +}) + +const ParentIndex = createPsqlIndexStatementHelper({ + tableName: "return_reason", + columns: "parent_return_reason_id", + where: "deleted_at IS NOT NULL", +}) + +type OptionalOrderProps = "parent_return_reason" | DAL.EntityDateColumns + +@Entity({ tableName: "return_reason" }) +export default class ReturnReason { + [OptionalProps]?: OptionalOrderProps + + @PrimaryKey({ columnType: "text" }) + id: string + + @Property({ columnType: "text" }) + @ValueIndex.MikroORMIndex() + value: string + + @Property({ columnType: "text" }) + label: string + + @Property({ columnType: "text", nullable: true }) + description: string | null = null + + @Property({ columnType: "text", nullable: true }) + @ParentIndex.MikroORMIndex() + parent_return_reason_id?: string | null + + @ManyToOne({ + entity: () => ReturnReason, + fieldName: "parent_return_reason_id", + nullable: true, + cascade: [Cascade.PERSIST], + }) + parent_return_reason?: ReturnReason | null + + @OneToMany( + () => ReturnReason, + (return_reason) => return_reason.parent_return_reason, + { cascade: [Cascade.PERSIST] } + ) + return_reason_children: ReturnReason[] + + @Property({ columnType: "jsonb", nullable: true }) + metadata: Record | null = null + + @Property({ + onCreate: () => new Date(), + columnType: "timestamptz", + defaultRaw: "now()", + }) + created_at: Date + + @Property({ + onCreate: () => new Date(), + onUpdate: () => new Date(), + columnType: "timestamptz", + defaultRaw: "now()", + }) + updated_at: Date + + @Property({ columnType: "timestamptz", nullable: true }) + @DeletedAtIndex.MikroORMIndex() + deleted_at: Date | null = null + + @BeforeCreate() + onCreate() { + this.id = generateEntityId(this.id, "rr") + } + + @OnInit() + onInit() { + this.id = generateEntityId(this.id, "rr") + } +} diff --git a/packages/modules/order/src/services/order-module-service.ts b/packages/modules/order/src/services/order-module-service.ts index 090419d977..bd1a84837f 100644 --- a/packages/modules/order/src/services/order-module-service.ts +++ b/packages/modules/order/src/services/order-module-service.ts @@ -10,6 +10,8 @@ import { OrderDTO, OrderTypes, UpdateOrderItemWithSelectorDTO, + UpdateOrderReturnReasonDTO, + UpdateOrderTransactionDTO, } from "@medusajs/types" import { createRawPropertiesFromBigNumber, @@ -37,6 +39,7 @@ import { OrderItem, OrderShippingMethod, OrderSummary, + ReturnReason, ShippingMethod, ShippingMethodAdjustment, ShippingMethodTaxLine, @@ -75,6 +78,7 @@ type InjectedDependencies = { orderItemService: ModulesSdkTypes.InternalModuleService orderSummaryService: ModulesSdkTypes.InternalModuleService orderShippingMethodService: ModulesSdkTypes.InternalModuleService + returnReasonService: ModulesSdkTypes.InternalModuleService } const generateMethodForModels = [ @@ -91,6 +95,7 @@ const generateMethodForModels = [ OrderItem, OrderSummary, OrderShippingMethod, + ReturnReason, ] export default class OrderModuleService< @@ -107,7 +112,8 @@ export default class OrderModuleService< TOrderChangeAction extends OrderChangeAction = OrderChangeAction, TOrderItem extends OrderItem = OrderItem, TOrderSummary extends OrderSummary = OrderSummary, - TOrderShippingMethod extends OrderShippingMethod = OrderShippingMethod + TOrderShippingMethod extends OrderShippingMethod = OrderShippingMethod, + TReturnReason extends ReturnReason = ReturnReason > extends ModulesSdkUtils.abstractModuleServiceFactory< InjectedDependencies, @@ -128,6 +134,7 @@ export default class OrderModuleService< OrderItem: { dto: OrderTypes.OrderItemDTO } OrderSummary: { dto: OrderTypes.OrderSummaryDTO } OrderShippingMethod: { dto: OrderShippingMethod } + ReturnReason: { dto: OrderTypes.OrderReturnReasonDTO } } >(Order, generateMethodForModels, entityNameToLinkableKeysMap) implements IOrderModuleService @@ -147,6 +154,7 @@ export default class OrderModuleService< protected orderItemService_: ModulesSdkTypes.InternalModuleService protected orderSummaryService_: ModulesSdkTypes.InternalModuleService protected orderShippingMethodService_: ModulesSdkTypes.InternalModuleService + protected returnReasonService_: ModulesSdkTypes.InternalModuleService constructor( { @@ -165,6 +173,7 @@ export default class OrderModuleService< orderItemService, orderSummaryService, orderShippingMethodService, + returnReasonService, }: InjectedDependencies, protected readonly moduleDeclaration: InternalModuleDeclaration ) { @@ -186,6 +195,7 @@ export default class OrderModuleService< this.orderItemService_ = orderItemService this.orderSummaryService_ = orderSummaryService this.orderShippingMethodService_ = orderShippingMethodService + this.returnReasonService_ = returnReasonService } __joinerConfig(): ModuleJoinerConfig { @@ -2261,4 +2271,277 @@ export default class OrderModuleService< await this.confirmOrderChange(change[0].id, sharedContext) } + + public async addTransactions( + transactionData: OrderTypes.CreateOrderTransactionDTO, + sharedContext?: Context + ): Promise + + public async addTransactions( + transactionData: OrderTypes.CreateOrderTransactionDTO[], + sharedContext?: Context + ): Promise + + @InjectTransactionManager("baseRepository_") + public async addTransactions( + transactionData: + | OrderTypes.CreateOrderTransactionDTO + | OrderTypes.CreateOrderTransactionDTO[], + sharedContext?: Context + ): Promise< + OrderTypes.OrderTransactionDTO | OrderTypes.OrderTransactionDTO[] + > { + const data = Array.isArray(transactionData) + ? transactionData + : [transactionData] + + const created = await this.transactionService_.create(data, sharedContext) + + return await this.baseRepository_.serialize( + !Array.isArray(transactionData) ? created[0] : created, + { + populate: true, + } + ) + } + + updateTransactions( + data: OrderTypes.UpdateOrderTransactionWithSelectorDTO[] + ): Promise + + updateTransactions( + selector: Partial, + data: OrderTypes.UpdateOrderTransactionDTO, + sharedContext?: Context + ): Promise + + updateTransactions( + id: string, + data: Partial, + sharedContext?: Context + ): Promise + + @InjectManager("baseRepository_") + async updateTransactions( + idOrDataOrSelector: + | string + | OrderTypes.UpdateOrderTransactionWithSelectorDTO[] + | Partial, + data?: + | OrderTypes.UpdateOrderTransactionDTO + | Partial, + @MedusaContext() sharedContext: Context = {} + ): Promise< + OrderTypes.OrderTransactionDTO[] | OrderTypes.OrderTransactionDTO + > { + let trxs: Transaction[] = [] + if (isString(idOrDataOrSelector)) { + const trx = await this.updateTransaction_( + idOrDataOrSelector, + data as Partial, + sharedContext + ) + + return await this.baseRepository_.serialize( + trx, + { + populate: true, + } + ) + } + + const toUpdate = Array.isArray(idOrDataOrSelector) + ? idOrDataOrSelector + : [ + { + selector: idOrDataOrSelector, + data: data, + } as OrderTypes.UpdateOrderTransactionWithSelectorDTO, + ] + + trxs = await this.updateTransactionsWithSelector_(toUpdate, sharedContext) + + return await this.baseRepository_.serialize< + OrderTypes.OrderTransactionDTO[] + >(trxs, { + populate: true, + }) + } + + @InjectTransactionManager("baseRepository_") + protected async updateTransaction_( + trxId: string, + data: Partial, + @MedusaContext() sharedContext: Context = {} + ): Promise { + const [trx] = await this.transactionService_.update( + [{ id: trxId, ...data }], + sharedContext + ) + + return trx + } + + @InjectTransactionManager("baseRepository_") + protected async updateTransactionsWithSelector_( + updates: OrderTypes.UpdateOrderTransactionWithSelectorDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + let toUpdate: UpdateOrderTransactionDTO[] = [] + + for (const { selector, data } of updates) { + const trxs = await super.listTransactions( + { ...selector }, + {}, + sharedContext + ) + + trxs.forEach((trx) => { + toUpdate.push({ + ...data, + id: trx.id, + }) + }) + } + + return await this.transactionService_.update(toUpdate, sharedContext) + } + + public async createReturnReasons( + transactionData: OrderTypes.CreateOrderReturnReasonDTO, + sharedContext?: Context + ): Promise + + public async createReturnReasons( + transactionData: OrderTypes.CreateOrderReturnReasonDTO[], + sharedContext?: Context + ): Promise + + @InjectTransactionManager("baseRepository_") + public async createReturnReasons( + returnReasonData: + | OrderTypes.CreateOrderReturnReasonDTO + | OrderTypes.CreateOrderReturnReasonDTO[], + sharedContext?: Context + ): Promise< + OrderTypes.OrderReturnReasonDTO | OrderTypes.OrderReturnReasonDTO[] + > { + const data = Array.isArray(returnReasonData) + ? returnReasonData + : [returnReasonData] + + const created = await this.returnReasonService_.create(data, sharedContext) + + return await this.baseRepository_.serialize( + !Array.isArray(returnReasonData) ? created[0] : created, + { + populate: true, + } + ) + } + + updateReturnReasons( + data: OrderTypes.UpdateOrderReturnReasonWithSelectorDTO[] + ): Promise + + updateReturnReasons( + selector: Partial, + data: OrderTypes.UpdateOrderReturnReasonDTO, + sharedContext?: Context + ): Promise + + updateReturnReasons( + id: string, + data: Partial, + sharedContext?: Context + ): Promise + + @InjectManager("baseRepository_") + async updateReturnReasons( + idOrDataOrSelector: + | string + | OrderTypes.UpdateOrderReturnReasonWithSelectorDTO[] + | Partial, + data?: + | OrderTypes.UpdateOrderReturnReasonDTO + | Partial, + @MedusaContext() sharedContext: Context = {} + ): Promise< + OrderTypes.OrderReturnReasonDTO[] | OrderTypes.OrderReturnReasonDTO + > { + let reasons: ReturnReason[] = [] + if (isString(idOrDataOrSelector)) { + const reason = await this.updateReturnReason_( + idOrDataOrSelector, + data as Partial, + sharedContext + ) + + return await this.baseRepository_.serialize( + reason, + { + populate: true, + } + ) + } + + const toUpdate = Array.isArray(idOrDataOrSelector) + ? idOrDataOrSelector + : [ + { + selector: idOrDataOrSelector, + data: data, + } as OrderTypes.UpdateOrderReturnReasonWithSelectorDTO, + ] + + reasons = await this.updateReturnReasonsWithSelector_( + toUpdate, + sharedContext + ) + + return await this.baseRepository_.serialize< + OrderTypes.OrderReturnReasonDTO[] + >(reasons, { + populate: true, + }) + } + + @InjectTransactionManager("baseRepository_") + protected async updateReturnReason_( + reasonId: string, + data: Partial, + @MedusaContext() sharedContext: Context = {} + ): Promise { + const [reason] = await this.returnReasonService_.update( + [{ id: reasonId, ...data }], + sharedContext + ) + + return reason + } + + @InjectTransactionManager("baseRepository_") + protected async updateReturnReasonsWithSelector_( + updates: OrderTypes.UpdateOrderReturnReasonWithSelectorDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + let toUpdate: UpdateOrderReturnReasonDTO[] = [] + + for (const { selector, data } of updates) { + const reasons = await super.listReturnReasons( + { ...selector }, + {}, + sharedContext + ) + + reasons.forEach((reason) => { + toUpdate.push({ + ...data, + id: reason.id, + }) + }) + } + + return await this.returnReasonService_.update(toUpdate, sharedContext) + } }