diff --git a/packages/medusa-js/src/resources/admin/orders.ts b/packages/medusa-js/src/resources/admin/orders.ts index 71a4a568ba..76c2a9edfd 100644 --- a/packages/medusa-js/src/resources/admin/orders.ts +++ b/packages/medusa-js/src/resources/admin/orders.ts @@ -15,21 +15,12 @@ import { AdminPostOrdersOrderSwapsReq, AdminPostOrdersOrderSwapsSwapFulfillmentsReq, AdminPostOrdersOrderSwapsSwapShipmentsReq, - AdminPostOrdersReq, } from "@medusajs/medusa" import qs from "qs" import { ResponsePromise } from "../../typings" import BaseResource from "../base" class AdminOrdersResource extends BaseResource { - create( - payload: AdminPostOrdersReq, - customHeaders: Record = {} - ): ResponsePromise { - const path = `/admin/orders` - return this.client.request("POST", path, payload, {}, customHeaders) - } - update( id: string, payload: AdminPostOrdersOrderReq, @@ -261,15 +252,6 @@ class AdminOrdersResource extends BaseResource { const path = `/admin/orders/${id}/claims/${claimId}/shipments` return this.client.request("POST", path, payload, {}, customHeaders) } - - deleteMetadata( - id: string, - key: string, - customHeaders: Record = {} - ): ResponsePromise { - const path = `/admin/orders/${id}/metadata/${key}` - return this.client.request("DELETE", path, undefined, {}, customHeaders) - } } export default AdminOrdersResource diff --git a/packages/medusa-react/src/hooks/admin/orders/mutations.ts b/packages/medusa-react/src/hooks/admin/orders/mutations.ts index 562c061402..396ccfc964 100644 --- a/packages/medusa-react/src/hooks/admin/orders/mutations.ts +++ b/packages/medusa-react/src/hooks/admin/orders/mutations.ts @@ -7,28 +7,12 @@ import { AdminPostOrdersOrderReturnsReq, AdminPostOrdersOrderShipmentReq, AdminPostOrdersOrderShippingMethodsReq, - AdminPostOrdersReq, } from "@medusajs/medusa" import { Response } from "@medusajs/medusa-js" import { useMutation, UseMutationOptions, useQueryClient } from "react-query" import { useMedusa } from "../../../contexts/medusa" import { buildOptions } from "../../utils/buildOptions" -export const useAdminCreateOrder = ( - options?: UseMutationOptions< - Response, - Error, - AdminPostOrdersReq - > -) => { - const { client } = useMedusa() - const queryClient = useQueryClient() - return useMutation( - (payload: AdminPostOrdersReq) => client.admin.orders.create(payload), - buildOptions(queryClient, adminOrderKeys.lists(), options) - ) -} - export const useAdminUpdateOrder = ( id: string, options?: UseMutationOptions< @@ -234,16 +218,3 @@ export const useAdminArchiveOrder = ( ) ) } - -export const useAdminDeleteOrderMetadata = ( - id: string, - options?: UseMutationOptions, Error, string> -) => { - const { client } = useMedusa() - const queryClient = useQueryClient() - - return useMutation( - (key: string) => client.admin.orders.deleteMetadata(id, key), - buildOptions(queryClient, adminOrderKeys.detail(id), options) - ) -} diff --git a/packages/medusa-react/test/hooks/admin/orders/mutations.test.ts b/packages/medusa-react/test/hooks/admin/orders/mutations.test.ts index a228b2160a..95f7a368e5 100644 --- a/packages/medusa-react/test/hooks/admin/orders/mutations.test.ts +++ b/packages/medusa-react/test/hooks/admin/orders/mutations.test.ts @@ -1,7 +1,5 @@ import { - useAdminCreateOrder, useAdminUpdateOrder, - useAdminDeleteOrderMetadata, useAdminCompleteOrder, useAdminCapturePayment, useAdminRefundPayment, @@ -16,67 +14,6 @@ import { renderHook } from "@testing-library/react-hooks" import { fixtures } from "../../../../mocks/data" import { createWrapper } from "../../../utils" -describe("useAdminCreateOrder hook", () => { - test("creates a order and returns it", async () => { - const order = { - email: "lebron@james.com", - billing_address: { - company: "medusa", - first_name: "Jane", - last_name: "Medusan", - address_1: "jane street", - address_2: "2nd floor", - city: "copenhagen", - country_code: "dk", - province: "copenhagen", - postal_code: "382793", - phone: "4897394", - metadata: null, - }, - shipping_address: { - company: "medusa", - first_name: "Jane", - last_name: "Medusan", - address_1: "jane street", - address_2: "2nd floor", - city: "copenhagen", - country_code: "dk", - province: "copenhagen", - postal_code: "382793", - phone: "4897394", - metadata: null, - }, - items: [ - { - variant_id: "test-variant", - quantity: 1, - }, - ], - region: "test-region", - customer_id: "cus_test", - payment_method: { - provider_id: "test-pay", - }, - } - - const { result, waitFor } = renderHook(() => useAdminCreateOrder(), { - wrapper: createWrapper(), - }) - - result.current.mutate(order) - - await waitFor(() => result.current.isSuccess) - - expect(result.current.data.response.status).toEqual(200) - expect(result.current.data.order).toEqual( - expect.objectContaining({ - ...fixtures.get("order"), - ...order, - }) - ) - }) -}) - describe("useAdminUpdateOrder hook", () => { test("updates a order and returns it", async () => { const order = { @@ -290,23 +227,3 @@ describe("useAdminCancelFulfillment hook", () => { expect(result.current.data.order).toEqual(fixtures.get("order")) }) }) - -describe("useAdminDeleteOrderMetadata hook", () => { - test("remove metadata field on order", async () => { - const id = fixtures.get("order").id - - const { result, waitFor } = renderHook( - () => useAdminDeleteOrderMetadata(id), - { - wrapper: createWrapper(), - } - ) - - result.current.mutate("some_key") - - await waitFor(() => result.current.isSuccess) - - expect(result.current.data.response.status).toEqual(200) - expect(result.current.data.order).toEqual(fixtures.get("order")) - }) -}) diff --git a/packages/medusa/src/api/routes/admin/orders/__tests__/create-order.js b/packages/medusa/src/api/routes/admin/orders/__tests__/create-order.js deleted file mode 100644 index 3a68bab85e..0000000000 --- a/packages/medusa/src/api/routes/admin/orders/__tests__/create-order.js +++ /dev/null @@ -1,144 +0,0 @@ -import { IdMap } from "medusa-test-utils" -import { request } from "../../../../../helpers/test-request" -import { - orders, - OrderServiceMock, -} from "../../../../../services/__mocks__/order" - -describe("POST /admin/orders", () => { - describe("successful creation", () => { - let subject - - beforeAll(async () => { - subject = await request("POST", "/admin/orders", { - payload: { - email: "virgil@vandijk.dk", - billing_address: { - first_name: "Virgil", - last_name: "Van Dijk", - address_1: "24 Dunks Drive", - city: "Los Angeles", - country_code: "US", - province: "CA", - postal_code: "93011", - phone: "+1 (222) 333 4444", - }, - shipping_address: { - first_name: "Virgil", - last_name: "Van Dijk", - address_1: "24 Dunks Drive", - city: "Los Angeles", - country_code: "US", - province: "CA", - postal_code: "93011", - phone: "+1 (222) 333 4444", - }, - items: [ - { - _id: IdMap.getId("existingLine"), - title: "merge line", - description: "This is a new line", - thumbnail: "test-img-yeah.com/thumb", - content: { - unit_price: 123, - variant: { - _id: IdMap.getId("can-cover"), - }, - product: { - _id: IdMap.getId("validId"), - }, - quantity: 1, - }, - quantity: 10, - }, - ], - region: IdMap.getId("testRegion"), - customer_id: IdMap.getId("testCustomer"), - payment_method: { - provider_id: "default_provider", - data: {}, - }, - shipping_method: [ - { - provider_id: "default_provider", - profile_id: IdMap.getId("validId"), - price: 123, - data: {}, - items: [], - }, - ], - }, - adminSession: { - jwt: { - userId: IdMap.getId("admin_user"), - }, - }, - }) - }) - - it("returns 200", () => { - expect(subject.status).toEqual(200) - }) - - it("calls OrderService create", () => { - expect(OrderServiceMock.create).toHaveBeenCalledTimes(1) - expect(OrderServiceMock.create).toHaveBeenCalledWith({ - email: "virgil@vandijk.dk", - billing_address: { - first_name: "Virgil", - last_name: "Van Dijk", - address_1: "24 Dunks Drive", - city: "Los Angeles", - country_code: "US", - province: "CA", - postal_code: "93011", - phone: "+1 (222) 333 4444", - }, - shipping_address: { - first_name: "Virgil", - last_name: "Van Dijk", - address_1: "24 Dunks Drive", - city: "Los Angeles", - country_code: "US", - province: "CA", - postal_code: "93011", - phone: "+1 (222) 333 4444", - }, - items: [ - { - _id: IdMap.getId("existingLine"), - title: "merge line", - description: "This is a new line", - thumbnail: "test-img-yeah.com/thumb", - content: { - unit_price: 123, - variant: { - _id: IdMap.getId("can-cover"), - }, - product: { - _id: IdMap.getId("validId"), - }, - quantity: 1, - }, - quantity: 10, - }, - ], - region: IdMap.getId("testRegion"), - customer_id: IdMap.getId("testCustomer"), - payment_method: { - provider_id: "default_provider", - data: {}, - }, - shipping_method: [ - { - provider_id: "default_provider", - profile_id: IdMap.getId("validId"), - price: 123, - data: {}, - items: [], - }, - ], - }) - }) - }) -}) diff --git a/packages/medusa/src/api/routes/admin/orders/__tests__/delete-metadata.js b/packages/medusa/src/api/routes/admin/orders/__tests__/delete-metadata.js deleted file mode 100644 index cbd3452225..0000000000 --- a/packages/medusa/src/api/routes/admin/orders/__tests__/delete-metadata.js +++ /dev/null @@ -1,35 +0,0 @@ -import { IdMap } from "medusa-test-utils" -import { request } from "../../../../../helpers/test-request" -import { OrderServiceMock } from "../../../../../services/__mocks__/order" - -describe("DELETE /admin/orders/:id/metadata/key", () => { - describe("successfully deletes metadata on order", () => { - let subject - - beforeAll(async () => { - subject = await request( - "DELETE", - `/admin/orders/${IdMap.getId("test-order")}/metadata/test-key`, - { - adminSession: { - jwt: { - userId: IdMap.getId("admin_user"), - }, - }, - } - ) - }) - - it("returns 200", () => { - expect(subject.status).toEqual(200) - }) - - it("calls OrderService deleteMetadata", () => { - expect(OrderServiceMock.deleteMetadata).toHaveBeenCalledTimes(1) - expect(OrderServiceMock.deleteMetadata).toHaveBeenCalledWith( - IdMap.getId("test-order"), - "test-key" - ) - }) - }) -}) diff --git a/packages/medusa/src/api/routes/admin/orders/create-fulfillment.ts b/packages/medusa/src/api/routes/admin/orders/create-fulfillment.ts index 6758e537a6..3470015283 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-fulfillment.ts +++ b/packages/medusa/src/api/routes/admin/orders/create-fulfillment.ts @@ -92,7 +92,7 @@ export class AdminPostOrdersOrderFulfillmentsReq { @IsObject() @IsOptional() - metadata?: object + metadata?: Record } class Item { diff --git a/packages/medusa/src/api/routes/admin/orders/create-order.ts b/packages/medusa/src/api/routes/admin/orders/create-order.ts deleted file mode 100644 index bbb65c562d..0000000000 --- a/packages/medusa/src/api/routes/admin/orders/create-order.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { Type } from "class-transformer" -import { - IsEmail, - IsOptional, - ValidateNested, - IsArray, - IsString, - IsBoolean, - IsObject, - IsInt, - IsNotEmpty, -} from "class-validator" -import { OrderService } from "../../../../services" -import { AddressPayload } from "../../../../types/common" -import { validator } from "../../../../utils/validator" -/** - * @oas [post] /orders - * operationId: "PostOrders" - * summary: "Create an order" - * description: "Creates and order" - * x-authenticated: true - * requestBody: - * content: - * application/json: - * required: - * - email - * - billing_address - * - shipping_address - * - items - * - region - * - customer_id - * - payment_method - * - shipping_method - * schema: - * properties: - * status: - * description: status of the order - * type: string - * email: - * description: the email for the order - * type: string - * billing_address: - * description: Billing address - * anyOf: - * - $ref: "#/components/schemas/address - * shipping_address: - * description: Shipping address - * anyOf: - * - $ref: "#/components/schemas/address - * items: - * description: The Line Items for the order - * type: array - * region: - * description: Region where the order belongs - * type: string - * discounts: - * description: Discounts applied to the order - * type: array - * customer_id: - * description: id of the customer - * type: string - * payment_method: - * description: payment method chosen for the order - * type: object - * required: - * - provider_id - * properties: - * provider_id: - * type: string - * description: id of the payment provider - * data: - * description: Data relevant for the given payment method - * type: object - * shipping_method: - * description: The Shipping Method used for shipping the order. - * type: object - * required: - * - provider_id - * - profile_id - * - price - * properties: - * provider_id: - * type: string - * description: The id of the shipping provider. - * profile_id: - * type: string - * description: The id of the shipping profile. - * price: - * type: integer - * description: The price of the shipping. - * data: - * type: object - * description: Data relevant to the specific shipping method. - * items: - * type: array - * description: Items to ship - * no_notification: - * description: A flag to indicate if no notifications should be emitted related to the updated order. - * type: boolean - * tags: - * - Order - * responses: - * 200: - * description: OK - * content: - * application/json: - * schema: - * properties: - * order: - * $ref: "#/components/schemas/order" - */ - -export default async (req, res) => { - const validated = await validator(AdminPostOrdersReq, req.body) - - const orderService: OrderService = req.scope.resolve("orderService") - let order = await orderService.create(validated) - order = await orderService.decorate(order, [], ["region"]) - - res.status(200).json({ order }) -} - -export class AdminPostOrdersReq { - @IsString() - @IsOptional() - status?: string - - @IsEmail() - @IsNotEmpty() - email: string - - @IsNotEmpty() - @ValidateNested() - @Type(() => AddressPayload) - billing_address: AddressPayload - - @IsNotEmpty() - @ValidateNested() - @Type(() => AddressPayload) - shipping_address: AddressPayload - - @IsArray() - @IsNotEmpty() - items: object[] - - @IsString() - @IsNotEmpty() - region: string - - @IsArray() - @IsOptional() - discounts?: object[] - - @IsString() - @IsNotEmpty() - customer_id: string - - @IsNotEmpty() - @ValidateNested() - @Type(() => PaymentMethod) - payment_method: PaymentMethod - - @IsNotEmpty() - @ValidateNested({ each: true }) - @Type(() => ShippingMethod) - shipping_method?: ShippingMethod[] - - @IsBoolean() - @IsOptional() - no_notification?: boolean -} - -class PaymentMethod { - @IsString() - @IsNotEmpty() - provider_id: string - - @IsObject() - @IsOptional() - data?: object -} - -class ShippingMethod { - @IsString() - @IsNotEmpty() - provider_id: string - - @IsString() - @IsNotEmpty() - profile_id: string - - @IsInt() - @IsNotEmpty() - price: number - - @IsObject() - @IsOptional() - data?: object - - @IsArray() - @IsOptional() - items?: object[] -} diff --git a/packages/medusa/src/api/routes/admin/orders/create-shipment.ts b/packages/medusa/src/api/routes/admin/orders/create-shipment.ts index 495ab1d7f0..49cca960e6 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-shipment.ts +++ b/packages/medusa/src/api/routes/admin/orders/create-shipment.ts @@ -8,6 +8,7 @@ import { import { defaultAdminOrdersRelations, defaultAdminOrdersFields } from "." import { OrderService } from "../../../../services" import { validator } from "../../../../utils/validator" +import { TrackingLink } from "../../../../models" /** * @oas [post] /orders/{id}/shipment * operationId: "PostOrdersOrderShipment" @@ -56,8 +57,13 @@ export default async (req, res) => { await orderService.createShipment( id, validated.fulfillment_id, - validated.tracking_numbers?.map((n) => ({ tracking_number: n })), - { no_notification: validated.no_notification } + validated.tracking_numbers?.map((n) => ({ + tracking_number: n, + })) as TrackingLink[], + { + metadata: {}, + no_notification: validated.no_notification, + } ) const order = await orderService.retrieve(id, { diff --git a/packages/medusa/src/api/routes/admin/orders/delete-metadata.ts b/packages/medusa/src/api/routes/admin/orders/delete-metadata.ts deleted file mode 100644 index ec4788b278..0000000000 --- a/packages/medusa/src/api/routes/admin/orders/delete-metadata.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { OrderService } from "../../../../services" - -/** - * @oas [delete] /order/{id}/metadata/{key} - * operationId: "DeleteOrdersOrderMetadataKey" - * summary: "Delete Metadata" - * description: "Deletes a metadata key." - * x-authenticated: true - * parameters: - * - (path) id=* {string} The id of the Order. - * - (path) key=* {string} The metadata key. - * tags: - * - Order - * responses: - * 200: - * description: OK - * content: - * application/json: - * schema: - * properties: - * order: - * $ref: "#/components/schemas/order" - */ -export default async (req, res) => { - const { id, key } = req.params - - const orderService: OrderService = req.scope.resolve("orderService") - - await orderService.deleteMetadata(id, key) - - const order = await orderService.retrieve(id, { - relations: ["region", "customer", "swaps"], - }) - - res.status(200).json({ order }) -} diff --git a/packages/medusa/src/api/routes/admin/orders/index.ts b/packages/medusa/src/api/routes/admin/orders/index.ts index cb5ccaa8b4..5da3245722 100644 --- a/packages/medusa/src/api/routes/admin/orders/index.ts +++ b/packages/medusa/src/api/routes/admin/orders/index.ts @@ -48,11 +48,6 @@ export default (app, featureFlagRouter: FlagRouter) => { middlewares.wrap(require("./get-order").default) ) - /** - * Create a new order - */ - route.post("/", middlewares.wrap(require("./create-order").default)) - /** * Update an order */ @@ -225,14 +220,6 @@ export default (app, featureFlagRouter: FlagRouter) => { middlewares.wrap(require("./create-claim-shipment").default) ) - /** - * Delete metadata key / value pair. - */ - route.delete( - "/:id/metadata/:key", - middlewares.wrap(require("./delete-metadata").default) - ) - return app } @@ -314,7 +301,7 @@ export const defaultAdminOrdersFields = [ "paid_total", "refundable_amount", "no_notification", -] +] as (keyof Order)[] export const allowedAdminOrdersFields = [ "id", @@ -393,11 +380,9 @@ export * from "./complete-order" export * from "./create-claim" export * from "./create-claim-shipment" export * from "./create-fulfillment" -export * from "./create-order" export * from "./create-shipment" export * from "./create-swap" export * from "./create-swap-shipment" -export * from "./delete-metadata" export * from "./fulfill-claim" export * from "./fulfill-swap" export * from "./get-order" diff --git a/packages/medusa/src/api/routes/admin/orders/list-orders.ts b/packages/medusa/src/api/routes/admin/orders/list-orders.ts index d63171006c..a9582607cd 100644 --- a/packages/medusa/src/api/routes/admin/orders/list-orders.ts +++ b/packages/medusa/src/api/routes/admin/orders/list-orders.ts @@ -3,6 +3,7 @@ import { pick } from "lodash" import { OrderService } from "../../../../services" import { AdminListOrdersSelector } from "../../../../types/orders" import { Type } from "class-transformer" +import { Order } from "../../../../models" /** * @oas [get] /orders @@ -55,7 +56,7 @@ export default async (req, res) => { req.listConfig ) - let data = orders + let data: Partial[] = orders const fields = [...select, ...relations] if (fields.length) { diff --git a/packages/medusa/src/api/routes/admin/orders/update-order.ts b/packages/medusa/src/api/routes/admin/orders/update-order.ts index f77b6d3d17..d6c3bea144 100644 --- a/packages/medusa/src/api/routes/admin/orders/update-order.ts +++ b/packages/medusa/src/api/routes/admin/orders/update-order.ts @@ -52,17 +52,17 @@ import { validator } from "../../../../utils/validator" * type: string * payment_method: * description: - * type: object + * type: Record * properties: * provider_id: * type: string * description: id of the payment provider * data: * description: Data relevant for the given payment method - * type: object + * type: Record * shipping_method: * description: The Shipping Method used for shipping the order. - * type: object + * type: Record * properties: * provider_id: * type: string @@ -74,7 +74,7 @@ import { validator } from "../../../../utils/validator" * type: integer * description: The price of the shipping. * data: - * type: object + * type: Record * description: Data relevant to the specific shipping method. * items: * type: array @@ -129,7 +129,7 @@ export class AdminPostOrdersOrderReq { @IsArray() @IsOptional() - items?: object[] + items?: Record[] @IsString() @IsOptional() @@ -137,7 +137,7 @@ export class AdminPostOrdersOrderReq { @IsArray() @IsOptional() - discounts?: object[] + discounts?: Record[] @IsString() @IsOptional() @@ -165,7 +165,7 @@ class PaymentMethod { @IsObject() @IsOptional() - data?: object + data?: Record } class ShippingMethod { @@ -183,9 +183,9 @@ class ShippingMethod { @IsObject() @IsOptional() - data?: object + data?: Record @IsArray() @IsOptional() - items?: object[] + items?: Record[] } diff --git a/packages/medusa/src/api/routes/store/customers/list-orders.ts b/packages/medusa/src/api/routes/store/customers/list-orders.ts index cc538989b5..bf3b7543cb 100644 --- a/packages/medusa/src/api/routes/store/customers/list-orders.ts +++ b/packages/medusa/src/api/routes/store/customers/list-orders.ts @@ -6,6 +6,8 @@ import { allowedStoreOrdersFields, allowedStoreOrdersRelations, } from "../orders" +import { FindConfig } from "../../../../types/common" +import { Order } from "../../../../models" /** * @oas [get] /customers/me/orders @@ -77,7 +79,7 @@ export default async (req, res) => { skip: validated.offset, take: validated.limit, order: { created_at: "DESC" }, - } + } as FindConfig const [orders, count] = await orderService.listAndCount(selector, listConfig) diff --git a/packages/medusa/src/api/routes/store/orders/index.ts b/packages/medusa/src/api/routes/store/orders/index.ts index 8b01f357c8..29f301a45e 100644 --- a/packages/medusa/src/api/routes/store/orders/index.ts +++ b/packages/medusa/src/api/routes/store/orders/index.ts @@ -65,7 +65,7 @@ export const defaultStoreOrdersFields = [ "gift_card_total", "subtotal", "total", -] +] as (keyof Order)[] export const allowedStoreOrdersRelations = [ "shipping_address", diff --git a/packages/medusa/src/models/index.ts b/packages/medusa/src/models/index.ts index 89068cda28..0cc4e271ec 100644 --- a/packages/medusa/src/models/index.ts +++ b/packages/medusa/src/models/index.ts @@ -65,4 +65,5 @@ export * from "./store" export * from "./swap" export * from "./tax-provider" export * from "./tax-rate" +export * from "./tracking-link" export * from "./user" diff --git a/packages/medusa/src/models/order.ts b/packages/medusa/src/models/order.ts index fd64620788..43abfeb176 100644 --- a/packages/medusa/src/models/order.ts +++ b/packages/medusa/src/models/order.ts @@ -242,7 +242,7 @@ export class Order extends BaseEntity { // Total fields shipping_total: number discount_total: number - tax_total: number + tax_total: number | null refunded_total: number total: number subtotal: number diff --git a/packages/medusa/src/models/payment-session.ts b/packages/medusa/src/models/payment-session.ts index 82b8e4fc89..dbcdbffe35 100644 --- a/packages/medusa/src/models/payment-session.ts +++ b/packages/medusa/src/models/payment-session.ts @@ -7,10 +7,10 @@ import { ManyToOne, Unique, } from "typeorm" -import { BaseEntity } from "../interfaces/models/base-entity" +import { BaseEntity } from "../interfaces" import { DbAwareColumn } from "../utils/db-aware-column" import { Cart } from "./cart" -import { generateEntityId } from "../utils/generate-entity-id" +import { generateEntityId } from "../utils" export enum PaymentSessionStatus { AUTHORIZED = "authorized", diff --git a/packages/medusa/src/repositories/order.ts b/packages/medusa/src/repositories/order.ts index 3d591ebd37..ee2da05a1a 100644 --- a/packages/medusa/src/repositories/order.ts +++ b/packages/medusa/src/repositories/order.ts @@ -5,7 +5,7 @@ import { Order } from "../models" @EntityRepository(Order) export class OrderRepository extends Repository { public async findWithRelations( - relations: Array = [], + relations: string[] = [], optionsWithoutRelations: Omit, "relations"> = {} ): Promise { const entities = await this.find(optionsWithoutRelations) @@ -38,7 +38,7 @@ export class OrderRepository extends Repository { } public async findOneWithRelations( - relations: Array = [], + relations: string[] = [], optionsWithoutRelations: Omit, "relations"> = {} ): Promise { // Limit 1 diff --git a/packages/medusa/src/services/__mocks__/totals.js b/packages/medusa/src/services/__mocks__/totals.js index 2825ea0230..903b48a12d 100644 --- a/packages/medusa/src/services/__mocks__/totals.js +++ b/packages/medusa/src/services/__mocks__/totals.js @@ -1,6 +1,9 @@ import { IdMap } from "medusa-test-utils" export const TotalsServiceMock = { + withTransaction: function () { + return this + }, getTotal: jest.fn().mockImplementation((cart) => { if (cart.total) { return cart.total diff --git a/packages/medusa/src/services/__tests__/cart.js b/packages/medusa/src/services/__tests__/cart.js index d67b1a91b2..b354f11c82 100644 --- a/packages/medusa/src/services/__tests__/cart.js +++ b/packages/medusa/src/services/__tests__/cart.js @@ -15,6 +15,9 @@ const eventBusService = { describe("CartService", () => { const totalsService = { + withTransaction: function () { + return this + }, getTotal: (o) => { return o.total || 0 }, diff --git a/packages/medusa/src/services/__tests__/order.js b/packages/medusa/src/services/__tests__/order.js index 903c92c938..77d4a9d86b 100644 --- a/packages/medusa/src/services/__tests__/order.js +++ b/packages/medusa/src/services/__tests__/order.js @@ -4,6 +4,9 @@ import { InventoryServiceMock } from "../__mocks__/inventory" describe("OrderService", () => { const totalsService = { + withTransaction: function () { + return this + }, getLineItemRefund: () => {}, getTotal: (o) => { return o.total || 0 @@ -45,35 +48,6 @@ describe("OrderService", () => { ...InventoryServiceMock, } - describe("create", () => { - const orderRepo = MockRepository({ create: (f) => f }) - const orderService = new OrderService({ - manager: MockManager, - orderRepository: orderRepo, - totalsService, - eventBusService, - }) - - beforeEach(async () => { - jest.clearAllMocks() - }) - - it("calls order model functions", async () => { - await orderService.create({ - email: "oliver@test.dk", - }) - - expect(orderRepo.create).toHaveBeenCalledTimes(1) - expect(orderRepo.create).toHaveBeenCalledWith({ - email: "oliver@test.dk", - }) - - expect(orderRepo.save).toHaveBeenCalledWith({ - email: "oliver@test.dk", - }) - }) - }) - describe("createFromCart", () => { const orderRepo = MockRepository({ create: (p) => p, @@ -336,7 +310,7 @@ describe("OrderService", () => { expect(giftCardService.update).toHaveBeenCalledTimes(1) expect(giftCardService.update).toHaveBeenCalledWith("gid", { balance: 0, - disabled: true, + is_disabled: true, }) expect(giftCardService.createTransaction).toHaveBeenCalledTimes(1) diff --git a/packages/medusa/src/services/__tests__/shipping-profile.js b/packages/medusa/src/services/__tests__/shipping-profile.js index e9078f26d7..f9cc55ebc1 100644 --- a/packages/medusa/src/services/__tests__/shipping-profile.js +++ b/packages/medusa/src/services/__tests__/shipping-profile.js @@ -1,12 +1,5 @@ -import { In } from "typeorm" import { IdMap, MockRepository, MockManager } from "medusa-test-utils" import ShippingProfileService from "../shipping-profile" -//import { ShippingProfileModelMock } from "../../models/__mocks__/shipping-profile" -//import { ProductServiceMock, products } from "../__mocks__/product" -//import { -// ShippingOptionServiceMock, -// shippingOptions, -//} from "../__mocks__/shipping-option" describe("ShippingProfileService", () => { describe("retrieve", () => { @@ -202,6 +195,9 @@ describe("ShippingProfileService", () => { } const customShippingOptionService = { + withTransaction: function () { + return this + }, list: jest.fn().mockImplementation(({ cart_id }, config) => { if (cart_id === "cso-cart") { return Promise.resolve([ diff --git a/packages/medusa/src/services/__tests__/totals.js b/packages/medusa/src/services/__tests__/totals.js index fb5ec68a99..9f633e0be7 100644 --- a/packages/medusa/src/services/__tests__/totals.js +++ b/packages/medusa/src/services/__tests__/totals.js @@ -100,7 +100,11 @@ const calculateAdjustment = (cart, lineItem, discount) => { describe("TotalsService", () => { const container = { - taxProviderService: {}, + taxProviderService: { + withTransaction: function () { + return this + }, + }, taxCalculationStrategy: {}, } @@ -537,6 +541,9 @@ describe("TotalsService", () => { const cradle = { taxProviderService: { + withTransaction: function () { + return this + }, getTaxLines: getTaxLinesMock, }, taxCalculationStrategy: { diff --git a/packages/medusa/src/services/fulfillment.ts b/packages/medusa/src/services/fulfillment.ts index 22bc2506e2..0756f335c7 100644 --- a/packages/medusa/src/services/fulfillment.ts +++ b/packages/medusa/src/services/fulfillment.ts @@ -302,7 +302,7 @@ class FulfillmentService extends TransactionBaseService { */ async createShipment( fulfillmentId: string, - trackingLinks: { tracking_number: string }[], + trackingLinks?: { tracking_number: string }[], config: CreateShipmentConfig = { metadata: {}, no_notification: undefined, diff --git a/packages/medusa/src/services/order.js b/packages/medusa/src/services/order.ts similarity index 70% rename from packages/medusa/src/services/order.js rename to packages/medusa/src/services/order.ts index 4214f15892..c68bb4a4f9 100644 --- a/packages/medusa/src/services/order.js +++ b/packages/medusa/src/services/order.ts @@ -1,9 +1,70 @@ import { MedusaError } from "medusa-core-utils" -import { BaseService } from "medusa-interfaces" -import { Brackets } from "typeorm" +import { Brackets, EntityManager } from "typeorm" +import CustomerService from "./customer" +import { OrderRepository } from "../repositories/order" +import PaymentProviderService from "./payment-provider" +import ShippingOptionService from "./shipping-option" +import ShippingProfileService from "./shipping-profile" +import DiscountService from "./discount" +import FulfillmentProviderService from "./fulfillment-provider" +import FulfillmentService from "./fulfillment" +import LineItemService from "./line-item" +import TotalsService from "./totals" +import RegionService from "./region" +import CartService from "./cart" +import { AddressRepository } from "../repositories/address" +import GiftCardService from "./gift-card" +import DraftOrderService from "./draft-order" +import InventoryService from "./inventory" +import EventBusService from "./event-bus" +import { TransactionBaseService } from "../interfaces" +import { buildQuery, setMetadata } from "../utils" +import { FindConfig, QuerySelector, Selector } from "../types/common" +import { + Address, + ClaimOrder, + Fulfillment, + FulfillmentItem, + FulfillmentStatus, + LineItem, + Order, + OrderStatus, + Payment, + PaymentStatus, + Return, + Swap, + TrackingLink, +} from "../models" +import { UpdateOrderInput } from "../types/orders" +import { CreateShippingMethodDto } from "../types/shipping-options" +import { + CreateFulfillmentOrder, + FulFillmentItemType, +} from "../types/fulfillment" -class OrderService extends BaseService { - static Events = { +type InjectedDependencies = { + manager: EntityManager + orderRepository: typeof OrderRepository + customerService: CustomerService + paymentProviderService: PaymentProviderService + shippingOptionService: ShippingOptionService + shippingProfileService: ShippingProfileService + discountService: DiscountService + fulfillmentProviderService: FulfillmentProviderService + fulfillmentService: FulfillmentService + lineItemService: LineItemService + totalsService: TotalsService + regionService: RegionService + cartService: CartService + addressRepository: typeof AddressRepository + giftCardService: GiftCardService + draftOrderService: DraftOrderService + inventoryService: InventoryService + eventBusService: EventBusService +} + +class OrderService extends TransactionBaseService { + static readonly Events = { GIFT_CARD_CREATED: "order.gift_card_created", PAYMENT_CAPTURED: "order.payment_captured", PAYMENT_CAPTURE_FAILED: "order.payment_capture_failed", @@ -22,6 +83,27 @@ class OrderService extends BaseService { COMPLETED: "order.completed", } + protected manager_: EntityManager + protected transactionManager_: EntityManager + + protected readonly orderRepository_: typeof OrderRepository + protected readonly customerService_: CustomerService + protected readonly paymentProviderService_: PaymentProviderService + protected readonly shippingOptionService_: ShippingOptionService + protected readonly shippingProfileService_: ShippingProfileService + protected readonly discountService_: DiscountService + protected readonly fulfillmentProviderService_: FulfillmentProviderService + protected readonly fulfillmentService_: FulfillmentService + protected readonly lineItemService_: LineItemService + protected readonly totalsService_: TotalsService + protected readonly regionService_: RegionService + protected readonly cartService_: CartService + protected readonly addressRepository_: typeof AddressRepository + protected readonly giftCardService_: GiftCardService + protected readonly draftOrderService_: DraftOrderService + protected readonly inventoryService_: InventoryService + protected readonly eventBus_: EventBusService + constructor({ manager, orderRepository, @@ -41,119 +123,48 @@ class OrderService extends BaseService { draftOrderService, inventoryService, eventBusService, - }) { - super() + }: InjectedDependencies) { + // eslint-disable-next-line prefer-rest-params + super(arguments[0]) - /** @private @constant {EntityManager} */ this.manager_ = manager - - /** @private @constant {OrderRepository} */ this.orderRepository_ = orderRepository - - /** @private @constant {CustomerService} */ this.customerService_ = customerService - - /** @private @constant {PaymentProviderService} */ this.paymentProviderService_ = paymentProviderService - - /** @private @constant {ShippingProvileService} */ this.shippingProfileService_ = shippingProfileService - - /** @private @constant {FulfillmentProviderService} */ this.fulfillmentProviderService_ = fulfillmentProviderService - - /** @private @constant {LineItemService} */ this.lineItemService_ = lineItemService - - /** @private @constant {TotalsService} */ this.totalsService_ = totalsService - - /** @private @constant {RegionService} */ this.regionService_ = regionService - - /** @private @constant {FulfillmentService} */ this.fulfillmentService_ = fulfillmentService - - /** @private @constant {DiscountService} */ this.discountService_ = discountService - - /** @private @constant {DiscountService} */ this.giftCardService_ = giftCardService - - /** @private @constant {EventBus} */ this.eventBus_ = eventBusService - - /** @private @constant {ShippingOptionService} */ this.shippingOptionService_ = shippingOptionService - - /** @private @constant {CartService} */ this.cartService_ = cartService - - /** @private @constant {AddressRepository} */ this.addressRepository_ = addressRepository - - /** @private @constant {DraftOrderService} */ this.draftOrderService_ = draftOrderService - - /** @private @constant {InventoryService} */ this.inventoryService_ = inventoryService } - withTransaction(manager) { - if (!manager) { - return this - } - - const cloned = new OrderService({ - manager, - orderRepository: this.orderRepository_, - eventBusService: this.eventBus_, - paymentProviderService: this.paymentProviderService_, - regionService: this.regionService_, - lineItemService: this.lineItemService_, - shippingOptionService: this.shippingOptionService_, - shippingProfileService: this.shippingProfileService_, - fulfillmentProviderService: this.fulfillmentProviderService_, - fulfillmentService: this.fulfillmentService_, - customerService: this.customerService_, - discountService: this.discountService_, - totalsService: this.totalsService_, - cartService: this.cartService_, - giftCardService: this.giftCardService_, - addressRepository: this.addressRepository_, - draftOrderService: this.draftOrderService_, - inventoryService: this.inventoryService_, - }) - - cloned.transactionManager_ = manager - cloned.manager_ = manager - - return cloned - } - /** - * Used to validate order ids. Throws an error if the cast fails - * @param {string} rawId - the raw order id to validate. - * @return {string} the validated id - */ - validateId_(rawId) { - return rawId - } - - /** - * @param {Object} selector - the query object for find - * @param {Object} config - the config to be used for find - * @return {Promise} the result of the find operation + * @param selector the query object for find + * @param config the config to be used for find + * @return the result of the find operation */ async list( - selector, - config = { skip: 0, take: 50, order: { created_at: "DESC" } } - ) { + selector: Selector, + config: FindConfig = { + skip: 0, + take: 50, + order: { created_at: "DESC" }, + } + ): Promise { const orderRepo = this.manager_.getCustomRepository(this.orderRepository_) - const query = this.buildQuery_(selector, config) + const query = buildQuery(selector, config) const { select, relations, totalsToSelect } = - this.transformQueryForTotals_(config) + this.transformQueryForTotals(config) if (select && select.length) { query.select = select @@ -166,14 +177,18 @@ class OrderService extends BaseService { const raw = await orderRepo.find(query) return await Promise.all( - raw.map(async (r) => await this.decorateTotals_(r, totalsToSelect)) + raw.map(async (r) => await this.decorateTotals(r, totalsToSelect)) ) } async listAndCount( - selector, - config = { skip: 0, take: 50, order: { created_at: "DESC" } } - ) { + selector: QuerySelector, + config: FindConfig = { + skip: 0, + take: 50, + order: { created_at: "DESC" }, + } + ): Promise<[Order[], number]> { const orderRepo = this.manager_.getCustomRepository(this.orderRepository_) let q @@ -182,7 +197,7 @@ class OrderService extends BaseService { delete selector.q } - const query = this.buildQuery_(selector, config) + const query = buildQuery(selector, config) if (q) { const where = query.where @@ -197,7 +212,7 @@ class OrderService extends BaseService { }, } - query.where = (qb) => { + query.where = (qb): void => { qb.where(where) qb.andWhere( @@ -213,7 +228,7 @@ class OrderService extends BaseService { } const { select, relations, totalsToSelect } = - this.transformQueryForTotals_(config) + this.transformQueryForTotals(config) if (select && select.length) { query.select = select @@ -225,13 +240,17 @@ class OrderService extends BaseService { const raw = await orderRepo.findWithRelations(rels, query) const count = await orderRepo.count(query) const orders = await Promise.all( - raw.map(async (r) => await this.decorateTotals_(r, totalsToSelect)) + raw.map(async (r) => await this.decorateTotals(r, totalsToSelect)) ) return [orders, count] } - transformQueryForTotals_(config) { + protected transformQueryForTotals(config: FindConfig): { + relations: string[] | undefined + select: FindConfig["select"] + totalsToSelect: FindConfig["select"] + } { let { select, relations } = config if (!select) { @@ -298,20 +317,22 @@ class OrderService extends BaseService { /** * Gets an order by id. - * @param {string} orderId - id of order to retrieve - * @param {Object} config - config of order to retrieve - * @return {Promise} the order document + * @param orderId - id of order to retrieve + * @param config - config of order to retrieve + * @return the order document */ - async retrieve(orderId, config = {}) { + async retrieve( + orderId: string, + config: FindConfig = {} + ): Promise { const orderRepo = this.manager_.getCustomRepository(this.orderRepository_) - const validatedId = this.validateId_(orderId) const { select, relations, totalsToSelect } = - this.transformQueryForTotals_(config) + this.transformQueryForTotals(config) const query = { - where: { id: validatedId }, - } + where: { id: orderId }, + } as FindConfig if (relations && relations.length > 0) { query.relations = relations @@ -331,24 +352,27 @@ class OrderService extends BaseService { ) } - return await this.decorateTotals_(raw, totalsToSelect) + return await this.decorateTotals(raw, totalsToSelect) } /** * Gets an order by cart id. - * @param {string} cartId - cart id to find order - * @param {Object} config - the config to be used to find order - * @return {Promise} the order document + * @param cartId - cart id to find order + * @param config - the config to be used to find order + * @return the order document */ - async retrieveByCartId(cartId, config = {}) { + async retrieveByCartId( + cartId: string, + config: FindConfig = {} + ): Promise { const orderRepo = this.manager_.getCustomRepository(this.orderRepository_) const { select, relations, totalsToSelect } = - this.transformQueryForTotals_(config) + this.transformQueryForTotals(config) const query = { where: { cart_id: cartId }, - } + } as FindConfig if (relations && relations.length > 0) { query.relations = relations @@ -367,25 +391,27 @@ class OrderService extends BaseService { ) } - const order = await this.decorateTotals_(raw, totalsToSelect) - return order + return await this.decorateTotals(raw, totalsToSelect) } /** * Gets an order by id. - * @param {string} externalId - id of order to retrieve - * @param {object} config - query config to get order by - * @return {Promise} the order document + * @param externalId - id of order to retrieve + * @param config - query config to get order by + * @return the order document */ - async retrieveByExternalId(externalId, config = {}) { + async retrieveByExternalId( + externalId: string, + config: FindConfig = {} + ): Promise { const orderRepo = this.manager_.getCustomRepository(this.orderRepository_) const { select, relations, totalsToSelect } = - this.transformQueryForTotals_(config) + this.transformQueryForTotals(config) const query = { where: { external_id: externalId }, - } + } as FindConfig if (relations && relations.length > 0) { query.relations = relations @@ -405,29 +431,25 @@ class OrderService extends BaseService { ) } - const order = await this.decorateTotals_(raw, totalsToSelect) - return order + return await this.decorateTotals(raw, totalsToSelect) } /** * Checks the existence of an order by cart id. - * @param {string} cartId - cart id to find order - * @return {Promise} the order document + * @param cartId - cart id to find order + * @return the order document */ - async existsByCartId(cartId) { - const order = await this.retrieveByCartId(cartId).catch((_) => undefined) - if (!order) { - return false - } - return true + async existsByCartId(cartId: string): Promise { + const order = await this.retrieveByCartId(cartId).catch(() => undefined) + return !!order } /** - * @param {string} orderId - id of the order to complete - * @return {Promise} the result of the find operation + * @param orderId - id of the order to complete + * @return the result of the find operation */ - async completeOrder(orderId) { - return this.atomicPhase_(async (manager) => { + async completeOrder(orderId: string): Promise { + return await this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId) if (order.status === "canceled") { @@ -437,20 +459,12 @@ class OrderService extends BaseService { ) } - // Run all other registered events - const completeOrderJob = await this.eventBus_.emit( - OrderService.Events.COMPLETED, - { - id: orderId, - no_notification: order.no_notification, - } - ) - - await completeOrderJob.finished().catch((error) => { - throw error + await this.eventBus_.emit(OrderService.Events.COMPLETED, { + id: orderId, + no_notification: order.no_notification, }) - order.status = "completed" + order.status = OrderStatus.COMPLETED const orderRepo = manager.getCustomRepository(this.orderRepository_) return orderRepo.save(order) @@ -459,11 +473,11 @@ class OrderService extends BaseService { /** * Creates an order from a cart - * @param {string} cartId - id of the cart to create an order from - * @return {Promise} resolves to the creation result. + * @param cartId - id of the cart to create an order from + * @return resolves to the creation result. */ - async createFromCart(cartId) { - return this.atomicPhase_(async (manager) => { + async createFromCart(cartId: string): Promise { + return await this.atomicPhase_(async (manager) => { const cartService = this.cartService_.withTransaction(manager) const inventoryService = this.inventoryService_.withTransaction(manager) @@ -530,7 +544,7 @@ class OrderService extends BaseService { .getStatus(payment) // If payment status is not authorized, we throw - if (paymentStatus !== "authorized" && paymentStatus !== "succeeded") { + if (paymentStatus !== "authorized") { throw new MedusaError( MedusaError.Types.INVALID_ARGUMENT, "Payment method is not authorized" @@ -553,7 +567,7 @@ class OrderService extends BaseService { cart_id: cart.id, currency_code: region.currency_code, metadata: cart.metadata || {}, - } + } as Partial if (cart.type === "draft_order") { const draft = await this.draftOrderService_ @@ -567,7 +581,7 @@ class OrderService extends BaseService { const o = orderRepo.create(toCreate) const result = await orderRepo.save(o) - if (total !== 0) { + if (total !== 0 && payment) { await this.paymentProviderService_ .withTransaction(manager) .updatePayment(payment.id, { @@ -583,7 +597,7 @@ class OrderService extends BaseService { const usage = g.balance - newBalance await gcService.update(g.id, { balance: newBalance, - disabled: newBalance === 0, + is_disabled: newBalance === 0, }) await gcService.createTransaction({ @@ -631,29 +645,32 @@ class OrderService extends BaseService { * Adds a shipment to the order to indicate that an order has left the * warehouse. Will ask the fulfillment provider for any documents that may * have been created in regards to the shipment. - * @param {string} orderId - the id of the order that has been shipped - * @param {string} fulfillmentId - the fulfillment that has now been shipped - * @param {TrackingLink[] | undefined} trackingLinks - array of tracking numebers + * @param orderId - the id of the order that has been shipped + * @param fulfillmentId - the fulfillment that has now been shipped + * @param trackingLinks - array of tracking numebers * associated with the shipment - * @param {Object} config - the config of the order that has been shipped - * @param {Dictionary} metadata - optional metadata to add to - * the fulfillment - * @return {order} the resulting order following the update. + * @param config - the config of the order that has been shipped + * @return the resulting order following the update. */ async createShipment( - orderId, - fulfillmentId, - trackingLinks, - config = { + orderId: string, + fulfillmentId: string, + trackingLinks?: TrackingLink[], + config: { + no_notification?: boolean + metadata: Record + } = { metadata: {}, no_notification: undefined, } - ) { + ): Promise { const { metadata, no_notification } = config - return this.atomicPhase_(async (manager) => { + return await this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId, { relations: ["items"] }) - const shipment = await this.fulfillmentService_.retrieve(fulfillmentId) + const shipment = await this.fulfillmentService_ + .withTransaction(manager) + .retrieve(fulfillmentId) if (order.status === "canceled") { throw new MedusaError( @@ -681,13 +698,13 @@ class OrderService extends BaseService { no_notification: evaluatedNoNotification, }) - order.fulfillment_status = "shipped" + order.fulfillment_status = FulfillmentStatus.SHIPPED for (const item of order.items) { const shipped = shipmentRes.items.find((si) => si.item_id === item.id) if (shipped) { const shippedQty = (item.shipped_quantity || 0) + shipped.quantity if (shippedQty !== item.quantity) { - order.fulfillment_status = "partially_shipped" + order.fulfillment_status = FulfillmentStatus.PARTIALLY_SHIPPED } await this.lineItemService_.withTransaction(manager).update(item.id, { @@ -695,7 +712,7 @@ class OrderService extends BaseService { }) } else { if (item.shipped_quantity !== item.quantity) { - order.fulfillment_status = "partially_shipped" + order.fulfillment_status = FulfillmentStatus.PARTIALLY_SHIPPED } } } @@ -715,39 +732,24 @@ class OrderService extends BaseService { }) } - /** - * Creates an order - * @param {object} data - the data to create an order - * @return {Promise} resolves to the creation result. - */ - async create(data) { - return this.atomicPhase_(async (manager) => { - const orderRepo = manager.getCustomRepository(this.orderRepository_) - const order = orderRepo.create(data) - const result = await orderRepo.save(order) - await this.eventBus_ - .withTransaction(manager) - .emit(OrderService.Events.PLACED, { - id: result.id, - no_notification: order.no_notification, - }) - return result - }) - } - /** * Updates the order's billing address. - * @param {object} order - the order to update - * @param {object} address - the value to set the billing address to - * @return {Promise} the result of the update operation + * @param order - the order to update + * @param address - the value to set the billing address to + * @return the result of the update operation */ - async updateBillingAddress_(order, address) { + protected async updateBillingAddress( + order: Order, + address: Address + ): Promise { const addrRepo = this.manager_.getCustomRepository(this.addressRepository_) - address.country_code = address.country_code.toLowerCase() + address.country_code = address.country_code?.toLowerCase() ?? null - const region = await this.regionService_.retrieve(order.region_id, { - relations: ["countries"], - }) + const region = await this.regionService_ + .withTransaction(this.manager_) + .retrieve(order.region_id, { + relations: ["countries"], + }) if (!region.countries.find(({ iso_2 }) => address.country_code === iso_2)) { throw new MedusaError( @@ -756,7 +758,7 @@ class OrderService extends BaseService { ) } - address.country_code = address.country_code.toLowerCase() + address.country_code = address.country_code?.toLowerCase() ?? null if (order.billing_address_id) { const addr = await addrRepo.findOne({ @@ -772,17 +774,22 @@ class OrderService extends BaseService { /** * Updates the order's shipping address. - * @param {object} order - the order to update - * @param {object} address - the value to set the shipping address to - * @return {Promise} the result of the update operation + * @param order - the order to update + * @param address - the value to set the shipping address to + * @return the result of the update operation */ - async updateShippingAddress_(order, address) { + protected async updateShippingAddress( + order: Order, + address: Address + ): Promise { const addrRepo = this.manager_.getCustomRepository(this.addressRepository_) - address.country_code = address.country_code.toLowerCase() + address.country_code = address.country_code?.toLowerCase() ?? null - const region = await this.regionService_.retrieve(order.region_id, { - relations: ["countries"], - }) + const region = await this.regionService_ + .withTransaction(this.manager_) + .retrieve(order.region_id, { + relations: ["countries"], + }) if (!region.countries.find(({ iso_2 }) => address.country_code === iso_2)) { throw new MedusaError( @@ -803,8 +810,13 @@ class OrderService extends BaseService { } } - async addShippingMethod(orderId, optionId, data, config = {}) { - return this.atomicPhase_(async (manager) => { + async addShippingMethod( + orderId: string, + optionId: string, + data?: Record, + config: CreateShippingMethodDto = {} + ): Promise { + return await this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId, { select: ["subtotal"], relations: [ @@ -826,7 +838,7 @@ class OrderService extends BaseService { const newMethod = await this.shippingOptionService_ .withTransaction(manager) - .createShippingMethod(optionId, data, { order, ...config }) + .createShippingMethod(optionId, data ?? {}, { order, ...config }) const methods = [newMethod] if (shipping_methods.length) { @@ -856,13 +868,13 @@ class OrderService extends BaseService { * Updates an order. Metadata updates should * use dedicated method, e.g. `setMetadata` etc. The function * will throw errors if metadata updates are attempted. - * @param {string} orderId - the id of the order. Must be a string that + * @param orderId - the id of the order. Must be a string that * can be casted to an ObjectId - * @param {object} update - an object with the update values. - * @return {Promise} resolves to the update result. + * @param update - an object with the update values. + * @return resolves to the update result. */ - async update(orderId, update) { - return this.atomicPhase_(async (manager) => { + async update(orderId: string, update: UpdateOrderInput): Promise { + return await this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId) if (order.status === "canceled") { @@ -901,23 +913,23 @@ class OrderService extends BaseService { } = update if (update.metadata) { - order.metadata = this.setMetadata_(order, metadata) + order.metadata = setMetadata(order, metadata ?? {}) } if (update.shipping_address) { - await this.updateShippingAddress_(order, shipping_address) + await this.updateShippingAddress(order, shipping_address as Address) } if (update.billing_address) { - await this.updateBillingAddress_(order, billing_address) + await this.updateBillingAddress(order, billing_address as Address) } if (update.no_notification) { - order.no_notification = no_notification + order.no_notification = no_notification ?? false } if (update.items) { - for (const item of items) { + for (const item of items as LineItem[]) { await this.lineItemService_.withTransaction(manager).create({ ...item, order_id: orderId, @@ -946,11 +958,11 @@ class OrderService extends BaseService { * Cancels an order. * Throws if fulfillment process has been initiated. * Throws if payment process has been initiated. - * @param {string} orderId - id of order to cancel. - * @return {Promise} result of the update operation. + * @param orderId - id of order to cancel. + * @return result of the update operation. */ - async cancel(orderId) { - return this.atomicPhase_(async (manager) => { + async cancel(orderId: string): Promise { + return await this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId, { relations: [ "fulfillments", @@ -969,17 +981,27 @@ class OrderService extends BaseService { ) } - const throwErrorIf = (arr, pred, type) => - arr?.filter(pred).find((_) => { + const throwErrorIf = ( + arr: (Fulfillment | Return | Swap | ClaimOrder)[], + pred: (obj: Fulfillment | Return | Swap | ClaimOrder) => boolean, + type: string + ): void | never => { + if (arr?.filter(pred).length) { throw new MedusaError( MedusaError.Types.NOT_ALLOWED, `All ${type} must be canceled before canceling an order` ) - }) - const notCanceled = (o) => !o.canceled_at + } + } + + const notCanceled = (o): boolean => !o.canceled_at throwErrorIf(order.fulfillments, notCanceled, "fulfillments") - throwErrorIf(order.returns, (r) => r.status !== "canceled", "returns") + throwErrorIf( + order.returns, + (r) => (r as Return).status !== "canceled", + "returns" + ) throwErrorIf(order.swaps, notCanceled, "swaps") throwErrorIf(order.claims, notCanceled, "claims") @@ -995,9 +1017,9 @@ class OrderService extends BaseService { .cancelPayment(p) } - order.status = "canceled" - order.fulfillment_status = "canceled" - order.payment_status = "canceled" + order.status = OrderStatus.CANCELED + order.fulfillment_status = FulfillmentStatus.CANCELED + order.payment_status = PaymentStatus.CANCELED order.canceled_at = new Date() const orderRepo = manager.getCustomRepository(this.orderRepository_) @@ -1015,11 +1037,11 @@ class OrderService extends BaseService { /** * Captures payment for an order. - * @param {string} orderId - id of order to capture payment for. - * @return {Promise} result of the update operation. + * @param orderId - id of order to capture payment for. + * @return result of the update operation. */ - async capturePayment(orderId) { - return this.atomicPhase_(async (manager) => { + async capturePayment(orderId: string): Promise { + return await this.atomicPhase_(async (manager) => { const orderRepo = manager.getCustomRepository(this.orderRepository_) const order = await this.retrieve(orderId, { relations: ["payments"] }) @@ -1030,7 +1052,7 @@ class OrderService extends BaseService { ) } - const payments = [] + const payments: Payment[] = [] for (const p of order.payments) { if (p.captured_at === null) { const result = await this.paymentProviderService_ @@ -1059,12 +1081,12 @@ class OrderService extends BaseService { order.payments = payments order.payment_status = payments.every((p) => p.captured_at !== null) - ? "captured" - : "requires_action" + ? PaymentStatus.CAPTURED + : PaymentStatus.REQUIRES_ACTION const result = await orderRepo.save(order) - if (order.payment_status === "captured") { + if (order.payment_status === PaymentStatus.CAPTURED) { this.eventBus_ .withTransaction(manager) .emit(OrderService.Events.PAYMENT_CAPTURED, { @@ -1082,13 +1104,16 @@ class OrderService extends BaseService { * fulfillable quantity is lower than the requested fulfillment quantity. * Fulfillable quantity is calculated by subtracting the already fulfilled * quantity from the quantity that was originally purchased. - * @param {LineItem} item - the line item to check has sufficient fulfillable + * @param item - the line item to check has sufficient fulfillable * quantity. - * @param {number} quantity - the quantity that is requested to be fulfilled. - * @return {LineItem} a line item that has the requested fulfillment quantity + * @param quantity - the quantity that is requested to be fulfilled. + * @return a line item that has the requested fulfillment quantity * set. */ - validateFulfillmentLineItem_(item, quantity) { + protected validateFulfillmentLineItem( + item: LineItem, + quantity: number + ): LineItem | null { if (!item) { // This will in most cases be called by a webhook so to ensure that // things go through smoothly in instances where extra items outside @@ -1105,7 +1130,7 @@ class OrderService extends BaseService { return { ...item, quantity, - } + } as LineItem } /** @@ -1113,22 +1138,25 @@ class OrderService extends BaseService { * In a situation where the order has more than one shipping method, * we need to partition the order items, such that they can be sent * to their respective fulfillment provider. - * @param {string} orderId - id of order to cancel. - * @param {Object} itemsToFulfill - items to fulfil. - * @param {Object} config - the config to cancel. - * @return {Promise} result of the update operation. + * @param orderId - id of order to cancel. + * @param itemsToFulfill - items to fulfil. + * @param config - the config to cancel. + * @return result of the update operation. */ async createFulfillment( - orderId, - itemsToFulfill, - config = { + orderId: string, + itemsToFulfill: FulFillmentItemType[], + config: { + no_notification?: boolean + metadata?: Record + } = { no_notification: undefined, metadata: {}, } - ) { + ): Promise { const { metadata, no_notification } = config - return this.atomicPhase_(async (manager) => { + return await this.atomicPhase_(async (manager) => { // NOTE: we are telling the service to calculate all totals for us which // will add to what is fetched from the database. We want this to happen // so that we get all order details. These will thereafter be forwarded @@ -1159,7 +1187,7 @@ class OrderService extends BaseService { ], }) - if (order.status === "canceled") { + if (order.status === OrderStatus.CANCELED) { throw new MedusaError( MedusaError.Types.NOT_ALLOWED, "A canceled order cannot be fulfilled" @@ -1175,17 +1203,21 @@ class OrderService extends BaseService { const fulfillments = await this.fulfillmentService_ .withTransaction(manager) - .createFulfillment(order, itemsToFulfill, { - metadata, - no_notification: no_notification, - order_id: orderId, - }) - let successfullyFulfilled = [] + .createFulfillment( + order as unknown as CreateFulfillmentOrder, + itemsToFulfill, + { + metadata, + no_notification: no_notification, + order_id: orderId, + } + ) + let successfullyFulfilled: FulfillmentItem[] = [] for (const f of fulfillments) { successfullyFulfilled = [...successfullyFulfilled, ...f.items] } - order.fulfillment_status = "fulfilled" + order.fulfillment_status = FulfillmentStatus.FULFILLED // Update all line items to reflect fulfillment for (const item of order.items) { @@ -1203,15 +1235,14 @@ class OrderService extends BaseService { }) if (item.quantity !== fulfilledQuantity) { - order.fulfillment_status = "partially_fulfilled" + order.fulfillment_status = FulfillmentStatus.PARTIALLY_FULFILLED } } else { if (item.quantity !== item.fulfilled_quantity) { - order.fulfillment_status = "partially_fulfilled" + order.fulfillment_status = FulfillmentStatus.PARTIALLY_FULFILLED } } } - const orderRepo = manager.getCustomRepository(this.orderRepository_) order.fulfillments = [...order.fulfillments, ...fulfillments] @@ -1237,11 +1268,11 @@ class OrderService extends BaseService { /** * Cancels a fulfillment (if related to an order) - * @param {string} fulfillmentId - the ID of the fulfillment to cancel - * @return {Promise} updated order + * @param fulfillmentId - the ID of the fulfillment to cancel + * @return updated order */ - async cancelFulfillment(fulfillmentId) { - return this.atomicPhase_(async (manager) => { + async cancelFulfillment(fulfillmentId: string): Promise { + return await this.atomicPhase_(async (manager) => { const canceled = await this.fulfillmentService_ .withTransaction(manager) .cancelFulfillment(fulfillmentId) @@ -1255,7 +1286,7 @@ class OrderService extends BaseService { const order = await this.retrieve(canceled.order_id) - order.fulfillment_status = "canceled" + order.fulfillment_status = FulfillmentStatus.CANCELED const orderRepo = manager.getCustomRepository(this.orderRepository_) const updated = await orderRepo.save(order) @@ -1274,33 +1305,37 @@ class OrderService extends BaseService { /** * Retrieves the order line items, given an array of items. - * @param {Order} order - the order to get line items from - * @param {{ item_id: string, quantity: number }} items - the items to get - * @param {function} transformer - a function to apply to each of the items + * @param order - the order to get line items from + * @param items - the items to get + * @param transformer - a function to apply to each of the items * retrieved from the order, should return a line item. If the transformer * returns an undefined value the line item will be filtered from the * returned array. - * @return {Promise>} the line items generated by the transformer. + * @return the line items generated by the transformer. */ - async getFulfillmentItems_(order, items, transformer) { - const toReturn = await Promise.all( - items.map(async ({ item_id, quantity }) => { - const item = order.items.find((i) => i.id.equals(item_id)) - return transformer(item, quantity) - }) - ) - - return toReturn.filter((i) => !!i) + protected async getFulfillmentItems( + order: Order, + items: FulFillmentItemType[], + transformer: (item: LineItem | undefined, quantity: number) => unknown + ): Promise { + return ( + await Promise.all( + items.map(async ({ item_id, quantity }) => { + const item = order.items.find((i) => i.id === item_id) + return transformer(item, quantity) + }) + ) + ).filter((i) => !!i) as LineItem[] } /** * Archives an order. It only alloved, if the order has been fulfilled * and payment has been captured. - * @param {string} orderId - the order to archive - * @return {Promise} the result of the update operation + * @param orderId - the order to archive + * @return the result of the update operation */ - async archive(orderId) { - return this.atomicPhase_(async (manager) => { + async archive(orderId: string): Promise { + return await this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId) if (order.status !== ("completed" || "refunded")) { @@ -1310,34 +1345,33 @@ class OrderService extends BaseService { ) } - order.status = "archived" + order.status = OrderStatus.ARCHIVED const orderRepo = manager.getCustomRepository(this.orderRepository_) - const result = await orderRepo.save(order) - return result + return await orderRepo.save(order) }) } /** * Refunds a given amount back to the customer. - * @param {string} orderId - id of the order to refund. - * @param {float} refundAmount - the amount to refund. - * @param {string} reason - the reason to refund. - * @param {string | undefined} note - note for refund. - * @param {Object} config - the config for refund. - * @return {Promise} the result of the refund operation. + * @param orderId - id of the order to refund. + * @param refundAmount - the amount to refund. + * @param reason - the reason to refund. + * @param note - note for refund. + * @param config - the config for refund. + * @return the result of the refund operation. */ async createRefund( - orderId, - refundAmount, - reason, - note, - config = { + orderId: string, + refundAmount: number, + reason: string, + note?: string, + config: { no_notification?: boolean } = { no_notification: undefined, } - ) { + ): Promise { const { no_notification } = config - return this.atomicPhase_(async (manager) => { + return await this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId, { select: ["refundable_amount", "total", "refunded_total"], relations: ["payments"], @@ -1375,7 +1409,10 @@ class OrderService extends BaseService { }) } - async decorateTotals_(order, totalsFields = []) { + protected async decorateTotals( + order: Order, + totalsFields: string[] = [] + ): Promise { for (const totalField of totalsFields) { switch (totalField) { case "shipping_total": { @@ -1401,7 +1438,9 @@ class OrderService extends BaseService { break } case "total": { - order.total = await this.totalsService_.getTotal(order) + order.total = await this.totalsService_ + .withTransaction(this.manager_) + .getTotal(order) break } case "refunded_total": { @@ -1424,8 +1463,8 @@ class OrderService extends BaseService { refundable: this.totalsService_.getLineItemRefund(order, { ...i, quantity: i.quantity - (i.returned_quantity || 0), - }), - })) + } as LineItem), + })) as LineItem[] break } case "swaps.additional_items.refundable": { @@ -1435,8 +1474,8 @@ class OrderService extends BaseService { refundable: this.totalsService_.getLineItemRefund(order, { ...i, quantity: i.quantity - (i.returned_quantity || 0), - }), - })) + } as LineItem), + })) as LineItem[] } break } @@ -1447,8 +1486,8 @@ class OrderService extends BaseService { refundable: this.totalsService_.getLineItemRefund(order, { ...i, quantity: i.quantity - (i.returned_quantity || 0), - }), - })) + } as LineItem), + })) as LineItem[] } break } @@ -1468,13 +1507,17 @@ class OrderService extends BaseService { * retuned items are not matching the requested items. Setting the * allowMismatch argument to true, will process the return, ignoring any * mismatches. - * @param {string} orderId - the order to return. - * @param {object} receivedReturn - the received return - * @param {float} customRefundAmount - the custom refund amount return - * @return {Promise} the result of the update operation + * @param orderId - the order to return. + * @param receivedReturn - the received return + * @param customRefundAmount - the custom refund amount return + * @return the result of the update operation */ - async registerReturnReceived(orderId, receivedReturn, customRefundAmount) { - return this.atomicPhase_(async (manager) => { + async registerReturnReceived( + orderId: string, + receivedReturn: Return, + customRefundAmount?: number + ): Promise { + return await this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId, { select: ["total", "refunded_total", "refundable_amount"], relations: ["items", "returns", "payments"], @@ -1499,7 +1542,7 @@ class OrderService extends BaseService { const orderRepo = manager.getCustomRepository(this.orderRepository_) if (refundAmount > order.refundable_amount) { - order.fulfillment_status = "requires_action" + order.fulfillment_status = FulfillmentStatus.REQUIRES_ACTION const result = await orderRepo.save(order) this.eventBus_ .withTransaction(manager) @@ -1527,9 +1570,9 @@ class OrderService extends BaseService { } if (isFullReturn) { - order.fulfillment_status = "returned" + order.fulfillment_status = FulfillmentStatus.RETURNED } else { - order.fulfillment_status = "partially_returned" + order.fulfillment_status = FulfillmentStatus.PARTIALLY_RETURNED } const result = await orderRepo.save(order) @@ -1543,30 +1586,6 @@ class OrderService extends BaseService { return result }) } - - /** - * Dedicated method to delete metadata for an order. - * @param {string} orderId - the order to delete metadata from. - * @param {string} key - key for metadata field - * @return {Promise} resolves to the updated result. - */ - async deleteMetadata(orderId, key) { - const validatedId = this.validateId_(orderId) - - if (typeof key !== "string") { - throw new MedusaError( - MedusaError.Types.INVALID_ARGUMENT, - "Key type is invalid. Metadata keys must be strings" - ) - } - - const keyPath = `metadata.${key}` - return this.orderModel_ - .updateOne({ _id: validatedId }, { $unset: { [keyPath]: "" } }) - .catch((err) => { - throw new MedusaError(MedusaError.Types.DB_ERROR, err.message) - }) - } } export default OrderService diff --git a/packages/medusa/src/services/pricing.ts b/packages/medusa/src/services/pricing.ts index c3a61a2d8e..c6f7452e33 100644 --- a/packages/medusa/src/services/pricing.ts +++ b/packages/medusa/src/services/pricing.ts @@ -407,14 +407,12 @@ class PricingService extends TransactionBaseService { pricingContext.automatic_taxes && pricingContext.price_selection.region_id ) { - shippingOptionRates = - await this.taxProviderService.getRegionRatesForShipping( - shippingOption.id, - { - id: pricingContext.price_selection.region_id, - tax_rate: pricingContext.tax_rate, - } - ) + shippingOptionRates = await this.taxProviderService + .withTransaction(this.manager_) + .getRegionRatesForShipping(shippingOption.id, { + id: pricingContext.price_selection.region_id, + tax_rate: pricingContext.tax_rate, + }) } const price = shippingOption.amount || 0 diff --git a/packages/medusa/src/services/shipping-profile.js b/packages/medusa/src/services/shipping-profile.js index 9df92e68f7..dfba1ba59a 100644 --- a/packages/medusa/src/services/shipping-profile.js +++ b/packages/medusa/src/services/shipping-profile.js @@ -430,12 +430,14 @@ class ShippingProfileService extends BaseService { admin_only: false, } - const customShippingOptions = await this.customShippingOptionService_.list( - { - cart_id: cart.id, - }, - { select: ["id", "shipping_option_id", "price"] } - ) + const customShippingOptions = await this.customShippingOptionService_ + .withTransaction(this.manager_) + .list( + { + cart_id: cart.id, + }, + { select: ["id", "shipping_option_id", "price"] } + ) const hasCustomShippingOptions = customShippingOptions?.length // if there are custom shipping options associated with the cart, use those @@ -443,9 +445,11 @@ class ShippingProfileService extends BaseService { selector.id = customShippingOptions.map((cso) => cso.shipping_option_id) } - const rawOpts = await this.shippingOptionService_.list(selector, { - relations: ["requirements", "profile"], - }) + const rawOpts = await this.shippingOptionService_ + .withTransaction(this.manager_) + .list(selector, { + relations: ["requirements", "profile"], + }) // if there are custom shipping options associated with the cart, return cart shipping options with custom price if (hasCustomShippingOptions) { @@ -464,10 +468,9 @@ class ShippingProfileService extends BaseService { const options = await Promise.all( rawOpts.map(async (so) => { try { - const option = await this.shippingOptionService_.validateCartOption( - so, - cart - ) + const option = await this.shippingOptionService_ + .withTransaction(this.manager_) + .validateCartOption(so, cart) if (option) { return option } diff --git a/packages/medusa/src/services/tax-provider.ts b/packages/medusa/src/services/tax-provider.ts index 4936a382e5..6020c4113e 100644 --- a/packages/medusa/src/services/tax-provider.ts +++ b/packages/medusa/src/services/tax-provider.ts @@ -1,30 +1,32 @@ import { MedusaError } from "medusa-core-utils" import { AwilixContainer } from "awilix" -import { BaseService } from "medusa-interfaces" -import { EntityManager, UpdateResult } from "typeorm" +import { EntityManager } from "typeorm" import Redis from "ioredis" import { LineItemTaxLineRepository } from "../repositories/line-item-tax-line" import { ShippingMethodTaxLineRepository } from "../repositories/shipping-method-tax-line" import { TaxProviderRepository } from "../repositories/tax-provider" -import { LineItemTaxLine } from "../models/line-item-tax-line" -import { TaxProvider } from "../models/tax-provider" -import { LineItem } from "../models/line-item" -import { ShippingMethodTaxLine } from "../models/shipping-method-tax-line" -import { ShippingMethod } from "../models/shipping-method" -import { Region } from "../models/region" -import { Cart } from "../models/cart" +import { + LineItemTaxLine, + TaxProvider, + LineItem, + ShippingMethodTaxLine, + ShippingMethod, + Region, + Cart, +} from "../models" import { isCart } from "../types/cart" -import { PostgresError } from "../utils/exception-formatter" import { ITaxService, ItemTaxCalculationLine, TaxCalculationContext, -} from "../interfaces/tax-service" + TransactionBaseService, +} from "../interfaces" import { TaxServiceRate } from "../types/tax-service" import TaxRateService from "./tax-rate" +import EventBusService from "./event-bus" const CACHE_TIME = 30 // seconds @@ -36,18 +38,20 @@ type RegionDetails = { /** * Finds tax providers and assists in tax related operations. */ -class TaxProviderService extends BaseService { - private container_: AwilixContainer - private manager_: EntityManager - private transactionManager_: EntityManager - private taxRateService_: TaxRateService - private taxLineRepo_: typeof LineItemTaxLineRepository - private smTaxLineRepo_: typeof ShippingMethodTaxLineRepository - private taxProviderRepo_: typeof TaxProviderRepository - private redis_: Redis +class TaxProviderService extends TransactionBaseService { + protected manager_: EntityManager + protected transactionManager_: EntityManager + + protected readonly container_: AwilixContainer + protected readonly taxRateService_: TaxRateService + protected readonly taxLineRepo_: typeof LineItemTaxLineRepository + protected readonly smTaxLineRepo_: typeof ShippingMethodTaxLineRepository + protected readonly taxProviderRepo_: typeof TaxProviderRepository + protected readonly redis_: Redis + protected readonly eventBus_: EventBusService constructor(container: AwilixContainer) { - super() + super(container) this.container_ = container this.taxLineRepo_ = container["lineItemTaxLineRepository"] @@ -59,19 +63,6 @@ class TaxProviderService extends BaseService { this.redis_ = container["redisClient"] } - withTransaction(transactionManager: EntityManager): TaxProviderService { - if (!transactionManager) { - return this - } - - const cloned = new TaxProviderService(this.container_) - - cloned.transactionManager_ = transactionManager - cloned.manager_ = transactionManager - - return cloned - } - async list(): Promise { const tpRepo = this.manager_.getCustomRepository(this.taxProviderRepo_) return tpRepo.find({}) @@ -101,15 +92,19 @@ class TaxProviderService extends BaseService { } async clearTaxLines(cartId: string): Promise { - const taxLineRepo = this.manager_.getCustomRepository(this.taxLineRepo_) - const shippingTaxRepo = this.manager_.getCustomRepository( - this.smTaxLineRepo_ - ) + return await this.atomicPhase_(async (transactionManager) => { + const taxLineRepo = transactionManager.getCustomRepository( + this.taxLineRepo_ + ) + const shippingTaxRepo = transactionManager.getCustomRepository( + this.smTaxLineRepo_ + ) - await Promise.all([ - taxLineRepo.deleteForCart(cartId), - shippingTaxRepo.deleteForCart(cartId), - ]) + await Promise.all([ + taxLineRepo.deleteForCart(cartId), + shippingTaxRepo.deleteForCart(cartId), + ]) + }) } /** @@ -122,43 +117,47 @@ class TaxProviderService extends BaseService { cartOrLineItems: Cart | LineItem[], calculationContext: TaxCalculationContext ): Promise<(ShippingMethodTaxLine | LineItemTaxLine)[]> { - let taxLines: (ShippingMethodTaxLine | LineItemTaxLine)[] = [] - if (isCart(cartOrLineItems)) { - taxLines = await this.getTaxLines( - cartOrLineItems.items, - calculationContext + return await this.atomicPhase_(async (transactionManager) => { + let taxLines: (ShippingMethodTaxLine | LineItemTaxLine)[] = [] + if (isCart(cartOrLineItems)) { + taxLines = await this.getTaxLines( + cartOrLineItems.items, + calculationContext + ) + } else { + taxLines = await this.getTaxLines(cartOrLineItems, calculationContext) + } + + const itemTaxLineRepo = transactionManager.getCustomRepository( + this.taxLineRepo_ + ) + const shippingTaxLineRepo = transactionManager.getCustomRepository( + this.smTaxLineRepo_ ) - } else { - taxLines = await this.getTaxLines(cartOrLineItems, calculationContext) - } - const itemTaxLineRepo = this.manager_.getCustomRepository(this.taxLineRepo_) - const shippingTaxLineRepo = this.manager_.getCustomRepository( - this.smTaxLineRepo_ - ) + const { shipping, lineItems } = taxLines.reduce<{ + shipping: ShippingMethodTaxLine[] + lineItems: LineItemTaxLine[] + }>( + (acc, tl) => { + if ("item_id" in tl) { + acc.lineItems.push(tl) + } else { + acc.shipping.push(tl) + } - const { shipping, lineItems } = taxLines.reduce<{ - shipping: ShippingMethodTaxLine[] - lineItems: LineItemTaxLine[] - }>( - (acc, tl) => { - if ("item_id" in tl) { - acc.lineItems.push(tl) - } else { - acc.shipping.push(tl) - } + return acc + }, + { shipping: [], lineItems: [] } + ) - return acc - }, - { shipping: [], lineItems: [] } - ) - - return ( - await Promise.all([ - itemTaxLineRepo.upsertLines(lineItems), - shippingTaxLineRepo.upsertLines(shipping), - ]) - ).flat() + return ( + await Promise.all([ + itemTaxLineRepo.upsertLines(lineItems), + shippingTaxLineRepo.upsertLines(shipping), + ]) + ).flat() + }) } /** @@ -172,11 +171,13 @@ class TaxProviderService extends BaseService { shippingMethod: ShippingMethod, calculationContext: TaxCalculationContext ): Promise<(ShippingMethodTaxLine | LineItemTaxLine)[]> { - const taxLines = await this.getShippingTaxLines( - shippingMethod, - calculationContext - ) - return this.manager_.save(taxLines) + return await this.atomicPhase_(async (transactionManager) => { + const taxLines = await this.getShippingTaxLines( + shippingMethod, + calculationContext + ) + return await transactionManager.save(taxLines) + }) } /** @@ -339,9 +340,9 @@ class TaxProviderService extends BaseService { } let toReturn: TaxServiceRate[] = [] - const optionRates = await this.taxRateService_.listByShippingOption( - optionId - ) + const optionRates = await this.taxRateService_ + .withTransaction(this.manager_) + .listByShippingOption(optionId) if (optionRates.length > 0) { toReturn = optionRates.map((pr) => { diff --git a/packages/medusa/src/services/totals.ts b/packages/medusa/src/services/totals.ts index e0b3d83065..b21d4fe702 100644 --- a/packages/medusa/src/services/totals.ts +++ b/packages/medusa/src/services/totals.ts @@ -97,9 +97,9 @@ class TotalsService extends TransactionBaseService { private taxCalculationStrategy_: ITaxCalculationStrategy constructor({ + manager, taxProviderService, taxCalculationStrategy, - manager, }: TotalsServiceProps) { super({ taxProviderService, @@ -107,6 +107,7 @@ class TotalsService extends TransactionBaseService { manager, }) + this.manager_ = manager this.taxProviderService_ = taxProviderService this.taxCalculationStrategy_ = taxCalculationStrategy this.manager_ = manager @@ -214,10 +215,9 @@ class TotalsService extends TransactionBaseService { taxLines = shippingMethod.tax_lines } else { - const orderLines = await this.taxProviderService_.getTaxLines( - cartOrOrder.items, - calculationContext - ) + const orderLines = await this.taxProviderService_ + .withTransaction(this.manager_) + .getTaxLines(cartOrOrder.items, calculationContext) taxLines = orderLines.filter((ol) => { if ("shipping_method_id" in ol) { @@ -343,10 +343,9 @@ class TotalsService extends TransactionBaseService { ) } } else { - taxLines = await this.taxProviderService_.getTaxLines( - cartOrOrder.items, - calculationContext - ) + taxLines = await this.taxProviderService_ + .withTransaction(this.manager_) + .getTaxLines(cartOrOrder.items, calculationContext) if (cartOrOrder.type === "swap") { const returnTaxLines = cartOrOrder.items.flatMap((i) => { diff --git a/packages/medusa/src/strategies/batch-jobs/order/export.ts b/packages/medusa/src/strategies/batch-jobs/order/export.ts index 49195615f1..6653d23d61 100644 --- a/packages/medusa/src/strategies/batch-jobs/order/export.ts +++ b/packages/medusa/src/strategies/batch-jobs/order/export.ts @@ -15,6 +15,7 @@ import { BatchJobStatus } from "../../../types/batch-job" import { prepareListQuery } from "../../../utils/get-query-config" import { FlagRouter } from "../../../utils/flag-router" import SalesChannelFeatureFlag from "../../../loaders/feature-flags/sales-channels" +import { FindConfig } from "../../../types/common" type InjectedDependencies = { fileService: IFileService @@ -146,7 +147,7 @@ class OrderExportStrategy extends AbstractBatchJobStrategy skip: offset as number, order: { created_at: "DESC" }, take: Math.min(batchJob.context.batch_size ?? Infinity, limit), - }) + } as FindConfig) count = orderCount } @@ -199,11 +200,11 @@ class OrderExportStrategy extends AbstractBatchJobStrategy order: { created_at: "DESC" }, skip: offset, take: Math.min(batchJob.context.batch_size ?? Infinity, limit), - } + } as FindConfig ) orderCount = batchJob.context?.batch_size ?? count - let orders = [] + let orders: Order[] = [] const lineDescriptor = this.getLineDescriptor( list_config.select as string[], @@ -230,7 +231,7 @@ class OrderExportStrategy extends AbstractBatchJobStrategy ...list_config, skip: offset, take: Math.min(orderCount - offset, limit), - }) + } as FindConfig) orders.forEach((order) => { const line = this.buildCSVLine(order, lineDescriptor) diff --git a/packages/medusa/src/strategies/batch-jobs/order/index.ts b/packages/medusa/src/strategies/batch-jobs/order/index.ts index f0cba7d1ff..2106a6e750 100644 --- a/packages/medusa/src/strategies/batch-jobs/order/index.ts +++ b/packages/medusa/src/strategies/batch-jobs/order/index.ts @@ -137,7 +137,7 @@ export const orderExportPropertiesDescriptors: OrderDescriptor[] = [ { fieldName: "tax_total", title: "Tax Total", - accessor: (order: Order): string => order.tax_total.toString(), + accessor: (order: Order): string => order.tax_total?.toString() ?? "", }, { fieldName: "total", diff --git a/packages/medusa/src/types/cart.ts b/packages/medusa/src/types/cart.ts index 099eff88f0..9a98ecc4d2 100644 --- a/packages/medusa/src/types/cart.ts +++ b/packages/medusa/src/types/cart.ts @@ -68,7 +68,7 @@ export type CartUpdateProps = { billing_address?: AddressPayload | string shipping_address?: AddressPayload | string completed_at?: Date - payment_authorized_at?: Date + payment_authorized_at?: Date | null gift_cards?: GiftCard[] discounts?: Discount[] customer_id?: string diff --git a/packages/medusa/src/types/gift-card.ts b/packages/medusa/src/types/gift-card.ts index 017cb78b78..4b88c56dff 100644 --- a/packages/medusa/src/types/gift-card.ts +++ b/packages/medusa/src/types/gift-card.ts @@ -19,5 +19,7 @@ export type CreateGiftCardTransactionInput = { gift_card_id: string order_id: string amount: number - created_at: Date + created_at?: Date + is_taxable?: boolean + tax_rate?: number | null } diff --git a/packages/medusa/src/types/orders.ts b/packages/medusa/src/types/orders.ts index 6556c33338..30b02e3472 100644 --- a/packages/medusa/src/types/orders.ts +++ b/packages/medusa/src/types/orders.ts @@ -9,8 +9,8 @@ import { ValidateNested, } from "class-validator" import { IsType } from "../utils/validators/is-type" -import { Order } from "../models/order" -import { DateComparisonOperator } from "./common" +import { Order, Payment } from "../models" +import { AddressPayload, DateComparisonOperator } from "./common" // eslint-disable-next-line @typescript-eslint/no-explicit-any export function isOrder(object: any): object is Order { @@ -47,6 +47,55 @@ enum PaymentStatus { requires_action = "requires_action", } +export type CreateOrderInput = { + status?: OrderStatus + email: string + billing_address: AddressPayload + shipping_address: AddressPayload + items: Record[] + region: string + discounts?: Record[] + customer_id: string + payment_method: { + provider_id: string + ata?: Record + } + shipping_method?: { + provider_id: string + profile_id: string + price: number + data?: Record + items?: Record[] + }[] + no_notification?: boolean +} + +export type UpdateOrderInput = { + email?: string + billing_address?: AddressPayload + shipping_address?: AddressPayload + items?: object[] + region?: string + discounts?: object[] + customer_id?: string + payment_method?: { + provider_id?: string + data?: Record + } + shipping_method?: { + provider_id?: string + profile_id?: string + price?: number + data?: Record + items?: Record[] + }[] + no_notification?: boolean + payment?: Payment + status?: OrderStatus + fulfillment_status?: FulfillmentStatus + payment_status?: PaymentStatus + metadata?: Record +} export class AdminListOrdersSelector { @IsString() @IsOptional()