diff --git a/.changeset/loud-cheetahs-obey.md b/.changeset/loud-cheetahs-obey.md new file mode 100644 index 0000000000..218488282d --- /dev/null +++ b/.changeset/loud-cheetahs-obey.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +fix(draft-order): create tax-inclusive with discount diff --git a/integration-tests/api/__tests__/admin/__snapshots__/colllections.js.snap b/integration-tests/api/__tests__/admin/__snapshots__/colllections.js.snap deleted file mode 100644 index f544c24ece..0000000000 --- a/integration-tests/api/__tests__/admin/__snapshots__/colllections.js.snap +++ /dev/null @@ -1,451 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`/admin/collections /admin/collections adds products to collection 1`] = ` -Object { - "collection": Object { - "created_at": Any, - "deleted_at": null, - "handle": "test-collection", - "id": "test-collection", - "metadata": null, - "products": Array [ - Object { - "collection_id": "test-collection", - "created_at": Any, - "deleted_at": null, - "description": "test-product-description", - "discountable": true, - "external_id": null, - "handle": "test-product", - "height": null, - "hs_code": null, - "id": "test-product", - "is_giftcard": false, - "length": null, - "material": null, - "metadata": null, - "mid_code": null, - "origin_country": null, - "profile_id": StringMatching /\\^sp_\\*/, - "status": "draft", - "subtitle": null, - "thumbnail": null, - "title": "Test product", - "type_id": "test-type", - "updated_at": Any, - "weight": null, - "width": null, - }, - Object { - "collection_id": "test-collection", - "created_at": Any, - "deleted_at": null, - "description": "test-product-description1", - "discountable": true, - "external_id": null, - "handle": "test-product1", - "height": null, - "hs_code": null, - "id": "test-product1", - "is_giftcard": false, - "length": null, - "material": null, - "metadata": null, - "mid_code": null, - "origin_country": null, - "profile_id": StringMatching /\\^sp_\\*/, - "status": "draft", - "subtitle": null, - "thumbnail": null, - "title": "Test product1", - "type_id": "test-type", - "updated_at": Any, - "weight": null, - "width": null, - }, - Object { - "collection_id": "test-collection", - "created_at": Any, - "deleted_at": null, - "description": "test-product-description", - "discountable": true, - "external_id": null, - "handle": "test-product_filtering_1", - "height": null, - "hs_code": null, - "id": "test-product_filtering_1", - "is_giftcard": false, - "length": null, - "material": null, - "metadata": null, - "mid_code": null, - "origin_country": null, - "profile_id": StringMatching /\\^sp_\\*/, - "status": "proposed", - "subtitle": null, - "thumbnail": null, - "title": "Test product filtering 1", - "type_id": "test-type", - "updated_at": Any, - "weight": null, - "width": null, - }, - ], - "title": "Test collection", - "updated_at": Any, - }, -} -`; - -exports[`/admin/collections /admin/collections creates a collection 1`] = ` -Object { - "collection": Object { - "created_at": Any, - "deleted_at": null, - "handle": "test-handle-creation", - "id": StringMatching /\\^pcol_\\*/, - "metadata": null, - "title": "test collection creation", - "updated_at": Any, - }, -} -`; - -exports[`/admin/collections /admin/collections filters collections by title 1`] = ` -Object { - "collections": Array [ - Object { - "created_at": Any, - "handle": "test-collection", - "id": "test-collection", - "products": Array [ - Object { - "collection_id": "test-collection", - "created_at": Any, - "deleted_at": null, - "description": "test-product-description", - "discountable": true, - "external_id": null, - "handle": "test-product", - "height": null, - "hs_code": null, - "id": "test-product", - "is_giftcard": false, - "length": null, - "material": null, - "metadata": null, - "mid_code": null, - "origin_country": null, - "profile_id": StringMatching /\\^sp_\\*/, - "status": "draft", - "subtitle": null, - "thumbnail": null, - "title": "Test product", - "type_id": "test-type", - "updated_at": Any, - "weight": null, - "width": null, - }, - Object { - "collection_id": "test-collection", - "created_at": Any, - "deleted_at": null, - "description": "test-product-description1", - "discountable": true, - "external_id": null, - "handle": "test-product1", - "height": null, - "hs_code": null, - "id": "test-product1", - "is_giftcard": false, - "length": null, - "material": null, - "metadata": null, - "mid_code": null, - "origin_country": null, - "profile_id": StringMatching /\\^sp_\\*/, - "status": "draft", - "subtitle": null, - "thumbnail": null, - "title": "Test product1", - "type_id": "test-type", - "updated_at": Any, - "weight": null, - "width": null, - }, - ], - "title": "Test collection", - "updated_at": Any, - }, - ], - "count": 1, - "limit": 10, - "offset": 0, -} -`; - -exports[`/admin/collections /admin/collections lists collections 1`] = ` -Object { - "collections": Array [ - Object { - "created_at": Any, - "handle": "test-collection2", - "id": "test-collection2", - "products": Array [ - Object { - "collection_id": "test-collection2", - "created_at": Any, - "deleted_at": null, - "description": "test-product-description", - "discountable": true, - "external_id": null, - "handle": "test-product_filtering_2", - "height": null, - "hs_code": null, - "id": "test-product_filtering_2", - "is_giftcard": false, - "length": null, - "material": null, - "metadata": null, - "mid_code": null, - "origin_country": null, - "profile_id": StringMatching /\\^sp_\\*/, - "status": "published", - "subtitle": null, - "thumbnail": null, - "title": "Test product filtering 2", - "type_id": "test-type", - "updated_at": Any, - "weight": null, - "width": null, - }, - ], - "title": "Test collection 2", - "updated_at": Any, - }, - Object { - "created_at": Any, - "handle": "test-collection1", - "id": "test-collection1", - "products": Array [ - Object { - "collection_id": "test-collection1", - "created_at": Any, - "deleted_at": null, - "description": "test-product-description", - "discountable": true, - "external_id": null, - "handle": "test-product_filtering_1", - "height": null, - "hs_code": null, - "id": "test-product_filtering_1", - "is_giftcard": false, - "length": null, - "material": null, - "metadata": null, - "mid_code": null, - "origin_country": null, - "profile_id": StringMatching /\\^sp_\\*/, - "status": "proposed", - "subtitle": null, - "thumbnail": null, - "title": "Test product filtering 1", - "type_id": "test-type", - "updated_at": Any, - "weight": null, - "width": null, - }, - Object { - "collection_id": "test-collection1", - "created_at": Any, - "deleted_at": null, - "description": "test-product-description", - "discountable": true, - "external_id": null, - "handle": "test-product_filtering_3", - "height": null, - "hs_code": null, - "id": "test-product_filtering_3", - "is_giftcard": false, - "length": null, - "material": null, - "metadata": null, - "mid_code": null, - "origin_country": null, - "profile_id": StringMatching /\\^sp_\\*/, - "status": "draft", - "subtitle": null, - "thumbnail": null, - "title": "Test product filtering 3", - "type_id": "test-type", - "updated_at": Any, - "weight": null, - "width": null, - }, - ], - "title": "Test collection 1", - "updated_at": Any, - }, - Object { - "created_at": Any, - "handle": "test-collection", - "id": "test-collection", - "products": Array [ - Object { - "collection_id": "test-collection", - "created_at": Any, - "deleted_at": null, - "description": "test-product-description", - "discountable": true, - "external_id": null, - "handle": "test-product", - "height": null, - "hs_code": null, - "id": "test-product", - "is_giftcard": false, - "length": null, - "material": null, - "metadata": null, - "mid_code": null, - "origin_country": null, - "profile_id": StringMatching /\\^sp_\\*/, - "status": "draft", - "subtitle": null, - "thumbnail": null, - "title": "Test product", - "type_id": "test-type", - "updated_at": Any, - "weight": null, - "width": null, - }, - Object { - "collection_id": "test-collection", - "created_at": Any, - "deleted_at": null, - "description": "test-product-description1", - "discountable": true, - "external_id": null, - "handle": "test-product1", - "height": null, - "hs_code": null, - "id": "test-product1", - "is_giftcard": false, - "length": null, - "material": null, - "metadata": null, - "mid_code": null, - "origin_country": null, - "profile_id": StringMatching /\\^sp_\\*/, - "status": "draft", - "subtitle": null, - "thumbnail": null, - "title": "Test product1", - "type_id": "test-type", - "updated_at": Any, - "weight": null, - "width": null, - }, - ], - "title": "Test collection", - "updated_at": Any, - }, - ], - "count": 3, - "limit": 10, - "offset": 0, -} -`; - -exports[`/admin/collections /admin/collections removes products from collection 1`] = ` -Object { - "id": "test-collection", - "object": "product-collection", - "removed_products": Array [ - "test-product", - ], -} -`; - -exports[`/admin/collections /admin/collections/:id gets collection 1`] = ` -Object { - "collection": Object { - "created_at": Any, - "deleted_at": null, - "handle": "test-collection", - "id": "test-collection", - "metadata": null, - "products": Array [ - Object { - "collection_id": "test-collection", - "created_at": Any, - "deleted_at": null, - "description": "test-product-description", - "discountable": true, - "external_id": null, - "handle": "test-product", - "height": null, - "hs_code": null, - "id": "test-product", - "is_giftcard": false, - "length": null, - "material": null, - "metadata": null, - "mid_code": null, - "origin_country": null, - "profile_id": StringMatching /\\^sp_\\*/, - "status": "draft", - "subtitle": null, - "thumbnail": null, - "title": "Test product", - "type_id": "test-type", - "updated_at": Any, - "weight": null, - "width": null, - }, - Object { - "collection_id": "test-collection", - "created_at": Any, - "deleted_at": null, - "description": "test-product-description1", - "discountable": true, - "external_id": null, - "handle": "test-product1", - "height": null, - "hs_code": null, - "id": "test-product1", - "is_giftcard": false, - "length": null, - "material": null, - "metadata": null, - "mid_code": null, - "origin_country": null, - "profile_id": StringMatching /\\^sp_\\*/, - "status": "draft", - "subtitle": null, - "thumbnail": null, - "title": "Test product1", - "type_id": "test-type", - "updated_at": Any, - "weight": null, - "width": null, - }, - ], - "title": "Test collection", - "updated_at": Any, - }, -} -`; - -exports[`/admin/collections /admin/collections/:id updates a collection 1`] = ` -Object { - "collection": Object { - "created_at": Any, - "deleted_at": null, - "handle": "test-handle-creation", - "id": StringMatching /\\^pcol_\\*/, - "metadata": null, - "title": "test collection creation", - "updated_at": Any, - }, -} -`; diff --git a/integration-tests/api/__tests__/admin/__snapshots__/draft-order.js.snap b/integration-tests/api/__tests__/admin/__snapshots__/draft-order.js.snap deleted file mode 100644 index f2baf17f05..0000000000 --- a/integration-tests/api/__tests__/admin/__snapshots__/draft-order.js.snap +++ /dev/null @@ -1,85 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`/admin/draft-orders POST /admin/draft-orders creates a draft order with a billing address that is an AddressPayload and a shipping address that is an ID 1`] = ` -Object { - "address_1": null, - "address_2": null, - "city": null, - "company": null, - "country_code": "us", - "created_at": Any, - "customer_id": null, - "deleted_at": null, - "first_name": "oli", - "id": "oli-shipping", - "last_name": "test", - "metadata": null, - "phone": null, - "postal_code": null, - "province": null, - "updated_at": Any, -} -`; - -exports[`/admin/draft-orders POST /admin/draft-orders creates a draft order with a billing address that is an AddressPayload and a shipping address that is an ID 2`] = ` -Object { - "address_1": null, - "address_2": null, - "city": null, - "company": null, - "country_code": "us", - "created_at": Any, - "customer_id": null, - "deleted_at": null, - "first_name": "kap", - "id": Any, - "last_name": "test", - "metadata": null, - "phone": null, - "postal_code": null, - "province": null, - "updated_at": Any, -} -`; - -exports[`/admin/draft-orders POST /admin/draft-orders creates a draft order with a shipping address that is an AddressPayload and a billing adress that is an ID 1`] = ` -Object { - "address_1": null, - "address_2": null, - "city": null, - "company": null, - "country_code": "us", - "created_at": Any, - "customer_id": null, - "deleted_at": null, - "first_name": "oli", - "id": "oli-shipping", - "last_name": "test", - "metadata": null, - "phone": null, - "postal_code": null, - "province": null, - "updated_at": Any, -} -`; - -exports[`/admin/draft-orders POST /admin/draft-orders creates a draft order with a shipping address that is an AddressPayload and a billing adress that is an ID 2`] = ` -Object { - "address_1": null, - "address_2": null, - "city": null, - "company": null, - "country_code": "us", - "created_at": Any, - "customer_id": null, - "deleted_at": null, - "first_name": "kap", - "id": Any, - "last_name": "test", - "metadata": null, - "phone": null, - "postal_code": null, - "province": null, - "updated_at": Any, -} -`; diff --git a/integration-tests/api/__tests__/admin/colllections.js b/integration-tests/api/__tests__/admin/colllections.js index b3284f0ca8..d00a5a91fe 100644 --- a/integration-tests/api/__tests__/admin/colllections.js +++ b/integration-tests/api/__tests__/admin/colllections.js @@ -70,15 +70,17 @@ describe("/admin/collections", () => { ) expect(response.status).toEqual(200) - expect(response.data).toMatchSnapshot({ - collection: { - id: expect.stringMatching(/^pcol_*/), - title: "test collection creation", - handle: "test-handle-creation", - created_at: expect.any(String), - updated_at: expect.any(String), - }, - }) + expect(response.data).toEqual( + expect.objectContaining({ + collection: expect.objectContaining({ + id: expect.stringMatching(/^pcol_*/), + title: "test collection creation", + handle: "test-handle-creation", + created_at: expect.any(String), + updated_at: expect.any(String), + }), + }) + ) }) it("deletes a collection", async () => { @@ -112,27 +114,29 @@ describe("/admin/collections", () => { headers: { Authorization: "Bearer test_token" }, }) - expect(response.data).toMatchSnapshot({ - collection: { - id: "test-collection", - created_at: expect.any(String), - updated_at: expect.any(String), - products: [ - { - collection_id: "test-collection", - created_at: expect.any(String), - updated_at: expect.any(String), - profile_id: expect.stringMatching(/^sp_*/), - }, - { - collection_id: "test-collection", - created_at: expect.any(String), - updated_at: expect.any(String), - profile_id: expect.stringMatching(/^sp_*/), - }, - ], - }, - }) + expect(response.data).toEqual( + expect.objectContaining({ + collection: expect.objectContaining({ + id: "test-collection", + created_at: expect.any(String), + updated_at: expect.any(String), + products: expect.arrayContaining([ + expect.objectContaining({ + collection_id: "test-collection", + created_at: expect.any(String), + updated_at: expect.any(String), + profile_id: expect.stringMatching(/^sp_*/), + }), + expect.objectContaining({ + collection_id: "test-collection", + created_at: expect.any(String), + updated_at: expect.any(String), + profile_id: expect.stringMatching(/^sp_*/), + }), + ]), + }), + }) + ) }) }) @@ -160,15 +164,17 @@ describe("/admin/collections", () => { ) expect(response.status).toEqual(200) - expect(response.data).toMatchSnapshot({ - collection: { - id: expect.stringMatching(/^pcol_*/), - title: "test collection creation", - handle: "test-handle-creation", - created_at: expect.any(String), - updated_at: expect.any(String), - }, - }) + expect(response.data).toEqual( + expect.objectContaining({ + collection: expect.objectContaining({ + id: expect.stringMatching(/^pcol_*/), + title: "test collection creation", + handle: "test-handle-creation", + created_at: expect.any(String), + updated_at: expect.any(String), + }), + }) + ) }) it("lists collections", async () => { @@ -178,68 +184,70 @@ describe("/admin/collections", () => { headers: { Authorization: "Bearer test_token" }, }) - expect(response.data).toMatchSnapshot({ - collections: [ - { - id: "test-collection2", - handle: "test-collection2", - title: "Test collection 2", - created_at: expect.any(String), - updated_at: expect.any(String), - products: [ - { - collection_id: "test-collection2", - created_at: expect.any(String), - updated_at: expect.any(String), - profile_id: expect.stringMatching(/^sp_*/), - }, - ], - }, - { - id: "test-collection1", - handle: "test-collection1", - title: "Test collection 1", - created_at: expect.any(String), - updated_at: expect.any(String), - products: [ - { - collection_id: "test-collection1", - created_at: expect.any(String), - updated_at: expect.any(String), - profile_id: expect.stringMatching(/^sp_*/), - }, - { - collection_id: "test-collection1", - created_at: expect.any(String), - updated_at: expect.any(String), - profile_id: expect.stringMatching(/^sp_*/), - }, - ], - }, - { - id: "test-collection", - handle: "test-collection", - title: "Test collection", - created_at: expect.any(String), - updated_at: expect.any(String), - products: [ - { - collection_id: "test-collection", - created_at: expect.any(String), - updated_at: expect.any(String), - profile_id: expect.stringMatching(/^sp_*/), - }, - { - collection_id: "test-collection", - created_at: expect.any(String), - updated_at: expect.any(String), - profile_id: expect.stringMatching(/^sp_*/), - }, - ], - }, - ], - count: 3, - }) + expect(response.data).toEqual( + expect.objectContaining({ + count: 3, + collections: expect.arrayContaining([ + expect.objectContaining({ + id: "test-collection2", + handle: "test-collection2", + title: "Test collection 2", + created_at: expect.any(String), + updated_at: expect.any(String), + products: expect.arrayContaining([ + expect.objectContaining({ + collection_id: "test-collection2", + created_at: expect.any(String), + updated_at: expect.any(String), + profile_id: expect.stringMatching(/^sp_*/), + }), + ]), + }), + expect.objectContaining({ + id: "test-collection1", + handle: "test-collection1", + title: "Test collection 1", + created_at: expect.any(String), + updated_at: expect.any(String), + products: expect.arrayContaining([ + expect.objectContaining({ + collection_id: "test-collection1", + created_at: expect.any(String), + updated_at: expect.any(String), + profile_id: expect.stringMatching(/^sp_*/), + }), + expect.objectContaining({ + collection_id: "test-collection1", + created_at: expect.any(String), + updated_at: expect.any(String), + profile_id: expect.stringMatching(/^sp_*/), + }), + ]), + }), + expect.objectContaining({ + id: "test-collection", + handle: "test-collection", + title: "Test collection", + created_at: expect.any(String), + updated_at: expect.any(String), + products: expect.arrayContaining([ + expect.objectContaining({ + collection_id: "test-collection", + created_at: expect.any(String), + updated_at: expect.any(String), + profile_id: expect.stringMatching(/^sp_*/), + }), + expect.objectContaining({ + collection_id: "test-collection", + created_at: expect.any(String), + updated_at: expect.any(String), + profile_id: expect.stringMatching(/^sp_*/), + }), + ]), + }), + ]), + }) + ) }) it("adds products to collection", async () => { @@ -258,36 +266,38 @@ describe("/admin/collections", () => { ) .catch((err) => console.warn(err)) - expect(response.data).toMatchSnapshot({ - collection: { - id: "test-collection", - created_at: expect.any(String), - updated_at: expect.any(String), - products: [ - { - collection_id: "test-collection", - id: "test-product", - created_at: expect.any(String), - updated_at: expect.any(String), - profile_id: expect.stringMatching(/^sp_*/), - }, - { - collection_id: "test-collection", - id: "test-product1", - created_at: expect.any(String), - updated_at: expect.any(String), - profile_id: expect.stringMatching(/^sp_*/), - }, - { - collection_id: "test-collection", - id: "test-product_filtering_1", - created_at: expect.any(String), - updated_at: expect.any(String), - profile_id: expect.stringMatching(/^sp_*/), - }, - ], - }, - }) + expect(response.data).toEqual( + expect.objectContaining({ + collection: expect.objectContaining({ + id: "test-collection", + created_at: expect.any(String), + updated_at: expect.any(String), + products: expect.arrayContaining([ + expect.objectContaining({ + collection_id: "test-collection", + id: "test-product", + created_at: expect.any(String), + updated_at: expect.any(String), + profile_id: expect.stringMatching(/^sp_*/), + }), + expect.objectContaining({ + collection_id: "test-collection", + id: "test-product_filtering_1", + created_at: expect.any(String), + updated_at: expect.any(String), + profile_id: expect.stringMatching(/^sp_*/), + }), + expect.objectContaining({ + collection_id: "test-collection", + id: "test-product1", + created_at: expect.any(String), + updated_at: expect.any(String), + profile_id: expect.stringMatching(/^sp_*/), + }), + ]), + }), + }) + ) expect(response.status).toEqual(200) }) @@ -302,11 +312,13 @@ describe("/admin/collections", () => { }) .catch((err) => console.warn(err)) - expect(response.data).toMatchSnapshot({ - id: "test-collection", - object: "product-collection", - removed_products: ["test-product"], - }) + expect(response.data).toEqual( + expect.objectContaining({ + id: "test-collection", + object: "product-collection", + removed_products: ["test-product"], + }) + ) expect(response.status).toEqual(200) }) @@ -320,34 +332,36 @@ describe("/admin/collections", () => { }) .catch((err) => console.log(err)) - expect(response.data).toMatchSnapshot({ - collections: [ - { - id: "test-collection", - handle: "test-collection", - title: "Test collection", - created_at: expect.any(String), - updated_at: expect.any(String), - products: [ - { - collection_id: "test-collection", - created_at: expect.any(String), - updated_at: expect.any(String), - profile_id: expect.stringMatching(/^sp_*/), - }, - { - collection_id: "test-collection", - created_at: expect.any(String), - updated_at: expect.any(String), - profile_id: expect.stringMatching(/^sp_*/), - }, - ], - }, - ], - count: 1, - limit: 10, - offset: 0, - }) + expect(response.data).toEqual( + expect.objectContaining({ + count: 1, + limit: 10, + offset: 0, + collections: expect.arrayContaining([ + expect.objectContaining({ + id: "test-collection", + handle: "test-collection", + title: "Test collection", + created_at: expect.any(String), + updated_at: expect.any(String), + products: expect.arrayContaining([ + expect.objectContaining({ + collection_id: "test-collection", + created_at: expect.any(String), + updated_at: expect.any(String), + profile_id: expect.stringMatching(/^sp_*/), + }), + expect.objectContaining({ + collection_id: "test-collection", + created_at: expect.any(String), + updated_at: expect.any(String), + profile_id: expect.stringMatching(/^sp_*/), + }), + ]), + }), + ]), + }) + ) }) it("returns a list of collections filtered by discount condition id", async () => { diff --git a/integration-tests/api/__tests__/admin/draft-order.js b/integration-tests/api/__tests__/admin/draft-order/draft-order.js similarity index 69% rename from integration-tests/api/__tests__/admin/draft-order.js rename to integration-tests/api/__tests__/admin/draft-order/draft-order.js index 4ecb7ac721..23bafb0d9e 100644 --- a/integration-tests/api/__tests__/admin/draft-order.js +++ b/integration-tests/api/__tests__/admin/draft-order/draft-order.js @@ -1,22 +1,32 @@ const path = require("path") -const setupServer = require("../../../helpers/setup-server") -const { useApi } = require("../../../helpers/use-api") -const { initDb, useDb } = require("../../../helpers/use-db") +const setupServer = require("../../../../helpers/setup-server") +const { useApi } = require("../../../../helpers/use-api") +const { initDb, useDb } = require("../../../../helpers/use-db") -const draftOrderSeeder = require("../../helpers/draft-order-seeder") -const adminSeeder = require("../../helpers/admin-seeder") +const draftOrderSeeder = require("../../../helpers/draft-order-seeder") +const adminSeeder = require("../../../helpers/admin-seeder") +const { + simpleRegionFactory, + simpleDiscountFactory, +} = require("../../../factories") jest.setTimeout(30000) +const adminReqConfig = { + headers: { + Authorization: "Bearer test_token", + }, +} + describe("/admin/draft-orders", () => { let medusaProcess let dbConnection beforeAll(async () => { - const cwd = path.resolve(path.join(__dirname, "..", "..")) + const cwd = path.resolve(path.join(__dirname, "..", "..", "..")) dbConnection = await initDb({ cwd }) - medusaProcess = await setupServer({ cwd }) + medusaProcess = await setupServer({ cwd, verbose: true }) }) afterAll(async () => { @@ -40,9 +50,15 @@ describe("/admin/draft-orders", () => { it("creates a draft order cart", async () => { const api = useApi() + await simpleDiscountFactory(dbConnection, { + code: "testytest", + regions: ["test-region"], + }) + const payload = { email: "oli@test.dk", shipping_address: "oli-shipping", + discounts: [{ code: "testytest" }], items: [ { variant_id: "test-variant", @@ -59,15 +75,11 @@ describe("/admin/draft-orders", () => { ], } - const response = await api - .post("/admin/draft-orders", payload, { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + const response = await api.post( + "/admin/draft-orders", + payload, + adminReqConfig + ) expect(response.status).toEqual(200) }) @@ -94,22 +106,18 @@ describe("/admin/draft-orders", () => { ], } - const response = await api.post("/admin/draft-orders", payload, { - headers: { - Authorization: "Bearer test_token", - }, - }) + const response = await api.post( + "/admin/draft-orders", + payload, + adminReqConfig + ) expect(response.status).toEqual(200) const draftOrderId = response.data.draft_order.id const draftOrderResponse = await api.get( `/admin/draft-orders/${draftOrderId}`, - { - headers: { - Authorization: "Bearer test_token", - }, - } + adminReqConfig ) expect(draftOrderResponse.status).toEqual(200) expect(draftOrderResponse.data.draft_order.cart.shipping_total).toEqual( @@ -147,15 +155,7 @@ describe("/admin/draft-orders", () => { const { status, data: { draft_order }, - } = await api - .post("/admin/draft-orders", payload, { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + } = await api.post("/admin/draft-orders", payload, adminReqConfig) expect(status).toEqual(200) expect(draft_order.cart.billing_address_id).not.toBeNull() @@ -163,29 +163,25 @@ describe("/admin/draft-orders", () => { const afterCreate = await api.get( `/admin/draft-orders/${draft_order.id}`, - { - headers: { - Authorization: "Bearer test_token", - }, - } + adminReqConfig ) - expect( - afterCreate.data.draft_order.cart.shipping_address - ).toMatchSnapshot({ - id: "oli-shipping", - created_at: expect.any(String), - updated_at: expect.any(String), - }) - expect(afterCreate.data.draft_order.cart.billing_address).toMatchSnapshot( - { + expect(afterCreate.data.draft_order.cart.shipping_address).toEqual( + expect.objectContaining({ + id: "oli-shipping", + created_at: expect.any(String), + updated_at: expect.any(String), + }) + ) + expect(afterCreate.data.draft_order.cart.billing_address).toEqual( + expect.objectContaining({ id: expect.any(String), created_at: expect.any(String), updated_at: expect.any(String), first_name: "kap", last_name: "test", country_code: "us", - } + }) ) }) @@ -219,15 +215,7 @@ describe("/admin/draft-orders", () => { const { status, data: { draft_order }, - } = await api - .post("/admin/draft-orders", payload, { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + } = await api.post("/admin/draft-orders", payload, adminReqConfig) expect(status).toEqual(200) expect(draft_order.cart.billing_address_id).not.toBeNull() @@ -235,30 +223,26 @@ describe("/admin/draft-orders", () => { const afterCreate = await api.get( `/admin/draft-orders/${draft_order.id}`, - { - headers: { - Authorization: "Bearer test_token", - }, - } + adminReqConfig ) - expect(afterCreate.data.draft_order.cart.billing_address).toMatchSnapshot( - { + expect(afterCreate.data.draft_order.cart.billing_address).toEqual( + expect.objectContaining({ id: "oli-shipping", created_at: expect.any(String), updated_at: expect.any(String), - } + }) + ) + expect(afterCreate.data.draft_order.cart.shipping_address).toEqual( + expect.objectContaining({ + id: expect.any(String), + created_at: expect.any(String), + updated_at: expect.any(String), + first_name: "kap", + last_name: "test", + country_code: "us", + }) ) - expect( - afterCreate.data.draft_order.cart.shipping_address - ).toMatchSnapshot({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - first_name: "kap", - last_name: "test", - country_code: "us", - }) }) it("creates a draft order cart and creates new user", async () => { @@ -283,15 +267,11 @@ describe("/admin/draft-orders", () => { ], } - const response = await api - .post("/admin/draft-orders", payload, { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + const response = await api.post( + "/admin/draft-orders", + payload, + adminReqConfig + ) expect(response.status).toEqual(200) @@ -324,11 +304,7 @@ describe("/admin/draft-orders", () => { } const response = await api - .post("/admin/draft-orders", payload, { - headers: { - Authorization: "Bearer test_token", - }, - }) + .post("/admin/draft-orders", payload, adminReqConfig) .catch((err) => { return err.response }) @@ -362,15 +338,11 @@ describe("/admin/draft-orders", () => { ], } - const response = await api - .post("/admin/draft-orders", payload, { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + const response = await api.post( + "/admin/draft-orders", + payload, + adminReqConfig + ) expect(response.status).toEqual(200) }) @@ -401,15 +373,11 @@ describe("/admin/draft-orders", () => { ], } - const response = await api - .post("/admin/draft-orders", payload, { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + const response = await api.post( + "/admin/draft-orders", + payload, + adminReqConfig + ) expect(response.status).toEqual(200) }) @@ -442,25 +410,16 @@ describe("/admin/draft-orders", () => { ], } - const response = await api - .post("/admin/draft-orders", payload, { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + const response = await api.post( + "/admin/draft-orders", + payload, + adminReqConfig + ) - const created = await api - .get(`/admin/draft-orders/${response.data.draft_order.id}`, { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + const created = await api.get( + `/admin/draft-orders/${response.data.draft_order.id}`, + adminReqConfig + ) expect(response.status).toEqual(200) expect(created.data.draft_order.cart.items).toEqual( @@ -505,15 +464,11 @@ describe("/admin/draft-orders", () => { ], } - const response = await api - .post("/admin/draft-orders", payload, { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + const response = await api.post( + "/admin/draft-orders", + payload, + adminReqConfig + ) const draftOrder = response.data.draft_order const lineItemId = draftOrder.cart.items[0].id @@ -561,25 +516,16 @@ describe("/admin/draft-orders", () => { ], } - const response = await api - .post("/admin/draft-orders", payload, { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + const response = await api.post( + "/admin/draft-orders", + payload, + adminReqConfig + ) - const created = await api - .get(`/admin/draft-orders/${response.data.draft_order.id}`, { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + const created = await api.get( + `/admin/draft-orders/${response.data.draft_order.id}`, + adminReqConfig + ) const draftOrder = created.data.draft_order const lineItemId = draftOrder.cart.items[0].id @@ -650,15 +596,11 @@ describe("/admin/draft-orders", () => { ], } - const response = await api - .post("/admin/draft-orders", payload, { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + const response = await api.post( + "/admin/draft-orders", + payload, + adminReqConfig + ) expect(response.status).toEqual(200) }) @@ -669,29 +611,17 @@ describe("/admin/draft-orders", () => { const orderResponse = await api.post( `/admin/draft-orders/test-draft-order/pay`, {}, - { - headers: { - Authorization: "Bearer test_token", - }, - } + adminReqConfig ) const createdOrder = await api.get( `/admin/orders/${orderResponse.data.order.id}`, - { - headers: { - Authorization: "Bearer test_token", - }, - } + adminReqConfig ) const updatedDraftOrder = await api.get( `/admin/draft-orders/test-draft-order`, - { - headers: { - Authorization: "Bearer test_token", - }, - } + adminReqConfig ) expect(orderResponse.status).toEqual(200) @@ -722,15 +652,7 @@ describe("/admin/draft-orders", () => { it("lists draft orders", async () => { const api = useApi() - const response = await api - .get("/admin/draft-orders", { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + const response = await api.get("/admin/draft-orders", adminReqConfig) expect(response.status).toEqual(200) @@ -744,15 +666,10 @@ describe("/admin/draft-orders", () => { it("lists draft orders with query", async () => { const api = useApi() - const response = await api - .get("/admin/draft-orders?q=oli@test", { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + const response = await api.get( + "/admin/draft-orders?q=oli@test", + adminReqConfig + ) expect(response.status).toEqual(200) @@ -768,15 +685,10 @@ describe("/admin/draft-orders", () => { it("lists no draft orders on query for non-existing email", async () => { const api = useApi() - const response = await api - .get("/admin/draft-orders?q=heyo@heyo.dk", { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + const response = await api.get( + "/admin/draft-orders?q=heyo@heyo.dk", + adminReqConfig + ) expect(response.status).toEqual(200) @@ -799,11 +711,10 @@ describe("/admin/draft-orders", () => { it("retrieves a draft-order should include the items totals", async () => { const api = useApi() - const order = await api.get("/admin/draft-orders/test-draft-order", { - headers: { - authorization: "Bearer test_token", - }, - }) + const order = await api.get( + "/admin/draft-orders/test-draft-order", + adminReqConfig + ) expect(order.status).toEqual(200) expect(order.data.draft_order).toEqual( @@ -833,15 +744,10 @@ describe("/admin/draft-orders", () => { it("deletes a draft order", async () => { const api = useApi() - const response = await api - .delete("/admin/draft-orders/test-draft-order", { - headers: { - Authorization: "Bearer test_token", - }, - }) - .catch((err) => { - console.log(err) - }) + const response = await api.delete( + "/admin/draft-orders/test-draft-order", + adminReqConfig + ) expect(response.status).toEqual(200) @@ -867,32 +773,20 @@ describe("/admin/draft-orders", () => { it("updates a line item on the draft order", async () => { const api = useApi() - const response = await api - .post( - "/admin/draft-orders/test-draft-order/line-items/test-item", - { - title: "Update title", - unit_price: 1000, - }, - { - headers: { - Authorization: "Bearer test_token", - }, - } - ) - .catch((err) => { - console.log(err) - }) + const response = await api.post( + "/admin/draft-orders/test-draft-order/line-items/test-item", + { + title: "Update title", + unit_price: 1000, + }, + adminReqConfig + ) expect(response.status).toEqual(200) const updatedDraftOrder = await api.get( `/admin/draft-orders/test-draft-order`, - { - headers: { - Authorization: "Bearer test_token", - }, - } + adminReqConfig ) const item = updatedDraftOrder.data.draft_order.cart.items[0] @@ -904,32 +798,20 @@ describe("/admin/draft-orders", () => { it("removes the line item, if quantity is 0", async () => { const api = useApi() - const response = await api - .post( - "/admin/draft-orders/test-draft-order/line-items/test-item", - { - title: "Update title", - quantity: 0, - }, - { - headers: { - Authorization: "Bearer test_token", - }, - } - ) - .catch((err) => { - console.log(err) - }) + const response = await api.post( + "/admin/draft-orders/test-draft-order/line-items/test-item", + { + title: "Update title", + quantity: 0, + }, + adminReqConfig + ) expect(response.status).toEqual(200) const updatedDraftOrder = await api.get( `/admin/draft-orders/test-draft-order`, - { - headers: { - Authorization: "Bearer test_token", - }, - } + adminReqConfig ) const items = updatedDraftOrder.data.draft_order.cart.items @@ -952,48 +834,36 @@ describe("/admin/draft-orders", () => { it("updates a line item on the draft order", async () => { const api = useApi() - const response = await api - .post( - "/admin/draft-orders/test-draft-order", - { - email: "lebron@james.com", - billing_address: { - first_name: "lebron", - last_name: "james", - address_1: "hollywood boulevard 1", - city: "hollywood", - country_code: "us", - postal_code: "2100", - }, - shipping_address: { - first_name: "lebron", - last_name: "james", - address_1: "hollywood boulevard 1", - city: "hollywood", - country_code: "us", - postal_code: "2100", - }, - discounts: [{ code: "TEST" }], + const response = await api.post( + "/admin/draft-orders/test-draft-order", + { + email: "lebron@james.com", + billing_address: { + first_name: "lebron", + last_name: "james", + address_1: "hollywood boulevard 1", + city: "hollywood", + country_code: "us", + postal_code: "2100", }, - { - headers: { - Authorization: "Bearer test_token", - }, - } - ) - .catch((err) => { - console.log(err) - }) + shipping_address: { + first_name: "lebron", + last_name: "james", + address_1: "hollywood boulevard 1", + city: "hollywood", + country_code: "us", + postal_code: "2100", + }, + discounts: [{ code: "TEST" }], + }, + adminReqConfig + ) expect(response.status).toEqual(200) const updatedDraftOrder = await api.get( `/admin/draft-orders/test-draft-order`, - { - headers: { - Authorization: "Bearer test_token", - }, - } + adminReqConfig ) const dorder = updatedDraftOrder.data.draft_order diff --git a/integration-tests/api/__tests__/admin/draft-order/ff-tax-inclusive-draft-order.js b/integration-tests/api/__tests__/admin/draft-order/ff-tax-inclusive-draft-order.js new file mode 100644 index 0000000000..39662d1592 --- /dev/null +++ b/integration-tests/api/__tests__/admin/draft-order/ff-tax-inclusive-draft-order.js @@ -0,0 +1,185 @@ +const path = require("path") + +const draftOrderSeeder = require("../../../helpers/draft-order-seeder") +const adminSeeder = require("../../../helpers/admin-seeder") +const { + simpleDiscountFactory, + simpleRegionFactory, + simpleShippingOptionFactory, +} = require("../../../factories") +const startServerWithEnvironment = + require("../../../../helpers/start-server-with-environment").default +const { useDb } = require("../../../../helpers/use-db") +const { useApi } = require("../../../../helpers/use-api") + +jest.setTimeout(30000) + +const adminReqConfig = { + headers: { + Authorization: "Bearer test_token", + }, +} + +describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/draft-orders", () => { + let medusaProcess + let dbConnection + + beforeAll(async () => { + const cwd = path.resolve(path.join(__dirname, "..", "..", "..")) + const [process, connection] = await startServerWithEnvironment({ + cwd, + env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true }, + verbose: false, + }) + + dbConnection = connection + medusaProcess = process + }) + + afterAll(async () => { + const db = useDb() + await db.shutdown() + + medusaProcess.kill() + }) + + describe("POST /admin/draft-orders", () => { + beforeEach(async () => { + await adminSeeder(dbConnection) + await draftOrderSeeder(dbConnection) + }) + + afterEach(async () => { + const db = useDb() + await db.teardown() + }) + + it("creates a draft order cart", async () => { + const api = useApi() + + await simpleRegionFactory(dbConnection, { + id: "taxincl-region", + includes_tax: true, + currency_code: "usd", + }) + + await dbConnection.manager.query( + `update currency + set includes_tax = true + where code = 'usd'` + ) + + await simpleDiscountFactory(dbConnection, { + code: "testytest", + regions: ["taxincl-region"], + }) + await simpleShippingOptionFactory(dbConnection, { + id: "taxincl-option", + price: 100, + region_id: "taxincl-region", + }) + + const payload = { + email: "oli@test.dk", + shipping_address: "oli-shipping", + discounts: [{ code: "testytest" }], + items: [ + { + variant_id: "test-variant", + quantity: 2, + metadata: {}, + }, + ], + region_id: "taxincl-region", + customer_id: "oli-test", + shipping_methods: [ + { + option_id: "taxincl-option", + }, + ], + } + + const response = await api.post( + "/admin/draft-orders", + payload, + adminReqConfig + ) + expect(response.status).toEqual(200) + }) + + it("creates a draft order with discount and line item", async () => { + const api = useApi() + + await simpleRegionFactory(dbConnection, { + id: "taxincl-region", + includes_tax: true, + currency_code: "usd", + }) + + await dbConnection.manager.query( + `update currency + set includes_tax = true + where code = 'usd'` + ) + + await simpleDiscountFactory(dbConnection, { + id: "disc_testytest", + code: "testytest", + regions: ["taxincl-region"], + }) + await simpleShippingOptionFactory(dbConnection, { + id: "taxincl-option", + price: 100, + region_id: "taxincl-region", + }) + + const payload = { + email: "oli@test.dk", + shipping_address: "oli-shipping", + discounts: [{ code: "testytest" }], + items: [ + { + variant_id: "test-variant", + quantity: 2, + metadata: {}, + }, + ], + region_id: "taxincl-region", + customer_id: "oli-test", + shipping_methods: [ + { + option_id: "taxincl-option", + }, + ], + } + + const response = await api.post( + "/admin/draft-orders", + payload, + adminReqConfig + ) + + const draftOrder = response.data.draft_order + const lineItemId = draftOrder.cart.items[0].id + + expect(response.status).toEqual(200) + expect(draftOrder.cart.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + variant_id: "test-variant", + unit_price: 8000, + quantity: 2, + adjustments: expect.arrayContaining([ + expect.objectContaining({ + item_id: lineItemId, + amount: 1600, + description: "discount", + discount_id: "disc_testytest", + }), + ]), + }), + ]) + ) + }) + }) +}) diff --git a/integration-tests/api/__tests__/admin/order/order.js b/integration-tests/api/__tests__/admin/order/order.js index 1be3f7cfb2..b947618642 100644 --- a/integration-tests/api/__tests__/admin/order/order.js +++ b/integration-tests/api/__tests__/admin/order/order.js @@ -22,6 +22,7 @@ const { callGet, partial, } = require("../../../helpers/call-helpers") +const { simpleShippingOptionFactory } = require("../../../factories") jest.setTimeout(30000) @@ -1760,6 +1761,12 @@ describe("/admin/orders", () => { it("creates a swap", async () => { const api = useApi() + await simpleShippingOptionFactory(dbConnection, { + id: "testytest", + is_return: true, + region_id: "test-region", + }) + const response = await api.post( "/admin/orders/test-order/swaps", { @@ -1770,6 +1777,10 @@ describe("/admin/orders", () => { }, ], additional_items: [{ variant_id: "test-variant-2", quantity: 1 }], + return_shipping: { + option_id: "testytest", + price: 400, + }, }, { headers: { diff --git a/packages/medusa/src/services/__tests__/draft-order.js b/packages/medusa/src/services/__tests__/draft-order.js index 1847f5379b..9dec9c3bf1 100644 --- a/packages/medusa/src/services/__tests__/draft-order.js +++ b/packages/medusa/src/services/__tests__/draft-order.js @@ -1,10 +1,10 @@ -import { MockRepository, MockManager } from "medusa-test-utils" +import { MockManager, MockRepository } from "medusa-test-utils" import { EventBusServiceMock } from "../__mocks__/event-bus" import DraftOrderService from "../draft-order" const eventBusService = { emit: jest.fn(), - withTransaction: function() { + withTransaction: function () { return this }, } @@ -37,7 +37,7 @@ describe("DraftOrderService", () => { const regionService = { retrieve: () => Promise.resolve({ id: "test-region", countries: [{ iso_2: "dk" }] }), - withTransaction: function() { + withTransaction: function () { return this }, } @@ -50,7 +50,7 @@ describe("DraftOrderService", () => { }, }) ), - withTransaction: function() { + withTransaction: function () { return this }, } @@ -63,7 +63,7 @@ describe("DraftOrderService", () => { }) ), create: jest.fn(), - withTransaction: function() { + withTransaction: function () { return this }, } @@ -76,20 +76,7 @@ describe("DraftOrderService", () => { profile_id: "test-profile", }, }), - withTransaction: function() { - return this - }, - } - - const cartService = { - create: jest.fn().mockImplementation((data) => - Promise.resolve({ - id: "test-cart", - ...data, - }) - ), - addShippingMethod: jest.fn(), - withTransaction: function() { + withTransaction: function () { return this }, } @@ -108,6 +95,25 @@ describe("DraftOrderService", () => { ], } + const cartService = { + create: jest.fn().mockImplementation((data) => + Promise.resolve({ + id: "test-cart", + ...data, + }) + ), + retrieve: jest.fn().mockReturnValue( + Promise.resolve({ + id: "test-cart", + ...testOrder, + }) + ), + addShippingMethod: jest.fn(), + withTransaction: function () { + return this + }, + } + const addressRepository = MockRepository({ create: (addr) => ({ ...addr, @@ -157,6 +163,11 @@ describe("DraftOrderService", () => { type: "draft_order", }) + expect(cartService.retrieve).toHaveBeenCalledTimes(1) + expect(cartService.retrieve).toHaveBeenCalledWith("test-cart", { + relations: ["discounts", "discounts.rule", "items", "region"], + }) + expect(cartService.addShippingMethod).toHaveBeenCalledTimes(1) expect(cartService.addShippingMethod).toHaveBeenCalledWith( "test-cart", diff --git a/packages/medusa/src/services/__tests__/line-item.js b/packages/medusa/src/services/__tests__/line-item.js index 048b7e8ca5..76dbdcccc3 100644 --- a/packages/medusa/src/services/__tests__/line-item.js +++ b/packages/medusa/src/services/__tests__/line-item.js @@ -413,6 +413,14 @@ describe("LineItemService", () => { metadata: {}, should_merge: true, includes_tax: true, + variant: expect.objectContaining({ + id: expect.any(String), + product: expect.objectContaining({ + thumbnail: "", + title: "Test product", + }), + title: "Test variant", + }), }) }) }) @@ -524,6 +532,14 @@ describe("LineItemService", () => { metadata: {}, should_merge: true, includes_tax: false, + variant: expect.objectContaining({ + id: expect.any(String), + product: expect.objectContaining({ + thumbnail: "", + title: "Test product", + }), + title: "Test variant", + }), }) }) }) diff --git a/packages/medusa/src/services/__tests__/new-totals.ts b/packages/medusa/src/services/__tests__/new-totals.ts index 7826708b34..dcf798e6c7 100644 --- a/packages/medusa/src/services/__tests__/new-totals.ts +++ b/packages/medusa/src/services/__tests__/new-totals.ts @@ -12,6 +12,7 @@ import { Discount, DiscountRuleType, LineItem, + ProductVariant, Region, ShippingMethod, } from "../../models" @@ -98,6 +99,7 @@ describe("New totals service", () => { it("should fetch the items tax lines to compute the totals", async () => { const testItem = { ...lineItems[0] } as LineItem + testItem.variant = new ProductVariant() testItem.tax_lines = [] const calculationContext = { @@ -706,6 +708,7 @@ describe("New totals service", () => { it("should fetch the tax lines to compute the totals", async () => { const testItem = { ...lineItems[0] } as LineItem testItem.tax_lines = [] + testItem.variant = new ProductVariant() testItem.includes_tax = true const calculationContext = { diff --git a/packages/medusa/src/services/__tests__/totals.js b/packages/medusa/src/services/__tests__/totals.js index 0b524efc54..70ebbcb191 100644 --- a/packages/medusa/src/services/__tests__/totals.js +++ b/packages/medusa/src/services/__tests__/totals.js @@ -113,14 +113,19 @@ const calculateAdjustment = (cart, lineItem, discount) => { } describe("TotalsService", () => { - const getTaxLinesMock = jest.fn(() => Promise.resolve([{ id: "line1" }])) + const getTaxLinesMock = jest.fn((items) => { + const taxLines = items.some((item) => item.id === "line1") + ? [{ id: "line1" }] + : [] + return Promise.resolve(taxLines) + }) const featureFlagRouter = new FlagRouter({ [TaxInclusivePricingFeatureFlag.key]: false, }) const container = { taxProviderService: { - withTransaction: function() { + withTransaction: function () { return this }, getTaxLines: getTaxLinesMock, @@ -353,6 +358,114 @@ describe("TotalsService", () => { }) }) + describe("getLineDiscounts", () => { + let res + const totalsService = new TotalsService(container) + + const mockCart = { + swaps: [], + claims: [], + items: [], + } + let cart + + beforeEach(() => { + jest.clearAllMocks() + cart = { ...mockCart } + }) + + it("returns [] if items is empty in cart", async () => { + res = totalsService.getLineDiscounts(cart, discounts.total10Fixed) + + expect(res).toEqual([]) + }) + + it("returns [] if items is undefined in cart", async () => { + cart.items = undefined + res = totalsService.getLineDiscounts(cart, discounts.total10Fixed) + + expect(res).toEqual([]) + }) + + it("returns flat list of discount amounts for a cart with multiple items, swaps, claims for a given discount", async () => { + const discountId = discounts.total10Fixed.id + const discountValue = discounts.total10Fixed.rule.value + const item1 = { + allow_discounts: true, + unit_price: 18, + adjustments: [ + { + discount_id: discountId, + amount: discountValue, + }, + ], + } + const item2 = { ...item1, unit_price: 19 } + const item3 = { ...item1, unit_price: 20 } + const item4 = { ...item1, unit_price: 21 } + const item5 = { ...item1, unit_price: 22 } + const item6 = { ...item1, unit_price: 23 } + const item7 = { ...item1, unit_price: 24 } + const item8 = { ...item1, unit_price: 25 } + + const swap1 = { + additional_items: [item3, item4], + } + const swap2 = { + additional_items: [item5], + } + const claim1 = { + additional_items: [item6, item7], + } + const claim2 = { + additional_items: [item8], + } + + cart.items = [item1, item2] + cart.swaps = [swap1, swap2] + cart.claims = [claim1, claim2] + + res = totalsService.getLineDiscounts(cart, discounts.total10Fixed) + + expect(res).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + amount: discountValue, + item: item1, + }), + expect.objectContaining({ + amount: discountValue, + item: item2, + }), + expect.objectContaining({ + amount: discountValue, + item: item3, + }), + expect.objectContaining({ + amount: discountValue, + item: item4, + }), + expect.objectContaining({ + amount: discountValue, + item: item5, + }), + expect.objectContaining({ + amount: discountValue, + item: item6, + }), + expect.objectContaining({ + amount: discountValue, + item: item7, + }), + expect.objectContaining({ + amount: discountValue, + item: item8, + }), + ]) + ) + }) + }) + describe("getRefundTotal", () => { let res const totalsService = new TotalsService(container) @@ -718,7 +831,7 @@ describe("TotalsService", () => { const totalsService = new TotalsService({ ...container, taxProviderService: { - withTransaction: function() { + withTransaction: function () { return this }, getTaxLines: getTaxLinesMock, @@ -757,13 +870,18 @@ describe("TotalsService", () => { let res let totalsService - const getTaxLinesMock = jest.fn(() => Promise.resolve([{ id: "line1" }])) + const getTaxLinesMock = jest.fn((items) => { + const taxLines = items.some((item) => item.id === "line1") + ? [{ id: "line1" }] + : [] + return Promise.resolve(taxLines) + }) const calculateMock = jest.fn(() => Promise.resolve(20.3)) const getAllocationMapMock = jest.fn(() => ({})) const cradle = { taxProviderService: { - withTransaction: function() { + withTransaction: function () { return this }, getTaxLines: getTaxLinesMock, @@ -854,6 +972,7 @@ describe("TotalsService", () => { }, items: [ { + id: "line1", unit_price: 20, quantity: 2, }, @@ -885,7 +1004,7 @@ describe("TotalsService", () => { expect(getTaxLinesMock).toHaveBeenCalledTimes(2) expect(getTaxLinesMock).toHaveBeenNthCalledWith( 2, - [{ quantity: 2, unit_price: 20 }], + [{ id: "line1", quantity: 2, unit_price: 20 }], { shipping_address: order.shipping_address, shipping_methods: order.shipping_methods, @@ -896,7 +1015,7 @@ describe("TotalsService", () => { } ) - expect(calculateMock).toHaveBeenCalledTimes(1) + expect(calculateMock).toHaveBeenCalledTimes(3) expect(calculateMock).toHaveBeenCalledWith( order.items, [{ id: "line1" }], diff --git a/packages/medusa/src/services/discount.ts b/packages/medusa/src/services/discount.ts index 136507bccc..a5b39818e6 100644 --- a/packages/medusa/src/services/discount.ts +++ b/packages/medusa/src/services/discount.ts @@ -10,6 +10,7 @@ import { } from "typeorm" import { EventBusService, + NewTotalsService, ProductService, RegionService, TotalsService, @@ -57,6 +58,7 @@ class DiscountService extends TransactionBaseService { protected readonly discountConditionRepository_: typeof DiscountConditionRepository protected readonly discountConditionService_: DiscountConditionService protected readonly totalsService_: TotalsService + protected readonly newTotalsService_: NewTotalsService protected readonly productService_: ProductService protected readonly regionService_: RegionService protected readonly eventBus_: EventBusService @@ -70,6 +72,7 @@ class DiscountService extends TransactionBaseService { discountConditionRepository, discountConditionService, totalsService, + newTotalsService, productService, regionService, customerService, @@ -86,6 +89,7 @@ class DiscountService extends TransactionBaseService { this.discountConditionRepository_ = discountConditionRepository this.discountConditionService_ = discountConditionService this.totalsService_ = totalsService + this.newTotalsService_ = newTotalsService this.productService_ = productService this.regionService_ = regionService this.customerService_ = customerService @@ -571,7 +575,7 @@ class DiscountService extends TransactionBaseService { lineItem: LineItem, cart: Cart ): Promise { - return await this.atomicPhase_(async () => { + return await this.atomicPhase_(async (transactionManager) => { let adjustment = 0 if (!lineItem.allow_discounts) { @@ -589,15 +593,18 @@ class DiscountService extends TransactionBaseService { ) && lineItem.includes_tax ) { - const lineItemTotals = await this.totalsService_.getLineItemTotals( - lineItem, - cart, - { - include_tax: true, - exclude_gift_cards: true, - } - ) - fullItemPrice = lineItemTotals.subtotal + const calculationContext = await this.totalsService_ + .withTransaction(transactionManager) + .getCalculationContext(cart, { + exclude_shipping: true, + }) + const lineItemTotals = await this.newTotalsService_ + .withTransaction(transactionManager) + .getLineItemTotals([lineItem], { + includeTax: true, + calculationContext, + }) + fullItemPrice = lineItemTotals[lineItem.id].subtotal } if (type === DiscountRuleType.PERCENTAGE) { diff --git a/packages/medusa/src/services/draft-order.ts b/packages/medusa/src/services/draft-order.ts index e969b3d50e..0e9212667d 100644 --- a/packages/medusa/src/services/draft-order.ts +++ b/packages/medusa/src/services/draft-order.ts @@ -274,10 +274,13 @@ class DraftOrderService extends TransactionBaseService { } } - const createdCart = await cartServiceTx.create({ + let createdCart = await cartServiceTx.create({ type: CartType.DRAFT_ORDER, ...rawCart, }) + createdCart = await cartServiceTx.retrieve(createdCart.id, { + relations: ["discounts", "discounts.rule", "items", "region"], + }) const draftOrder = draftOrderRepo.create({ cart_id: createdCart.id, diff --git a/packages/medusa/src/services/line-item-adjustment.ts b/packages/medusa/src/services/line-item-adjustment.ts index 947319a8a8..7ee16d272b 100644 --- a/packages/medusa/src/services/line-item-adjustment.ts +++ b/packages/medusa/src/services/line-item-adjustment.ts @@ -184,14 +184,18 @@ class LineItemAdjustmentService extends TransactionBaseService { generatedLineItem: LineItem, context: AdjustmentContext ): Promise { + const lineItem = { + ...generatedLineItem, + } as LineItem + return this.atomicPhase_(async (manager) => { // if lineItem should not be discounted // or lineItem is a return line item // or the cart does not have any discounts // then do nothing if ( - !generatedLineItem.allow_discounts || - generatedLineItem.is_return || + !lineItem.allow_discounts || + lineItem.is_return || !cart?.discounts?.length ) { return [] @@ -217,9 +221,11 @@ class LineItemAdjustmentService extends TransactionBaseService { return [] } + // In case of a generated line item the id is not available, it is mocked instead to be used for totals calculations + lineItem.id = lineItem.id ?? new Date().getTime() const amount = await this.discountService.calculateDiscountForLineItem( discount.id, - generatedLineItem, + lineItem, cart ) diff --git a/packages/medusa/src/services/line-item.ts b/packages/medusa/src/services/line-item.ts index 95fb6e8960..39c3e98e04 100644 --- a/packages/medusa/src/services/line-item.ts +++ b/packages/medusa/src/services/line-item.ts @@ -256,7 +256,10 @@ class LineItemService extends TransactionBaseService { const lineItemRepo = transactionManager.getCustomRepository( this.lineItemRepository_ ) - const lineItem = lineItemRepo.create(rawLineItem) + const lineItem = lineItemRepo.create({ + ...rawLineItem, + variant, + }) if (context.cart) { const adjustments = await this.lineItemAdjustmentService_ diff --git a/packages/medusa/src/services/new-totals.ts b/packages/medusa/src/services/new-totals.ts index 845be26908..50efa2c671 100644 --- a/packages/medusa/src/services/new-totals.ts +++ b/packages/medusa/src/services/new-totals.ts @@ -104,6 +104,13 @@ export default class NewTotalsService extends TransactionBaseService { lineItemsTaxLinesMap[item.id] = item.tax_lines ?? [] }) } else { + if (items.some((item) => !item.variant)) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "Unable to fetch tax lines to compute line item totals as one of the item variant is missing but required for the tax lines to be computed. Might be due to a missing relation items.variant" + ) + } + const { lineItemsTaxLines } = await this.taxProviderService_ .withTransaction(manager) .getTaxLinesMap(items, calculationContext) diff --git a/packages/medusa/src/services/totals.ts b/packages/medusa/src/services/totals.ts index 52307aae21..936d6d085c 100644 --- a/packages/medusa/src/services/totals.ts +++ b/packages/medusa/src/services/totals.ts @@ -703,7 +703,7 @@ class TotalsService extends TransactionBaseService { cartOrOrder: Cart | Order, discount: Discount ): LineDiscountAmount[] { - let merged: LineItem[] = [...cartOrOrder.items] + let merged: LineItem[] = [...(cartOrOrder.items ?? [])] // merge items from order with items from order swaps if ("swaps" in cartOrOrder && cartOrOrder.swaps.length) { @@ -842,16 +842,9 @@ class TotalsService extends TransactionBaseService { } taxLines = lineItem.tax_lines } else { - const orderLines = await this.taxProviderService_ + taxLines = (await this.taxProviderService_ .withTransaction(this.manager_) - .getTaxLines(cartOrOrder.items, calculationContext) - - taxLines = orderLines.filter((ol) => { - if ("item_id" in ol) { - return ol.item_id === lineItem.id - } - return false - }) as LineItemTaxLine[] + .getTaxLines([lineItem], calculationContext)) as LineItemTaxLine[] } }