fix(draft-order): create tax-inclusive with discount (#2579)

**What**
Fix system error (500) with DraftOrder create operation when payload includes discount in a tax-inclusive context.

**How**
* Ensure newly created cart contains all required relation in order to calculate line item tax-inclusive pricing with discounts.
* Add resilience to TotalsService.getLineDiscounts()
* Ensure newly generate line items have variant relation loaded.
* fix TotalsService.getLineItemTotals to use the passed lineItem instead of relying on cartOrOrder.items.

**Test**
* Unit:
  *  TotalsService.getLineDiscounts - coverage
* Integration:
  * Admin API draft-order - coverage
  * Admin API draft-order create w/ discount in tax-inclusive

Resolves: CORE-771

Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2022-11-16 15:34:18 +00:00
1 parent 03fc9e18e9
commit 022a84691e
17 files changed
+621 -904

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
fix(draft-order): create tax-inclusive with discount
@@ -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<String>,
"deleted_at": null,
"handle": "test-collection",
"id": "test-collection",
"metadata": null,
"products": Array [
Object {
"collection_id": "test-collection",
"created_at": Any<String>,
"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<String>,
"weight": null,
"width": null,
},
Object {
"collection_id": "test-collection",
"created_at": Any<String>,
"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<String>,
"weight": null,
"width": null,
},
Object {
"collection_id": "test-collection",
"created_at": Any<String>,
"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<String>,
"weight": null,
"width": null,
},
],
"title": "Test collection",
"updated_at": Any<String>,
},
}
`;
exports[`/admin/collections /admin/collections creates a collection 1`] = `
Object {
"collection": Object {
"created_at": Any<String>,
"deleted_at": null,
"handle": "test-handle-creation",
"id": StringMatching /\\^pcol_\\*/,
"metadata": null,
"title": "test collection creation",
"updated_at": Any<String>,
},
}
`;
exports[`/admin/collections /admin/collections filters collections by title 1`] = `
Object {
"collections": Array [
Object {
"created_at": Any<String>,
"handle": "test-collection",
"id": "test-collection",
"products": Array [
Object {
"collection_id": "test-collection",
"created_at": Any<String>,
"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<String>,
"weight": null,
"width": null,
},
Object {
"collection_id": "test-collection",
"created_at": Any<String>,
"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<String>,
"weight": null,
"width": null,
},
],
"title": "Test collection",
"updated_at": Any<String>,
},
],
"count": 1,
"limit": 10,
"offset": 0,
}
`;
exports[`/admin/collections /admin/collections lists collections 1`] = `
Object {
"collections": Array [
Object {
"created_at": Any<String>,
"handle": "test-collection2",
"id": "test-collection2",
"products": Array [
Object {
"collection_id": "test-collection2",
"created_at": Any<String>,
"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<String>,
"weight": null,
"width": null,
},
],
"title": "Test collection 2",
"updated_at": Any<String>,
},
Object {
"created_at": Any<String>,
"handle": "test-collection1",
"id": "test-collection1",
"products": Array [
Object {
"collection_id": "test-collection1",
"created_at": Any<String>,
"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<String>,
"weight": null,
"width": null,
},
Object {
"collection_id": "test-collection1",
"created_at": Any<String>,
"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<String>,
"weight": null,
"width": null,
},
],
"title": "Test collection 1",
"updated_at": Any<String>,
},
Object {
"created_at": Any<String>,
"handle": "test-collection",
"id": "test-collection",
"products": Array [
Object {
"collection_id": "test-collection",
"created_at": Any<String>,
"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<String>,
"weight": null,
"width": null,
},
Object {
"collection_id": "test-collection",
"created_at": Any<String>,
"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<String>,
"weight": null,
"width": null,
},
],
"title": "Test collection",
"updated_at": Any<String>,
},
],
"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<String>,
"deleted_at": null,
"handle": "test-collection",
"id": "test-collection",
"metadata": null,
"products": Array [
Object {
"collection_id": "test-collection",
"created_at": Any<String>,
"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<String>,
"weight": null,
"width": null,
},
Object {
"collection_id": "test-collection",
"created_at": Any<String>,
"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<String>,
"weight": null,
"width": null,
},
],
"title": "Test collection",
"updated_at": Any<String>,
},
}
`;
exports[`/admin/collections /admin/collections/:id updates a collection 1`] = `
Object {
"collection": Object {
"created_at": Any<String>,
"deleted_at": null,
"handle": "test-handle-creation",
"id": StringMatching /\\^pcol_\\*/,
"metadata": null,
"title": "test collection creation",
"updated_at": Any<String>,
},
}
`;
@@ -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<String>,
"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<String>,
}
`;
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<String>,
"customer_id": null,
"deleted_at": null,
"first_name": "kap",
"id": Any<String>,
"last_name": "test",
"metadata": null,
"phone": null,
"postal_code": null,
"province": null,
"updated_at": Any<String>,
}
`;
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<String>,
"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<String>,
}
`;
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<String>,
"customer_id": null,
"deleted_at": null,
"first_name": "kap",
"id": Any<String>,
"last_name": "test",
"metadata": null,
"phone": null,
"postal_code": null,
"province": null,
"updated_at": Any<String>,
}
`;
@@ -70,15 +70,17 @@ describe("/admin/collections", () => {
) )
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
expect(response.data).toMatchSnapshot({ expect(response.data).toEqual(
collection: { expect.objectContaining({
collection: expect.objectContaining({
id: expect.stringMatching(/^pcol_*/), id: expect.stringMatching(/^pcol_*/),
title: "test collection creation", title: "test collection creation",
handle: "test-handle-creation", handle: "test-handle-creation",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
}, }),
}) })
)
}) })
it("deletes a collection", async () => { it("deletes a collection", async () => {
@@ -112,27 +114,29 @@ describe("/admin/collections", () => {
headers: { Authorization: "Bearer test_token" }, headers: { Authorization: "Bearer test_token" },
}) })
expect(response.data).toMatchSnapshot({ expect(response.data).toEqual(
collection: { expect.objectContaining({
collection: expect.objectContaining({
id: "test-collection", id: "test-collection",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
products: [ products: expect.arrayContaining([
{ expect.objectContaining({
collection_id: "test-collection", collection_id: "test-collection",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/), profile_id: expect.stringMatching(/^sp_*/),
}, }),
{ expect.objectContaining({
collection_id: "test-collection", collection_id: "test-collection",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/), profile_id: expect.stringMatching(/^sp_*/),
}, }),
], ]),
}, }),
}) })
)
}) })
}) })
@@ -160,15 +164,17 @@ describe("/admin/collections", () => {
) )
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
expect(response.data).toMatchSnapshot({ expect(response.data).toEqual(
collection: { expect.objectContaining({
collection: expect.objectContaining({
id: expect.stringMatching(/^pcol_*/), id: expect.stringMatching(/^pcol_*/),
title: "test collection creation", title: "test collection creation",
handle: "test-handle-creation", handle: "test-handle-creation",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
}, }),
}) })
)
}) })
it("lists collections", async () => { it("lists collections", async () => {
@@ -178,68 +184,70 @@ describe("/admin/collections", () => {
headers: { Authorization: "Bearer test_token" }, headers: { Authorization: "Bearer test_token" },
}) })
expect(response.data).toMatchSnapshot({ expect(response.data).toEqual(
collections: [ expect.objectContaining({
{ count: 3,
collections: expect.arrayContaining([
expect.objectContaining({
id: "test-collection2", id: "test-collection2",
handle: "test-collection2", handle: "test-collection2",
title: "Test collection 2", title: "Test collection 2",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
products: [ products: expect.arrayContaining([
{ expect.objectContaining({
collection_id: "test-collection2", collection_id: "test-collection2",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/), profile_id: expect.stringMatching(/^sp_*/),
}, }),
], ]),
}, }),
{ expect.objectContaining({
id: "test-collection1", id: "test-collection1",
handle: "test-collection1", handle: "test-collection1",
title: "Test collection 1", title: "Test collection 1",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
products: [ products: expect.arrayContaining([
{ expect.objectContaining({
collection_id: "test-collection1", collection_id: "test-collection1",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/), profile_id: expect.stringMatching(/^sp_*/),
}, }),
{ expect.objectContaining({
collection_id: "test-collection1", collection_id: "test-collection1",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/), profile_id: expect.stringMatching(/^sp_*/),
}, }),
], ]),
}, }),
{ expect.objectContaining({
id: "test-collection", id: "test-collection",
handle: "test-collection", handle: "test-collection",
title: "Test collection", title: "Test collection",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
products: [ products: expect.arrayContaining([
{ expect.objectContaining({
collection_id: "test-collection", collection_id: "test-collection",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/), profile_id: expect.stringMatching(/^sp_*/),
}, }),
{ expect.objectContaining({
collection_id: "test-collection", collection_id: "test-collection",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/), profile_id: expect.stringMatching(/^sp_*/),
}, }),
], ]),
}, }),
], ]),
count: 3,
}) })
)
}) })
it("adds products to collection", async () => { it("adds products to collection", async () => {
@@ -258,36 +266,38 @@ describe("/admin/collections", () => {
) )
.catch((err) => console.warn(err)) .catch((err) => console.warn(err))
expect(response.data).toMatchSnapshot({ expect(response.data).toEqual(
collection: { expect.objectContaining({
collection: expect.objectContaining({
id: "test-collection", id: "test-collection",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
products: [ products: expect.arrayContaining([
{ expect.objectContaining({
collection_id: "test-collection", collection_id: "test-collection",
id: "test-product", id: "test-product",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/), 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_*/),
},
{
collection_id: "test-collection", collection_id: "test-collection",
id: "test-product_filtering_1", id: "test-product_filtering_1",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/), 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) expect(response.status).toEqual(200)
}) })
@@ -302,11 +312,13 @@ describe("/admin/collections", () => {
}) })
.catch((err) => console.warn(err)) .catch((err) => console.warn(err))
expect(response.data).toMatchSnapshot({ expect(response.data).toEqual(
expect.objectContaining({
id: "test-collection", id: "test-collection",
object: "product-collection", object: "product-collection",
removed_products: ["test-product"], removed_products: ["test-product"],
}) })
)
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
}) })
@@ -320,34 +332,36 @@ describe("/admin/collections", () => {
}) })
.catch((err) => console.log(err)) .catch((err) => console.log(err))
expect(response.data).toMatchSnapshot({ expect(response.data).toEqual(
collections: [ expect.objectContaining({
{ count: 1,
limit: 10,
offset: 0,
collections: expect.arrayContaining([
expect.objectContaining({
id: "test-collection", id: "test-collection",
handle: "test-collection", handle: "test-collection",
title: "Test collection", title: "Test collection",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
products: [ products: expect.arrayContaining([
{ expect.objectContaining({
collection_id: "test-collection", collection_id: "test-collection",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/), profile_id: expect.stringMatching(/^sp_*/),
}, }),
{ expect.objectContaining({
collection_id: "test-collection", collection_id: "test-collection",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/), profile_id: expect.stringMatching(/^sp_*/),
}, }),
], ]),
}, }),
], ]),
count: 1,
limit: 10,
offset: 0,
}) })
)
}) })
it("returns a list of collections filtered by discount condition id", async () => { it("returns a list of collections filtered by discount condition id", async () => {
@@ -1,22 +1,32 @@
const path = require("path") const path = require("path")
const setupServer = require("../../../helpers/setup-server") const setupServer = require("../../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api") const { useApi } = require("../../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db") const { initDb, useDb } = require("../../../../helpers/use-db")
const draftOrderSeeder = require("../../helpers/draft-order-seeder") const draftOrderSeeder = require("../../../helpers/draft-order-seeder")
const adminSeeder = require("../../helpers/admin-seeder") const adminSeeder = require("../../../helpers/admin-seeder")
const {
simpleRegionFactory,
simpleDiscountFactory,
} = require("../../../factories")
jest.setTimeout(30000) jest.setTimeout(30000)
const adminReqConfig = {
headers: {
Authorization: "Bearer test_token",
},
}
describe("/admin/draft-orders", () => { describe("/admin/draft-orders", () => {
let medusaProcess let medusaProcess
let dbConnection let dbConnection
beforeAll(async () => { beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..")) const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
dbConnection = await initDb({ cwd }) dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd }) medusaProcess = await setupServer({ cwd, verbose: true })
}) })
afterAll(async () => { afterAll(async () => {
@@ -40,9 +50,15 @@ describe("/admin/draft-orders", () => {
it("creates a draft order cart", async () => { it("creates a draft order cart", async () => {
const api = useApi() const api = useApi()
await simpleDiscountFactory(dbConnection, {
code: "testytest",
regions: ["test-region"],
})
const payload = { const payload = {
email: "oli@test.dk", email: "oli@test.dk",
shipping_address: "oli-shipping", shipping_address: "oli-shipping",
discounts: [{ code: "testytest" }],
items: [ items: [
{ {
variant_id: "test-variant", variant_id: "test-variant",
@@ -59,15 +75,11 @@ describe("/admin/draft-orders", () => {
], ],
} }
const response = await api const response = await api.post(
.post("/admin/draft-orders", payload, { "/admin/draft-orders",
headers: { payload,
Authorization: "Bearer test_token", adminReqConfig
}, )
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
}) })
@@ -94,22 +106,18 @@ describe("/admin/draft-orders", () => {
], ],
} }
const response = await api.post("/admin/draft-orders", payload, { const response = await api.post(
headers: { "/admin/draft-orders",
Authorization: "Bearer test_token", payload,
}, adminReqConfig
}) )
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
const draftOrderId = response.data.draft_order.id const draftOrderId = response.data.draft_order.id
const draftOrderResponse = await api.get( const draftOrderResponse = await api.get(
`/admin/draft-orders/${draftOrderId}`, `/admin/draft-orders/${draftOrderId}`,
{ adminReqConfig
headers: {
Authorization: "Bearer test_token",
},
}
) )
expect(draftOrderResponse.status).toEqual(200) expect(draftOrderResponse.status).toEqual(200)
expect(draftOrderResponse.data.draft_order.cart.shipping_total).toEqual( expect(draftOrderResponse.data.draft_order.cart.shipping_total).toEqual(
@@ -147,15 +155,7 @@ describe("/admin/draft-orders", () => {
const { const {
status, status,
data: { draft_order }, data: { draft_order },
} = await api } = await api.post("/admin/draft-orders", payload, adminReqConfig)
.post("/admin/draft-orders", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(status).toEqual(200) expect(status).toEqual(200)
expect(draft_order.cart.billing_address_id).not.toBeNull() expect(draft_order.cart.billing_address_id).not.toBeNull()
@@ -163,29 +163,25 @@ describe("/admin/draft-orders", () => {
const afterCreate = await api.get( const afterCreate = await api.get(
`/admin/draft-orders/${draft_order.id}`, `/admin/draft-orders/${draft_order.id}`,
{ adminReqConfig
headers: {
Authorization: "Bearer test_token",
},
}
) )
expect( expect(afterCreate.data.draft_order.cart.shipping_address).toEqual(
afterCreate.data.draft_order.cart.shipping_address expect.objectContaining({
).toMatchSnapshot({
id: "oli-shipping", id: "oli-shipping",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
}) })
expect(afterCreate.data.draft_order.cart.billing_address).toMatchSnapshot( )
{ expect(afterCreate.data.draft_order.cart.billing_address).toEqual(
expect.objectContaining({
id: expect.any(String), id: expect.any(String),
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
first_name: "kap", first_name: "kap",
last_name: "test", last_name: "test",
country_code: "us", country_code: "us",
} })
) )
}) })
@@ -219,15 +215,7 @@ describe("/admin/draft-orders", () => {
const { const {
status, status,
data: { draft_order }, data: { draft_order },
} = await api } = await api.post("/admin/draft-orders", payload, adminReqConfig)
.post("/admin/draft-orders", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(status).toEqual(200) expect(status).toEqual(200)
expect(draft_order.cart.billing_address_id).not.toBeNull() expect(draft_order.cart.billing_address_id).not.toBeNull()
@@ -235,23 +223,18 @@ describe("/admin/draft-orders", () => {
const afterCreate = await api.get( const afterCreate = await api.get(
`/admin/draft-orders/${draft_order.id}`, `/admin/draft-orders/${draft_order.id}`,
{ adminReqConfig
headers: {
Authorization: "Bearer test_token",
},
}
) )
expect(afterCreate.data.draft_order.cart.billing_address).toMatchSnapshot( expect(afterCreate.data.draft_order.cart.billing_address).toEqual(
{ expect.objectContaining({
id: "oli-shipping", id: "oli-shipping",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
} })
) )
expect( expect(afterCreate.data.draft_order.cart.shipping_address).toEqual(
afterCreate.data.draft_order.cart.shipping_address expect.objectContaining({
).toMatchSnapshot({
id: expect.any(String), id: expect.any(String),
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
@@ -259,6 +242,7 @@ describe("/admin/draft-orders", () => {
last_name: "test", last_name: "test",
country_code: "us", country_code: "us",
}) })
)
}) })
it("creates a draft order cart and creates new user", async () => { it("creates a draft order cart and creates new user", async () => {
@@ -283,15 +267,11 @@ describe("/admin/draft-orders", () => {
], ],
} }
const response = await api const response = await api.post(
.post("/admin/draft-orders", payload, { "/admin/draft-orders",
headers: { payload,
Authorization: "Bearer test_token", adminReqConfig
}, )
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
@@ -324,11 +304,7 @@ describe("/admin/draft-orders", () => {
} }
const response = await api const response = await api
.post("/admin/draft-orders", payload, { .post("/admin/draft-orders", payload, adminReqConfig)
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => { .catch((err) => {
return err.response return err.response
}) })
@@ -362,15 +338,11 @@ describe("/admin/draft-orders", () => {
], ],
} }
const response = await api const response = await api.post(
.post("/admin/draft-orders", payload, { "/admin/draft-orders",
headers: { payload,
Authorization: "Bearer test_token", adminReqConfig
}, )
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
}) })
@@ -401,15 +373,11 @@ describe("/admin/draft-orders", () => {
], ],
} }
const response = await api const response = await api.post(
.post("/admin/draft-orders", payload, { "/admin/draft-orders",
headers: { payload,
Authorization: "Bearer test_token", adminReqConfig
}, )
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
}) })
@@ -442,25 +410,16 @@ describe("/admin/draft-orders", () => {
], ],
} }
const response = await api const response = await api.post(
.post("/admin/draft-orders", payload, { "/admin/draft-orders",
headers: { payload,
Authorization: "Bearer test_token", adminReqConfig
}, )
})
.catch((err) => {
console.log(err)
})
const created = await api const created = await api.get(
.get(`/admin/draft-orders/${response.data.draft_order.id}`, { `/admin/draft-orders/${response.data.draft_order.id}`,
headers: { adminReqConfig
Authorization: "Bearer test_token", )
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
expect(created.data.draft_order.cart.items).toEqual( expect(created.data.draft_order.cart.items).toEqual(
@@ -505,15 +464,11 @@ describe("/admin/draft-orders", () => {
], ],
} }
const response = await api const response = await api.post(
.post("/admin/draft-orders", payload, { "/admin/draft-orders",
headers: { payload,
Authorization: "Bearer test_token", adminReqConfig
}, )
})
.catch((err) => {
console.log(err)
})
const draftOrder = response.data.draft_order const draftOrder = response.data.draft_order
const lineItemId = draftOrder.cart.items[0].id const lineItemId = draftOrder.cart.items[0].id
@@ -561,25 +516,16 @@ describe("/admin/draft-orders", () => {
], ],
} }
const response = await api const response = await api.post(
.post("/admin/draft-orders", payload, { "/admin/draft-orders",
headers: { payload,
Authorization: "Bearer test_token", adminReqConfig
}, )
})
.catch((err) => {
console.log(err)
})
const created = await api const created = await api.get(
.get(`/admin/draft-orders/${response.data.draft_order.id}`, { `/admin/draft-orders/${response.data.draft_order.id}`,
headers: { adminReqConfig
Authorization: "Bearer test_token", )
},
})
.catch((err) => {
console.log(err)
})
const draftOrder = created.data.draft_order const draftOrder = created.data.draft_order
const lineItemId = draftOrder.cart.items[0].id const lineItemId = draftOrder.cart.items[0].id
@@ -650,15 +596,11 @@ describe("/admin/draft-orders", () => {
], ],
} }
const response = await api const response = await api.post(
.post("/admin/draft-orders", payload, { "/admin/draft-orders",
headers: { payload,
Authorization: "Bearer test_token", adminReqConfig
}, )
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
}) })
@@ -669,29 +611,17 @@ describe("/admin/draft-orders", () => {
const orderResponse = await api.post( const orderResponse = await api.post(
`/admin/draft-orders/test-draft-order/pay`, `/admin/draft-orders/test-draft-order/pay`,
{}, {},
{ adminReqConfig
headers: {
Authorization: "Bearer test_token",
},
}
) )
const createdOrder = await api.get( const createdOrder = await api.get(
`/admin/orders/${orderResponse.data.order.id}`, `/admin/orders/${orderResponse.data.order.id}`,
{ adminReqConfig
headers: {
Authorization: "Bearer test_token",
},
}
) )
const updatedDraftOrder = await api.get( const updatedDraftOrder = await api.get(
`/admin/draft-orders/test-draft-order`, `/admin/draft-orders/test-draft-order`,
{ adminReqConfig
headers: {
Authorization: "Bearer test_token",
},
}
) )
expect(orderResponse.status).toEqual(200) expect(orderResponse.status).toEqual(200)
@@ -722,15 +652,7 @@ describe("/admin/draft-orders", () => {
it("lists draft orders", async () => { it("lists draft orders", async () => {
const api = useApi() const api = useApi()
const response = await api const response = await api.get("/admin/draft-orders", adminReqConfig)
.get("/admin/draft-orders", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
@@ -744,15 +666,10 @@ describe("/admin/draft-orders", () => {
it("lists draft orders with query", async () => { it("lists draft orders with query", async () => {
const api = useApi() const api = useApi()
const response = await api const response = await api.get(
.get("/admin/draft-orders?q=oli@test", { "/admin/draft-orders?q=oli@test",
headers: { adminReqConfig
Authorization: "Bearer test_token", )
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200) 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 () => { it("lists no draft orders on query for non-existing email", async () => {
const api = useApi() const api = useApi()
const response = await api const response = await api.get(
.get("/admin/draft-orders?q=heyo@heyo.dk", { "/admin/draft-orders?q=heyo@heyo.dk",
headers: { adminReqConfig
Authorization: "Bearer test_token", )
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
@@ -799,11 +711,10 @@ describe("/admin/draft-orders", () => {
it("retrieves a draft-order should include the items totals", async () => { it("retrieves a draft-order should include the items totals", async () => {
const api = useApi() const api = useApi()
const order = await api.get("/admin/draft-orders/test-draft-order", { const order = await api.get(
headers: { "/admin/draft-orders/test-draft-order",
authorization: "Bearer test_token", adminReqConfig
}, )
})
expect(order.status).toEqual(200) expect(order.status).toEqual(200)
expect(order.data.draft_order).toEqual( expect(order.data.draft_order).toEqual(
@@ -833,15 +744,10 @@ describe("/admin/draft-orders", () => {
it("deletes a draft order", async () => { it("deletes a draft order", async () => {
const api = useApi() const api = useApi()
const response = await api const response = await api.delete(
.delete("/admin/draft-orders/test-draft-order", { "/admin/draft-orders/test-draft-order",
headers: { adminReqConfig
Authorization: "Bearer test_token", )
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
@@ -867,32 +773,20 @@ describe("/admin/draft-orders", () => {
it("updates a line item on the draft order", async () => { it("updates a line item on the draft order", async () => {
const api = useApi() const api = useApi()
const response = await api const response = await api.post(
.post(
"/admin/draft-orders/test-draft-order/line-items/test-item", "/admin/draft-orders/test-draft-order/line-items/test-item",
{ {
title: "Update title", title: "Update title",
unit_price: 1000, unit_price: 1000,
}, },
{ adminReqConfig
headers: {
Authorization: "Bearer test_token",
},
}
) )
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
const updatedDraftOrder = await api.get( const updatedDraftOrder = await api.get(
`/admin/draft-orders/test-draft-order`, `/admin/draft-orders/test-draft-order`,
{ adminReqConfig
headers: {
Authorization: "Bearer test_token",
},
}
) )
const item = updatedDraftOrder.data.draft_order.cart.items[0] 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 () => { it("removes the line item, if quantity is 0", async () => {
const api = useApi() const api = useApi()
const response = await api const response = await api.post(
.post(
"/admin/draft-orders/test-draft-order/line-items/test-item", "/admin/draft-orders/test-draft-order/line-items/test-item",
{ {
title: "Update title", title: "Update title",
quantity: 0, quantity: 0,
}, },
{ adminReqConfig
headers: {
Authorization: "Bearer test_token",
},
}
) )
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
const updatedDraftOrder = await api.get( const updatedDraftOrder = await api.get(
`/admin/draft-orders/test-draft-order`, `/admin/draft-orders/test-draft-order`,
{ adminReqConfig
headers: {
Authorization: "Bearer test_token",
},
}
) )
const items = updatedDraftOrder.data.draft_order.cart.items const items = updatedDraftOrder.data.draft_order.cart.items
@@ -952,8 +834,7 @@ describe("/admin/draft-orders", () => {
it("updates a line item on the draft order", async () => { it("updates a line item on the draft order", async () => {
const api = useApi() const api = useApi()
const response = await api const response = await api.post(
.post(
"/admin/draft-orders/test-draft-order", "/admin/draft-orders/test-draft-order",
{ {
email: "lebron@james.com", email: "lebron@james.com",
@@ -975,25 +856,14 @@ describe("/admin/draft-orders", () => {
}, },
discounts: [{ code: "TEST" }], discounts: [{ code: "TEST" }],
}, },
{ adminReqConfig
headers: {
Authorization: "Bearer test_token",
},
}
) )
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
const updatedDraftOrder = await api.get( const updatedDraftOrder = await api.get(
`/admin/draft-orders/test-draft-order`, `/admin/draft-orders/test-draft-order`,
{ adminReqConfig
headers: {
Authorization: "Bearer test_token",
},
}
) )
const dorder = updatedDraftOrder.data.draft_order const dorder = updatedDraftOrder.data.draft_order
@@ -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",
}),
]),
}),
])
)
})
})
})
@@ -22,6 +22,7 @@ const {
callGet, callGet,
partial, partial,
} = require("../../../helpers/call-helpers") } = require("../../../helpers/call-helpers")
const { simpleShippingOptionFactory } = require("../../../factories")
jest.setTimeout(30000) jest.setTimeout(30000)
@@ -1760,6 +1761,12 @@ describe("/admin/orders", () => {
it("creates a swap", async () => { it("creates a swap", async () => {
const api = useApi() const api = useApi()
await simpleShippingOptionFactory(dbConnection, {
id: "testytest",
is_return: true,
region_id: "test-region",
})
const response = await api.post( const response = await api.post(
"/admin/orders/test-order/swaps", "/admin/orders/test-order/swaps",
{ {
@@ -1770,6 +1777,10 @@ describe("/admin/orders", () => {
}, },
], ],
additional_items: [{ variant_id: "test-variant-2", quantity: 1 }], additional_items: [{ variant_id: "test-variant-2", quantity: 1 }],
return_shipping: {
option_id: "testytest",
price: 400,
},
}, },
{ {
headers: { headers: {
@@ -1,4 +1,4 @@
import { MockRepository, MockManager } from "medusa-test-utils" import { MockManager, MockRepository } from "medusa-test-utils"
import { EventBusServiceMock } from "../__mocks__/event-bus" import { EventBusServiceMock } from "../__mocks__/event-bus"
import DraftOrderService from "../draft-order" import DraftOrderService from "../draft-order"
@@ -81,19 +81,6 @@ describe("DraftOrderService", () => {
}, },
} }
const cartService = {
create: jest.fn().mockImplementation((data) =>
Promise.resolve({
id: "test-cart",
...data,
})
),
addShippingMethod: jest.fn(),
withTransaction: function() {
return this
},
}
const testOrder = { const testOrder = {
region_id: "test-region", region_id: "test-region",
shipping_address_id: "test-shipping", shipping_address_id: "test-shipping",
@@ -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({ const addressRepository = MockRepository({
create: (addr) => ({ create: (addr) => ({
...addr, ...addr,
@@ -157,6 +163,11 @@ describe("DraftOrderService", () => {
type: "draft_order", 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).toHaveBeenCalledTimes(1)
expect(cartService.addShippingMethod).toHaveBeenCalledWith( expect(cartService.addShippingMethod).toHaveBeenCalledWith(
"test-cart", "test-cart",
@@ -413,6 +413,14 @@ describe("LineItemService", () => {
metadata: {}, metadata: {},
should_merge: true, should_merge: true,
includes_tax: 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: {}, metadata: {},
should_merge: true, should_merge: true,
includes_tax: false, includes_tax: false,
variant: expect.objectContaining({
id: expect.any(String),
product: expect.objectContaining({
thumbnail: "",
title: "Test product",
}),
title: "Test variant",
}),
}) })
}) })
}) })
@@ -12,6 +12,7 @@ import {
Discount, Discount,
DiscountRuleType, DiscountRuleType,
LineItem, LineItem,
ProductVariant,
Region, Region,
ShippingMethod, ShippingMethod,
} from "../../models" } from "../../models"
@@ -98,6 +99,7 @@ describe("New totals service", () => {
it("should fetch the items tax lines to compute the totals", async () => { it("should fetch the items tax lines to compute the totals", async () => {
const testItem = { ...lineItems[0] } as LineItem const testItem = { ...lineItems[0] } as LineItem
testItem.variant = new ProductVariant()
testItem.tax_lines = [] testItem.tax_lines = []
const calculationContext = { const calculationContext = {
@@ -706,6 +708,7 @@ describe("New totals service", () => {
it("should fetch the tax lines to compute the totals", async () => { it("should fetch the tax lines to compute the totals", async () => {
const testItem = { ...lineItems[0] } as LineItem const testItem = { ...lineItems[0] } as LineItem
testItem.tax_lines = [] testItem.tax_lines = []
testItem.variant = new ProductVariant()
testItem.includes_tax = true testItem.includes_tax = true
const calculationContext = { const calculationContext = {
@@ -113,7 +113,12 @@ const calculateAdjustment = (cart, lineItem, discount) => {
} }
describe("TotalsService", () => { 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({ const featureFlagRouter = new FlagRouter({
[TaxInclusivePricingFeatureFlag.key]: false, [TaxInclusivePricingFeatureFlag.key]: false,
}) })
@@ -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", () => { describe("getRefundTotal", () => {
let res let res
const totalsService = new TotalsService(container) const totalsService = new TotalsService(container)
@@ -757,7 +870,12 @@ describe("TotalsService", () => {
let res let res
let totalsService 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 calculateMock = jest.fn(() => Promise.resolve(20.3))
const getAllocationMapMock = jest.fn(() => ({})) const getAllocationMapMock = jest.fn(() => ({}))
@@ -854,6 +972,7 @@ describe("TotalsService", () => {
}, },
items: [ items: [
{ {
id: "line1",
unit_price: 20, unit_price: 20,
quantity: 2, quantity: 2,
}, },
@@ -885,7 +1004,7 @@ describe("TotalsService", () => {
expect(getTaxLinesMock).toHaveBeenCalledTimes(2) expect(getTaxLinesMock).toHaveBeenCalledTimes(2)
expect(getTaxLinesMock).toHaveBeenNthCalledWith( expect(getTaxLinesMock).toHaveBeenNthCalledWith(
2, 2,
[{ quantity: 2, unit_price: 20 }], [{ id: "line1", quantity: 2, unit_price: 20 }],
{ {
shipping_address: order.shipping_address, shipping_address: order.shipping_address,
shipping_methods: order.shipping_methods, shipping_methods: order.shipping_methods,
@@ -896,7 +1015,7 @@ describe("TotalsService", () => {
} }
) )
expect(calculateMock).toHaveBeenCalledTimes(1) expect(calculateMock).toHaveBeenCalledTimes(3)
expect(calculateMock).toHaveBeenCalledWith( expect(calculateMock).toHaveBeenCalledWith(
order.items, order.items,
[{ id: "line1" }], [{ id: "line1" }],
+17 -10
View File
@@ -10,6 +10,7 @@ import {
} from "typeorm" } from "typeorm"
import { import {
EventBusService, EventBusService,
NewTotalsService,
ProductService, ProductService,
RegionService, RegionService,
TotalsService, TotalsService,
@@ -57,6 +58,7 @@ class DiscountService extends TransactionBaseService {
protected readonly discountConditionRepository_: typeof DiscountConditionRepository protected readonly discountConditionRepository_: typeof DiscountConditionRepository
protected readonly discountConditionService_: DiscountConditionService protected readonly discountConditionService_: DiscountConditionService
protected readonly totalsService_: TotalsService protected readonly totalsService_: TotalsService
protected readonly newTotalsService_: NewTotalsService
protected readonly productService_: ProductService protected readonly productService_: ProductService
protected readonly regionService_: RegionService protected readonly regionService_: RegionService
protected readonly eventBus_: EventBusService protected readonly eventBus_: EventBusService
@@ -70,6 +72,7 @@ class DiscountService extends TransactionBaseService {
discountConditionRepository, discountConditionRepository,
discountConditionService, discountConditionService,
totalsService, totalsService,
newTotalsService,
productService, productService,
regionService, regionService,
customerService, customerService,
@@ -86,6 +89,7 @@ class DiscountService extends TransactionBaseService {
this.discountConditionRepository_ = discountConditionRepository this.discountConditionRepository_ = discountConditionRepository
this.discountConditionService_ = discountConditionService this.discountConditionService_ = discountConditionService
this.totalsService_ = totalsService this.totalsService_ = totalsService
this.newTotalsService_ = newTotalsService
this.productService_ = productService this.productService_ = productService
this.regionService_ = regionService this.regionService_ = regionService
this.customerService_ = customerService this.customerService_ = customerService
@@ -571,7 +575,7 @@ class DiscountService extends TransactionBaseService {
lineItem: LineItem, lineItem: LineItem,
cart: Cart cart: Cart
): Promise<number> { ): Promise<number> {
return await this.atomicPhase_(async () => { return await this.atomicPhase_(async (transactionManager) => {
let adjustment = 0 let adjustment = 0
if (!lineItem.allow_discounts) { if (!lineItem.allow_discounts) {
@@ -589,15 +593,18 @@ class DiscountService extends TransactionBaseService {
) && ) &&
lineItem.includes_tax lineItem.includes_tax
) { ) {
const lineItemTotals = await this.totalsService_.getLineItemTotals( const calculationContext = await this.totalsService_
lineItem, .withTransaction(transactionManager)
cart, .getCalculationContext(cart, {
{ exclude_shipping: true,
include_tax: true, })
exclude_gift_cards: true, const lineItemTotals = await this.newTotalsService_
} .withTransaction(transactionManager)
) .getLineItemTotals([lineItem], {
fullItemPrice = lineItemTotals.subtotal includeTax: true,
calculationContext,
})
fullItemPrice = lineItemTotals[lineItem.id].subtotal
} }
if (type === DiscountRuleType.PERCENTAGE) { if (type === DiscountRuleType.PERCENTAGE) {
+4 -1
View File
@@ -274,10 +274,13 @@ class DraftOrderService extends TransactionBaseService {
} }
} }
const createdCart = await cartServiceTx.create({ let createdCart = await cartServiceTx.create({
type: CartType.DRAFT_ORDER, type: CartType.DRAFT_ORDER,
...rawCart, ...rawCart,
}) })
createdCart = await cartServiceTx.retrieve(createdCart.id, {
relations: ["discounts", "discounts.rule", "items", "region"],
})
const draftOrder = draftOrderRepo.create({ const draftOrder = draftOrderRepo.create({
cart_id: createdCart.id, cart_id: createdCart.id,
@@ -184,14 +184,18 @@ class LineItemAdjustmentService extends TransactionBaseService {
generatedLineItem: LineItem, generatedLineItem: LineItem,
context: AdjustmentContext context: AdjustmentContext
): Promise<GeneratedAdjustment[]> { ): Promise<GeneratedAdjustment[]> {
const lineItem = {
...generatedLineItem,
} as LineItem
return this.atomicPhase_(async (manager) => { return this.atomicPhase_(async (manager) => {
// if lineItem should not be discounted // if lineItem should not be discounted
// or lineItem is a return line item // or lineItem is a return line item
// or the cart does not have any discounts // or the cart does not have any discounts
// then do nothing // then do nothing
if ( if (
!generatedLineItem.allow_discounts || !lineItem.allow_discounts ||
generatedLineItem.is_return || lineItem.is_return ||
!cart?.discounts?.length !cart?.discounts?.length
) { ) {
return [] return []
@@ -217,9 +221,11 @@ class LineItemAdjustmentService extends TransactionBaseService {
return [] 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( const amount = await this.discountService.calculateDiscountForLineItem(
discount.id, discount.id,
generatedLineItem, lineItem,
cart cart
) )
+4 -1
View File
@@ -256,7 +256,10 @@ class LineItemService extends TransactionBaseService {
const lineItemRepo = transactionManager.getCustomRepository( const lineItemRepo = transactionManager.getCustomRepository(
this.lineItemRepository_ this.lineItemRepository_
) )
const lineItem = lineItemRepo.create(rawLineItem) const lineItem = lineItemRepo.create({
...rawLineItem,
variant,
})
if (context.cart) { if (context.cart) {
const adjustments = await this.lineItemAdjustmentService_ const adjustments = await this.lineItemAdjustmentService_
@@ -104,6 +104,13 @@ export default class NewTotalsService extends TransactionBaseService {
lineItemsTaxLinesMap[item.id] = item.tax_lines ?? [] lineItemsTaxLinesMap[item.id] = item.tax_lines ?? []
}) })
} else { } 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_ const { lineItemsTaxLines } = await this.taxProviderService_
.withTransaction(manager) .withTransaction(manager)
.getTaxLinesMap(items, calculationContext) .getTaxLinesMap(items, calculationContext)
+3 -10
View File
@@ -703,7 +703,7 @@ class TotalsService extends TransactionBaseService {
cartOrOrder: Cart | Order, cartOrOrder: Cart | Order,
discount: Discount discount: Discount
): LineDiscountAmount[] { ): LineDiscountAmount[] {
let merged: LineItem[] = [...cartOrOrder.items] let merged: LineItem[] = [...(cartOrOrder.items ?? [])]
// merge items from order with items from order swaps // merge items from order with items from order swaps
if ("swaps" in cartOrOrder && cartOrOrder.swaps.length) { if ("swaps" in cartOrOrder && cartOrOrder.swaps.length) {
@@ -842,16 +842,9 @@ class TotalsService extends TransactionBaseService {
} }
taxLines = lineItem.tax_lines taxLines = lineItem.tax_lines
} else { } else {
const orderLines = await this.taxProviderService_ taxLines = (await this.taxProviderService_
.withTransaction(this.manager_) .withTransaction(this.manager_)
.getTaxLines(cartOrOrder.items, calculationContext) .getTaxLines([lineItem], calculationContext)) as LineItemTaxLine[]
taxLines = orderLines.filter((ol) => {
if ("item_id" in ol) {
return ol.item_id === lineItem.id
}
return false
}) as LineItemTaxLine[]
} }
} }