diff --git a/integration-tests/http/__tests__/cart/store/cart.spec.ts b/integration-tests/http/__tests__/cart/store/cart.spec.ts index b9eaf33ec9..a7ec1a6674 100644 --- a/integration-tests/http/__tests__/cart/store/cart.spec.ts +++ b/integration-tests/http/__tests__/cart/store/cart.spec.ts @@ -1,212 +1,1242 @@ +import { + Modules, + PriceListStatus, + PriceListType, + ProductStatus, + PromotionRuleOperator, + PromotionType, +} from "@medusajs/utils" import { medusaIntegrationTestRunner } from "medusa-test-utils" import { - adminHeaders, createAdminUser, + generatePublishableKey, + generateStoreHeaders, } from "../../../../helpers/create-admin-user" +import { setupTaxStructure } from "../../../../modules/__tests__/fixtures" -jest.setTimeout(50000) +jest.setTimeout(100000) + +const env = { MEDUSA_FF_MEDUSA_V2: true } +const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } } + +const generateStoreHeadersWithCustomer = async ({ + api, + storeHeaders, + customer, +}) => { + const registeredCustomerToken = ( + await api.post("/auth/customer/emailpass/register", { + email: customer.email, + password: "password", + }) + ).data.token + + return { + headers: { + ...storeHeaders.headers, + authorization: `Bearer ${registeredCustomerToken}`, + }, + } +} + +const shippingAddressData = { + address_1: "test address 1", + address_2: "test address 2", + city: "SF", + country_code: "US", + province: "CA", + postal_code: "94016", +} + +const productData = { + title: "Medusa T-Shirt", + handle: "t-shirt", + status: ProductStatus.PUBLISHED, + options: [ + { + title: "Size", + values: ["S"], + }, + { + title: "Color", + values: ["Black", "White"], + }, + ], + variants: [ + { + title: "S / Black", + sku: "SHIRT-S-BLACK", + options: { + Size: "S", + Color: "Black", + }, + manage_inventory: false, + prices: [ + { + amount: 1500, + currency_code: "usd", + }, + { + amount: 1500, + currency_code: "eur", + }, + { + amount: 1300, + currency_code: "dkk", + }, + ], + }, + { + title: "S / White", + sku: "SHIRT-S-WHITE", + options: { + Size: "S", + Color: "White", + }, + manage_inventory: false, + prices: [ + { + amount: 1500, + currency_code: "usd", + }, + { + amount: 1500, + currency_code: "eur", + }, + { + amount: 1300, + currency_code: "dkk", + }, + ], + }, + ], +} medusaIntegrationTestRunner({ + env, testSuite: ({ dbConnection, getContainer, api }) => { - beforeEach(async () => { - await createAdminUser(dbConnection, adminHeaders, getContainer()) - }) + describe("Store Carts API", () => { + let appContainer + let storeHeaders + let storeHeadersWithCustomer + let region, + noAutomaticRegion, + product, + salesChannel, + cart, + customer, + promotion - describe("noop", () => { - it("noop", () => {}) + beforeAll(async () => { + appContainer = getContainer() + }) + + beforeEach(async () => { + await createAdminUser(dbConnection, adminHeaders, appContainer) + const publishableKey = await generatePublishableKey(appContainer) + storeHeaders = generateStoreHeaders({ publishableKey }) + + customer = ( + await api.post( + "/admin/customers", + { + first_name: "tony", + email: "tony@stark-industries.com", + }, + adminHeaders + ) + ).data.customer + + storeHeadersWithCustomer = await generateStoreHeadersWithCustomer({ + storeHeaders, + api, + customer, + }) + + await setupTaxStructure(appContainer.resolve(Modules.TAX)) + + region = ( + await api.post( + "/admin/regions", + { name: "US", currency_code: "usd", countries: ["us"] }, + adminHeaders + ) + ).data.region + + noAutomaticRegion = ( + await api.post( + "/admin/regions", + { name: "EUR", currency_code: "eur", automatic_taxes: false }, + adminHeaders + ) + ).data.region + + product = (await api.post("/admin/products", productData, adminHeaders)) + .data.product + + salesChannel = ( + await api.post( + "/admin/sales-channels", + { name: "Webshop", description: "channel" }, + adminHeaders + ) + ).data.sales_channel + + await api.post( + "/admin/price-preferences", + { + attribute: "currency_code", + value: "usd", + is_tax_inclusive: true, + }, + adminHeaders + ) + + promotion = ( + await api.post( + `/admin/promotions`, + { + code: "PROMOTION_APPLIED", + type: PromotionType.STANDARD, + application_method: { + type: "fixed", + target_type: "items", + allocation: "each", + value: 100, + max_quantity: 1, + currency_code: "usd", + target_rules: [ + { + attribute: "product_id", + operator: "in", + values: [product.id], + }, + ], + }, + }, + adminHeaders + ) + ).data.promotion + }) + + describe("POST /store/carts", () => { + it("should succesffully create a cart", async () => { + const response = await api.post( + `/store/carts`, + { + currency_code: "usd", + sales_channel_id: salesChannel.id, + region_id: region.id, + shipping_address: shippingAddressData, + items: [{ variant_id: product.variants[0].id, quantity: 1 }], + }, + storeHeadersWithCustomer + ) + + expect(response.status).toEqual(200) + expect(response.data.cart).toEqual( + expect.objectContaining({ + currency_code: "usd", + items: expect.arrayContaining([ + expect.objectContaining({ + unit_price: 1500, + compare_at_unit_price: null, + is_tax_inclusive: true, + quantity: 1, + tax_lines: [ + expect.objectContaining({ + description: "CA Default Rate", + code: "CADEFAULT", + rate: 5, + provider_id: "system", + }), + ], + adjustments: [], + }), + ]), + }) + ) + }) + + describe("with sale price lists", () => { + let priceList + + beforeEach(async () => { + priceList = ( + await api.post( + `/admin/price-lists`, + { + title: "test price list", + description: "test", + status: PriceListStatus.ACTIVE, + type: PriceListType.SALE, + prices: [ + { + amount: 350, + currency_code: "usd", + variant_id: product.variants[0].id, + }, + ], + }, + adminHeaders + ) + ).data.price_list + }) + + it("should successfully create cart with price from price list", async () => { + const response = await api.post( + `/store/carts`, + { + currency_code: "usd", + sales_channel_id: salesChannel.id, + region_id: region.id, + shipping_address: shippingAddressData, + items: [{ variant_id: product.variants[0].id, quantity: 1 }], + }, + storeHeadersWithCustomer + ) + + expect(response.status).toEqual(200) + expect(response.data.cart).toEqual( + expect.objectContaining({ + currency_code: "usd", + items: expect.arrayContaining([ + expect.objectContaining({ + unit_price: 350, + compare_at_unit_price: 1500, + is_tax_inclusive: true, + quantity: 1, + tax_lines: [ + expect.objectContaining({ + description: "CA Default Rate", + code: "CADEFAULT", + rate: 5, + provider_id: "system", + }), + ], + adjustments: [], + }), + ]), + }) + ) + }) + }) + }) + + describe("POST /store/carts/:id/line-items", () => { + beforeEach(async () => { + cart = ( + await api.post( + `/store/carts`, + { + currency_code: "usd", + sales_channel_id: salesChannel.id, + region_id: region.id, + shipping_address: shippingAddressData, + items: [{ variant_id: product.variants[0].id, quantity: 1 }], + promo_codes: [promotion.code], + }, + storeHeadersWithCustomer + ) + ).data.cart + }) + + it("should add item to cart", async () => { + let response = await api.post( + `/store/carts/${cart.id}/line-items`, + { + variant_id: product.variants[0].id, + quantity: 1, + }, + storeHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.cart).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "usd", + items: expect.arrayContaining([ + expect.objectContaining({ + unit_price: 1500, + compare_at_unit_price: null, + is_tax_inclusive: true, + quantity: 2, + tax_lines: [ + expect.objectContaining({ + description: "CA Default Rate", + code: "CADEFAULT", + rate: 5, + provider_id: "system", + }), + ], + adjustments: [ + { + id: expect.any(String), + code: "PROMOTION_APPLIED", + promotion_id: promotion.id, + amount: 100, + }, + ], + }), + ]), + }) + ) + }) + + describe("with sale price lists", () => { + let priceList + + beforeEach(async () => { + priceList = ( + await api.post( + `/admin/price-lists`, + { + title: "test price list", + description: "test", + status: PriceListStatus.ACTIVE, + type: PriceListType.SALE, + prices: [ + { + amount: 350, + currency_code: "usd", + variant_id: product.variants[0].id, + }, + ], + }, + adminHeaders + ) + ).data.price_list + }) + + it("should add price from price list and set compare_at_unit_price", async () => { + let response = await api.post( + `/store/carts/${cart.id}/line-items`, + { + variant_id: product.variants[0].id, + quantity: 1, + }, + storeHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.cart).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "usd", + items: expect.arrayContaining([ + expect.objectContaining({ + unit_price: 350, + compare_at_unit_price: 1500, + is_tax_inclusive: true, + quantity: 2, + tax_lines: [ + expect.objectContaining({ + description: "CA Default Rate", + code: "CADEFAULT", + rate: 5, + provider_id: "system", + }), + ], + adjustments: [ + { + id: expect.any(String), + code: "PROMOTION_APPLIED", + promotion_id: promotion.id, + amount: 100, + }, + ], + }), + ]), + }) + ) + }) + }) + }) + + describe("POST /store/carts/:id/complete", () => { + beforeEach(async () => { + cart = ( + await api.post( + `/store/carts`, + { + currency_code: "usd", + sales_channel_id: salesChannel.id, + region_id: region.id, + shipping_address: shippingAddressData, + items: [{ variant_id: product.variants[0].id, quantity: 1 }], + promo_codes: [promotion.code], + }, + storeHeadersWithCustomer + ) + ).data.cart + + const paymentCollection = ( + await api.post( + `/store/payment-collections`, + { cart_id: cart.id }, + storeHeaders + ) + ).data.payment_collection + + await api.post( + `/store/payment-collections/${paymentCollection.id}/payment-sessions`, + { provider_id: "pp_system_default" }, + storeHeaders + ) + }) + + it("should successfully complete cart", async () => { + const response = await api.post( + `/store/carts/${cart.id}/complete`, + {}, + storeHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.order).toEqual( + expect.objectContaining({ + id: expect.any(String), + currency_code: "usd", + items: expect.arrayContaining([ + expect.objectContaining({ + unit_price: 1500, + compare_at_unit_price: null, + quantity: 1, + }), + ]), + }) + ) + }) + + describe("with sale price lists", () => { + let priceList + + beforeEach(async () => { + priceList = ( + await api.post( + `/admin/price-lists`, + { + title: "test price list", + description: "test", + status: PriceListStatus.ACTIVE, + type: PriceListType.SALE, + prices: [ + { + amount: 350, + currency_code: "usd", + variant_id: product.variants[0].id, + }, + ], + }, + adminHeaders + ) + ).data.price_list + + await api.post( + `/store/carts/${cart.id}/line-items`, + { variant_id: product.variants[0].id, quantity: 1 }, + storeHeaders + ) + + const paymentCollection = ( + await api.post( + `/store/payment-collections`, + { cart_id: cart.id }, + storeHeaders + ) + ).data.payment_collection + + await api.post( + `/store/payment-collections/${paymentCollection.id}/payment-sessions`, + { provider_id: "pp_system_default" }, + storeHeaders + ) + }) + + it("should add price from price list and set compare_at_unit_price for order item", async () => { + const response = await api.post( + `/store/carts/${cart.id}/complete`, + { variant_id: product.variants[0].id, quantity: 1 }, + storeHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.order).toEqual( + expect.objectContaining({ + items: expect.arrayContaining([ + expect.objectContaining({ + unit_price: 350, + compare_at_unit_price: 1500, + is_tax_inclusive: true, + quantity: 2, + }), + ]), + }) + ) + }) + }) + }) + + describe("POST /store/carts/:id", () => { + let otherRegion + + beforeEach(async () => { + cart = ( + await api.post( + `/store/carts`, + { + email: "tony@stark.com", + currency_code: "usd", + sales_channel_id: salesChannel.id, + region_id: region.id, + shipping_address: shippingAddressData, + items: [{ variant_id: product.variants[0].id, quantity: 1 }], + promo_codes: [promotion.code], + }, + storeHeadersWithCustomer + ) + ).data.cart + + otherRegion = ( + await api.post( + "/admin/regions", + { name: "dk", currency_code: "dkk", countries: ["dk"] }, + adminHeaders + ) + ).data.region + }) + + it("should update prices when region is changed", async () => { + let updated = await api.post( + `/store/carts/${cart.id}/line-items`, + { variant_id: product.variants[0].id, quantity: 1 }, + storeHeaders + ) + + expect(updated.status).toEqual(200) + expect(updated.data.cart).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "usd", + items: [ + expect.objectContaining({ + unit_price: 1500, + quantity: 2, + }), + ], + }) + ) + + updated = await api.post( + `/store/carts/${cart.id}`, + { region_id: otherRegion.id }, + storeHeaders + ) + + expect(updated.status).toEqual(200) + expect(updated.data.cart).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "dkk", + items: [ + expect.objectContaining({ + unit_price: 1300, + quantity: 2, + }), + ], + }) + ) + + updated = await api.post( + `/store/carts/${cart.id}/line-items`, + { variant_id: product.variants[0].id, quantity: 1 }, + storeHeaders + ) + + expect(updated.status).toEqual(200) + expect(updated.data.cart).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "dkk", + items: [ + expect.objectContaining({ + unit_price: 1300, + quantity: 3, + }), + ], + }) + ) + }) + + it("should update a cart with promo codes with a replace action", async () => { + const newPromotion = ( + await api.post( + `/admin/promotions`, + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + application_method: { + type: "fixed", + target_type: "items", + allocation: "across", + currency_code: "usd", + value: 1000, + apply_to_quantity: 1, + target_rules: [ + { + attribute: "product_id", + operator: PromotionRuleOperator.IN, + values: [product.id], + }, + ], + }, + }, + adminHeaders + ) + ).data.promotion + + await api.post( + `/store/carts/${cart.id}/line-items`, + { + variant_id: product.variants[0].id, + quantity: 1, + }, + storeHeaders + ) + + // Should remove earlier adjustments from other promocodes + let updated = await api.post( + `/store/carts/${cart.id}`, + { promo_codes: [newPromotion.code] }, + storeHeaders + ) + + expect(updated.status).toEqual(200) + expect(updated.data.cart).toEqual( + expect.objectContaining({ + id: cart.id, + items: [ + expect.objectContaining({ + adjustments: [ + expect.objectContaining({ + code: newPromotion.code, + }), + ], + }), + ], + }) + ) + + // Should remove all adjustments from other promo codes + updated = await api.post( + `/store/carts/${cart.id}`, + { promo_codes: [] }, + storeHeaders + ) + + expect(updated.status).toEqual(200) + expect(updated.data.cart).toEqual( + expect.objectContaining({ + id: cart.id, + items: [ + expect.objectContaining({ + adjustments: [], + }), + ], + }) + ) + }) + + it("should not generate tax lines if automatic taxes is false", async () => { + let updated = await api.post( + `/store/carts/${cart.id}`, + { email: "another@tax.com" }, + storeHeaders + ) + + expect(updated.status).toEqual(200) + expect(updated.data.cart).toEqual( + expect.objectContaining({ + id: cart.id, + items: [ + expect.objectContaining({ + tax_lines: [ + expect.objectContaining({ + description: "CA Default Rate", + code: "CADEFAULT", + rate: 5, + provider_id: "system", + }), + ], + }), + ], + }) + ) + + updated = await api.post( + `/store/carts/${cart.id}`, + { email: "another@tax.com", region_id: noAutomaticRegion.id }, + storeHeaders + ) + + expect(updated.status).toEqual(200) + expect(updated.data.cart).toEqual( + expect.objectContaining({ + id: cart.id, + items: [ + expect.objectContaining({ + tax_lines: [], + }), + ], + }) + ) + }) + + it("should update a cart's region, sales channel, customer data and tax lines", async () => { + const newSalesChannel = ( + await api.post( + "/admin/sales-channels", + { name: "Webshop", description: "channel" }, + adminHeaders + ) + ).data.sales_channel + + let updated = await api.post( + `/store/carts/${cart.id}`, + { + region_id: noAutomaticRegion.id, + email: "tony@stark.com", + sales_channel_id: newSalesChannel.id, + }, + storeHeaders + ) + + expect(updated.status).toEqual(200) + expect(updated.data.cart).toEqual( + expect.objectContaining({ + id: cart.id, + region: expect.objectContaining({ + id: noAutomaticRegion.id, + currency_code: "eur", + }), + email: "tony@stark.com", + customer: expect.objectContaining({ + email: "tony@stark.com", + }), + sales_channel_id: newSalesChannel.id, + }) + ) + }) + + it("should update tax lines on cart items when region changes", async () => { + let response = await api.post( + `/store/carts/${cart.id}`, + { + region_id: otherRegion.id, + shipping_address: { + country_code: "dk", + }, + }, + storeHeaders + ) + + expect(response.data.cart).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "dkk", + region_id: otherRegion.id, + items: expect.arrayContaining([ + expect.objectContaining({ + unit_price: 1300, + quantity: 1, + tax_lines: [ + // Uses the danish default rate + expect.objectContaining({ + description: "Denmark Default Rate", + code: "DK_DEF", + rate: 25, + provider_id: "system", + }), + ], + }), + ]), + }) + ) + }) + + it("should update region + set shipping address country code to dk when region has only one country", async () => { + const updated = await api.post( + `/store/carts/${cart.id}`, + { + region_id: otherRegion.id, + }, + storeHeaders + ) + + expect(updated.status).toEqual(200) + expect(updated.data.cart).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "dkk", + region: expect.objectContaining({ + id: otherRegion.id, + currency_code: "dkk", + countries: [expect.objectContaining({ iso_2: "dk" })], + }), + shipping_address: expect.objectContaining({ + country_code: "dk", + }), + }) + ) + }) + + it("should update region + set shipping address to null when region has more than one country", async () => { + const regionWithMultipleCountries = ( + await api.post( + "/admin/regions", + { name: "dks", currency_code: "dkk", countries: ["ae", "no"] }, + adminHeaders + ) + ).data.region + + const updated = await api.post( + `/store/carts/${cart.id}`, + { region_id: regionWithMultipleCountries.id }, + storeHeaders + ) + + expect(updated.status).toEqual(200) + expect(updated.data.cart).toEqual( + expect.objectContaining({ + currency_code: "dkk", + region: expect.objectContaining({ + currency_code: "dkk", + countries: expect.arrayContaining([ + expect.objectContaining({ iso_2: "ae" }), + expect.objectContaining({ iso_2: "no" }), + ]), + }), + shipping_address: null, + }) + ) + }) + + it("should update region and shipping address when country code is within region", async () => { + const updated = await api.post( + `/store/carts/${cart.id}`, + { + region_id: region.id, + shipping_address: { + country_code: "us", + }, + }, + storeHeaders + ) + + expect(updated.status).toEqual(200) + expect(updated.data.cart).toEqual( + expect.objectContaining({ + id: cart.id, + region: expect.objectContaining({ + id: region.id, + countries: [expect.objectContaining({ iso_2: "us" })], + }), + shipping_address: expect.objectContaining({ + country_code: "us", + }), + }) + ) + }) + + it("should throw when updating shipping address country code when country is not within region", async () => { + let errResponse = await api + .post( + `/store/carts/${cart.id}`, + { + shipping_address: { + country_code: "dk", + }, + }, + storeHeaders + ) + .catch((e) => e) + + expect(errResponse.response.status).toEqual(400) + expect(errResponse.response.data.message).toEqual( + `Country with code dk is not within region ${region.name}` + ) + }) + + it("should throw when updating region and shipping address, but shipping address country code is not within region", async () => { + let errResponse = await api + .post( + `/store/carts/${cart.id}`, + { + region_id: region.id, + shipping_address: { + country_code: "dk", + }, + }, + storeHeaders + ) + .catch((e) => e) + + expect(errResponse.response.status).toEqual(400) + expect(errResponse.response.data.message).toEqual( + `Country with code dk is not within region ${region.name}` + ) + }) + + it("should remove tax lines on cart items and shipping methods when country changes and there is no tax region for that country", async () => { + const stockLocation = ( + await api.post( + `/admin/stock-locations`, + { name: "test location" }, + adminHeaders + ) + ).data.stock_location + + await api.post( + `/admin/stock-locations/${stockLocation.id}/sales-channels`, + { add: [salesChannel.id] }, + adminHeaders + ) + + const shippingProfile = ( + await api.post( + `/admin/shipping-profiles`, + { name: `test-${stockLocation.id}`, type: "default" }, + adminHeaders + ) + ).data.shipping_profile + + const fulfillmentSets = ( + await api.post( + `/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`, + { + name: `Test-${shippingProfile.id}`, + type: "test-type", + }, + adminHeaders + ) + ).data.stock_location.fulfillment_sets + + const fulfillmentSet = ( + await api.post( + `/admin/fulfillment-sets/${fulfillmentSets[0].id}/service-zones`, + { + name: `Test-${shippingProfile.id}`, + geo_zones: [ + { type: "country", country_code: "it" }, + { type: "country", country_code: "us" }, + ], + }, + adminHeaders + ) + ).data.fulfillment_set + + await api.post( + `/admin/stock-locations/${stockLocation.id}/fulfillment-providers`, + { add: ["manual_test-provider"] }, + adminHeaders + ) + + const shippingOption = ( + await api.post( + `/admin/shipping-options`, + { + name: `Test shipping option ${fulfillmentSet.id}`, + service_zone_id: fulfillmentSet.service_zones[0].id, + shipping_profile_id: shippingProfile.id, + provider_id: "manual_test-provider", + price_type: "flat", + type: { + label: "Test type", + description: "Test description", + code: "test-code", + }, + prices: [ + { currency_code: "usd", amount: 1000 }, + { currency_code: "eur", amount: 1000 }, + { currency_code: "dkk", amount: 1000 }, + ], + rules: [], + }, + adminHeaders + ) + ).data.shipping_option + + const regionWithoutTax = ( + await api.post( + "/admin/regions", + { name: "Italy", currency_code: "eur", countries: ["it"] }, + adminHeaders + ) + ).data.region + + await api.post( + `/store/carts/${cart.id}`, + { region_id: regionWithoutTax.id }, + storeHeaders + ) + + await api.post( + `/store/carts/${cart.id}/shipping-methods`, + { option_id: shippingOption.id }, + storeHeaders + ) + + const response = await api.post( + `/store/carts/${cart.id}`, + { region_id: regionWithoutTax.id }, + storeHeaders + ) + + expect(response.data.cart).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "eur", + region_id: regionWithoutTax.id, + items: expect.arrayContaining([ + expect.objectContaining({ + tax_lines: [ + // Italy has no tax region, so we clear the tax lines + ], + }), + ]), + shipping_methods: expect.arrayContaining([ + expect.objectContaining({ + tax_lines: [ + // Italy has no tax region, so we clear the tax lines + ], + }), + ]), + }) + ) + }) + + it("should remove invalid shipping methods", async () => { + const stockLocation = ( + await api.post( + `/admin/stock-locations`, + { name: "test location" }, + adminHeaders + ) + ).data.stock_location + + await api.post( + `/admin/stock-locations/${stockLocation.id}/sales-channels`, + { add: [salesChannel.id] }, + adminHeaders + ) + + const shippingProfile = ( + await api.post( + `/admin/shipping-profiles`, + { name: `test-${stockLocation.id}`, type: "default" }, + adminHeaders + ) + ).data.shipping_profile + + const fulfillmentSets = ( + await api.post( + `/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`, + { + name: `Test-${shippingProfile.id}`, + type: "test-type", + }, + adminHeaders + ) + ).data.stock_location.fulfillment_sets + + const fulfillmentSet = ( + await api.post( + `/admin/fulfillment-sets/${fulfillmentSets[0].id}/service-zones`, + { + name: `Test-${shippingProfile.id}`, + geo_zones: [{ type: "country", country_code: "it" }], + }, + adminHeaders + ) + ).data.fulfillment_set + + await api.post( + `/admin/stock-locations/${stockLocation.id}/fulfillment-providers`, + { add: ["manual_test-provider"] }, + adminHeaders + ) + + const shippingOption = ( + await api.post( + `/admin/shipping-options`, + { + name: `Test shipping option ${fulfillmentSet.id}`, + service_zone_id: fulfillmentSet.service_zones[0].id, + shipping_profile_id: shippingProfile.id, + provider_id: "manual_test-provider", + price_type: "flat", + type: { + label: "Test type", + description: "Test description", + code: "test-code", + }, + prices: [ + { currency_code: "usd", amount: 1000 }, + { currency_code: "eur", amount: 1000 }, + ], + rules: [ + { + attribute: "enabled_in_store", + value: '"true"', + operator: "eq", + }, + { + attribute: "is_return", + value: "false", + operator: "eq", + }, + ], + }, + adminHeaders + ) + ).data.shipping_option + + const regionWithoutTax = ( + await api.post( + "/admin/regions", + { name: "Italy", currency_code: "eur", countries: ["it"] }, + adminHeaders + ) + ).data.region + + await api.post( + `/store/carts/${cart.id}`, + { region_id: regionWithoutTax.id }, + storeHeaders + ) + + await api.post( + `/store/carts/${cart.id}/shipping-methods`, + { option_id: shippingOption.id }, + storeHeaders + ) + + let updated = await api.post( + `/store/carts/${cart.id}`, + { region_id: region.id }, + storeHeaders + ) + + expect(updated.status).toEqual(200) + expect(updated.data.cart).toEqual( + expect.objectContaining({ + id: cart.id, + shipping_methods: [], + }) + ) + }) + }) }) }, }) - -// TODO: Implement the tests below, which were migrated from v1. -// describe("POST /store/carts/:id", () => { -// let product -// const pubKeyId = IdMap.getId("pubkey-get-id") - -// beforeEach(async () => { -// await adminSeeder(dbConnection) - -// await simpleRegionFactory(dbConnection, { -// id: "test-region", -// }) - -// await simplePublishableApiKeyFactory(dbConnection, { -// id: pubKeyId, -// created_by: adminUserId, -// }) - -// product = await simpleProductFactory(dbConnection, { -// sales_channels: [ -// { -// id: "sales-channel", -// name: "Sales channel", -// description: "Sales channel", -// }, -// { -// id: "sales-channel2", -// name: "Sales channel2", -// description: "Sales channel2", -// }, -// ], -// }) -// }) - -// afterEach(async () => { -// const db = useDb() -// return await db.teardown() -// }) - -// it("should assign sales channel to order on cart completion if PK is present in the header", async () => { -// const api = useApi() - -// await api.post( -// `/admin/api-keys/${pubKeyId}/sales-channels/batch`, -// { -// sales_channel_ids: [{ id: "sales-channel" }], -// }, -// adminHeaders -// ) - -// const customerRes = await api.post("/store/customers", customerData, { -// withCredentials: true, -// }) - -// const createCartRes = await api.post( -// "/store/carts", -// { -// region_id: "test-region", -// items: [ -// { -// variant_id: product.variants[0].id, -// quantity: 1, -// }, -// ], -// }, -// { -// headers: { -// "x-medusa-access-token": "test_token", -// "x-publishable-api-key": pubKeyId, -// }, -// } -// ) - -// const cart = createCartRes.data.cart - -// await api.post(`/store/carts/${cart.id}`, { -// customer_id: customerRes.data.customer.id, -// }) - -// await api.post(`/store/carts/${cart.id}/payment-sessions`) - -// const createdOrder = await api.post( -// `/store/carts/${cart.id}/complete-cart` -// ) - -// expect(createdOrder.data.type).toEqual("order") -// expect(createdOrder.status).toEqual(200) -// expect(createdOrder.data.data).toEqual( -// expect.objectContaining({ -// sales_channel_id: "sales-channel", -// }) -// ) -// }) - -// it("SC from params defines where product is assigned (passed SC still has to be in the scope of PK from the header)", async () => { -// const api = useApi() - -// await api.post( -// `/admin/api-keys/${pubKeyId}/sales-channels/batch`, -// { -// sales_channel_ids: [ -// { id: "sales-channel" }, -// { id: "sales-channel2" }, -// ], -// }, -// adminHeaders -// ) - -// const customerRes = await api.post("/store/customers", customerData, { -// withCredentials: true, -// }) - -// const createCartRes = await api.post( -// "/store/carts", -// { -// sales_channel_id: "sales-channel2", -// region_id: "test-region", -// items: [ -// { -// variant_id: product.variants[0].id, -// quantity: 1, -// }, -// ], -// }, -// { -// headers: { -// "x-medusa-access-token": "test_token", -// "x-publishable-api-key": pubKeyId, -// }, -// } -// ) - -// const cart = createCartRes.data.cart - -// await api.post(`/store/carts/${cart.id}`, { -// customer_id: customerRes.data.customer.id, -// }) - -// await api.post(`/store/carts/${cart.id}/payment-sessions`) - -// const createdOrder = await api.post( -// `/store/carts/${cart.id}/complete-cart` -// ) - -// expect(createdOrder.data.type).toEqual("order") -// expect(createdOrder.status).toEqual(200) -// expect(createdOrder.data.data).toEqual( -// expect.objectContaining({ -// sales_channel_id: "sales-channel2", -// }) -// ) -// }) - -// it("should throw because SC id in the body is not in the scope of PK from the header", async () => { -// const api = useApi() - -// await api.post( -// `/admin/api-keys/${pubKeyId}/sales-channels/batch`, -// { -// sales_channel_ids: [{ id: "sales-channel" }], -// }, -// adminHeaders -// ) - -// try { -// await api.post( -// "/store/carts", -// { -// sales_channel_id: "sales-channel2", // SC not in the PK scope -// region_id: "test-region", -// items: [ -// { -// variant_id: product.variants[0].id, -// quantity: 1, -// }, -// ], -// }, -// { -// headers: { -// "x-medusa-access-token": "test_token", -// "x-publishable-api-key": pubKeyId, -// }, -// } -// ) -// } catch (error) { -// expect(error.response.status).toEqual(400) -// expect(error.response.data.errors[0]).toEqual( -// `Provided sales channel id param: sales-channel2 is not associated with the Publishable API Key passed in the header of the request.` -// ) -// } -// }) -// }) -// }) diff --git a/integration-tests/http/__tests__/order-edits/order-edits.spec.ts b/integration-tests/http/__tests__/order-edits/order-edits.spec.ts index 06a7c651d9..000d43fe72 100644 --- a/integration-tests/http/__tests__/order-edits/order-edits.spec.ts +++ b/integration-tests/http/__tests__/order-edits/order-edits.spec.ts @@ -369,22 +369,56 @@ medusaIntegrationTestRunner({ expect(result.summary.current_order_total).toEqual(84) expect(result.summary.original_order_total).toEqual(60) - // Update item quantity + // Update item quantity and unit_price with the same amount as we have originally should not change totals result = ( await api.post( `/admin/order-edits/${orderId}/items/item/${item.id}`, { - quantity: 4, + quantity: 2, + unit_price: 25, }, adminHeaders ) ).data.order_preview - expect(result.summary.current_order_total).toEqual(134) + expect(result.summary.current_order_total).toEqual(84) + expect(result.summary.original_order_total).toEqual(60) + + // Update item quantity, but keep the price as it was originally, should add + 25 to previous amount + result = ( + await api.post( + `/admin/order-edits/${orderId}/items/item/${item.id}`, + { + quantity: 3, + unit_price: 25, + }, + adminHeaders + ) + ).data.order_preview + + expect(result.summary.current_order_total).toEqual(109) + expect(result.summary.original_order_total).toEqual(60) + + // Update item quantity, with a new price + // 30 * 3 = 90 (new item) + // 12 * 2 = 24 (custom item) + // 10 * 1 = 10 (shipping item) + // total = 124 + result = ( + await api.post( + `/admin/order-edits/${orderId}/items/item/${item.id}`, + { + quantity: 3, + unit_price: 30, + }, + adminHeaders + ) + ).data.order_preview + + expect(result.summary.current_order_total).toEqual(124) expect(result.summary.original_order_total).toEqual(60) // Remove the item by setting the quantity to 0 - result = ( await api.post( `/admin/order-edits/${orderId}/items/item/${item.id}`, @@ -439,7 +473,7 @@ medusaIntegrationTestRunner({ ) ).data.order_changes - expect(result[0].actions).toHaveLength(3) + expect(result[0].actions).toHaveLength(5) expect(result[0].status).toEqual("confirmed") expect(result[0].confirmed_by).toEqual(expect.stringContaining("user_")) }) diff --git a/integration-tests/modules/__tests__/cart/store/carts.spec.ts b/integration-tests/modules/__tests__/cart/store/carts.spec.ts index 6097730fc1..93c4c5c598 100644 --- a/integration-tests/modules/__tests__/cart/store/carts.spec.ts +++ b/integration-tests/modules/__tests__/cart/store/carts.spec.ts @@ -18,7 +18,6 @@ import { MedusaError, Modules, ProductStatus, - PromotionRuleOperator, PromotionType, RuleOperator, } from "@medusajs/utils" @@ -605,1292 +604,6 @@ medusaIntegrationTestRunner({ }) }) - describe("POST /store/carts/:id", () => { - it("should update a cart with promo codes with a replace action", async () => { - await setupTaxStructure(taxModule) - - const region = await regionModule.createRegions({ - name: "US", - currency_code: "usd", - }) - - const targetRules = [ - { - attribute: "product_id", - operator: PromotionRuleOperator.IN, - values: ["prod_tshirt"], - }, - ] - - const appliedPromotion = await promotionModule.createPromotions({ - code: "PROMOTION_APPLIED", - type: PromotionType.STANDARD, - application_method: { - type: "fixed", - target_type: "items", - allocation: "each", - value: 300, - currency_code: "usd", - apply_to_quantity: 1, - max_quantity: 1, - target_rules: targetRules, - }, - }) - - const createdPromotion = await promotionModule.createPromotions({ - code: "PROMOTION_TEST", - type: PromotionType.STANDARD, - application_method: { - type: "fixed", - target_type: "items", - allocation: "across", - currency_code: "usd", - value: 1000, - apply_to_quantity: 1, - target_rules: targetRules, - }, - }) - - const cart = await cartModule.createCarts({ - currency_code: "usd", - email: "tony@stark.com", - region_id: region.id, - shipping_address: { - address_1: "test address 1", - address_2: "test address 2", - city: "NY", - country_code: "US", - province: "NY", - postal_code: "94016", - }, - items: [ - { - id: "item-1", - unit_price: 2000, - quantity: 1, - title: "Test item", - product_id: "prod_tshirt", - } as any, - ], - }) - - const [adjustment] = await cartModule.addLineItemAdjustments([ - { - code: appliedPromotion.code!, - amount: 300, - item_id: "item-1", - promotion_id: appliedPromotion.id, - }, - ]) - - await remoteLink.create([ - { - [Modules.CART]: { cart_id: cart.id }, - [Modules.PROMOTION]: { promotion_id: appliedPromotion.id }, - }, - ]) - - // Should remove earlier adjustments from other promocodes - let updated = await api.post( - `/store/carts/${cart.id}`, - { promo_codes: [createdPromotion.code] }, - storeHeaders - ) - - expect(updated.status).toEqual(200) - expect(updated.data.cart).toEqual( - expect.objectContaining({ - id: cart.id, - items: [ - expect.objectContaining({ - id: "item-1", - tax_lines: [ - expect.objectContaining({ - description: "NY Default Rate", - code: "NYDEFAULT", - rate: 6, - provider_id: "system", - }), - ], - adjustments: [ - expect.objectContaining({ - id: expect.not.stringContaining(adjustment.id), - code: createdPromotion.code, - }), - ], - }), - ], - }) - ) - // Should remove all adjustments from other promo codes - updated = await api.post( - `/store/carts/${cart.id}`, - { promo_codes: [] }, - storeHeaders - ) - - expect(updated.status).toEqual(200) - expect(updated.data.cart).toEqual( - expect.objectContaining({ - id: cart.id, - items: [ - expect.objectContaining({ - id: "item-1", - adjustments: [], - tax_lines: [ - expect.objectContaining({ - description: "NY Default Rate", - code: "NYDEFAULT", - rate: 6, - provider_id: "system", - }), - ], - }), - ], - }) - ) - }) - - it("should not generate tax lines if region is not present or automatic taxes is false", async () => { - await setupTaxStructure(taxModule) - - const cart = await cartModule.createCarts({ - currency_code: "usd", - email: "tony@stark.com", - shipping_address: { - address_1: "test address 1", - address_2: "test address 2", - city: "NY", - country_code: "US", - province: "NY", - postal_code: "94016", - }, - items: [ - { - id: "item-1", - unit_price: 2000, - quantity: 1, - title: "Test item", - product_id: "prod_tshirt", - } as any, - ], - }) - - let updated = await api.post( - `/store/carts/${cart.id}`, - { email: "another@tax.com" }, - storeHeaders - ) - - expect(updated.status).toEqual(200) - expect(updated.data.cart).toEqual( - expect.objectContaining({ - id: cart.id, - items: [ - expect.objectContaining({ - id: "item-1", - tax_lines: [], - adjustments: [], - }), - ], - }) - ) - - const region = await regionModule.createRegions({ - name: "US", - currency_code: "usd", - automatic_taxes: false, - }) - - updated = await api.post( - `/store/carts/${cart.id}`, - { email: "another@tax.com", region_id: region.id }, - storeHeaders - ) - - expect(updated.status).toEqual(200) - expect(updated.data.cart).toEqual( - expect.objectContaining({ - id: cart.id, - items: [ - expect.objectContaining({ - id: "item-1", - adjustments: [], - tax_lines: [], - }), - ], - }) - ) - }) - - it("should update a cart's region, sales channel, customer data and tax lines", async () => { - await setupTaxStructure(taxModule) - - const region = await regionModule.createRegions({ - name: "us", - currency_code: "usd", - countries: ["us"], - }) - - const salesChannel = await scModule.createSalesChannels({ - name: "Webshop", - }) - - const cart = await cartModule.createCarts({ - currency_code: "eur", - email: "tony@stark.com", - shipping_address: { - address_1: "test address 1", - address_2: "test address 2", - city: "ny", - country_code: "us", - province: "ny", - postal_code: "94016", - }, - items: [ - { - id: "item-1", - unit_price: 2000, - quantity: 1, - title: "Test item", - product_id: "prod_tshirt", - } as any, - ], - }) - - const shippingProfile = - await fulfillmentModule.createShippingProfiles({ - name: "Test", - type: "default", - }) - - const fulfillmentSet = await fulfillmentModule.createFulfillmentSets({ - name: "Test", - type: "test-type", - service_zones: [ - { - name: "Test", - geo_zones: [{ type: "country", country_code: "us" }], - }, - ], - }) - - const shippingOption = await fulfillmentModule.createShippingOptions({ - name: "Test shipping option", - service_zone_id: fulfillmentSet.service_zones[0].id, - shipping_profile_id: shippingProfile.id, - provider_id: "manual_test-provider", - price_type: "flat", - type: { - label: "Test type", - description: "Test description", - code: "test-code", - }, - rules: [ - { - operator: RuleOperator.EQ, - attribute: "customer.email", - value: "tony@stark.com", - }, - ], - }) - - const shippingOption2 = await fulfillmentModule.createShippingOptions( - { - name: "Test shipping option", - service_zone_id: fulfillmentSet.service_zones[0].id, - shipping_profile_id: shippingProfile.id, - provider_id: "manual_test-provider", - price_type: "flat", - type: { - label: "Test type", - description: "Test description", - code: "test-code", - }, - rules: [ - { - operator: RuleOperator.EQ, - attribute: "customer.email", - value: "tony@stark.com", - }, - ], - } - ) - - // Manually inserting shipping methods here since the cart does not - // currently support it. Move to API when ready. - await cartModule.addShippingMethods(cart.id, [ - { - amount: 500, - name: "express", - shipping_option_id: shippingOption.id, - }, - { - amount: 500, - name: "standard", - shipping_option_id: shippingOption2.id, - }, - ]) - - let updated = await api.post( - `/store/carts/${cart.id}`, - { - region_id: region.id, - email: "tony@stark.com", - sales_channel_id: salesChannel.id, - }, - storeHeaders - ) - - expect(updated.status).toEqual(200) - expect(updated.data.cart).toEqual( - expect.objectContaining({ - id: cart.id, - currency_code: "usd", - region: expect.objectContaining({ - id: region.id, - currency_code: "usd", - }), - email: "tony@stark.com", - customer: expect.objectContaining({ - email: "tony@stark.com", - }), - sales_channel_id: salesChannel.id, - shipping_address: expect.objectContaining({ - // We clear the shipping address on region update and only set the country code if the region has one country - city: null, - country_code: "us", - province: null, - }), - shipping_methods: expect.arrayContaining([ - expect.objectContaining({ - shipping_option_id: shippingOption2.id, - amount: 500, - tax_lines: [ - // Since we clear the shipping address on region update, the tax lines are computed based on the new country code - expect.objectContaining({ - description: "US Default Rate", - code: "US_DEF", - rate: 2, - provider_id: "system", - }), - ], - adjustments: [], - }), - expect.objectContaining({ - shipping_option_id: shippingOption.id, - amount: 500, - tax_lines: [ - // Since we clear the shipping address on region update, the tax lines are computed based on the new country code - expect.objectContaining({ - description: "US Default Rate", - code: "US_DEF", - rate: 2, - provider_id: "system", - }), - ], - adjustments: [], - }), - ]), - items: [ - expect.objectContaining({ - id: "item-1", - tax_lines: [ - expect.objectContaining({ - description: "US Default Rate", - code: "US_DEF", - rate: 2, - provider_id: "system", - }), - ], - adjustments: [], - }), - ], - }) - ) - - // updated = await api.post( - // `/store/carts/${cart.id}`, - // { - // email: null, - // sales_channel_id: null, - // }, - // storeHeaders - // ) - - // expect(updated.status).toEqual(200) - // expect(updated.data.cart).toEqual( - // expect.objectContaining({ - // id: cart.id, - // currency_code: "usd", - // email: null, - // customer_id: null, - // sales_channel_id: null, - // items: [ - // expect.objectContaining({ - // id: "item-1", - // tax_lines: [ - // expect.objectContaining({ - // description: "NY Default Rate", - // code: "NYDEFAULT", - // rate: 6, - // provider_id: "system", - // }), - // ], - // adjustments: [], - // }), - // ], - // }) - // ) - }) - - it("should update tax lines on cart items when region changes", async () => { - await setupTaxStructure(taxModule) - - const region = await regionModule.createRegions({ - name: "us", - currency_code: "usd", - countries: ["us"], - }) - - const otherRegion = await regionModule.createRegions({ - name: "dk", - currency_code: "dkk", - countries: ["dk"], - }) - - const salesChannel = await scModule.createSalesChannels({ - name: "Webshop", - }) - - const [productWithDefaultTax] = await productModule.createProducts([ - { - title: "Test product default tax", - variants: [ - { title: "Test variant default tax", manage_inventory: false }, - ], - }, - ]) - - const [priceSetDefaultTax] = await pricingModule.createPriceSets([ - { - prices: [{ amount: 2000, currency_code: "usd" }], - }, - ]) - - await remoteLink.create([ - { - [Modules.PRODUCT]: { - variant_id: productWithDefaultTax.variants[0].id, - }, - [Modules.PRICING]: { price_set_id: priceSetDefaultTax.id }, - }, - ]) - - await api.post( - "/admin/price-preferences", - { - attribute: "currency_code", - value: "usd", - is_tax_inclusive: true, - }, - adminHeaders - ) - - let response = await api.post( - `/store/carts`, - { - sales_channel_id: salesChannel.id, - shipping_address: { - country_code: "us", - }, - region_id: region.id, - items: [ - { - variant_id: productWithDefaultTax.variants[0].id, - quantity: 1, - }, - ], - }, - storeHeaders - ) - - expect(response.data.cart).toEqual( - expect.objectContaining({ - id: response.data.cart.id, - currency_code: "usd", - region_id: region.id, - items: expect.arrayContaining([ - expect.objectContaining({ - unit_price: 2000, - quantity: 1, - title: "Test variant default tax", - tax_lines: [ - // Uses the california default rate - expect.objectContaining({ - description: "US Default Rate", - code: "US_DEF", - rate: 2, - provider_id: "system", - }), - ], - }), - ]), - }) - ) - - response = await api.post( - `/store/carts/${response.data.cart.id}`, - { - region_id: otherRegion.id, - shipping_address: { - country_code: "dk", - }, - }, - storeHeaders - ) - - expect(response.data.cart).toEqual( - expect.objectContaining({ - id: response.data.cart.id, - currency_code: "dkk", - region_id: otherRegion.id, - items: expect.arrayContaining([ - expect.objectContaining({ - unit_price: 2000, - quantity: 1, - title: "Test variant default tax", - tax_lines: [ - // Uses the danish default rate - expect.objectContaining({ - description: "Denmark Default Rate", - code: "DK_DEF", - rate: 25, - provider_id: "system", - }), - ], - }), - ]), - }) - ) - }) - - it("should update tax inclusivity on cart items when region changes", async () => { - await setupTaxStructure(taxModule) - - const [region, otherRegion] = await regionModule.createRegions([ - { - name: "us", - currency_code: "usd", - countries: ["us"], - }, - { - name: "dk", - currency_code: "dkk", - countries: ["dk"], - }, - ]) - - const salesChannel = await scModule.createSalesChannels({ - name: "Webshop", - }) - - const [productWithDefaultTax] = await productModule.createProducts([ - { - title: "Test product default tax", - variants: [ - { title: "Test variant default tax", manage_inventory: false }, - ], - }, - ]) - - const [priceSetDefaultTax] = await pricingModule.createPriceSets([ - { - prices: [{ amount: 2000, currency_code: "usd" }], - }, - ]) - - await remoteLink.create([ - { - [Modules.PRODUCT]: { - variant_id: productWithDefaultTax.variants[0].id, - }, - [Modules.PRICING]: { price_set_id: priceSetDefaultTax.id }, - }, - ]) - - await api.post( - "/admin/price-preferences", - { - attribute: "currency_code", - value: "usd", - is_tax_inclusive: true, - }, - adminHeaders - ) - - let response = await api.post( - `/store/carts`, - { - sales_channel_id: salesChannel.id, - shipping_address: { - country_code: "us", - }, - region_id: region.id, - items: [ - { - variant_id: productWithDefaultTax.variants[0].id, - quantity: 1, - }, - ], - }, - storeHeaders - ) - - expect(response.data.cart).toEqual( - expect.objectContaining({ - id: response.data.cart.id, - currency_code: "usd", - region_id: region.id, - items: expect.arrayContaining([ - expect.objectContaining({ - unit_price: 2000, - quantity: 1, - title: "Test variant default tax", - tax_lines: [ - // Uses the california default rate - expect.objectContaining({ - description: "US Default Rate", - code: "US_DEF", - rate: 2, - provider_id: "system", - }), - ], - }), - ]), - }) - ) - - response = await api.post( - `/store/carts/${response.data.cart.id}`, - { - region_id: otherRegion.id, - shipping_address: { - country_code: "dk", - }, - }, - storeHeaders - ) - - expect(response.data.cart).toEqual( - expect.objectContaining({ - id: response.data.cart.id, - currency_code: "dkk", - region_id: otherRegion.id, - items: expect.arrayContaining([ - expect.objectContaining({ - unit_price: 2000, - quantity: 1, - title: "Test variant default tax", - tax_lines: [ - // Uses the danish default rate - expect.objectContaining({ - description: "Denmark Default Rate", - code: "DK_DEF", - rate: 25, - provider_id: "system", - }), - ], - }), - ]), - }) - ) - }) - - it("should update region + set shipping address country code to dk when region has only one country", async () => { - await setupTaxStructure(taxModule) - - const region = await regionModule.createRegions({ - name: "us", - currency_code: "usd", - countries: ["us"], - }) - - const otherRegion = await regionModule.createRegions({ - name: "dk", - currency_code: "usd", - countries: ["dk"], - }) - - const cart = await cartModule.createCarts({ - currency_code: "eur", - region_id: region.id, - email: "tony@stark.com", - shipping_address: { - country_code: "us", - }, - }) - - const updated = await api.post( - `/store/carts/${cart.id}`, - { - region_id: otherRegion.id, - }, - storeHeaders - ) - - expect(updated.status).toEqual(200) - expect(updated.data.cart).toEqual( - expect.objectContaining({ - id: cart.id, - currency_code: "usd", - region: expect.objectContaining({ - id: otherRegion.id, - currency_code: "usd", - countries: [expect.objectContaining({ iso_2: "dk" })], - }), - shipping_address: expect.objectContaining({ - country_code: "dk", - }), - }) - ) - }) - - it("should update region + set shipping address to null when region has more than one country", async () => { - await setupTaxStructure(taxModule) - - const region = await regionModule.createRegions({ - name: "us", - currency_code: "usd", - countries: ["us"], - }) - - const otherRegion = await regionModule.createRegions({ - name: "dk", - currency_code: "eur", - countries: ["dk", "no"], - }) - - const cart = await cartModule.createCarts({ - currency_code: "eur", - region_id: region.id, - email: "tony@stark.com", - shipping_address: { - country_code: "us", - }, - }) - - const updated = await api.post( - `/store/carts/${cart.id}`, - { - region_id: otherRegion.id, - }, - storeHeaders - ) - - expect(updated.status).toEqual(200) - expect(updated.data.cart).toEqual( - expect.objectContaining({ - id: cart.id, - currency_code: "eur", - region: expect.objectContaining({ - id: otherRegion.id, - currency_code: "eur", - countries: [ - expect.objectContaining({ iso_2: "dk" }), - expect.objectContaining({ iso_2: "no" }), - ], - }), - shipping_address: null, - }) - ) - }) - - it("should update region and shipping address when country code is within region", async () => { - await setupTaxStructure(taxModule) - - const region = await regionModule.createRegions({ - name: "us", - currency_code: "usd", - countries: ["us"], - }) - - const otherRegion = await regionModule.createRegions({ - name: "dk", - currency_code: "eur", - countries: ["dk", "no"], - }) - - const cart = await cartModule.createCarts({ - currency_code: "eur", - region_id: region.id, - email: "tony@stark.com", - shipping_address: { - country_code: "us", - }, - }) - - const updated = await api.post( - `/store/carts/${cart.id}`, - { - region_id: otherRegion.id, - shipping_address: { - country_code: "dk", - }, - }, - storeHeaders - ) - - expect(updated.status).toEqual(200) - expect(updated.data.cart).toEqual( - expect.objectContaining({ - id: cart.id, - currency_code: "eur", - region: expect.objectContaining({ - id: otherRegion.id, - currency_code: "eur", - countries: [ - expect.objectContaining({ iso_2: "dk" }), - expect.objectContaining({ iso_2: "no" }), - ], - }), - shipping_address: expect.objectContaining({ - country_code: "dk", - }), - }) - ) - }) - - it("should throw when updating shipping address country code when country is not within region", async () => { - await setupTaxStructure(taxModule) - - const region = await regionModule.createRegions({ - name: "Unites States", - currency_code: "usd", - countries: ["us"], - }) - - const cart = await cartModule.createCarts({ - currency_code: "eur", - email: "tony@stark.com", - region_id: region.id, - shipping_address: { - country_code: "us", - }, - }) - - let errResponse = await api - .post( - `/store/carts/${cart.id}`, - { - shipping_address: { - country_code: "dk", - }, - }, - storeHeaders - ) - .catch((e) => e) - - expect(errResponse.response.status).toEqual(400) - expect(errResponse.response.data.message).toEqual( - `Country with code dk is not within region ${region.name}` - ) - }) - - it("should throw when updating region and shipping address, but shipping address country code is not within region", async () => { - await setupTaxStructure(taxModule) - - const region = await regionModule.createRegions({ - name: "Unites States", - currency_code: "usd", - countries: ["us"], - }) - - const cart = await cartModule.createCarts({ - currency_code: "eur", - email: "tony@stark.com", - shipping_address: { - country_code: "us", - }, - }) - - let errResponse = await api - .post( - `/store/carts/${cart.id}`, - { - region_id: region.id, - shipping_address: { - country_code: "dk", - }, - }, - storeHeaders - ) - .catch((e) => e) - - expect(errResponse.response.status).toEqual(400) - expect(errResponse.response.data.message).toEqual( - `Country with code dk is not within region ${region.name}` - ) - }) - - it("should remove tax lines on cart items and shipping methods when country changes and there is no tax region for that country", async () => { - await setupTaxStructure(taxModule) - - const region = await regionModule.createRegions({ - name: "us", - currency_code: "usd", - countries: ["us"], - }) - - const otherRegion = await regionModule.createRegions({ - name: "Italy", - currency_code: "eur", - countries: ["it"], - }) - - const shippingProfile = - await fulfillmentModule.createShippingProfiles({ - name: "Test", - type: "default", - }) - - const fulfillmentSet = await fulfillmentModule.createFulfillmentSets({ - name: "Test", - type: "test-type", - service_zones: [ - { - name: "Test", - geo_zones: [ - { type: "country", country_code: "us" }, - { type: "country", country_code: "it" }, - ], - }, - ], - }) - - const shippingOption = await fulfillmentModule.createShippingOptions({ - name: "Test shipping option", - service_zone_id: fulfillmentSet.service_zones[0].id, - shipping_profile_id: shippingProfile.id, - provider_id: "manual_test-provider", - price_type: "flat", - type: { - label: "Test type", - description: "Test description", - code: "test-code", - }, - }) - - const cart = await cartModule.createCarts({ - currency_code: "eur", - email: "tony@stark.com", - items: [ - { - id: "item-1", - unit_price: 2000, - quantity: 1, - title: "Test item", - product_id: "prod_tshirt", - } as any, - ], - }) - - // Manually inserting shipping methods here since the cart does not - // currently support it. Move to API when ready. - await cartModule.addShippingMethods(cart.id, [ - { - amount: 500, - name: "express", - shipping_option_id: shippingOption.id, - }, - ]) - - let response = await api.post( - `/store/carts/${cart.id}`, - { - region_id: region.id, - shipping_address: { - country_code: "us", - }, - }, - storeHeaders - ) - - expect(response.data.cart).toEqual( - expect.objectContaining({ - id: response.data.cart.id, - currency_code: "usd", - region_id: region.id, - items: expect.arrayContaining([ - expect.objectContaining({ - unit_price: 2000, - quantity: 1, - title: "Test item", - tax_lines: [ - // Uses the california default rate - expect.objectContaining({ - description: "US Default Rate", - code: "US_DEF", - rate: 2, - provider_id: "system", - }), - ], - }), - ]), - shipping_methods: expect.arrayContaining([ - expect.objectContaining({ - shipping_option_id: shippingOption.id, - amount: 500, - tax_lines: [ - expect.objectContaining({ - description: "US Default Rate", - code: "US_DEF", - rate: 2, - provider_id: "system", - }), - ], - adjustments: [], - }), - ]), - }) - ) - - response = await api.post( - `/store/carts/${response.data.cart.id}`, - { - region_id: otherRegion.id, - shipping_address: { - country_code: "it", - }, - }, - storeHeaders - ) - - expect(response.data.cart).toEqual( - expect.objectContaining({ - id: response.data.cart.id, - currency_code: "eur", - region_id: otherRegion.id, - items: expect.arrayContaining([ - expect.objectContaining({ - unit_price: 2000, - quantity: 1, - title: "Test item", - tax_lines: [ - // Italy has no tax region, so we clear the tax lines - ], - }), - ]), - shipping_methods: expect.arrayContaining([ - expect.objectContaining({ - shipping_option_id: shippingOption.id, - amount: 500, - tax_lines: [ - // Italy has no tax region, so we clear the tax lines - ], - adjustments: [], - }), - ]), - }) - ) - }) - - it("should remove invalid shipping methods", async () => { - await setupTaxStructure(taxModule) - - const region = await regionModule.createRegions({ - name: "US", - currency_code: "usd", - }) - - const cart = await cartModule.createCarts({ - region_id: region.id, - currency_code: "eur", - email: "tony@stark.com", - shipping_address: { - address_1: "test address 1", - address_2: "test address 2", - city: "ny", - country_code: "us", - province: "ny", - postal_code: "94016", - }, - }) - - const shippingProfile = - await fulfillmentModule.createShippingProfiles({ - name: "Test", - type: "default", - }) - - const fulfillmentSet = await fulfillmentModule.createFulfillmentSets({ - name: "Test", - type: "test-type", - service_zones: [ - { - name: "Test", - geo_zones: [{ type: "country", country_code: "us" }], - }, - ], - }) - - const shippingOptionOldValid = - await fulfillmentModule.createShippingOptions({ - name: "Test shipping option", - service_zone_id: fulfillmentSet.service_zones[0].id, - shipping_profile_id: shippingProfile.id, - provider_id: "manual_test-provider", - price_type: "flat", - type: { - label: "Test type", - description: "Test description", - code: "test-code", - }, - rules: [ - { - operator: RuleOperator.EQ, - attribute: "customer.email", - value: "tony@stark.com", - }, - ], - }) - - const shippingOptionNewValid = - await fulfillmentModule.createShippingOptions({ - name: "Test shipping option new", - service_zone_id: fulfillmentSet.service_zones[0].id, - shipping_profile_id: shippingProfile.id, - provider_id: "manual_test-provider", - price_type: "flat", - type: { - label: "Test type", - description: "Test description", - code: "test-code", - }, - rules: [ - { - operator: RuleOperator.EQ, - attribute: "customer.email", - value: "jon@stark.com", - }, - ], - }) - - await cartModule.addShippingMethods(cart.id, [ - // should be removed - { - amount: 500, - name: "express", - shipping_option_id: shippingOptionOldValid.id, - }, - // should be kept - { - amount: 500, - name: "express-new", - shipping_option_id: shippingOptionNewValid.id, - }, - // should be removed - { - amount: 500, - name: "standard", - shipping_option_id: "does-not-exist", - }, - ]) - - let updated = await api.post( - `/store/carts/${cart.id}`, - { email: "jon@stark.com" }, - storeHeaders - ) - - expect(updated.status).toEqual(200) - expect(updated.data.cart).toEqual( - expect.objectContaining({ - id: cart.id, - email: "jon@stark.com", - shipping_methods: [ - expect.objectContaining({ - shipping_option_id: shippingOptionNewValid.id, - }), - ], - }) - ) - - updated = await api.post( - `/store/carts/${cart.id}`, - { - email: null, - sales_channel_id: null, - }, - storeHeaders - ) - - expect(updated.status).toEqual(200) - expect(updated.data.cart).toEqual( - expect.objectContaining({ - id: cart.id, - email: null, - shipping_methods: [], - }) - ) - }) - }) - - describe("POST /store/carts/:id", () => { - it("should create and update a cart", async () => { - const region = await regionModule.createRegions({ - name: "US", - currency_code: "usd", - }) - - const salesChannel = await scModule.createSalesChannels({ - name: "Webshop", - }) - - const created = await api.post( - `/store/carts`, - { - email: "tony@stark.com", - currency_code: "usd", - region_id: region.id, - sales_channel_id: salesChannel.id, - }, - storeHeaders - ) - - expect(created.status).toEqual(200) - expect(created.data.cart).toEqual( - expect.objectContaining({ - id: created.data.cart.id, - currency_code: "usd", - email: "tony@stark.com", - region: expect.objectContaining({ - id: region.id, - currency_code: "usd", - }), - sales_channel_id: salesChannel.id, - }) - ) - - const updated = await api.post( - `/store/carts/${created.data.cart.id}`, - { - email: "tony@stark-industries.com", - }, - storeHeaders - ) - - expect(updated.status).toEqual(200) - expect(updated.data.cart).toEqual( - expect.objectContaining({ - id: updated.data.cart.id, - currency_code: "usd", - email: "tony@stark-industries.com", - }) - ) - }) - }) - describe("GET /store/carts", () => { it("should get cart", async () => { const region = await regionModule.createRegions({ diff --git a/packages/core/core-flows/src/cart/steps/get-line-item-actions.ts b/packages/core/core-flows/src/cart/steps/get-line-item-actions.ts index a321588654..a8587d793c 100644 --- a/packages/core/core-flows/src/cart/steps/get-line-item-actions.ts +++ b/packages/core/core-flows/src/cart/steps/get-line-item-actions.ts @@ -48,8 +48,8 @@ export const getLineItemActionsStep = createStep( if (existingItem && metadataMatches) { const quantity = MathBN.sum( existingItem.quantity as number, - item.quantity || 1 - ).toNumber() + item.quantity ?? 1 + ) itemsToUpdate.push({ selector: { id: existingItem.id }, @@ -57,6 +57,9 @@ export const getLineItemActionsStep = createStep( id: existingItem.id, quantity: quantity, variant_id: item.variant_id!, + unit_price: item.unit_price ?? existingItem.unit_price, + compare_at_unit_price: + item.compare_at_unit_price ?? existingItem.compare_at_unit_price, }, }) } else { diff --git a/packages/core/core-flows/src/cart/utils/fields.ts b/packages/core/core-flows/src/cart/utils/fields.ts index 658d4cef67..b26f4984a8 100644 --- a/packages/core/core-flows/src/cart/utils/fields.ts +++ b/packages/core/core-flows/src/cart/utils/fields.ts @@ -1,10 +1,13 @@ export const cartFieldsForRefreshSteps = [ "id", + "currency_code", + "quantity", "subtotal", "item_subtotal", "shipping_subtotal", "region_id", "currency_code", + "metadata", "completed_at", "region.*", "items.*", @@ -20,6 +23,7 @@ export const cartFieldsForRefreshSteps = [ "shipping_methods.tax_lines.*", "customer.*", "customer.groups.*", + "promotions.code", ] export const completeCartFields = [ @@ -113,8 +117,7 @@ export const productVariantsFields = [ "product.collection.title", "product.handle", "product.discountable", - "calculated_price.calculated_amount", - "calculated_price.is_calculated_price_tax_inclusive", + "calculated_price.*", "inventory_items.inventory_item_id", "inventory_items.required_quantity", "inventory_items.inventory.requires_shipping", diff --git a/packages/core/core-flows/src/cart/utils/prepare-line-item-data.ts b/packages/core/core-flows/src/cart/utils/prepare-line-item-data.ts index a69aaf18c6..e054422f2d 100644 --- a/packages/core/core-flows/src/cart/utils/prepare-line-item-data.ts +++ b/packages/core/core-flows/src/cart/utils/prepare-line-item-data.ts @@ -6,16 +6,24 @@ import { InventoryItemDTO, ProductVariantDTO, } from "@medusajs/framework/types" -import { isDefined } from "@medusajs/framework/utils" +import { isDefined, MathBN, PriceListType } from "@medusajs/framework/utils" interface Input { item?: CartLineItemDTO quantity: BigNumberInput metadata?: Record unitPrice: BigNumberInput + compareAtUnitPrice?: BigNumberInput | null isTaxInclusive?: boolean variant: ProductVariantDTO & { inventory_items: { inventory: InventoryItemDTO }[] + calculated_price: { + calculated_price: { + price_list_type: string + } + original_amount: BigNumberInput + calculated_amount: BigNumberInput + } } taxLines?: CreateOrderLineItemTaxLineDTO[] adjustments?: CreateOrderAdjustmentDTO[] @@ -39,6 +47,20 @@ export function prepareLineItemData(data: Input) { throw new Error("Variant does not have a product") } + let compareAtUnitPrice = data.compareAtUnitPrice + + if ( + !isDefined(compareAtUnitPrice) && + variant.calculated_price.calculated_price.price_list_type === + PriceListType.SALE && + !MathBN.eq( + variant.calculated_price.original_amount, + variant.calculated_price.calculated_amount + ) + ) { + compareAtUnitPrice = variant.calculated_price.original_amount + } + // Note: If any of the items require shipping, we enable fulfillment // unless explicitly set to not require shipping by the item in the request const { inventory_items: inventoryItems } = variant @@ -78,7 +100,9 @@ export function prepareLineItemData(data: Input) { requires_shipping: requiresShipping, unit_price: unitPrice, + compare_at_unit_price: compareAtUnitPrice, is_tax_inclusive: !!isTaxInclusive, + metadata, } diff --git a/packages/core/core-flows/src/cart/workflows/complete-cart.ts b/packages/core/core-flows/src/cart/workflows/complete-cart.ts index 96c11e5ea7..e47a5f9973 100644 --- a/packages/core/core-flows/src/cart/workflows/complete-cart.ts +++ b/packages/core/core-flows/src/cart/workflows/complete-cart.ts @@ -1,6 +1,6 @@ import { CartWorkflowDTO, - UsageComputedActions + UsageComputedActions, } from "@medusajs/framework/types" import { Modules, @@ -85,20 +85,19 @@ export const completeCartWorkflow = createWorkflow( }) const { variants, sales_channel_id } = transform({ cart }, (data) => { - const allItems: any[] = [] - const allVariants: any[] = [] + const variantsMap: Record = {} + const allItems = data.cart?.items?.map((item) => { + variantsMap[item.variant_id] = item.variant - data.cart?.items?.forEach((item) => { - allItems.push({ - id: item.id, - variant_id: item.variant_id, - quantity: item.quantity, + return { + id: item.id, + variant_id: item.variant_id, + quantity: item.quantity, + } }) - allVariants.push(item.variant) - }) return { - variants: allVariants, + variants: Object.values(variantsMap), items: allItems, sales_channel_id: data.cart.sales_channel_id, } @@ -110,6 +109,8 @@ export const completeCartWorkflow = createWorkflow( item, variant: item.variant, unitPrice: item.raw_unit_price ?? item.unit_price, + compareAtUnitPrice: + item.raw_compare_at_unit_price ?? item.compare_at_unit_price, isTaxInclusive: item.is_tax_inclusive, quantity: item.raw_quantity ?? item.quantity, metadata: item?.metadata, diff --git a/packages/core/core-flows/src/cart/workflows/refresh-cart-items.ts b/packages/core/core-flows/src/cart/workflows/refresh-cart-items.ts new file mode 100644 index 0000000000..d3331368fd --- /dev/null +++ b/packages/core/core-flows/src/cart/workflows/refresh-cart-items.ts @@ -0,0 +1,131 @@ +import { isDefined, PromotionActions } from "@medusajs/framework/utils" +import { + createWorkflow, + transform, + WorkflowData, + WorkflowResponse, +} from "@medusajs/framework/workflows-sdk" +import { useRemoteQueryStep } from "../../common/steps/use-remote-query" +import { refreshCartShippingMethodsStep, updateLineItemsStep } from "../steps" +import { validateVariantPricesStep } from "../steps/validate-variant-prices" +import { + cartFieldsForRefreshSteps, + productVariantsFields, +} from "../utils/fields" +import { prepareLineItemData } from "../utils/prepare-line-item-data" +import { refreshPaymentCollectionForCartWorkflow } from "./refresh-payment-collection" +import { updateCartPromotionsWorkflow } from "./update-cart-promotions" +import { updateTaxLinesWorkflow } from "./update-tax-lines" + +export const refreshCartItemsWorkflowId = "refresh-cart-items" +/** + * This workflow refreshes a cart's items + */ +export const refreshCartItemsWorkflow = createWorkflow( + refreshCartItemsWorkflowId, + ( + input: WorkflowData<{ + cart_id: string + promo_codes?: string[] + }> + ) => { + const cart = useRemoteQueryStep({ + entry_point: "cart", + fields: cartFieldsForRefreshSteps, + variables: { id: input.cart_id }, + list: false, + }) + + const variantIds = transform({ cart }, (data) => { + return (data.cart.items ?? []).map((i) => i.variant_id) + }) + + const pricingContext = transform( + { cart }, + ({ cart: { currency_code, region_id, customer_id } }) => { + return { + currency_code, + region_id, + customer_id, + } + } + ) + + const variants = useRemoteQueryStep({ + entry_point: "variants", + fields: productVariantsFields, + variables: { + id: variantIds, + calculated_price: { + context: pricingContext, + }, + }, + throw_if_key_not_found: true, + }).config({ name: "fetch-variants" }) + + validateVariantPricesStep({ variants }) + + const lineItems = transform({ cart, variants }, ({ cart, variants }) => { + const items = cart.items.map((item) => { + const variant = variants.find((v) => v.id === item.variant_id)! + + const preparedItem = prepareLineItemData({ + variant: variant, + unitPrice: variant.calculated_price.calculated_amount, + isTaxInclusive: + variant.calculated_price.is_calculated_price_tax_inclusive, + quantity: item.quantity, + metadata: item.metadata, + cartId: cart.id, + }) + + return { + selector: { id: item.id }, + data: preparedItem, + } + }) + + return items + }) + + const items = updateLineItemsStep({ + id: cart.id, + items: lineItems, + }) + + const refetchedCart = useRemoteQueryStep({ + entry_point: "cart", + fields: cartFieldsForRefreshSteps, + variables: { id: cart.id }, + list: false, + }).config({ name: "refetch–cart" }) + + refreshCartShippingMethodsStep({ cart: refetchedCart }) + + updateTaxLinesWorkflow.runAsStep({ + input: { cart_id: cart.id, items }, + }) + + const cartPromoCodes = transform({ cart, input }, ({ cart, input }) => { + if (isDefined(input.promo_codes)) { + return input.promo_codes + } else { + return cart.promotions.map((p) => p.code) + } + }) + + updateCartPromotionsWorkflow.runAsStep({ + input: { + cart_id: cart.id, + promo_codes: cartPromoCodes, + action: PromotionActions.REPLACE, + }, + }) + + refreshPaymentCollectionForCartWorkflow.runAsStep({ + input: { cart_id: cart.id }, + }) + + return new WorkflowResponse(refetchedCart) + } +) diff --git a/packages/core/core-flows/src/cart/workflows/update-cart.ts b/packages/core/core-flows/src/cart/workflows/update-cart.ts index 6f30a186f2..009cac5769 100644 --- a/packages/core/core-flows/src/cart/workflows/update-cart.ts +++ b/packages/core/core-flows/src/cart/workflows/update-cart.ts @@ -2,7 +2,7 @@ import { AdditionalData, UpdateCartWorkflowInputDTO, } from "@medusajs/framework/types" -import { MedusaError, PromotionActions } from "@medusajs/framework/utils" +import { MedusaError } from "@medusajs/framework/utils" import { createHook, createWorkflow, @@ -16,13 +16,9 @@ import { useRemoteQueryStep } from "../../common" import { findOrCreateCustomerStep, findSalesChannelStep, - refreshCartShippingMethodsStep, updateCartsStep, } from "../steps" -import { cartFieldsForRefreshSteps } from "../utils/fields" -import { refreshPaymentCollectionForCartWorkflow } from "./refresh-payment-collection" -import { updateCartPromotionsWorkflow } from "./update-cart-promotions" -import { updateTaxLinesWorkflow } from "./update-tax-lines" +import { refreshCartItemsWorkflow } from "./refresh-cart-items" export const updateCartWorkflowId = "update-cart" /** @@ -133,35 +129,10 @@ export const updateCartWorkflow = createWorkflow( } ) - const carts = updateCartsStep([cartInput]) + updateCartsStep([cartInput]) - const cart = useRemoteQueryStep({ - entry_point: "cart", - fields: cartFieldsForRefreshSteps, - variables: { id: cartInput.id }, - list: false, - }).config({ name: "refetch–cart" }) - - refreshCartShippingMethodsStep({ cart }) - - updateTaxLinesWorkflow.runAsStep({ - input: { - cart_id: carts[0].id, - }, - }) - - updateCartPromotionsWorkflow.runAsStep({ - input: { - cart_id: input.id, - promo_codes: input.promo_codes, - action: PromotionActions.REPLACE, - }, - }) - - refreshPaymentCollectionForCartWorkflow.runAsStep({ - input: { - cart_id: input.id, - }, + const cart = refreshCartItemsWorkflow.runAsStep({ + input: { cart_id: cartInput.id, promo_codes: input.promo_codes }, }) const cartUpdated = createHook("cartUpdated", { diff --git a/packages/core/core-flows/src/order/utils/fields.ts b/packages/core/core-flows/src/order/utils/fields.ts index 2c74d425f5..b80ae74020 100644 --- a/packages/core/core-flows/src/order/utils/fields.ts +++ b/packages/core/core-flows/src/order/utils/fields.ts @@ -13,7 +13,7 @@ export const productVariantsFields = [ "product.type.value", "product.collection.title", "product.handle", - "calculated_price.calculated_amount", + "calculated_price.*", "inventory_items.inventory_item_id", "inventory_items.required_quantity", "inventory_items.inventory.requires_shipping", diff --git a/packages/core/core-flows/src/order/workflows/order-edit/confirm-order-edit-request.ts b/packages/core/core-flows/src/order/workflows/order-edit/confirm-order-edit-request.ts index e5b4214ed2..0a6db89022 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/confirm-order-edit-request.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/confirm-order-edit-request.ts @@ -171,6 +171,10 @@ export const confirmOrderEditRequestWorkflow = createWorkflow( const unitPrice: BigNumberInput = itemAction.raw_unit_price ?? itemAction.unit_price + const compareAtUnitPrice: BigNumberInput | undefined = + itemAction.raw_compare_at_unit_price ?? + itemAction.compare_at_unit_price + const updateAction = itemAction.actions!.find( (a) => a.action === ChangeActionType.ITEM_UPDATE ) @@ -196,6 +200,7 @@ export const confirmOrderEditRequestWorkflow = createWorkflow( variant_id: ordItem.variant_id, quantity: reservationQuantity, unit_price: unitPrice, + compare_at_unit_price: compareAtUnitPrice, }) allVariants.push(ordItem.variant) }) diff --git a/packages/core/core-flows/src/order/workflows/order-edit/order-edit-add-new-item.ts b/packages/core/core-flows/src/order/workflows/order-edit/order-edit-add-new-item.ts index 7e77dfc36e..dfc01889dd 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/order-edit-add-new-item.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/order-edit-add-new-item.ts @@ -104,6 +104,9 @@ export const orderEditAddNewItemWorkflow = createWorkflow( reference_id: lineItems[index].id, quantity: item.quantity, unit_price: item.unit_price ?? lineItems[index].unit_price, + compare_at_unit_price: + item.compare_at_unit_price ?? + lineItems[index].compare_at_unit_price, metadata: item.metadata, }, })) diff --git a/packages/core/core-flows/src/order/workflows/order-edit/order-edit-update-item-quantity.ts b/packages/core/core-flows/src/order/workflows/order-edit/order-edit-update-item-quantity.ts index 53f4bd0936..a70e674f6b 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/order-edit-update-item-quantity.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/order-edit-update-item-quantity.ts @@ -85,6 +85,7 @@ export const orderEditUpdateItemQuantityWorkflow = createWorkflow( reference_id: item.id, quantity: item.quantity, unit_price: item.unit_price, + compare_at_unit_price: item.compare_at_unit_price, }, })) } diff --git a/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-add-item.ts b/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-add-item.ts index aae49f85e4..821d207734 100644 --- a/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-add-item.ts +++ b/packages/core/core-flows/src/order/workflows/order-edit/update-order-edit-add-item.ts @@ -105,6 +105,9 @@ export const updateOrderEditAddItemWorkflow = createWorkflow( details: { quantity: data.quantity ?? originalAction.details?.quantity, unit_price: data.unit_price ?? originalAction.details?.unit_price, + compare_at_unit_price: + data.compare_at_unit_price ?? + originalAction.details?.compare_at_unit_price, }, internal_note: data.internal_note, } diff --git a/packages/core/types/src/http/cart/store/payloads.ts b/packages/core/types/src/http/cart/store/payloads.ts index f354650043..2cf1262b14 100644 --- a/packages/core/types/src/http/cart/store/payloads.ts +++ b/packages/core/types/src/http/cart/store/payloads.ts @@ -6,6 +6,7 @@ export interface StoreCreateCart { currency_code?: string items?: StoreAddCartLineItem[] sales_channel_id?: string + promo_codes?: string[] metadata?: Record } diff --git a/packages/core/types/src/workflow/order/items.ts b/packages/core/types/src/workflow/order/items.ts index 90e4259953..a069d15196 100644 --- a/packages/core/types/src/workflow/order/items.ts +++ b/packages/core/types/src/workflow/order/items.ts @@ -4,7 +4,8 @@ import { BigNumberInput } from "../../totals" interface NewItem { variant_id: string quantity: BigNumberInput - unit_price?: BigNumberInput + unit_price?: BigNumberInput | null + compare_at_unit_price?: BigNumberInput | null internal_note?: string | null metadata?: Record | null } @@ -12,7 +13,8 @@ interface NewItem { interface ExistingItem { id: string quantity: BigNumberInput - unit_price?: BigNumberInput + unit_price?: BigNumberInput | null + compare_at_unit_price?: BigNumberInput | null internal_note?: string | null } @@ -60,7 +62,8 @@ export interface UpdateOrderEditAddNewItemWorkflowInput { action_id: string data: { quantity?: BigNumberInput - unit_price?: BigNumberInput + unit_price?: BigNumberInput | null + compare_at_unit_price?: BigNumberInput | null internal_note?: string | null } } diff --git a/packages/medusa/src/api/admin/order-edits/validators.ts b/packages/medusa/src/api/admin/order-edits/validators.ts index fcf32306bb..2195271245 100644 --- a/packages/medusa/src/api/admin/order-edits/validators.ts +++ b/packages/medusa/src/api/admin/order-edits/validators.ts @@ -37,8 +37,9 @@ export const AdminPostOrderEditsAddItemsReqSchema = z.object({ z.object({ variant_id: z.string(), quantity: z.number(), - unit_price: z.number().optional(), - internal_note: z.string().optional(), + unit_price: z.number().nullish(), + compare_at_unit_price: z.number().nullish(), + internal_note: z.string().nullish(), allow_backorder: z.boolean().optional(), metadata: z.record(z.unknown()).optional(), }) @@ -51,7 +52,8 @@ export type AdminPostOrderEditsAddItemsReqSchemaType = z.infer< export const AdminPostOrderEditsItemsActionReqSchema = z.object({ quantity: z.number().optional(), - unit_price: z.number().optional(), + unit_price: z.number().nullish(), + compare_at_unit_price: z.number().nullish(), internal_note: z.string().nullish().optional(), }) @@ -61,7 +63,8 @@ export type AdminPostOrderEditsItemsActionReqSchemaType = z.infer< export const AdminPostOrderEditsUpdateItemQuantityReqSchema = z.object({ quantity: z.number(), - unit_price: z.number().optional(), + unit_price: z.number().nullish(), + compare_at_unit_price: z.number().nullish(), internal_note: z.string().nullish().optional(), }) diff --git a/packages/medusa/src/api/store/carts/query-config.ts b/packages/medusa/src/api/store/carts/query-config.ts index 06a3e460c0..56b0edfa35 100644 --- a/packages/medusa/src/api/store/carts/query-config.ts +++ b/packages/medusa/src/api/store/carts/query-config.ts @@ -57,6 +57,7 @@ export const defaultStoreCartFields = [ "items.title", "items.quantity", "items.unit_price", + "items.compare_at_unit_price", "items.is_tax_inclusive", "items.tax_lines.id", "items.tax_lines.description", diff --git a/packages/medusa/src/api/store/carts/validators.ts b/packages/medusa/src/api/store/carts/validators.ts index fd79bd35bd..ee83af9a5a 100644 --- a/packages/medusa/src/api/store/carts/validators.ts +++ b/packages/medusa/src/api/store/carts/validators.ts @@ -21,6 +21,7 @@ export const CreateCart = z currency_code: z.string().nullish(), items: z.array(ItemSchema).optional(), sales_channel_id: z.string().nullish(), + promo_codes: z.array(z.string()).optional(), metadata: z.record(z.unknown()).nullish(), }) .strict() @@ -45,7 +46,7 @@ export const StoreRemoveCartPromotions = z export type StoreUpdateCartType = z.infer export const UpdateCart = z .object({ - region_id: z.string().nullish(), + region_id: z.string().optional(), email: z.string().email().nullish(), billing_address: z.union([AddressPayload, z.string()]).optional(), shipping_address: z.union([AddressPayload, z.string()]).optional(), diff --git a/packages/modules/order/src/migrations/.snapshot-medusa-order.json b/packages/modules/order/src/migrations/.snapshot-medusa-order.json index 85d6c0cb83..295200767b 100644 --- a/packages/modules/order/src/migrations/.snapshot-medusa-order.json +++ b/packages/modules/order/src/migrations/.snapshot-medusa-order.json @@ -1,5 +1,7 @@ { - "namespaces": ["public"], + "namespaces": [ + "public" + ], "name": "public", "tables": [ { @@ -149,7 +151,9 @@ "indexes": [ { "keyName": "IDX_order_address_customer_id", - "columnNames": ["customer_id"], + "columnNames": [ + "customer_id" + ], "composite": false, "primary": false, "unique": false, @@ -157,7 +161,9 @@ }, { "keyName": "order_address_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -353,7 +359,9 @@ "indexes": [ { "keyName": "IDX_order_display_id", - "columnNames": ["display_id"], + "columnNames": [ + "display_id" + ], "composite": false, "primary": false, "unique": false, @@ -361,7 +369,9 @@ }, { "keyName": "IDX_order_region_id", - "columnNames": ["region_id"], + "columnNames": [ + "region_id" + ], "composite": false, "primary": false, "unique": false, @@ -369,7 +379,9 @@ }, { "keyName": "IDX_order_customer_id", - "columnNames": ["customer_id"], + "columnNames": [ + "customer_id" + ], "composite": false, "primary": false, "unique": false, @@ -377,7 +389,9 @@ }, { "keyName": "IDX_order_customer_id", - "columnNames": ["sales_channel_id"], + "columnNames": [ + "sales_channel_id" + ], "composite": false, "primary": false, "unique": false, @@ -385,7 +399,9 @@ }, { "keyName": "IDX_order_is_draft_order", - "columnNames": ["is_draft_order"], + "columnNames": [ + "is_draft_order" + ], "composite": false, "primary": false, "unique": false, @@ -393,7 +409,9 @@ }, { "keyName": "IDX_order_currency_code", - "columnNames": ["currency_code"], + "columnNames": [ + "currency_code" + ], "composite": false, "primary": false, "unique": false, @@ -401,7 +419,9 @@ }, { "keyName": "IDX_order_shipping_address_id", - "columnNames": ["shipping_address_id"], + "columnNames": [ + "shipping_address_id" + ], "composite": false, "primary": false, "unique": false, @@ -409,7 +429,9 @@ }, { "keyName": "IDX_order_billing_address_id", - "columnNames": ["billing_address_id"], + "columnNames": [ + "billing_address_id" + ], "composite": false, "primary": false, "unique": false, @@ -417,7 +439,9 @@ }, { "keyName": "IDX_order_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -425,7 +449,9 @@ }, { "keyName": "order_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -435,18 +461,26 @@ "foreignKeys": { "order_shipping_address_id_foreign": { "constraintName": "order_shipping_address_id_foreign", - "columnNames": ["shipping_address_id"], + "columnNames": [ + "shipping_address_id" + ], "localTableName": "public.order", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_address", "deleteRule": "set null", "updateRule": "cascade" }, "order_billing_address_id_foreign": { "constraintName": "order_billing_address_id_foreign", - "columnNames": ["billing_address_id"], + "columnNames": [ + "billing_address_id" + ], "localTableName": "public.order", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_address", "deleteRule": "set null", "updateRule": "cascade" @@ -719,7 +753,9 @@ "indexes": [ { "keyName": "IDX_order_line_item_variant_id", - "columnNames": ["variant_id"], + "columnNames": [ + "variant_id" + ], "composite": false, "primary": false, "unique": false, @@ -727,7 +763,9 @@ }, { "keyName": "IDX_order_line_item_product_id", - "columnNames": ["product_id"], + "columnNames": [ + "product_id" + ], "composite": false, "primary": false, "unique": false, @@ -735,7 +773,9 @@ }, { "keyName": "IDX_order_line_item_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -743,7 +783,9 @@ }, { "keyName": "order_line_item_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -808,6 +850,24 @@ "nullable": true, "mappedType": "json" }, + "compare_at_unit_price": { + "name": "compare_at_unit_price", + "type": "numeric", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "decimal" + }, + "raw_compare_at_unit_price": { + "name": "raw_compare_at_unit_price", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, "quantity": { "name": "quantity", "type": "numeric", @@ -999,7 +1059,9 @@ "indexes": [ { "keyName": "IDX_order_item_order_id", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "composite": false, "primary": false, "unique": false, @@ -1007,7 +1069,9 @@ }, { "keyName": "IDX_order_item_version", - "columnNames": ["version"], + "columnNames": [ + "version" + ], "composite": false, "primary": false, "unique": false, @@ -1015,7 +1079,9 @@ }, { "keyName": "IDX_order_item_item_id", - "columnNames": ["item_id"], + "columnNames": [ + "item_id" + ], "composite": false, "primary": false, "unique": false, @@ -1023,7 +1089,9 @@ }, { "keyName": "IDX_order_item_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -1031,7 +1099,9 @@ }, { "keyName": "order_item_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -1041,17 +1111,25 @@ "foreignKeys": { "order_item_order_id_foreign": { "constraintName": "order_item_order_id_foreign", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "localTableName": "public.order_item", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order", "updateRule": "cascade" }, "order_item_item_id_foreign": { "constraintName": "order_item_item_id_foreign", - "columnNames": ["item_id"], + "columnNames": [ + "item_id" + ], "localTableName": "public.order_item", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_line_item", "updateRule": "cascade" } @@ -1169,7 +1247,9 @@ "indexes": [ { "keyName": "IDX_order_line_item_adjustment_item_id", - "columnNames": ["item_id"], + "columnNames": [ + "item_id" + ], "composite": false, "primary": false, "unique": false, @@ -1177,7 +1257,9 @@ }, { "keyName": "order_line_item_adjustment_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -1187,9 +1269,13 @@ "foreignKeys": { "order_line_item_adjustment_item_id_foreign": { "constraintName": "order_line_item_adjustment_item_id_foreign", - "columnNames": ["item_id"], + "columnNames": [ + "item_id" + ], "localTableName": "public.order_line_item_adjustment", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_line_item", "deleteRule": "cascade", "updateRule": "cascade" @@ -1308,7 +1394,9 @@ "indexes": [ { "keyName": "IDX_order_line_item_tax_line_item_id", - "columnNames": ["item_id"], + "columnNames": [ + "item_id" + ], "composite": false, "primary": false, "unique": false, @@ -1316,7 +1404,9 @@ }, { "keyName": "order_line_item_tax_line_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -1326,9 +1416,13 @@ "foreignKeys": { "order_line_item_tax_line_item_id_foreign": { "constraintName": "order_line_item_tax_line_item_id_foreign", - "columnNames": ["item_id"], + "columnNames": [ + "item_id" + ], "localTableName": "public.order_line_item_tax_line", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_line_item", "deleteRule": "cascade", "updateRule": "cascade" @@ -1467,7 +1561,9 @@ "indexes": [ { "keyName": "IDX_order_shipping_method_shipping_option_id", - "columnNames": ["shipping_option_id"], + "columnNames": [ + "shipping_option_id" + ], "composite": false, "primary": false, "unique": false, @@ -1475,7 +1571,9 @@ }, { "keyName": "IDX_order_shipping_method_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -1483,7 +1581,9 @@ }, { "keyName": "order_shipping_method_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -1604,7 +1704,9 @@ "indexes": [ { "keyName": "IDX_order_shipping_method_adjustment_shipping_method_id", - "columnNames": ["shipping_method_id"], + "columnNames": [ + "shipping_method_id" + ], "composite": false, "primary": false, "unique": false, @@ -1612,7 +1714,9 @@ }, { "keyName": "order_shipping_method_adjustment_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -1622,9 +1726,13 @@ "foreignKeys": { "order_shipping_method_adjustment_shipping_method_id_foreign": { "constraintName": "order_shipping_method_adjustment_shipping_method_id_foreign", - "columnNames": ["shipping_method_id"], + "columnNames": [ + "shipping_method_id" + ], "localTableName": "public.order_shipping_method_adjustment", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_shipping_method", "deleteRule": "cascade", "updateRule": "cascade" @@ -1743,7 +1851,9 @@ "indexes": [ { "keyName": "IDX_order_shipping_method_tax_line_shipping_method_id", - "columnNames": ["shipping_method_id"], + "columnNames": [ + "shipping_method_id" + ], "composite": false, "primary": false, "unique": false, @@ -1751,7 +1861,9 @@ }, { "keyName": "order_shipping_method_tax_line_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -1761,9 +1873,13 @@ "foreignKeys": { "order_shipping_method_tax_line_shipping_method_id_foreign": { "constraintName": "order_shipping_method_tax_line_shipping_method_id_foreign", - "columnNames": ["shipping_method_id"], + "columnNames": [ + "shipping_method_id" + ], "localTableName": "public.order_shipping_method_tax_line", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_shipping_method", "deleteRule": "cascade", "updateRule": "cascade" @@ -1847,7 +1963,9 @@ "indexes": [ { "keyName": "IDX_order_summary_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -1863,7 +1981,9 @@ }, { "keyName": "order_summary_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -1873,9 +1993,13 @@ "foreignKeys": { "order_summary_order_id_foreign": { "constraintName": "order_summary_order_id_foreign", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "localTableName": "public.order_summary", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order", "deleteRule": "cascade", "updateRule": "cascade" @@ -2076,14 +2200,18 @@ "schema": "public", "indexes": [ { - "columnNames": ["exchange_id"], + "columnNames": [ + "exchange_id" + ], "composite": false, "keyName": "return_exchange_id_unique", "primary": false, "unique": true }, { - "columnNames": ["claim_id"], + "columnNames": [ + "claim_id" + ], "composite": false, "keyName": "return_claim_id_unique", "primary": false, @@ -2091,7 +2219,9 @@ }, { "keyName": "IDX_return_order_id", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "composite": false, "primary": false, "unique": false, @@ -2099,7 +2229,9 @@ }, { "keyName": "IDX_return_exchange_id", - "columnNames": ["exchange_id"], + "columnNames": [ + "exchange_id" + ], "composite": false, "primary": false, "unique": false, @@ -2107,7 +2239,9 @@ }, { "keyName": "IDX_return_claim_id", - "columnNames": ["claim_id"], + "columnNames": [ + "claim_id" + ], "composite": false, "primary": false, "unique": false, @@ -2115,7 +2249,9 @@ }, { "keyName": "IDX_return_display_id", - "columnNames": ["display_id"], + "columnNames": [ + "display_id" + ], "composite": false, "primary": false, "unique": false, @@ -2123,7 +2259,9 @@ }, { "keyName": "IDX_return_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -2131,7 +2269,9 @@ }, { "keyName": "return_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -2141,26 +2281,38 @@ "foreignKeys": { "return_order_id_foreign": { "constraintName": "return_order_id_foreign", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "localTableName": "public.return", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order", "updateRule": "cascade" }, "return_exchange_id_foreign": { "constraintName": "return_exchange_id_foreign", - "columnNames": ["exchange_id"], + "columnNames": [ + "exchange_id" + ], "localTableName": "public.return", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_exchange", "deleteRule": "set null", "updateRule": "cascade" }, "return_claim_id_foreign": { "constraintName": "return_claim_id_foreign", - "columnNames": ["claim_id"], + "columnNames": [ + "claim_id" + ], "localTableName": "public.return", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_claim", "deleteRule": "set null", "updateRule": "cascade" @@ -2316,7 +2468,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "composite": false, "keyName": "order_exchange_return_id_unique", "primary": false, @@ -2324,7 +2478,9 @@ }, { "keyName": "IDX_order_exchange_order_id", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "composite": false, "primary": false, "unique": false, @@ -2332,7 +2488,9 @@ }, { "keyName": "IDX_order_exchange_return_id", - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "composite": false, "primary": false, "unique": false, @@ -2340,7 +2498,9 @@ }, { "keyName": "IDX_order_exchange_display_id", - "columnNames": ["display_id"], + "columnNames": [ + "display_id" + ], "composite": false, "primary": false, "unique": false, @@ -2348,7 +2508,9 @@ }, { "keyName": "IDX_order_exchange_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -2356,7 +2518,9 @@ }, { "keyName": "order_exchange_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -2366,17 +2530,25 @@ "foreignKeys": { "order_exchange_order_id_foreign": { "constraintName": "order_exchange_order_id_foreign", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "localTableName": "public.order_exchange", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order", "updateRule": "cascade" }, "order_exchange_return_id_foreign": { "constraintName": "order_exchange_return_id_foreign", - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "localTableName": "public.order_exchange", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.return", "deleteRule": "set null", "updateRule": "cascade" @@ -2486,7 +2658,9 @@ "indexes": [ { "keyName": "IDX_order_exchange_item_exchange_id", - "columnNames": ["exchange_id"], + "columnNames": [ + "exchange_id" + ], "composite": false, "primary": false, "unique": false, @@ -2494,7 +2668,9 @@ }, { "keyName": "IDX_order_exchange_item_item_id", - "columnNames": ["item_id"], + "columnNames": [ + "item_id" + ], "composite": false, "primary": false, "unique": false, @@ -2502,7 +2678,9 @@ }, { "keyName": "IDX_order_claim_item_image_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -2510,7 +2688,9 @@ }, { "keyName": "order_exchange_item_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -2520,18 +2700,26 @@ "foreignKeys": { "order_exchange_item_exchange_id_foreign": { "constraintName": "order_exchange_item_exchange_id_foreign", - "columnNames": ["exchange_id"], + "columnNames": [ + "exchange_id" + ], "localTableName": "public.order_exchange_item", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_exchange", "deleteRule": "cascade", "updateRule": "cascade" }, "order_exchange_item_item_id_foreign": { "constraintName": "order_exchange_item_item_id_foreign", - "columnNames": ["item_id"], + "columnNames": [ + "item_id" + ], "localTableName": "public.order_exchange_item", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_line_item", "updateRule": "cascade" } @@ -2591,7 +2779,10 @@ "autoincrement": false, "primary": false, "nullable": false, - "enumItems": ["refund", "replace"], + "enumItems": [ + "refund", + "replace" + ], "mappedType": "enum" }, "no_notification": { @@ -2686,7 +2877,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "composite": false, "keyName": "order_claim_return_id_unique", "primary": false, @@ -2694,7 +2887,9 @@ }, { "keyName": "IDX_order_claim_order_id", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "composite": false, "primary": false, "unique": false, @@ -2702,7 +2897,9 @@ }, { "keyName": "IDX_order_claim_return_id", - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "composite": false, "primary": false, "unique": false, @@ -2710,7 +2907,9 @@ }, { "keyName": "IDX_order_claim_display_id", - "columnNames": ["display_id"], + "columnNames": [ + "display_id" + ], "composite": false, "primary": false, "unique": false, @@ -2718,7 +2917,9 @@ }, { "keyName": "IDX_order_claim_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -2726,7 +2927,9 @@ }, { "keyName": "order_claim_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -2736,17 +2939,25 @@ "foreignKeys": { "order_claim_order_id_foreign": { "constraintName": "order_claim_order_id_foreign", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "localTableName": "public.order_claim", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order", "updateRule": "cascade" }, "order_claim_return_id_foreign": { "constraintName": "order_claim_return_id_foreign", - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "localTableName": "public.order_claim", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.return", "deleteRule": "set null", "updateRule": "cascade" @@ -2893,7 +3104,9 @@ "indexes": [ { "keyName": "IDX_order_transaction_order_id", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "composite": false, "primary": false, "unique": false, @@ -2901,7 +3114,9 @@ }, { "keyName": "IDX_order_transaction_return_id", - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "composite": false, "primary": false, "unique": false, @@ -2909,7 +3124,9 @@ }, { "keyName": "IDX_order_transaction_exchange_id", - "columnNames": ["exchange_id"], + "columnNames": [ + "exchange_id" + ], "composite": false, "primary": false, "unique": false, @@ -2917,7 +3134,9 @@ }, { "keyName": "IDX_order_transaction_claim_id", - "columnNames": ["claim_id"], + "columnNames": [ + "claim_id" + ], "composite": false, "primary": false, "unique": false, @@ -2925,7 +3144,9 @@ }, { "keyName": "IDX_order_transaction_currency_code", - "columnNames": ["currency_code"], + "columnNames": [ + "currency_code" + ], "composite": false, "primary": false, "unique": false, @@ -2933,7 +3154,9 @@ }, { "keyName": "IDX_order_transaction_reference_id", - "columnNames": ["reference_id"], + "columnNames": [ + "reference_id" + ], "composite": false, "primary": false, "unique": false, @@ -2941,7 +3164,9 @@ }, { "keyName": "IDX_order_transaction_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -2957,7 +3182,9 @@ }, { "keyName": "order_transaction_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -2967,36 +3194,52 @@ "foreignKeys": { "order_transaction_order_id_foreign": { "constraintName": "order_transaction_order_id_foreign", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "localTableName": "public.order_transaction", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order", "deleteRule": "cascade", "updateRule": "cascade" }, "order_transaction_return_id_foreign": { "constraintName": "order_transaction_return_id_foreign", - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "localTableName": "public.order_transaction", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.return", "deleteRule": "set null", "updateRule": "cascade" }, "order_transaction_exchange_id_foreign": { "constraintName": "order_transaction_exchange_id_foreign", - "columnNames": ["exchange_id"], + "columnNames": [ + "exchange_id" + ], "localTableName": "public.order_transaction", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_exchange", "deleteRule": "set null", "updateRule": "cascade" }, "order_transaction_claim_id_foreign": { "constraintName": "order_transaction_claim_id_foreign", - "columnNames": ["claim_id"], + "columnNames": [ + "claim_id" + ], "localTableName": "public.order_transaction", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_claim", "deleteRule": "set null", "updateRule": "cascade" @@ -3106,7 +3349,9 @@ "indexes": [ { "keyName": "IDX_order_shipping_order_id", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "composite": false, "primary": false, "unique": false, @@ -3114,7 +3359,9 @@ }, { "keyName": "IDX_order_shipping_return_id", - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "composite": false, "primary": false, "unique": false, @@ -3122,7 +3369,9 @@ }, { "keyName": "IDX_order_shipping_exchange_id", - "columnNames": ["exchange_id"], + "columnNames": [ + "exchange_id" + ], "composite": false, "primary": false, "unique": false, @@ -3130,7 +3379,9 @@ }, { "keyName": "IDX_order_shipping_claim_id", - "columnNames": ["claim_id"], + "columnNames": [ + "claim_id" + ], "composite": false, "primary": false, "unique": false, @@ -3138,7 +3389,9 @@ }, { "keyName": "IDX_order_shipping_version", - "columnNames": ["version"], + "columnNames": [ + "version" + ], "composite": false, "primary": false, "unique": false, @@ -3146,7 +3399,9 @@ }, { "keyName": "IDX_order_shipping_shipping_method_id", - "columnNames": ["shipping_method_id"], + "columnNames": [ + "shipping_method_id" + ], "composite": false, "primary": false, "unique": false, @@ -3154,7 +3409,9 @@ }, { "keyName": "IDX_order_shipping_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -3162,7 +3419,9 @@ }, { "keyName": "order_shipping_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -3172,44 +3431,64 @@ "foreignKeys": { "order_shipping_order_id_foreign": { "constraintName": "order_shipping_order_id_foreign", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "localTableName": "public.order_shipping", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order", "updateRule": "cascade" }, "order_shipping_return_id_foreign": { "constraintName": "order_shipping_return_id_foreign", - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "localTableName": "public.order_shipping", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.return", "deleteRule": "set null", "updateRule": "cascade" }, "order_shipping_exchange_id_foreign": { "constraintName": "order_shipping_exchange_id_foreign", - "columnNames": ["exchange_id"], + "columnNames": [ + "exchange_id" + ], "localTableName": "public.order_shipping", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_exchange", "deleteRule": "set null", "updateRule": "cascade" }, "order_shipping_claim_id_foreign": { "constraintName": "order_shipping_claim_id_foreign", - "columnNames": ["claim_id"], + "columnNames": [ + "claim_id" + ], "localTableName": "public.order_shipping", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_claim", "deleteRule": "set null", "updateRule": "cascade" }, "order_shipping_shipping_method_id_foreign": { "constraintName": "order_shipping_shipping_method_id_foreign", - "columnNames": ["shipping_method_id"], + "columnNames": [ + "shipping_method_id" + ], "localTableName": "public.order_shipping", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_shipping_method", "updateRule": "cascade" } @@ -3343,7 +3622,9 @@ "indexes": [ { "keyName": "IDX_order_claim_item_claim_id", - "columnNames": ["claim_id"], + "columnNames": [ + "claim_id" + ], "composite": false, "primary": false, "unique": false, @@ -3351,7 +3632,9 @@ }, { "keyName": "IDX_order_claim_item_item_id", - "columnNames": ["item_id"], + "columnNames": [ + "item_id" + ], "composite": false, "primary": false, "unique": false, @@ -3359,7 +3642,9 @@ }, { "keyName": "IDX_order_claim_item_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -3367,7 +3652,9 @@ }, { "keyName": "order_claim_item_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -3377,18 +3664,26 @@ "foreignKeys": { "order_claim_item_claim_id_foreign": { "constraintName": "order_claim_item_claim_id_foreign", - "columnNames": ["claim_id"], + "columnNames": [ + "claim_id" + ], "localTableName": "public.order_claim_item", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_claim", "deleteRule": "cascade", "updateRule": "cascade" }, "order_claim_item_item_id_foreign": { "constraintName": "order_claim_item_item_id_foreign", - "columnNames": ["item_id"], + "columnNames": [ + "item_id" + ], "localTableName": "public.order_claim_item", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_line_item", "updateRule": "cascade" } @@ -3470,7 +3765,9 @@ "indexes": [ { "keyName": "IDX_order_claim_item_image_claim_item_id", - "columnNames": ["claim_item_id"], + "columnNames": [ + "claim_item_id" + ], "composite": false, "primary": false, "unique": false, @@ -3478,7 +3775,9 @@ }, { "keyName": "IDX_order_claim_item_image_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -3486,7 +3785,9 @@ }, { "keyName": "order_claim_item_image_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -3496,9 +3797,13 @@ "foreignKeys": { "order_claim_item_image_claim_item_id_foreign": { "constraintName": "order_claim_item_image_claim_item_id_foreign", - "columnNames": ["claim_item_id"], + "columnNames": [ + "claim_item_id" + ], "localTableName": "public.order_claim_item_image", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_claim_item", "updateRule": "cascade" } @@ -3745,7 +4050,9 @@ "indexes": [ { "keyName": "IDX_order_change_order_id", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "composite": false, "primary": false, "unique": false, @@ -3753,7 +4060,9 @@ }, { "keyName": "IDX_order_change_return_id", - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "composite": false, "primary": false, "unique": false, @@ -3761,7 +4070,9 @@ }, { "keyName": "IDX_order_change_claim_id", - "columnNames": ["claim_id"], + "columnNames": [ + "claim_id" + ], "composite": false, "primary": false, "unique": false, @@ -3769,7 +4080,9 @@ }, { "keyName": "IDX_order_change_exchange_id", - "columnNames": ["exchange_id"], + "columnNames": [ + "exchange_id" + ], "composite": false, "primary": false, "unique": false, @@ -3777,7 +4090,9 @@ }, { "keyName": "IDX_order_change_order_id_version", - "columnNames": ["version"], + "columnNames": [ + "version" + ], "composite": false, "primary": false, "unique": false, @@ -3785,7 +4100,9 @@ }, { "keyName": "IDX_order_change_status", - "columnNames": ["status"], + "columnNames": [ + "status" + ], "composite": false, "primary": false, "unique": false, @@ -3793,7 +4110,9 @@ }, { "keyName": "IDX_order_change_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -3809,7 +4128,9 @@ }, { "keyName": "order_change_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -3819,36 +4140,52 @@ "foreignKeys": { "order_change_order_id_foreign": { "constraintName": "order_change_order_id_foreign", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "localTableName": "public.order_change", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order", "deleteRule": "cascade", "updateRule": "cascade" }, "order_change_return_id_foreign": { "constraintName": "order_change_return_id_foreign", - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "localTableName": "public.order_change", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.return", "deleteRule": "set null", "updateRule": "cascade" }, "order_change_claim_id_foreign": { "constraintName": "order_change_claim_id_foreign", - "columnNames": ["claim_id"], + "columnNames": [ + "claim_id" + ], "localTableName": "public.order_change", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_claim", "deleteRule": "set null", "updateRule": "cascade" }, "order_change_exchange_id_foreign": { "constraintName": "order_change_exchange_id_foreign", - "columnNames": ["exchange_id"], + "columnNames": [ + "exchange_id" + ], "localTableName": "public.order_change", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_exchange", "deleteRule": "set null", "updateRule": "cascade" @@ -4040,7 +4377,9 @@ "indexes": [ { "keyName": "IDX_order_change_action_ordering", - "columnNames": ["ordering"], + "columnNames": [ + "ordering" + ], "composite": false, "primary": false, "unique": false, @@ -4048,7 +4387,9 @@ }, { "keyName": "IDX_order_change_action_order_id", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "composite": false, "primary": false, "unique": false, @@ -4056,7 +4397,9 @@ }, { "keyName": "IDX_order_change_action_return_id", - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "composite": false, "primary": false, "unique": false, @@ -4064,7 +4407,9 @@ }, { "keyName": "IDX_order_change_action_claim_id", - "columnNames": ["claim_id"], + "columnNames": [ + "claim_id" + ], "composite": false, "primary": false, "unique": false, @@ -4072,7 +4417,9 @@ }, { "keyName": "IDX_order_change_action_exchange_id", - "columnNames": ["exchange_id"], + "columnNames": [ + "exchange_id" + ], "composite": false, "primary": false, "unique": false, @@ -4080,7 +4427,9 @@ }, { "keyName": "IDX_order_change_action_order_change_id", - "columnNames": ["order_change_id"], + "columnNames": [ + "order_change_id" + ], "composite": false, "primary": false, "unique": false, @@ -4088,7 +4437,9 @@ }, { "keyName": "IDX_order_change_action_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -4096,7 +4447,9 @@ }, { "keyName": "order_change_action_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -4106,45 +4459,65 @@ "foreignKeys": { "order_change_action_order_id_foreign": { "constraintName": "order_change_action_order_id_foreign", - "columnNames": ["order_id"], + "columnNames": [ + "order_id" + ], "localTableName": "public.order_change_action", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order", "deleteRule": "cascade", "updateRule": "cascade" }, "order_change_action_return_id_foreign": { "constraintName": "order_change_action_return_id_foreign", - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "localTableName": "public.order_change_action", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.return", "deleteRule": "set null", "updateRule": "cascade" }, "order_change_action_claim_id_foreign": { "constraintName": "order_change_action_claim_id_foreign", - "columnNames": ["claim_id"], + "columnNames": [ + "claim_id" + ], "localTableName": "public.order_change_action", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_claim", "deleteRule": "set null", "updateRule": "cascade" }, "order_change_action_exchange_id_foreign": { "constraintName": "order_change_action_exchange_id_foreign", - "columnNames": ["exchange_id"], + "columnNames": [ + "exchange_id" + ], "localTableName": "public.order_change_action", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_exchange", "deleteRule": "set null", "updateRule": "cascade" }, "order_change_action_order_change_id_foreign": { "constraintName": "order_change_action_order_change_id_foreign", - "columnNames": ["order_change_id"], + "columnNames": [ + "order_change_id" + ], "localTableName": "public.order_change_action", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_change", "deleteRule": "cascade", "updateRule": "cascade" @@ -4245,7 +4618,9 @@ "indexes": [ { "keyName": "IDX_return_reason_value", - "columnNames": ["value"], + "columnNames": [ + "value" + ], "composite": false, "primary": false, "unique": false, @@ -4253,7 +4628,9 @@ }, { "keyName": "IDX_return_reason_parent_return_reason_id", - "columnNames": ["parent_return_reason_id"], + "columnNames": [ + "parent_return_reason_id" + ], "composite": false, "primary": false, "unique": false, @@ -4261,7 +4638,9 @@ }, { "keyName": "IDX_return_reason_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -4269,7 +4648,9 @@ }, { "keyName": "return_reason_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -4279,9 +4660,13 @@ "foreignKeys": { "return_reason_parent_return_reason_id_foreign": { "constraintName": "return_reason_parent_return_reason_id_foreign", - "columnNames": ["parent_return_reason_id"], + "columnNames": [ + "parent_return_reason_id" + ], "localTableName": "public.return_reason", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.return_reason", "deleteRule": "set null", "updateRule": "cascade" @@ -4438,7 +4823,9 @@ "indexes": [ { "keyName": "IDX_return_item_reason_id", - "columnNames": ["reason_id"], + "columnNames": [ + "reason_id" + ], "composite": false, "primary": false, "unique": false, @@ -4446,7 +4833,9 @@ }, { "keyName": "IDX_return_item_return_id", - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "composite": false, "primary": false, "unique": false, @@ -4454,7 +4843,9 @@ }, { "keyName": "IDX_return_item_item_id", - "columnNames": ["item_id"], + "columnNames": [ + "item_id" + ], "composite": false, "primary": false, "unique": false, @@ -4462,7 +4853,9 @@ }, { "keyName": "IDX_return_item_deleted_at", - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "primary": false, "unique": false, @@ -4470,7 +4863,9 @@ }, { "keyName": "return_item_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -4480,27 +4875,39 @@ "foreignKeys": { "return_item_reason_id_foreign": { "constraintName": "return_item_reason_id_foreign", - "columnNames": ["reason_id"], + "columnNames": [ + "reason_id" + ], "localTableName": "public.return_item", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.return_reason", "deleteRule": "set null", "updateRule": "cascade" }, "return_item_return_id_foreign": { "constraintName": "return_item_return_id_foreign", - "columnNames": ["return_id"], + "columnNames": [ + "return_id" + ], "localTableName": "public.return_item", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.return", "deleteRule": "cascade", "updateRule": "cascade" }, "return_item_item_id_foreign": { "constraintName": "return_item_item_id_foreign", - "columnNames": ["item_id"], + "columnNames": [ + "item_id" + ], "localTableName": "public.return_item", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.order_line_item", "updateRule": "cascade" } diff --git a/packages/modules/order/src/migrations/Migration20241014142943.ts b/packages/modules/order/src/migrations/Migration20241014142943.ts new file mode 100644 index 0000000000..b2398bccdb --- /dev/null +++ b/packages/modules/order/src/migrations/Migration20241014142943.ts @@ -0,0 +1,18 @@ +import { Migration } from "@mikro-orm/migrations" + +export class Migration20241014142943 extends Migration { + async up(): Promise { + this.addSql( + 'alter table if exists "order_item" add column if not exists "compare_at_unit_price" numeric null, add column if not exists "raw_compare_at_unit_price" jsonb null;' + ) + } + + async down(): Promise { + this.addSql( + 'alter table if exists "order_item" drop column if exists "compare_at_unit_price";' + ) + this.addSql( + 'alter table if exists "order_item" drop column if exists "raw_compare_at_unit_price";' + ) + } +} diff --git a/packages/modules/order/src/models/order-item.ts b/packages/modules/order/src/models/order-item.ts index 6482a73647..4844274d1e 100644 --- a/packages/modules/order/src/models/order-item.ts +++ b/packages/modules/order/src/models/order-item.ts @@ -90,6 +90,12 @@ export default class OrderItem { @Property({ columnType: "jsonb", nullable: true }) raw_unit_price: BigNumberRawValue | null = null + @MikroOrmBigNumberProperty({ nullable: true }) + compare_at_unit_price: BigNumber | number | null = null + + @Property({ columnType: "jsonb", nullable: true }) + raw_compare_at_unit_price: BigNumberRawValue | null = null + @MikroOrmBigNumberProperty() quantity: BigNumber | number diff --git a/packages/modules/order/src/services/__tests__/util/actions/exchanges.ts b/packages/modules/order/src/services/__tests__/util/actions/exchanges.ts index fd1f8bda23..0a87c01d72 100644 --- a/packages/modules/order/src/services/__tests__/util/actions/exchanges.ts +++ b/packages/modules/order/src/services/__tests__/util/actions/exchanges.ts @@ -10,6 +10,7 @@ describe("Order Exchange - Actions", function () { id: "1", quantity: 1, unit_price: 10, + compare_at_unit_price: null, order_id: "1", detail: { @@ -28,6 +29,7 @@ describe("Order Exchange - Actions", function () { id: "2", quantity: 2, unit_price: 100, + compare_at_unit_price: null, order_id: "1", detail: { @@ -46,6 +48,7 @@ describe("Order Exchange - Actions", function () { id: "3", quantity: 3, unit_price: 20, + compare_at_unit_price: null, order_id: "1", detail: { @@ -127,6 +130,7 @@ describe("Order Exchange - Actions", function () { order_id: "1", quantity: 1, unit_price: 10, + compare_at_unit_price: null, detail: { quantity: 1, order_id: "1", @@ -144,6 +148,7 @@ describe("Order Exchange - Actions", function () { order_id: "1", quantity: 2, unit_price: 100, + compare_at_unit_price: null, detail: { quantity: 2, order_id: "1", @@ -161,6 +166,7 @@ describe("Order Exchange - Actions", function () { order_id: "1", quantity: 3, unit_price: 20, + compare_at_unit_price: null, detail: { quantity: 3, order_id: "1", diff --git a/packages/modules/order/src/services/__tests__/util/actions/returns.ts b/packages/modules/order/src/services/__tests__/util/actions/returns.ts index cd81a9d17b..3be35a4314 100644 --- a/packages/modules/order/src/services/__tests__/util/actions/returns.ts +++ b/packages/modules/order/src/services/__tests__/util/actions/returns.ts @@ -10,6 +10,7 @@ describe("Order Return - Actions", function () { id: "1", quantity: 1, unit_price: 10, + compare_at_unit_price: null, order_id: "1", detail: { @@ -28,6 +29,7 @@ describe("Order Return - Actions", function () { id: "2", quantity: 2, unit_price: 100, + compare_at_unit_price: null, order_id: "1", detail: { @@ -46,6 +48,7 @@ describe("Order Return - Actions", function () { id: "3", quantity: 3, unit_price: 20, + compare_at_unit_price: null, order_id: "1", detail: { @@ -144,6 +147,7 @@ describe("Order Return - Actions", function () { order_id: "1", quantity: 1, unit_price: 10, + compare_at_unit_price: null, detail: { order_id: "1", quantity: 1, @@ -161,6 +165,7 @@ describe("Order Return - Actions", function () { order_id: "1", quantity: 2, unit_price: 100, + compare_at_unit_price: null, detail: { order_id: "1", quantity: 2, @@ -178,6 +183,7 @@ describe("Order Return - Actions", function () { order_id: "1", quantity: 3, unit_price: 20, + compare_at_unit_price: null, detail: { quantity: 3, order_id: "1", @@ -284,6 +290,7 @@ describe("Order Return - Actions", function () { order_id: "1", quantity: 1, unit_price: 10, + compare_at_unit_price: null, detail: { quantity: 1, order_id: "1", @@ -301,6 +308,7 @@ describe("Order Return - Actions", function () { order_id: "1", quantity: 2, unit_price: 100, + compare_at_unit_price: null, detail: { quantity: 2, order_id: "1", @@ -318,6 +326,7 @@ describe("Order Return - Actions", function () { order_id: "1", quantity: 3, unit_price: 20, + compare_at_unit_price: null, detail: { quantity: 3, order_id: "1", diff --git a/packages/modules/order/src/services/order-module-service.ts b/packages/modules/order/src/services/order-module-service.ts index b5fab5b78d..409974daf8 100644 --- a/packages/modules/order/src/services/order-module-service.ts +++ b/packages/modules/order/src/services/order-module-service.ts @@ -1994,7 +1994,10 @@ export default class OrderModuleService< if (!isExistingItem) { addedItems[item.id] = { ...item, - unit_price: item.detail?.unit_price ?? item.unit_price, + quantity: item.detail?.quantity ?? item.quantity, + unit_price: item.detail?.unit_price || item.unit_price, + compare_at_unit_price: + item.detail?.compare_at_unit_price || item.compare_at_unit_price, } } } @@ -2026,13 +2029,15 @@ export default class OrderModuleService< const newItem = itemsToUpsert.find((d) => d.item_id === item.id)! const unitPrice = newItem?.unit_price ?? item.unit_price + const compareAtUnitPrice = + newItem?.compare_at_unit_price ?? item.compare_at_unit_price calculated.order.items[idx] = { ...lineItem, actions, quantity: newItem.quantity, unit_price: unitPrice, - raw_unit_price: new BigNumber(unitPrice), + compare_at_unit_price: compareAtUnitPrice, detail: { ...newItem, ...item, diff --git a/packages/modules/order/src/types/utils/index.ts b/packages/modules/order/src/types/utils/index.ts index 271e1cbe8b..127d6cbb42 100644 --- a/packages/modules/order/src/types/utils/index.ts +++ b/packages/modules/order/src/types/utils/index.ts @@ -11,6 +11,7 @@ export type VirtualOrder = { exchange_id?: string unit_price: BigNumberInput + compare_at_unit_price: BigNumberInput | null quantity: BigNumberInput detail: { @@ -22,6 +23,7 @@ export type VirtualOrder = { item_id?: string unit_price?: BigNumberInput + compare_at_unit_price?: BigNumberInput | null quantity: BigNumberInput shipped_quantity: BigNumberInput fulfilled_quantity: BigNumberInput diff --git a/packages/modules/order/src/utils/actions/item-add.ts b/packages/modules/order/src/utils/actions/item-add.ts index f6e206fe78..1ac7f6c8d8 100644 --- a/packages/modules/order/src/utils/actions/item-add.ts +++ b/packages/modules/order/src/utils/actions/item-add.ts @@ -31,6 +31,7 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_ADD, { exchange_id: action.exchange_id, unit_price: action.details.unit_price, + compare_at_unit_price: action.details.compare_at_unit_price, quantity: action.details.quantity, } as VirtualOrder["items"][0] diff --git a/packages/modules/order/src/utils/actions/item-update.ts b/packages/modules/order/src/utils/actions/item-update.ts index 5b28bb7a91..33e94d8acc 100644 --- a/packages/modules/order/src/utils/actions/item-update.ts +++ b/packages/modules/order/src/utils/actions/item-update.ts @@ -1,5 +1,4 @@ import { - BigNumber, ChangeActionType, MathBN, MedusaError, @@ -13,36 +12,28 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_UPDATE, { (item) => item.id === action.details.reference_id ) - const unitPrice = action.details.unit_price const existing = currentOrder.items[existingIndex] - existing.detail.quantity ??= 0 - - let quantityDiff = MathBN.sub( - action.details.quantity, - existing.detail.quantity + const originalQuantity = MathBN.convert( + existing.detail.quantity ?? existing.quantity + ) + const originalUnitPrice = MathBN.convert( + existing.detail.unit_price ?? existing.unit_price ) - const quant = new BigNumber(action.details.quantity) - existing.quantity = quant - existing.detail.quantity = quant + const currentQuantity = MathBN.convert(action.details.quantity) + const quantityDiff = MathBN.sub(currentQuantity, originalQuantity) - if (unitPrice) { - const currentUnitPriceBN = MathBN.convert(unitPrice) - const originalUnitPriceBn = MathBN.convert( - existing.detail.unit_price ?? existing.unit_price - ) + existing.quantity = currentQuantity + existing.detail.quantity = currentQuantity - const currentQuantityBn = MathBN.convert(action.details.quantity) - const originalQuantityBn = MathBN.convert( - existing.detail.quantity ?? existing.quantity - ) + if (action.details.unit_price) { + const currentUnitPrice = MathBN.convert(action.details.unit_price) + const originalTotal = MathBN.mult(originalUnitPrice, originalQuantity) + const currentTotal = MathBN.mult(currentUnitPrice, currentQuantity) - const originalTotal = MathBN.mult(originalUnitPriceBn, originalQuantityBn) - const currentTotal = MathBN.mult(currentUnitPriceBN, currentQuantityBn) - - existing.unit_price = currentUnitPriceBN - existing.detail.unit_price = currentUnitPriceBN + existing.unit_price = currentUnitPrice + existing.detail.unit_price = currentUnitPrice setActionReference(existing, action, options) @@ -50,6 +41,7 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_UPDATE, { } setActionReference(existing, action, options) + return MathBN.mult(existing.unit_price, quantityDiff) }, validate({ action, currentOrder }) { diff --git a/packages/modules/order/src/utils/apply-order-changes.ts b/packages/modules/order/src/utils/apply-order-changes.ts index e7705d667d..7ccdaa310c 100644 --- a/packages/modules/order/src/utils/apply-order-changes.ts +++ b/packages/modules/order/src/utils/apply-order-changes.ts @@ -58,6 +58,8 @@ export function applyChangesToOrder( version, quantity: orderItem.quantity, unit_price: item.unit_price ?? orderItem.unit_price, + compare_at_unit_price: + item.compare_at_unit_price ?? orderItem.compare_at_unit_price, fulfilled_quantity: orderItem.fulfilled_quantity ?? 0, delivered_quantity: orderItem.delivered_quantity ?? 0, shipped_quantity: orderItem.shipped_quantity ?? 0, diff --git a/packages/modules/order/src/utils/transform-order.ts b/packages/modules/order/src/utils/transform-order.ts index a74076de4f..2a65c5a764 100644 --- a/packages/modules/order/src/utils/transform-order.ts +++ b/packages/modules/order/src/utils/transform-order.ts @@ -48,6 +48,11 @@ export function formatOrder( raw_quantity: detail.raw_quantity, unit_price: detail.unit_price ?? orderItem.item.unit_price, raw_unit_price: detail.raw_unit_price ?? orderItem.item.raw_unit_price, + compare_at_unit_price: + detail.compare_at_unit_price ?? orderItem.item.compare_at_unit_price, + raw_compare_at_unit_price: + detail.raw_compare_at_unit_price ?? + orderItem.item.raw_compare_at_unit_price, detail, } }) @@ -254,6 +259,11 @@ export function mapRepositoryToOrderModel(config, isRelatedEntity = false) { delete conf.where.items.item.unit_price } + if (original.compare_at_unit_price) { + conf.where.items.compare_at_unit_price = original.compare_at_unit_price + delete conf.where.items.item.compare_at_unit_price + } + if (original.detail) { conf.where.items = { ...original.detail, diff --git a/packages/modules/pricing/src/services/pricing-module.ts b/packages/modules/pricing/src/services/pricing-module.ts index 0d02c2defa..bd29e03896 100644 --- a/packages/modules/pricing/src/services/pricing-module.ts +++ b/packages/modules/pricing/src/services/pricing-module.ts @@ -47,8 +47,8 @@ import { PriceSet, } from "@models" -import { eventBuilders, validatePriceListDates } from "@utils" import { ServiceTypes } from "@types" +import { eventBuilders, validatePriceListDates } from "@utils" import { joinerConfig } from "../joiner-config" type InjectedDependencies = {