diff --git a/.changeset/green-badgers-jog.md b/.changeset/green-badgers-jog.md new file mode 100644 index 0000000000..dc185afd96 --- /dev/null +++ b/.changeset/green-badgers-jog.md @@ -0,0 +1,9 @@ +--- +"@medusajs/medusa": patch +"@medusajs/order": patch +"@medusajs/types": patch +"@medusajs/utils": patch +"@medusajs/cart": patch +--- + +Order endpoints and Cart totals diff --git a/integration-tests/modules/__tests__/order/order.spec.ts b/integration-tests/modules/__tests__/order/order.spec.ts new file mode 100644 index 0000000000..68d7860c00 --- /dev/null +++ b/integration-tests/modules/__tests__/order/order.spec.ts @@ -0,0 +1,484 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { + ICartModuleService, + ICustomerModuleService, + IFulfillmentModuleService, + IInventoryServiceNext, + IOrderModuleService, + IPaymentModuleService, + IPricingModuleService, + IProductModuleService, + IRegionModuleService, + ISalesChannelModuleService, + IStockLocationServiceNext, + ITaxModuleService, +} from "@medusajs/types" +import { ContainerRegistrationKeys } from "@medusajs/utils" +import { medusaIntegrationTestRunner } from "medusa-test-utils" +import { + adminHeaders, + createAdminUser, +} from "../../../helpers/create-admin-user" + +jest.setTimeout(50000) + +const env = { MEDUSA_FF_MEDUSA_V2: true } + +medusaIntegrationTestRunner({ + debug: true, + env, + testSuite: ({ dbConnection, getContainer, api }) => { + let appContainer + let cartModuleService: ICartModuleService + let regionModuleService: IRegionModuleService + let scModuleService: ISalesChannelModuleService + let customerModule: ICustomerModuleService + let productModule: IProductModuleService + let pricingModule: IPricingModuleService + let paymentModule: IPaymentModuleService + let inventoryModule: IInventoryServiceNext + let stockLocationModule: IStockLocationServiceNext + let fulfillmentModule: IFulfillmentModuleService + let locationModule: IStockLocationServiceNext + let taxModule: ITaxModuleService + let orderModule: IOrderModuleService + let remoteLink, remoteQuery + + beforeAll(async () => { + appContainer = getContainer() + cartModuleService = appContainer.resolve(ModuleRegistrationName.CART) + regionModuleService = appContainer.resolve(ModuleRegistrationName.REGION) + scModuleService = appContainer.resolve( + ModuleRegistrationName.SALES_CHANNEL + ) + customerModule = appContainer.resolve(ModuleRegistrationName.CUSTOMER) + productModule = appContainer.resolve(ModuleRegistrationName.PRODUCT) + pricingModule = appContainer.resolve(ModuleRegistrationName.PRICING) + paymentModule = appContainer.resolve(ModuleRegistrationName.PAYMENT) + inventoryModule = appContainer.resolve(ModuleRegistrationName.INVENTORY) + stockLocationModule = appContainer.resolve( + ModuleRegistrationName.STOCK_LOCATION + ) + fulfillmentModule = appContainer.resolve( + ModuleRegistrationName.FULFILLMENT + ) + locationModule = appContainer.resolve( + ModuleRegistrationName.STOCK_LOCATION + ) + taxModule = appContainer.resolve(ModuleRegistrationName.TAX) + remoteLink = appContainer.resolve(ContainerRegistrationKeys.REMOTE_LINK) + remoteQuery = appContainer.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + orderModule = appContainer.resolve(ModuleRegistrationName.ORDER) + }) + + beforeEach(async () => { + await createAdminUser(dbConnection, adminHeaders, appContainer) + }) + + describe("Orders - Admin", () => { + it("should get an order", async () => { + const created = await orderModule.create({ + region_id: "test_region_idclear", + email: "foo@bar.com", + items: [ + { + title: "Custom Item 2", + quantity: 1, + unit_price: 50, + adjustments: [ + { + code: "VIP_25 ETH", + amount: "0.000000000000000005", + description: "VIP discount", + promotion_id: "prom_123", + provider_id: "coupon_kings", + }, + ], + }, + ], + sales_channel_id: "test", + shipping_address: { + first_name: "Test", + last_name: "Test", + address_1: "Test", + city: "Test", + country_code: "US", + postal_code: "12345", + phone: "12345", + }, + billing_address: { + first_name: "Test", + last_name: "Test", + address_1: "Test", + city: "Test", + country_code: "US", + postal_code: "12345", + }, + shipping_methods: [ + { + name: "Test shipping method", + amount: 10, + data: {}, + tax_lines: [ + { + description: "shipping Tax 1", + tax_rate_id: "tax_usa_shipping", + code: "code", + rate: 10, + }, + ], + adjustments: [ + { + code: "VIP_10", + amount: 1, + description: "VIP discount", + promotion_id: "prom_123", + }, + ], + }, + ], + currency_code: "usd", + customer_id: "joe", + }) + + const response = await api.get( + "/admin/orders/" + + created.id + + "?fields=%2Braw_total,%2Braw_subtotal,%2Braw_discount_total", + adminHeaders + ) + + const order = response.data.order + expect(order).toEqual({ + id: expect.any(String), + status: "pending", + version: 1, + summary: { + total: 50, + }, + total: 59.8, + subtotal: 50, + tax_total: 0.9, + discount_total: 1, + discount_tax_total: 0.1, + original_total: 61, + original_tax_total: 1, + item_total: 50, + item_subtotal: 50, + item_tax_total: 0, + original_item_total: 50, + original_item_subtotal: 50, + original_item_tax_total: 0, + shipping_total: 9.9, + shipping_subtotal: 10, + shipping_tax_total: 0.9, + original_shipping_tax_total: 1, + original_shipping_tax_subtotal: 10, + original_shipping_total: 11, + created_at: expect.any(String), + updated_at: expect.any(String), + raw_total: { + value: "59.799999999999999995", + precision: 20, + }, + raw_subtotal: { + value: "50", + precision: 20, + }, + raw_discount_total: { + value: "1.000000000000000005", + precision: 20, + }, + items: [ + { + id: expect.any(String), + title: "Custom Item 2", + subtitle: null, + thumbnail: null, + variant_id: null, + product_id: null, + product_title: null, + product_description: null, + product_subtitle: null, + product_type: null, + product_collection: null, + product_handle: null, + variant_sku: null, + variant_barcode: null, + variant_title: null, + variant_option_values: null, + requires_shipping: true, + is_discountable: true, + is_tax_inclusive: false, + raw_compare_at_unit_price: null, + raw_unit_price: { + value: "50", + precision: 20, + }, + metadata: null, + created_at: expect.any(String), + updated_at: expect.any(String), + tax_lines: [], + adjustments: [ + { + id: expect.any(String), + description: "VIP discount", + promotion_id: expect.any(String), + code: "VIP_25 ETH", + raw_amount: { + value: "5.0000000000000000000e-18", + precision: 20, + }, + provider_id: expect.any(String), + created_at: expect.any(String), + updated_at: expect.any(String), + item_id: expect.any(String), + amount: 5e-18, + subtotal: 5e-18, + total: 5e-18, + raw_subtotal: { + value: "5.0000000000000000000e-18", + precision: 20, + }, + raw_total: { + value: "5.0000000000000000000e-18", + precision: 20, + }, + }, + ], + compare_at_unit_price: null, + unit_price: 50, + quantity: 1, + raw_quantity: { + value: "1", + precision: 20, + }, + detail: { + id: expect.any(String), + order_id: expect.any(String), + version: 1, + item_id: expect.any(String), + raw_quantity: { + value: "1", + precision: 20, + }, + raw_fulfilled_quantity: { + value: "0", + precision: 20, + }, + raw_shipped_quantity: { + value: "0", + precision: 20, + }, + raw_return_requested_quantity: { + value: "0", + precision: 20, + }, + raw_return_received_quantity: { + value: "0", + precision: 20, + }, + raw_return_dismissed_quantity: { + value: "0", + precision: 20, + }, + raw_written_off_quantity: { + value: "0", + precision: 20, + }, + metadata: null, + created_at: expect.any(String), + updated_at: expect.any(String), + quantity: 1, + fulfilled_quantity: 0, + shipped_quantity: 0, + return_requested_quantity: 0, + return_received_quantity: 0, + return_dismissed_quantity: 0, + written_off_quantity: 0, + }, + subtotal: 50, + total: 50, + original_total: 50, + discount_total: 5e-18, + discount_tax_total: 0, + tax_total: 0, + original_tax_total: 0, + raw_subtotal: { + value: "50", + precision: 20, + }, + raw_total: { + value: "49.999999999999999995", + precision: 20, + }, + raw_original_total: { + value: "50", + precision: 20, + }, + raw_discount_total: { + value: "5.0000000000000000000e-18", + precision: 20, + }, + raw_discount_tax_total: { + value: "0", + precision: 20, + }, + raw_tax_total: { + value: "0", + precision: 20, + }, + raw_original_tax_total: { + value: "0", + precision: 20, + }, + }, + ], + shipping_address: { + id: expect.any(String), + customer_id: null, + company: null, + first_name: "Test", + last_name: "Test", + address_1: "Test", + address_2: null, + city: "Test", + country_code: "US", + province: null, + postal_code: "12345", + phone: "12345", + metadata: null, + created_at: expect.any(String), + updated_at: expect.any(String), + }, + billing_address: { + id: expect.any(String), + customer_id: null, + company: null, + first_name: "Test", + last_name: "Test", + address_1: "Test", + address_2: null, + city: "Test", + country_code: "US", + province: null, + postal_code: "12345", + phone: null, + metadata: null, + created_at: expect.any(String), + updated_at: expect.any(String), + }, + shipping_methods: [ + { + id: expect.any(String), + order_id: expect.any(String), + version: 1, + name: "Test shipping method", + description: null, + raw_amount: { + value: "10", + precision: 20, + }, + is_tax_inclusive: false, + shipping_option_id: null, + data: {}, + metadata: null, + created_at: expect.any(String), + updated_at: expect.any(String), + tax_lines: [ + { + id: expect.any(String), + description: "shipping Tax 1", + tax_rate_id: expect.any(String), + code: "code", + raw_rate: { + value: "10", + precision: 20, + }, + provider_id: null, + created_at: expect.any(String), + updated_at: expect.any(String), + shipping_method_id: expect.any(String), + rate: 10, + total: 0.9, + subtotal: 1, + raw_total: { + value: "0.9", + precision: 20, + }, + raw_subtotal: { + value: "1", + precision: 20, + }, + }, + ], + adjustments: [ + { + id: expect.any(String), + description: "VIP discount", + promotion_id: expect.any(String), + code: "VIP_10", + raw_amount: { + value: "1", + precision: 20, + }, + provider_id: null, + created_at: expect.any(String), + updated_at: expect.any(String), + shipping_method_id: expect.any(String), + amount: 1, + subtotal: 1, + total: 1.1, + raw_subtotal: { + value: "1", + precision: 20, + }, + raw_total: { + value: "1.1", + precision: 20, + }, + }, + ], + amount: 10, + subtotal: 10, + total: 9.9, + original_total: 11, + discount_total: 1, + discount_tax_total: 0.1, + tax_total: 0.9, + original_tax_total: 1, + raw_subtotal: { + value: "10", + precision: 20, + }, + raw_total: { + value: "9.9", + precision: 20, + }, + raw_original_total: { + value: "11", + precision: 20, + }, + raw_discount_total: { + value: "1", + precision: 20, + }, + raw_discount_tax_total: { + value: "0.1", + precision: 20, + }, + raw_tax_total: { + value: "0.9", + precision: 20, + }, + raw_original_tax_total: { + value: "1", + precision: 20, + }, + }, + ], + }) + }) + }) + }, +}) diff --git a/packages/cart/integration-tests/__tests__/services/cart-module/index.spec.ts b/packages/cart/integration-tests/__tests__/services/cart-module/index.spec.ts index 841751720c..fea491991c 100644 --- a/packages/cart/integration-tests/__tests__/services/cart-module/index.spec.ts +++ b/packages/cart/integration-tests/__tests__/services/cart-module/index.spec.ts @@ -1,5 +1,6 @@ import { Modules } from "@medusajs/modules-sdk" import { ICartModuleService } from "@medusajs/types" +import { BigNumber } from "@medusajs/utils" import { CheckConstraintViolationException } from "@mikro-orm/core" import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" @@ -2341,5 +2342,409 @@ moduleIntegrationTestRunner({ }) }) }) + + it("should calculate totals of a cart", async () => { + const [createdCart] = await service.create([ + { + currency_code: "eur", + }, + ]) + + const [itemOne] = await service.addLineItems(createdCart.id, [ + { + quantity: 1, + unit_price: 100, + title: "test", + }, + ]) + + const [itemTwo] = await service.addLineItems(createdCart.id, [ + { + quantity: 2, + unit_price: 200, + title: "test-2", + }, + ]) + + await service.setLineItemAdjustments(createdCart.id, [ + { + item_id: itemOne.id, + amount: 100, + code: "FREE", + }, + { + item_id: itemTwo.id, + amount: 200, + code: "FREE-2", + }, + ]) + + await service.addShippingMethods(createdCart.id, [ + { + amount: 10, + name: "Test", + }, + ]) + + const cart = await service.retrieve(createdCart.id, { select: ["total"] }) + expect(cart.total).toBeInstanceOf(BigNumber) + + const asJson = JSON.parse(JSON.stringify(cart)) + expect(asJson).toEqual({ + id: createdCart.id, + items: [ + { + id: itemOne.id, + cart_id: createdCart.id, + title: "test", + subtitle: null, + thumbnail: null, + quantity: 1, + variant_id: null, + product_id: null, + product_title: null, + product_description: null, + product_subtitle: null, + product_type: null, + product_collection: null, + product_handle: null, + variant_sku: null, + variant_barcode: null, + variant_title: null, + variant_option_values: null, + requires_shipping: true, + is_discountable: true, + is_tax_inclusive: false, + raw_compare_at_unit_price: null, + raw_unit_price: { + value: "100", + precision: 20, + }, + metadata: null, + created_at: expect.any(String), + updated_at: expect.any(String), + deleted_at: null, + tax_lines: [], + adjustments: [ + { + id: expect.any(String), + description: null, + code: "FREE", + raw_amount: { + value: "100", + precision: 20, + }, + provider_id: null, + metadata: null, + created_at: expect.any(String), + updated_at: expect.any(String), + item_id: expect.any(String), + promotion_id: null, + deleted_at: null, + amount: 100, + subtotal: 100, + total: 100, + raw_subtotal: { + value: "100", + precision: 20, + }, + raw_total: { + value: "100", + precision: 20, + }, + }, + ], + compare_at_unit_price: null, + unit_price: 100, + subtotal: 100, + total: 0, + original_total: 100, + discount_total: 100, + discount_tax_total: 0, + tax_total: 0, + original_tax_total: 0, + raw_subtotal: { + value: "100", + precision: 20, + }, + raw_total: { + value: "0", + precision: 20, + }, + raw_original_total: { + value: "100", + precision: 20, + }, + raw_discount_total: { + value: "100", + precision: 20, + }, + raw_discount_tax_total: { + value: "0", + precision: 20, + }, + raw_tax_total: { + value: "0", + precision: 20, + }, + raw_original_tax_total: { + value: "0", + precision: 20, + }, + }, + { + id: itemTwo.id, + cart_id: createdCart.id, + title: "test-2", + subtitle: null, + thumbnail: null, + quantity: 2, + variant_id: null, + product_id: null, + product_title: null, + product_description: null, + product_subtitle: null, + product_type: null, + product_collection: null, + product_handle: null, + variant_sku: null, + variant_barcode: null, + variant_title: null, + variant_option_values: null, + requires_shipping: true, + is_discountable: true, + is_tax_inclusive: false, + raw_compare_at_unit_price: null, + raw_unit_price: { + value: "200", + precision: 20, + }, + metadata: null, + created_at: expect.any(String), + updated_at: expect.any(String), + deleted_at: null, + tax_lines: [], + adjustments: [ + { + id: expect.any(String), + description: null, + code: "FREE-2", + raw_amount: { + value: "200", + precision: 20, + }, + provider_id: null, + metadata: null, + created_at: expect.any(String), + updated_at: expect.any(String), + item_id: expect.any(String), + promotion_id: null, + deleted_at: null, + amount: 200, + subtotal: 200, + total: 200, + raw_subtotal: { + value: "200", + precision: 20, + }, + raw_total: { + value: "200", + precision: 20, + }, + }, + ], + compare_at_unit_price: null, + unit_price: 200, + subtotal: 400, + total: 200, + original_total: 400, + discount_total: 200, + discount_tax_total: 0, + tax_total: 0, + original_tax_total: 0, + raw_subtotal: { + value: "400", + precision: 20, + }, + raw_total: { + value: "200", + precision: 20, + }, + raw_original_total: { + value: "400", + precision: 20, + }, + raw_discount_total: { + value: "200", + precision: 20, + }, + raw_discount_tax_total: { + value: "0", + precision: 20, + }, + raw_tax_total: { + value: "0", + precision: 20, + }, + raw_original_tax_total: { + value: "0", + precision: 20, + }, + }, + ], + shipping_methods: [ + { + id: expect.any(String), + cart_id: expect.any(String), + name: "Test", + description: null, + raw_amount: { + value: "10", + precision: 20, + }, + is_tax_inclusive: false, + shipping_option_id: null, + data: null, + metadata: null, + created_at: expect.any(String), + updated_at: expect.any(String), + deleted_at: null, + tax_lines: [], + adjustments: [], + amount: 10, + subtotal: 10, + total: 10, + original_total: 10, + discount_total: 0, + discount_tax_total: 0, + tax_total: 0, + original_tax_total: 0, + raw_subtotal: { + value: "10", + precision: 20, + }, + raw_total: { + value: "10", + precision: 20, + }, + raw_original_total: { + value: "10", + precision: 20, + }, + raw_discount_total: { + value: "0", + precision: 20, + }, + raw_discount_tax_total: { + value: "0", + precision: 20, + }, + raw_tax_total: { + value: "0", + precision: 20, + }, + raw_original_tax_total: { + value: "0", + precision: 20, + }, + }, + ], + total: 210, + subtotal: 500, + tax_total: 0, + discount_total: 300, + discount_tax_total: 0, + original_total: 210, + original_tax_total: 0, + item_total: 200, + item_subtotal: 500, + item_tax_total: 0, + original_item_total: 500, + original_item_subtotal: 500, + original_item_tax_total: 0, + shipping_total: 10, + shipping_subtotal: 10, + shipping_tax_total: 0, + original_shipping_tax_total: 0, + original_shipping_tax_subtotal: 10, + original_shipping_total: 10, + raw_total: { + value: "210", + precision: 20, + }, + raw_subtotal: { + value: "500", + precision: 20, + }, + raw_tax_total: { + value: "0", + precision: 20, + }, + raw_discount_total: { + value: "300", + precision: 20, + }, + raw_discount_tax_total: { + value: "0", + precision: 20, + }, + raw_original_total: { + value: "210", + precision: 20, + }, + raw_original_tax_total: { + value: "0", + precision: 20, + }, + raw_item_total: { + value: "200", + precision: 20, + }, + raw_item_subtotal: { + value: "500", + precision: 20, + }, + raw_item_tax_total: { + value: "0", + precision: 20, + }, + raw_original_item_total: { + value: "500", + precision: 20, + }, + raw_original_item_subtotal: { + value: "500", + precision: 20, + }, + raw_original_item_tax_total: { + value: "0", + precision: 20, + }, + raw_shipping_total: { + value: "10", + precision: 20, + }, + raw_shipping_subtotal: { + value: "10", + precision: 20, + }, + raw_shipping_tax_total: { + value: "0", + precision: 20, + }, + raw_original_shipping_tax_total: { + value: "0", + precision: 20, + }, + raw_original_shipping_tax_subtotal: { + value: "10", + precision: 20, + }, + raw_original_shipping_total: { + value: "10", + precision: 20, + }, + }) + }) }, }) diff --git a/packages/cart/src/services/cart-module.ts b/packages/cart/src/services/cart-module.ts index 89408ab719..fc728718d5 100644 --- a/packages/cart/src/services/cart-module.ts +++ b/packages/cart/src/services/cart-module.ts @@ -1,7 +1,9 @@ import { + CartDTO, CartTypes, Context, DAL, + FindConfig, ICartModuleService, InternalModuleDeclaration, ModuleJoinerConfig, @@ -13,6 +15,9 @@ import { MedusaContext, MedusaError, ModulesSdkUtils, + createRawPropertiesFromBigNumber, + decorateCartTotals, + deduplicate, isObject, isString, } from "@medusajs/utils" @@ -126,6 +131,125 @@ export default class CartModuleService< return joinerConfig } + private shouldIncludeTotals(config: FindConfig): boolean { + const totalFields = [ + "total", + "subtotal", + "tax_total", + "discount_total", + "discount_tax_total", + "original_total", + "original_tax_total", + "item_total", + "item_subtotal", + "item_tax_total", + "original_item_total", + "original_item_subtotal", + "original_item_tax_total", + "shipping_total", + "shipping_subtotal", + "shipping_tax_total", + "original_shipping_tax_total", + "original_shipping_tax_subtotal", + "original_shipping_total", + ] + + const includeTotals = (config?.select ?? []).some((field) => + totalFields.includes(field as string) + ) + + if (includeTotals) { + this.addRelationsToCalculateTotals(config, totalFields) + } + + return includeTotals + } + + private addRelationsToCalculateTotals(config: FindConfig, totalFields) { + config.relations ??= [] + config.select ??= [] + + const requiredFieldsForTotals = [ + "items", + "items.tax_lines", + "items.adjustments", + "shipping_methods", + "shipping_methods.tax_lines", + "shipping_methods.adjustments", + ] + config.relations = deduplicate([ + ...config.relations, + ...requiredFieldsForTotals, + ]) + + config.select = config.select.filter((field) => { + return ( + !requiredFieldsForTotals.some((val) => + val.startsWith(field as string) + ) && !totalFields.includes(field) + ) + }) + } + + async retrieve( + id: string, + config?: FindConfig | undefined, + sharedContext?: Context | undefined + ): Promise { + config ??= {} + const includeTotals = this.shouldIncludeTotals(config) + + const cart = await super.retrieve(id, config, sharedContext) + + if (includeTotals) { + createRawPropertiesFromBigNumber(decorateCartTotals(cart)) + } + + return cart + } + + async list( + filters?: any, + config?: FindConfig | undefined, + sharedContext?: Context | undefined + ): Promise { + config ??= {} + const includeTotals = this.shouldIncludeTotals(config) + + const carts = await super.list(filters, config, sharedContext) + + if (includeTotals) { + carts.forEach((cart) => { + createRawPropertiesFromBigNumber(decorateCartTotals(cart)) + }) + } + + return carts + } + + async listAndCount( + filters?: any, + config?: FindConfig | undefined, + sharedContext?: Context | undefined + ): Promise<[CartDTO[], number]> { + config ??= {} + const includeTotals = this.shouldIncludeTotals(config) + + const [carts, count] = await super.listAndCount( + filters, + config, + sharedContext + ) + + if (includeTotals) { + carts.forEach((cart) => { + createRawPropertiesFromBigNumber(decorateCartTotals(cart)) + }) + } + + return [carts, count] + } + async create( data: CartTypes.CreateCartDTO[], sharedContext?: Context diff --git a/packages/medusa/src/api-v2/admin/orders/[id]/route.ts b/packages/medusa/src/api-v2/admin/orders/[id]/route.ts new file mode 100644 index 0000000000..9dee2407d3 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/orders/[id]/route.ts @@ -0,0 +1,17 @@ +import { remoteQueryObjectFromString } from "@medusajs/utils" +import { MedusaRequest, MedusaResponse } from "../../../../types/routing" + +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const remoteQuery = req.scope.resolve("remoteQuery") + + const variables = { id: req.params.id } + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "order", + variables, + fields: req.remoteQueryConfig.fields, + }) + + const [order] = await remoteQuery(queryObject) + res.status(200).json({ order }) +} diff --git a/packages/medusa/src/api-v2/admin/orders/middlewares.ts b/packages/medusa/src/api-v2/admin/orders/middlewares.ts new file mode 100644 index 0000000000..a5974dad04 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/orders/middlewares.ts @@ -0,0 +1,33 @@ +import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { authenticate } from "../../../utils/authenticate-middleware" +import { validateAndTransformQuery } from "../../utils/validate-query" +import * as QueryConfig from "./query-config" +import { AdminGetOrdersOrderParams, AdminGetOrdersParams } from "./validators" + +export const adminOrderRoutesMiddlewares: MiddlewareRoute[] = [ + { + method: ["ALL"], + matcher: "/admin/orders*", + middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + }, + { + method: ["GET"], + matcher: "/admin/orders", + middlewares: [ + validateAndTransformQuery( + AdminGetOrdersParams, + QueryConfig.listTransformQueryConfig + ), + ], + }, + { + method: ["GET"], + matcher: "/admin/orders/:id", + middlewares: [ + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, +] diff --git a/packages/medusa/src/api-v2/admin/orders/query-config.ts b/packages/medusa/src/api-v2/admin/orders/query-config.ts new file mode 100644 index 0000000000..fc3153f1d9 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/orders/query-config.ts @@ -0,0 +1,59 @@ +export const defaultAdminOrderFields = [ + "id", + "status", + "version", + "summary", + "metadata", + "created_at", + "updated_at", +] + +export const defaultAdminRetrieveOrderFields = [ + "id", + "status", + "version", + "summary", + "total", + "subtotal", + "tax_total", + "discount_total", + "discount_tax_total", + "original_total", + "original_tax_total", + "item_total", + "item_subtotal", + "item_tax_total", + "original_item_total", + "original_item_subtotal", + "original_item_tax_total", + "shipping_total", + "shipping_subtotal", + "shipping_tax_total", + "original_shipping_tax_total", + "original_shipping_tax_subtotal", + "original_shipping_total", + "created_at", + "updated_at", + "*items", + "*items.tax_lines", + "*items.adjustments", + "*items.detail", + "*items.tax_lines", + "*items.adjustments", + "*shipping_address", + "*billing_address", + "*shipping_methods", + "*shipping_methods.tax_lines", + "*shipping_methods.adjustments", +] + +export const retrieveTransformQueryConfig = { + defaultFields: defaultAdminRetrieveOrderFields, + isList: false, +} + +export const listTransformQueryConfig = { + defaults: defaultAdminOrderFields, + defaultLimit: 20, + isList: true, +} diff --git a/packages/medusa/src/api-v2/admin/orders/route.ts b/packages/medusa/src/api-v2/admin/orders/route.ts new file mode 100644 index 0000000000..a2ec16172d --- /dev/null +++ b/packages/medusa/src/api-v2/admin/orders/route.ts @@ -0,0 +1,27 @@ +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { MedusaRequest, MedusaResponse } from "../../../types/routing" + +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "order", + variables: { + filters: req.filterableFields, + ...req.remoteQueryConfig.pagination, + }, + fields: req.remoteQueryConfig.fields, + }) + + const { rows: orders, metadata } = await remoteQuery(queryObject) + + res.json({ + orders, + count: metadata.count, + offset: metadata.skip, + limit: metadata.take, + }) +} diff --git a/packages/medusa/src/api-v2/admin/orders/validators.ts b/packages/medusa/src/api-v2/admin/orders/validators.ts new file mode 100644 index 0000000000..8c2fed6abc --- /dev/null +++ b/packages/medusa/src/api-v2/admin/orders/validators.ts @@ -0,0 +1,38 @@ +import { z } from "zod" +import { + createFindParams, + createOperatorMap, + createSelectParams, +} from "../../utils/validators" + +export const AdminGetOrdersOrderParams = createSelectParams().merge( + z.object({ + id: z.union([z.string(), z.array(z.string())]).optional(), + status: z.union([z.string(), z.array(z.string())]).optional(), + created_at: createOperatorMap().optional(), + updated_at: createOperatorMap().optional(), + deleted_at: createOperatorMap().optional(), + }) +) + +export type AdminGetOrdersOrderParamsType = z.infer< + typeof AdminGetOrdersOrderParams +> + +/** + * Parameters used to filter and configure the pagination of the retrieved order. + */ +export const AdminGetOrdersParams = createFindParams({ + limit: 15, + offset: 0, +}).merge( + z.object({ + id: z.union([z.string(), z.array(z.string())]).optional(), + status: z.union([z.string(), z.array(z.string())]).optional(), + created_at: createOperatorMap().optional(), + updated_at: createOperatorMap().optional(), + deleted_at: createOperatorMap().optional(), + }) +) + +export type AdminGetOrdersParamsType = z.infer diff --git a/packages/medusa/src/api-v2/middlewares.ts b/packages/medusa/src/api-v2/middlewares.ts index edf19cfcfe..176994133e 100644 --- a/packages/medusa/src/api-v2/middlewares.ts +++ b/packages/medusa/src/api-v2/middlewares.ts @@ -10,6 +10,7 @@ import { adminFulfillmentSetsRoutesMiddlewares } from "./admin/fulfillment-sets/ import { adminFulfillmentsRoutesMiddlewares } from "./admin/fulfillments/middlewares" import { adminInventoryRoutesMiddlewares } from "./admin/inventory-items/middlewares" import { adminInviteRoutesMiddlewares } from "./admin/invites/middlewares" +import { adminOrderRoutesMiddlewares } from "./admin/orders/middlewares" import { adminPaymentRoutesMiddlewares } from "./admin/payments/middlewares" import { adminPriceListsRoutesMiddlewares } from "./admin/price-lists/middlewares" import { adminPricingRoutesMiddlewares } from "./admin/pricing/middlewares" @@ -71,6 +72,7 @@ export const config: MiddlewaresConfig = { ...adminProductTypeRoutesMiddlewares, ...adminUploadRoutesMiddlewares, ...adminFulfillmentSetsRoutesMiddlewares, + ...adminOrderRoutesMiddlewares, ...adminReservationRoutesMiddlewares, ...adminProductCategoryRoutesMiddlewares, ...adminReservationRoutesMiddlewares, diff --git a/packages/medusa/src/api-v2/store/carts/query-config.ts b/packages/medusa/src/api-v2/store/carts/query-config.ts index f7aced7472..ccd66c5e5e 100644 --- a/packages/medusa/src/api-v2/store/carts/query-config.ts +++ b/packages/medusa/src/api-v2/store/carts/query-config.ts @@ -4,6 +4,25 @@ export const defaultStoreCartFields = [ "email", "created_at", "updated_at", + "total", + "subtotal", + "tax_total", + "discount_total", + "discount_tax_total", + "original_total", + "original_tax_total", + "item_total", + "item_subtotal", + "item_tax_total", + "original_item_total", + "original_item_subtotal", + "original_item_tax_total", + "shipping_total", + "shipping_subtotal", + "shipping_tax_total", + "original_shipping_tax_total", + "original_shipping_tax_subtotal", + "original_shipping_total", "items.id", "items.variant_id", "items.product_id", diff --git a/packages/medusa/src/api/middlewares/transform-query.ts b/packages/medusa/src/api/middlewares/transform-query.ts index b9e5f2df44..ba5303a8c7 100644 --- a/packages/medusa/src/api/middlewares/transform-query.ts +++ b/packages/medusa/src/api/middlewares/transform-query.ts @@ -34,6 +34,7 @@ export function transformQuery< req.query, config ) + req.validatedQuery = validated req.filterableFields = getFilterableFields(validated) diff --git a/packages/order/src/services/order-module-service.ts b/packages/order/src/services/order-module-service.ts index 2272cb6155..3f12ebe398 100644 --- a/packages/order/src/services/order-module-service.ts +++ b/packages/order/src/services/order-module-service.ts @@ -179,14 +179,77 @@ export default class OrderModuleService< return joinerConfig } + private shouldIncludeTotals(config: FindConfig): boolean { + const totalFields = [ + "total", + "subtotal", + "tax_total", + "discount_total", + "discount_tax_total", + "original_total", + "original_tax_total", + "item_total", + "item_subtotal", + "item_tax_total", + "original_item_total", + "original_item_subtotal", + "original_item_tax_total", + "shipping_total", + "shipping_subtotal", + "shipping_tax_total", + "original_shipping_tax_total", + "original_shipping_tax_subtotal", + "original_shipping_total", + ] + + const includeTotals = (config?.select ?? []).some((field) => + totalFields.includes(field as string) + ) + + if (includeTotals) { + this.addRelationsToCalculateTotals(config, totalFields) + } + + return includeTotals + } + + private addRelationsToCalculateTotals(config: FindConfig, totalFields) { + config.relations ??= [] + config.select ??= [] + + const requiredFieldsForTotals = [ + "items", + "items.tax_lines", + "items.adjustments", + "shipping_methods", + "shipping_methods.tax_lines", + "shipping_methods.adjustments", + ] + config.relations = deduplicate([ + ...config.relations, + ...requiredFieldsForTotals, + ]) + + config.select = config.select.filter((field) => { + return ( + !requiredFieldsForTotals.some((val) => + val.startsWith(field as string) + ) && !totalFields.includes(field) + ) + }) + } + async retrieve( id: string, config?: FindConfig | undefined, sharedContext?: Context | undefined ): Promise { + config ??= {} + const includeTotals = this.shouldIncludeTotals(config) + const order = await super.retrieve(id, config, sharedContext) - return formatOrder(order) as OrderTypes.OrderDTO + return formatOrder(order, { includeTotals }) as OrderTypes.OrderDTO } async list( @@ -194,9 +257,14 @@ export default class OrderModuleService< config?: FindConfig | undefined, sharedContext?: Context | undefined ): Promise { + config ??= {} + const includeTotals = this.shouldIncludeTotals(config) + const orders = await super.list(filters, config, sharedContext) - return formatOrder(orders) as OrderTypes.OrderDTO[] + return formatOrder(orders, { + includeTotals, + }) as OrderTypes.OrderDTO[] } async listAndCount( @@ -204,13 +272,19 @@ export default class OrderModuleService< config?: FindConfig | undefined, sharedContext?: Context | undefined ): Promise<[OrderTypes.OrderDTO[], number]> { + config ??= {} + const includeTotals = this.shouldIncludeTotals(config) + const [orders, count] = await super.listAndCount( filters, config, sharedContext ) - return [formatOrder(orders) as OrderTypes.OrderDTO[], count] + return [ + formatOrder(orders, { includeTotals }) as OrderTypes.OrderDTO[], + count, + ] } async create( diff --git a/packages/order/src/utils/transform-order.ts b/packages/order/src/utils/transform-order.ts index cd70f56db1..c7cd8893de 100644 --- a/packages/order/src/utils/transform-order.ts +++ b/packages/order/src/utils/transform-order.ts @@ -1,8 +1,16 @@ import { OrderTypes } from "@medusajs/types" -import { decorateCartTotals, deduplicate, isDefined } from "@medusajs/utils" +import { + createRawPropertiesFromBigNumber, + decorateCartTotals, + deduplicate, + isDefined, +} from "@medusajs/utils" export function formatOrder( - order + order, + options: { + includeTotals?: boolean + } ): OrderTypes.OrderDTO | OrderTypes.OrderDTO[] { const isArray = Array.isArray(order) const orders = [...(isArray ? order : [order])] @@ -23,7 +31,9 @@ export function formatOrder( order.summary = order.summary?.[0]?.totals - return decorateCartTotals(order) + return options?.includeTotals + ? createRawPropertiesFromBigNumber(decorateCartTotals(order)) + : order }) return isArray ? orders : orders[0] diff --git a/packages/types/src/cart/common.ts b/packages/types/src/cart/common.ts index b27f73adc9..5444704e84 100644 --- a/packages/types/src/cart/common.ts +++ b/packages/types/src/cart/common.ts @@ -1,6 +1,6 @@ import { BaseFilterable } from "../dal" import { OperatorMap } from "../dal/utils" -import { BigNumberValue } from "../totals" +import { BigNumberRawValue, BigNumberValue } from "../totals" /** * The adjustment line details. @@ -22,6 +22,11 @@ export interface AdjustmentLineDTO { */ amount: BigNumberValue + /** + * The raw amount to adjust the original amount with. + */ + raw_amount: BigNumberRawValue + /** * The ID of the associated cart. */ @@ -153,6 +158,16 @@ export interface ShippingMethodTaxLineDTO extends TaxLineDTO { * The subtotal tax relative to the shipping method. */ subtotal: BigNumberValue + + /** + * The raw total tax relative to the shipping method. + */ + raw_total: BigNumberRawValue + + /** + * The raw subtotal tax relative to the shipping method. + */ + raw_subtotal: BigNumberRawValue } /** @@ -178,6 +193,16 @@ export interface LineItemTaxLineDTO extends TaxLineDTO { * The subtotal tax relative to the item. */ subtotal: BigNumberValue + + /** + * The raw total tax relative to the item. + */ + raw_total: BigNumberRawValue + + /** + * The raw subtotal tax relative to the item. + */ + raw_subtotal: BigNumberRawValue } /** @@ -372,6 +397,46 @@ export interface CartShippingMethodDTO { * The discount tax total of the cart shipping method. */ discount_tax_total: BigNumberValue + + /** + * The raw original total of the cart shipping method. + */ + raw_original_total: BigNumberRawValue + + /** + * The raw original subtotal of the cart shipping method. + */ + raw_original_subtotal: BigNumberRawValue + + /** + * The raw original tax total of the cart shipping method. + */ + raw_original_tax_total: BigNumberRawValue + + /** + * The raw total of the cart shipping method. + */ + raw_total: BigNumberRawValue + + /** + * The raw subtotal of the cart shipping method. + */ + raw_subtotal: BigNumberRawValue + + /** + * The raw tax total of the cart shipping method. + */ + raw_tax_total: BigNumberRawValue + + /** + * The raw discount total of the cart shipping method. + */ + raw_discount_total: BigNumberRawValue + + /** + * The raw discount tax total of the cart shipping method. + */ + raw_discount_tax_total: BigNumberRawValue } /** @@ -432,6 +497,61 @@ export interface CartLineItemTotalsDTO { * The discount tax total of the cart line item. */ discount_tax_total: BigNumberValue + + /** + * The raw original total of the cart line item. + */ + raw_original_total: BigNumberRawValue + + /** + * The raw original subtotal of the cart line item. + */ + raw_original_subtotal: BigNumberRawValue + + /** + * The raw original tax total of the cart line item. + */ + raw_original_tax_total: BigNumberRawValue + + /** + * The raw item total of the cart line item. + */ + raw_item_total: BigNumberRawValue + + /** + * The raw item subtotal of the cart line item. + */ + raw_item_subtotal: BigNumberRawValue + + /** + * The raw item tax total of the cart line item. + */ + raw_item_tax_total: BigNumberRawValue + + /** + * The raw total of the cart line item. + */ + raw_total: BigNumberRawValue + + /** + * The raw subtotal of the cart line item. + */ + raw_subtotal: BigNumberRawValue + + /** + * The raw tax total of the cart line item. + */ + raw_tax_total: BigNumberRawValue + + /** + * The raw discount total of the cart line item. + */ + raw_discount_total: BigNumberRawValue + + /** + * The raw discount tax total of the cart line item. + */ + raw_discount_tax_total: BigNumberRawValue } /** @@ -737,11 +857,6 @@ export interface CartDTO { */ discount_total: BigNumberValue - /** - * The raw discount total of the cart. - */ - raw_discount_total: any - /** * The discount tax total of the cart. */ @@ -786,6 +901,116 @@ export interface CartDTO { * The original shipping tax total of the cart. */ original_shipping_tax_total: BigNumberValue + + /** + * The raw original item total of the cart. + */ + raw_original_item_total: BigNumberRawValue + + /** + * The raw original item subtotal of the cart. + */ + raw_original_item_subtotal: BigNumberRawValue + + /** + * The raw original item tax total of the cart. + */ + raw_original_item_tax_total: BigNumberRawValue + + /** + * The raw item total of the cart. + */ + raw_item_total: BigNumberRawValue + + /** + * The raw item subtotal of the cart. + */ + raw_item_subtotal: BigNumberRawValue + + /** + * The raw item tax total of the cart. + */ + raw_item_tax_total: BigNumberRawValue + + /** + * The raw original total of the cart. + */ + raw_original_total: BigNumberRawValue + + /** + * The raw original subtotal of the cart. + */ + raw_original_subtotal: BigNumberRawValue + + /** + * The raw original tax total of the cart. + */ + raw_original_tax_total: BigNumberRawValue + + /** + * The raw total of the cart. + */ + raw_total: BigNumberRawValue + + /** + * The raw subtotal of the cart. (Excluding taxes) + */ + raw_subtotal: BigNumberRawValue + + /** + * The raw tax total of the cart. + */ + raw_tax_total: BigNumberRawValue + + /** + * The raw discount total of the cart. + */ + raw_discount_total: BigNumberRawValue + + /** + * The raw discount tax total of the cart. + */ + raw_discount_tax_total: BigNumberRawValue + + /** + * The raw gift card total of the cart. + */ + raw_gift_card_total: BigNumberRawValue + + /** + * The raw gift card tax total of the cart. + */ + raw_gift_card_tax_total: BigNumberRawValue + + /** + * The raw shipping total of the cart. + */ + raw_shipping_total: BigNumberRawValue + + /** + * The raw shipping subtotal of the cart. + */ + raw_shipping_subtotal: BigNumberRawValue + + /** + * The raw shipping tax total of the cart. + */ + raw_shipping_tax_total: BigNumberRawValue + + /** + * The raw original shipping total of the cart. + */ + raw_original_shipping_total: BigNumberRawValue + + /** + * The raw original shipping subtotal of the cart. + */ + raw_original_shipping_subtotal: BigNumberRawValue + + /** + * The raw original shipping tax total of the cart. + */ + raw_original_shipping_tax_total: BigNumberRawValue } /** diff --git a/packages/types/src/http/index.ts b/packages/types/src/http/index.ts index b5fc0360d2..b839f776b3 100644 --- a/packages/types/src/http/index.ts +++ b/packages/types/src/http/index.ts @@ -2,8 +2,9 @@ export * from "./api-key" export * from "./customer" export * from "./fulfillment" export * from "./inventory" +export * from "./order" export * from "./pricing" +export * from "./product-category" export * from "./sales-channel" export * from "./stock-locations" export * from "./tax" -export * from "./product-category" diff --git a/packages/types/src/http/order/admin/index.ts b/packages/types/src/http/order/admin/index.ts new file mode 100644 index 0000000000..c0d290f082 --- /dev/null +++ b/packages/types/src/http/order/admin/index.ts @@ -0,0 +1,314 @@ +import { BigNumberRawValue } from "../../../totals" +import { PaginatedResponse } from "../../common" + +interface OrderSummary { + total: number + subtotal: number + total_tax: number + ordered_total: number + fulfilled_total: number + returned_total: number + return_request_total: number + write_off_total: number + projected_total: number + net_total: number + net_subtotal: number + net_total_tax: number + future_total: number + future_subtotal: number + future_total_tax: number + future_projected_total: number + balance: number + future_balance: number +} + +interface OrderAdjustmentLine { + id: string + code?: string + amount: number + order_id: string + description?: string + promotion_id?: string + provider_id?: string + created_at: Date | string + updated_at: Date | string +} + +interface OrderShippingMethodAdjustment extends OrderAdjustmentLine { + shipping_method: OrderShippingMethod + shipping_method_id: string +} + +interface OrderLineItemAdjustment extends OrderAdjustmentLine { + item: OrderLineItem + item_id: string +} + +interface OrderTaxLine { + id: string + description?: string + tax_rate_id?: string + code: string + rate: number + provider_id?: string + created_at: Date | string + updated_at: Date | string +} + +interface OrderShippingMethodTaxLine extends OrderTaxLine { + shipping_method: OrderShippingMethod + shipping_method_id: string + total: number + subtotal: number + raw_total?: BigNumberRawValue + raw_subtotal?: BigNumberRawValue +} + +interface OrderLineItemTaxLine extends OrderTaxLine { + item: OrderLineItem + item_id: string + total: number + subtotal: number + raw_total?: BigNumberRawValue + raw_subtotal?: BigNumberRawValue +} + +interface OrderAddress { + id: string + customer_id?: string + first_name?: string + last_name?: string + phone?: string + company?: string + address_1?: string + address_2?: string + city?: string + country_code?: string + province?: string + postal_code?: string + metadata: Record | null + created_at: Date | string + updated_at: Date | string +} + +interface OrderShippingMethod { + id: string + order_id: string + name: string + description?: string + amount: number + raw_amount?: BigNumberRawValue + is_tax_inclusive: boolean + shipping_option_id: string | null + data: Record | null + metadata: Record | null + tax_lines?: OrderShippingMethodTaxLine[] + adjustments?: OrderShippingMethodAdjustment[] + created_at: Date | string + updated_at: Date | string +} + +interface OrderLineItem { + id: string + title: string + subtitle: string | null + thumbnail: string | null + variant_id: string | null + product_id: string | null + product_title: string | null + product_description: string | null + product_subtitle: string | null + product_type: string | null + product_collection: string | null + product_handle: string | null + variant_sku: string | null + variant_barcode: string | null + variant_title: string | null + variant_option_values: Record | null + requires_shipping: boolean + is_discountable: boolean + is_tax_inclusive: boolean + compare_at_unit_price?: number + raw_compare_at_unit_price?: BigNumberRawValue + unit_price: number + raw_unit_price?: BigNumberRawValue + quantity: number + raw_quantity?: BigNumberRawValue + tax_lines?: OrderLineItemTaxLine[] + adjustments?: OrderLineItemAdjustment[] + detail: OrderItemDetail + created_at: Date + updated_at: Date + metadata: Record | null + original_total: number + original_subtotal: number + original_tax_total: number + item_total: number + item_subtotal: number + item_tax_total: number + total: number + subtotal: number + tax_total: number + discount_total: number + discount_tax_total: number + raw_original_total?: BigNumberRawValue + raw_original_subtotal?: BigNumberRawValue + raw_original_tax_total?: BigNumberRawValue + raw_item_total?: BigNumberRawValue + raw_item_subtotal?: BigNumberRawValue + raw_item_tax_total?: BigNumberRawValue + raw_total?: BigNumberRawValue + raw_subtotal?: BigNumberRawValue + raw_tax_total?: BigNumberRawValue + raw_discount_total?: BigNumberRawValue + raw_discount_tax_total?: BigNumberRawValue +} + +interface OrderItemDetail { + id: string + item_id: string + item: OrderLineItem + quantity: number + fulfilled_quantity: number + shipped_quantity: number + return_requested_quantity: number + return_received_quantity: number + return_dismissed_quantity: number + written_off_quantity: number + raw_quantity?: BigNumberRawValue + raw_fulfilled_quantity?: BigNumberRawValue + raw_shipped_quantity?: BigNumberRawValue + raw_return_requested_quantity?: BigNumberRawValue + raw_return_received_quantity?: BigNumberRawValue + raw_return_dismissed_quantity?: BigNumberRawValue + raw_written_off_quantity?: BigNumberRawValue + metadata: Record | null + created_at: Date + updated_at: Date +} + +interface OrderChange { + id: string + order_id: string + actions: OrderChangeAction[] + status: string + requested_by: string | null + requested_at: Date | string | null + confirmed_by: string | null + confirmed_at: Date | string | null + declined_by: string | null + declined_reason: string | null + metadata: Record | null + declined_at: Date | string | null + canceled_by: string | null + canceled_at: Date | string | null + created_at: Date | string + updated_at: Date | string +} + +interface OrderChangeAction { + id: string + order_change_id: string | null + order_change: OrderChange | null + order_id: string | null + reference: string + reference_id: string + action: string + details: Record | null + internal_note: string | null + created_at: Date | string + updated_at: Date | string +} + +interface OrderTransaction { + id: string + order_id: string + amount: number + raw_amount?: BigNumberRawValue + currency_code: string + reference: string + reference_id: string + metadata: Record | null + created_at: Date | string + updated_at: Date | string +} + +/** + * @experimental + */ +export interface OrderResponse { + id: string + version: number + region_id: string | null + customer_id: string | null + sales_channel_id: string | null + email: string | null + currency_code: string + shipping_address?: OrderAddress + billing_address?: OrderAddress + items: OrderLineItem[] | null + shipping_methods: OrderShippingMethod[] | null + transactions?: OrderTransaction[] + summary: OrderSummary + metadata: Record | null + created_at: string | Date + updated_at: string | Date + original_item_total: number + original_item_subtotal: number + original_item_tax_total: number + item_total: number + item_subtotal: number + item_tax_total: number + original_total: number + original_subtotal: number + original_tax_total: number + total: number + subtotal: number + tax_total: number + discount_total: number + discount_tax_total: number + gift_card_total: number + gift_card_tax_total: number + shipping_total: number + shipping_subtotal: number + shipping_tax_total: number + original_shipping_total: number + original_shipping_subtotal: number + original_shipping_tax_total: number + raw_original_item_total?: BigNumberRawValue + raw_original_item_subtotal?: BigNumberRawValue + raw_original_item_tax_total?: BigNumberRawValue + raw_item_total?: BigNumberRawValue + raw_item_subtotal?: BigNumberRawValue + raw_item_tax_total?: BigNumberRawValue + raw_original_total?: BigNumberRawValue + raw_original_subtotal?: BigNumberRawValue + raw_original_tax_total?: BigNumberRawValue + raw_total?: BigNumberRawValue + raw_subtotal?: BigNumberRawValue + raw_tax_total?: BigNumberRawValue + raw_discount_total?: BigNumberRawValue + raw_discount_tax_total?: BigNumberRawValue + raw_gift_card_total?: BigNumberRawValue + raw_gift_card_tax_total?: BigNumberRawValue + raw_shipping_total?: BigNumberRawValue + raw_shipping_subtotal?: BigNumberRawValue + raw_shipping_tax_total?: BigNumberRawValue + raw_original_shipping_total?: BigNumberRawValue + raw_original_shipping_subtotal?: BigNumberRawValue + raw_original_shipping_tax_total?: BigNumberRawValue +} + +/** + * @experimental + */ +export interface AdminOrderListResponse extends PaginatedResponse { + orders: OrderResponse[] +} + +/** + * @experimental + */ +export interface AdminOrderResponse { + order: OrderResponse +} diff --git a/packages/types/src/http/order/index.ts b/packages/types/src/http/order/index.ts new file mode 100644 index 0000000000..26b8eb9dad --- /dev/null +++ b/packages/types/src/http/order/index.ts @@ -0,0 +1 @@ +export * from "./admin" diff --git a/packages/types/src/order/common.ts b/packages/types/src/order/common.ts index 08fb89a914..2ee1aa1a9f 100644 --- a/packages/types/src/order/common.ts +++ b/packages/types/src/order/common.ts @@ -144,6 +144,16 @@ export interface OrderShippingMethodTaxLineDTO extends OrderTaxLineDTO { * The subtotal tax relative to the shipping method. */ subtotal: BigNumberValue + + /** + * The raw total tax relative to the shipping method. + */ + raw_total: BigNumberRawValue + + /** + * The raw subtotal tax relative to the shipping method. + */ + raw_subtotal: BigNumberRawValue } export interface OrderLineItemTaxLineDTO extends OrderTaxLineDTO { @@ -165,6 +175,16 @@ export interface OrderLineItemTaxLineDTO extends OrderTaxLineDTO { * The subtotal tax relative to the item. */ subtotal: BigNumberValue + + /** + * The raw total tax relative to the item. + */ + raw_total: BigNumberRawValue + + /** + * The raw subtotal tax relative to the item. + */ + raw_subtotal: BigNumberRawValue } export interface OrderAddressDTO { @@ -253,7 +273,12 @@ export interface OrderShippingMethodDTO { /** * The price of the shipping method */ - amount: BigNumberRawValue + amount: BigNumberValue + + /** + * The raw price of the shipping method + */ + raw_amount: BigNumberRawValue /** * Whether the shipping method price is tax inclusive or not */ @@ -335,22 +360,158 @@ export interface OrderShippingMethodDTO { * The discount tax total of the order shipping method. */ discount_tax_total: BigNumberValue + + /** + * The raw original total of the order shipping method. + */ + raw_original_total: BigNumberRawValue + + /** + * The raw original subtotal of the order shipping method. + */ + raw_original_subtotal: BigNumberRawValue + + /** + * The raw original tax total of the order shipping method. + */ + raw_original_tax_total: BigNumberRawValue + + /** + * The raw total of the order shipping method. + */ + raw_total: BigNumberRawValue + + /** + * The raw subtotal of the order shipping method. + */ + raw_subtotal: BigNumberRawValue + + /** + * The raw tax total of the order shipping method. + */ + raw_tax_total: BigNumberRawValue + + /** + * The raw discount total of the order shipping method. + */ + raw_discount_total: BigNumberRawValue + + /** + * The raw discount tax total of the order shipping method. + */ + raw_discount_tax_total: BigNumberRawValue } export interface OrderLineItemTotalsDTO { - original_total: number - original_subtotal: number - original_tax_total: number + /** + * The original total of the order line item. + */ + original_total: BigNumberValue - item_total: number - item_subtotal: number - item_tax_total: number + /** + * The original subtotal of the order line item. + */ + original_subtotal: BigNumberValue - total: number - subtotal: number - tax_total: number - discount_total: number - discount_tax_total: number + /** + * The original tax total of the order line item. + */ + original_tax_total: BigNumberValue + + /** + * The item total of the order line item. + */ + item_total: BigNumberValue + + /** + * The item subtotal of the order line item. + */ + item_subtotal: BigNumberValue + + /** + * The item tax total of the order line item. + */ + item_tax_total: BigNumberValue + + /** + * The total of the order line item. + */ + total: BigNumberValue + + /** + * The subtotal of the order line item. + */ + subtotal: BigNumberValue + + /** + * The tax total of the order line item. + */ + tax_total: BigNumberValue + + /** + * The discount total of the order line item. + */ + discount_total: BigNumberValue + + /** + * The discount tax total of the order line item. + */ + discount_tax_total: BigNumberValue + + /** + * The raw original total of the order line item. + */ + raw_original_total: BigNumberRawValue + + /** + * The raw original subtotal of the order line item. + */ + raw_original_subtotal: BigNumberRawValue + + /** + * The raw original tax total of the order line item. + */ + raw_original_tax_total: BigNumberRawValue + + /** + * The raw item total of the order line item. + */ + raw_item_total: BigNumberRawValue + + /** + * The raw item subtotal of the order line item. + */ + raw_item_subtotal: BigNumberRawValue + + /** + * The raw item tax total of the order line item. + */ + raw_item_tax_total: BigNumberRawValue + + /** + * The raw total of the order line item. + */ + raw_total: BigNumberRawValue + + /** + * The raw subtotal of the order line item. + */ + raw_subtotal: BigNumberRawValue + + /** + * The raw tax total of the order line item. + */ + raw_tax_total: BigNumberRawValue + + /** + * The raw discount total of the order line item. + */ + raw_discount_total: BigNumberRawValue + + /** + * The raw discount tax total of the order line item. + */ + raw_discount_tax_total: BigNumberRawValue } export interface OrderLineItemDTO extends OrderLineItemTotalsDTO { @@ -691,6 +852,226 @@ export interface OrderDTO { * When the order was updated. */ updated_at?: string | Date + + /** + * The original item total of the order. + */ + original_item_total: BigNumberValue + + /** + * The original item subtotal of the order. + */ + original_item_subtotal: BigNumberValue + + /** + * The original item tax total of the order. + */ + original_item_tax_total: BigNumberValue + + /** + * The item total of the order. + */ + item_total: BigNumberValue + + /** + * The item subtotal of the order. + */ + item_subtotal: BigNumberValue + + /** + * The item tax total of the order. + */ + item_tax_total: BigNumberValue + + /** + * The original total of the order. + */ + original_total: BigNumberValue + + /** + * The original subtotal of the order. + */ + original_subtotal: BigNumberValue + + /** + * The original tax total of the order. + */ + original_tax_total: BigNumberValue + + /** + * The total of the order. + */ + total: BigNumberValue + + /** + * The subtotal of the order. (Excluding taxes) + */ + subtotal: BigNumberValue + + /** + * The tax total of the order. + */ + tax_total: BigNumberValue + + /** + * The discount total of the order. + */ + discount_total: BigNumberValue + + /** + * The discount tax total of the order. + */ + discount_tax_total: BigNumberValue + + /** + * The gift card total of the order. + */ + gift_card_total: BigNumberValue + + /** + * The gift card tax total of the order. + */ + gift_card_tax_total: BigNumberValue + + /** + * The shipping total of the order. + */ + shipping_total: BigNumberValue + + /** + * The shipping subtotal of the order. + */ + shipping_subtotal: BigNumberValue + + /** + * The shipping tax total of the order. + */ + shipping_tax_total: BigNumberValue + + /** + * The original shipping total of the order. + */ + original_shipping_total: BigNumberValue + + /** + * The original shipping subtotal of the order. + */ + original_shipping_subtotal: BigNumberValue + + /** + * The original shipping tax total of the order. + */ + original_shipping_tax_total: BigNumberValue + + /** + * The raw original item total of the order. + */ + raw_original_item_total: BigNumberRawValue + + /** + * The raw original item subtotal of the order. + */ + raw_original_item_subtotal: BigNumberRawValue + + /** + * The raw original item tax total of the order. + */ + raw_original_item_tax_total: BigNumberRawValue + + /** + * The raw item total of the order. + */ + raw_item_total: BigNumberRawValue + + /** + * The raw item subtotal of the order. + */ + raw_item_subtotal: BigNumberRawValue + + /** + * The raw item tax total of the order. + */ + raw_item_tax_total: BigNumberRawValue + + /** + * The raw original total of the order. + */ + raw_original_total: BigNumberRawValue + + /** + * The raw original subtotal of the order. + */ + raw_original_subtotal: BigNumberRawValue + + /** + * The raw original tax total of the order. + */ + raw_original_tax_total: BigNumberRawValue + + /** + * The raw total of the order. + */ + raw_total: BigNumberRawValue + + /** + * The raw subtotal of the order. (Excluding taxes) + */ + raw_subtotal: BigNumberRawValue + + /** + * The raw tax total of the order. + */ + raw_tax_total: BigNumberRawValue + + /** + * The raw discount total of the order. + */ + raw_discount_total: BigNumberRawValue + + /** + * The raw discount tax total of the order. + */ + raw_discount_tax_total: BigNumberRawValue + + /** + * The raw gift card total of the order. + */ + raw_gift_card_total: BigNumberRawValue + + /** + * The raw gift card tax total of the order. + */ + raw_gift_card_tax_total: BigNumberRawValue + + /** + * The raw shipping total of the order. + */ + raw_shipping_total: BigNumberRawValue + + /** + * The raw shipping subtotal of the order. + */ + raw_shipping_subtotal: BigNumberRawValue + + /** + * The raw shipping tax total of the order. + */ + raw_shipping_tax_total: BigNumberRawValue + + /** + * The raw original shipping total of the order. + */ + raw_original_shipping_total: BigNumberRawValue + + /** + * The raw original shipping subtotal of the order. + */ + raw_original_shipping_subtotal: BigNumberRawValue + + /** + * The raw original shipping tax total of the order. + */ + raw_original_shipping_tax_total: BigNumberRawValue } export interface OrderChangeDTO { @@ -843,7 +1224,11 @@ export interface OrderTransactionDTO { /** * The amount of the transaction */ - amount: number + amount: BigNumberValue + /** + * The raw amount of the transaction + */ + raw_amount: BigNumberRawValue /** * The currency code of the transaction */ diff --git a/packages/utils/src/totals/__tests__/create-raw-properties-from-bignumber.ts b/packages/utils/src/totals/__tests__/create-raw-properties-from-bignumber.ts new file mode 100644 index 0000000000..eff49b5d97 --- /dev/null +++ b/packages/utils/src/totals/__tests__/create-raw-properties-from-bignumber.ts @@ -0,0 +1,88 @@ +import { BigNumber } from "../big-number" +import { createRawPropertiesFromBigNumber } from "../create-raw-properties-from-bignumber" + +describe("Create Raw properties from BigNumber", function () { + it("should create raw properties from BigNumber properties", function () { + const obj = { + price: new BigNumber({ + value: "42", + precision: 10, + }), + field: 111, + metadata: { + numeric_field: new BigNumber({ + value: "100", + }), + random_field: 134, + }, + + abc: null, + raw_abc: { + value: "9.00000010000103991234", + precision: 20, + }, + } + + createRawPropertiesFromBigNumber(obj) + + expect(obj).toEqual( + expect.objectContaining({ + raw_price: { + value: "42", + precision: 10, + }, + field: 111, + metadata: expect.objectContaining({ + raw_numeric_field: { + value: "100", + precision: 20, + }, + random_field: 134, + }), + abc: null, + raw_abc: { + value: "9.00000010000103991234", + precision: 20, + }, + }) + ) + }) + + it("should create all properties containing BigNumber properties excluding selected ones", function () { + const obj = { + price: new BigNumber({ + value: "42", + precision: 10, + }), + field: 111, + metadata: { + numeric_field: new BigNumber({ + value: "100", + }), + random_field: 134, + }, + } + + createRawPropertiesFromBigNumber(obj, { + exclude: ["metadata.numeric_field"], + }) + + expect(obj).toEqual({ + price: new BigNumber({ + value: "42", + precision: 10, + }), + raw_price: { + value: "42", + precision: 10, + }, + field: 111, + metadata: { + numeric_field: new BigNumber({ + value: "100", + }), + random_field: 134, + }, + }) + }) +}) diff --git a/packages/utils/src/totals/cart/index.ts b/packages/utils/src/totals/cart/index.ts index f9f98d026b..afd4b38120 100644 --- a/packages/utils/src/totals/cart/index.ts +++ b/packages/utils/src/totals/cart/index.ts @@ -63,22 +63,24 @@ export function decorateCartTotals( let discountTaxTotal = MathBN.convert(0) let itemsSubtotal = MathBN.convert(0) - let itemsSubtotalWithoutTaxes = MathBN.convert(0) - let itemsTotal = MathBN.convert(0) + let itemsOriginalTotal = MathBN.convert(0) let itemsOriginalSubtotal = MathBN.convert(0) let itemsTaxTotal = MathBN.convert(0) + let itemsOriginalTaxTotal = MathBN.convert(0) let shippingSubtotal = MathBN.convert(0) - let shippingTotal = MathBN.convert(0) + let shippingOriginalTotal = MathBN.convert(0) let shippingOriginalSubtotal = MathBN.convert(0) let shippingTaxTotal = MathBN.convert(0) + let shippingTaxSubTotal = MathBN.convert(0) + let shippingOriginalTaxTotal = MathBN.convert(0) let shippingOriginalTaxSubtotal = MathBN.convert(0) @@ -94,6 +96,7 @@ export function decorateCartTotals( const itemOriginalTaxTotal = MathBN.convert(itemTotals.original_tax_total) const itemDiscountTotal = MathBN.convert(itemTotals.discount_total) + const itemDiscountTaxTotal = MathBN.convert(itemTotals.discount_tax_total) subtotal = MathBN.add(subtotal, itemSubtotal) @@ -108,6 +111,7 @@ export function decorateCartTotals( itemsSubtotal = MathBN.add(itemsSubtotal, itemSubtotal) itemsTaxTotal = MathBN.add(itemsTaxTotal, itemTaxTotal) + itemsOriginalTaxTotal = MathBN.add( itemsOriginalTaxTotal, itemOriginalTaxTotal @@ -164,6 +168,7 @@ export function decorateCartTotals( }) const taxTotal = MathBN.add(itemsTaxTotal, shippingTaxTotal) + const originalTaxTotal = MathBN.add( itemsOriginalTaxTotal, shippingOriginalTaxTotal @@ -181,7 +186,7 @@ export function decorateCartTotals( const tempTotal = MathBN.add(subtotal, shippingTotal, taxTotal) const total = MathBN.sub(tempTotal, discountTotal) - const cart = { ...cartLike } as any + const cart = cartLike as any cart.total = new BigNumber(total) cart.subtotal = new BigNumber(subtotal) diff --git a/packages/utils/src/totals/create-raw-properties-from-bignumber.ts b/packages/utils/src/totals/create-raw-properties-from-bignumber.ts new file mode 100644 index 0000000000..fc0407f755 --- /dev/null +++ b/packages/utils/src/totals/create-raw-properties-from-bignumber.ts @@ -0,0 +1,59 @@ +import { isDefined, trimZeros } from "../common" +import { BigNumber } from "./big-number" + +export function createRawPropertiesFromBigNumber( + obj, + { + prefix = "raw_", + exclude = [], + }: { + prefix?: string + exclude?: string[] + } = {} +) { + const stack = [{ current: obj, path: "" }] + + while (stack.length > 0) { + const { current, path } = stack.pop()! + + if ( + current == null || + typeof current !== "object" || + current instanceof BigNumber + ) { + continue + } + + if (Array.isArray(current)) { + current.forEach((element, index) => + stack.push({ current: element, path }) + ) + } else { + for (const key of Object.keys(current)) { + const value = current[key] + const currentPath = path ? `${path}.${key}` : key + + if (value != null && !exclude.includes(currentPath)) { + const isBigNumber = + typeof value === "object" && + isDefined(value.raw_) && + isDefined(value.numeric_) + + if (isBigNumber) { + const newKey = prefix + key + const newPath = path ? `${path}.${newKey}` : newKey + if (!exclude.includes(newPath)) { + current[newKey] = { + ...value.raw_, + value: trimZeros(value.raw_.value), + } + continue + } + } + } + + stack.push({ current: value, path: currentPath }) + } + } + } +} diff --git a/packages/utils/src/totals/index.ts b/packages/utils/src/totals/index.ts index 5668567ad0..f14979d933 100644 --- a/packages/utils/src/totals/index.ts +++ b/packages/utils/src/totals/index.ts @@ -1,4 +1,5 @@ export * from "./cart" +export * from "./create-raw-properties-from-bignumber" export * from "./line-item" export * from "./math" export * from "./promotion"