From b3e4be72087d0b528c3cce322edf9325b855c8ae Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Fri, 13 Jan 2023 13:52:09 +0100 Subject: [PATCH] fix(medusa): Draft order totals not working with custom items (#3008) --- .changeset/wicked-cherries-sip.md | 5 + .../medusa/src/services/__tests__/cart.js | 105 ++++++++++++++++++ packages/medusa/src/services/new-totals.ts | 13 +-- packages/medusa/src/services/tax-provider.ts | 9 +- 4 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 .changeset/wicked-cherries-sip.md diff --git a/.changeset/wicked-cherries-sip.md b/.changeset/wicked-cherries-sip.md new file mode 100644 index 0000000000..dbb4d24da7 --- /dev/null +++ b/.changeset/wicked-cherries-sip.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +fix(medusa): Draft order totals not working with custom items diff --git a/packages/medusa/src/services/__tests__/cart.js b/packages/medusa/src/services/__tests__/cart.js index 1e8fb3fb84..ed4a963f85 100644 --- a/packages/medusa/src/services/__tests__/cart.js +++ b/packages/medusa/src/services/__tests__/cart.js @@ -1,4 +1,5 @@ import _ from "lodash" +import { asClass, asValue, createContainer } from "awilix" import { MedusaError } from "medusa-core-utils" import { IdMap, MockManager, MockRepository } from "medusa-test-utils" import { FlagRouter } from "../../utils/flag-router" @@ -8,6 +9,18 @@ import { LineItemAdjustmentServiceMock } from "../__mocks__/line-item-adjustment import { newTotalsServiceMock } from "../__mocks__/new-totals" import { taxProviderServiceMock } from "../__mocks__/tax-provider" import { PaymentSessionStatus } from "../../models" +import { NewTotalsService, TaxProviderService } from "../index" +import { cacheServiceMock } from "../__mocks__/cache" +import { EventBusServiceMock } from "../__mocks__/event-bus" +import { PaymentProviderServiceMock } from "../__mocks__/payment-provider" +import { ProductServiceMock } from "../__mocks__/product" +import { ProductVariantServiceMock } from "../__mocks__/product-variant" +import { RegionServiceMock } from "../__mocks__/region" +import { LineItemServiceMock } from "../__mocks__/line-item" +import { ShippingOptionServiceMock } from "../__mocks__/shipping-option" +import { CustomerServiceMock } from "../__mocks__/customer" +import TaxCalculationStrategy from "../../strategies/tax-calculation" +import SystemTaxService from "../system-tax" const eventBusService = { emit: jest.fn(), @@ -2512,4 +2525,96 @@ describe("CartService", () => { }) }) }) + + describe("decorateTotals integration", () => { + const legacyTotalServiceMock = { + ...totalsService, + getCalculationContext: () => ({ + shipping_methods: [], + region: { + tax_rate: 10, + currency_code: "eur", + }, + allocation_map: {}, + }), + } + // TODO: extract that to a fixture to be used in this file in the rest of the tests. Needs some update on the registration + // as it is for now adapted to this case + const container = createContainer() + container + .register("manager", asValue(MockManager)) + .register("paymentSessionRepository", asValue(MockRepository({}))) + .register("addressRepository", asValue(MockRepository({}))) + .register("cartRepository", asValue(MockRepository({}))) + .register("lineItemRepository", asValue(MockRepository({}))) + .register("shippingMethodRepository", asValue(MockRepository({}))) + .register("paymentProviderService", asValue(PaymentProviderServiceMock)) + .register("productService", asValue(ProductServiceMock)) + .register("productVariantService", asValue(ProductVariantServiceMock)) + .register("regionService", asValue(RegionServiceMock)) + .register("lineItemService", asValue(LineItemServiceMock)) + .register("shippingOptionService", asValue(ShippingOptionServiceMock)) + .register("customerService", asValue(CustomerServiceMock)) + .register("discountService", asValue({})) + .register("giftCardService", asValue({})) + .register("totalsService", asValue(legacyTotalServiceMock)) + .register("customShippingOptionService", asValue({})) + .register("lineItemAdjustmentService", asValue({})) + .register("priceSelectionStrategy", asValue({})) + .register("productVariantInventoryService", asValue({})) + .register("salesChannelService", asValue({})) + .register("storeService", asValue({})) + .register("featureFlagRouter", asValue(new FlagRouter({}))) + .register("taxRateService", asValue({})) + .register("systemTaxService", asValue(new SystemTaxService())) + .register("tp_test", asValue("good")) + .register("cacheService", asValue(cacheServiceMock)) + .register("taxProviderRepository", asValue(MockRepository)) + .register( + "lineItemTaxLineRepository", + asValue(MockRepository({ create: (d) => d })) + ) + .register("shippingMethodTaxLineRepository", asValue(MockRepository)) + .register("eventBusService", asValue(EventBusServiceMock)) + // Register the real class for the service below to do the integration tests + .register("taxCalculationStrategy", asClass(TaxCalculationStrategy)) + .register("taxProviderService", asClass(TaxProviderService)) + .register("newTotalsService", asClass(NewTotalsService)) + .register("cartService", asClass(CartService)) + + const cartService = container.resolve("cartService") + + it("should decorate totals with a cart containing custom items", async () => { + const cart = { + id: IdMap.getId("cartWithPaySessions"), + region_id: IdMap.getId("testRegion"), + items: [ + { + id: IdMap.getId("existingLine"), + title: "merge line", + description: "This is a new line", + thumbnail: "test-img-yeah.com/thumb", + variant_id: null, + unit_price: 100, + quantity: 10, + }, + ], + shipping_address: {}, + billing_address: {}, + discounts: [], + region: { + tax_rate: 10, + currency_code: "eur", + }, + gift_cards: [], + } + + const totals = await cartService.decorateTotals(cart, { + force_taxes: true, + }) + + expect(totals.total).toEqual(1000) + expect(totals.subtotal).toEqual(1000) + }) + }) }) diff --git a/packages/medusa/src/services/new-totals.ts b/packages/medusa/src/services/new-totals.ts index a76b21712d..5f22fbd957 100644 --- a/packages/medusa/src/services/new-totals.ts +++ b/packages/medusa/src/services/new-totals.ts @@ -112,13 +112,6 @@ export default class NewTotalsService extends TransactionBaseService { lineItemsTaxLinesMap[item.id] = item.tax_lines ?? [] }) } else { - if (items.some((item) => !item.variant)) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - "Unable to fetch tax lines to compute line item totals as one of the item variant is missing but required for the tax lines to be computed. Might be due to a missing relation items.variant" - ) - } - const { lineItemsTaxLines } = await this.taxProviderService_ .withTransaction(manager) .getTaxLinesMap(items, calculationContext) @@ -199,7 +192,7 @@ export default class NewTotalsService extends TransactionBaseService { ? totals.tax_lines : (taxLines as LineItemTaxLine[]) - if (!totals.tax_lines) { + if (!totals.tax_lines && item.variant_id) { throw new MedusaError( MedusaError.Types.UNEXPECTED_STATE, "Tax Lines must be joined to calculate taxes" @@ -208,7 +201,7 @@ export default class NewTotalsService extends TransactionBaseService { } if (item.is_return) { - if (!isDefined(item.tax_lines)) { + if (!isDefined(item.tax_lines) && item.variant_id) { throw new MedusaError( MedusaError.Types.UNEXPECTED_STATE, "Return Line Items must join tax lines" @@ -216,7 +209,7 @@ export default class NewTotalsService extends TransactionBaseService { } } - if (totals.tax_lines.length > 0) { + if (totals.tax_lines?.length > 0) { totals.tax_total = await this.taxCalculationStrategy_.calculate( [item], totals.tax_lines, diff --git a/packages/medusa/src/services/tax-provider.ts b/packages/medusa/src/services/tax-provider.ts index f7f3adea79..4e7d7b23be 100644 --- a/packages/medusa/src/services/tax-provider.ts +++ b/packages/medusa/src/services/tax-provider.ts @@ -263,7 +263,14 @@ class TaxProviderService extends TransactionBaseService { return null } - if (l.variant && l.variant.product_id) { + if (l.variant_id && !l.variant) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Unable to get the tax lines for the item ${l.id}, it contains a variant_id but the variant is missing.` + ) + } + + if (l.variant?.product_id) { return { item: l, rates: await this.getRegionRatesForProduct(