feat: line item adjustments (#1319)

* add: crud services + model + totals

* fix: enforce unique constraint on line item adjustment model and update service (#1241)

* add: unique constraint on model + fix service

* fix: unique constraint

* fix: add cascade on delete + fix discount relation

* fix: remove optional unique prop

* add: tests for ensuring line item adjustment db constraints (#1279)

* add: tests for ensuring db constraints

* fix: use given when then

* feat: adjust cart to include line item adjustments (#1242)

* fix: cart service + cart tests

* fix: remaining tests

* fix: swap tests

* fix: add relationship + fix oas

* refactor: applyDiscount

* fix: refactor applyDiscount and fix + add unit tests

* fix: plugins tests

* feat: line item adjustments draft orders (#1243)

* fix: draft order tests

* fix: constraint

* fix: wrong variable name

* fix: unique constraint

* progress: add tests

* fix: add cascade on delete + fix discount relation

* fix: remove optional unique prop

* fix: cart removeLineItem + tests

* fix: cart unit tests

* fix: update snapshot

* remove: verbose option

* rename arg

Co-authored-by: Sebastian Rindom <skrindom@gmail.com>

* add: create adjustments for swap additional_items

* add: create adjustments for return lines

* fix: unit test for creating adjustment for additional_items

* fix: create adjustments only for non return items + no deletion when item is a return item

* add: integration tests

* refactor: use refreshAdjustments method

* refactor test

Co-authored-by: Sebastian Rindom <skrindom@gmail.com>

Co-authored-by: Sebastian Rindom <skrindom@gmail.com>

Co-authored-by: Sebastian Rindom <skrindom@gmail.com>
This commit is contained in:
Zakaria El Asri
2022-04-12 15:49:31 +02:00
committed by GitHub
co-authored by Sebastian Rindom
parent 607a382b4e
commit 1cfeb5dbd8
45 changed files with 3290 additions and 581 deletions
+557 -4
View File
@@ -16,7 +16,7 @@ const { initDb, useDb } = require("../../../helpers/use-db")
const cartSeeder = require("../../helpers/cart-seeder")
const productSeeder = require("../../helpers/product-seeder")
const swapSeeder = require("../../helpers/swap-seeder")
const { simpleCartFactory } = require("../../factories")
const { simpleCartFactory, simpleLineItemFactory } = require("../../factories")
const {
simpleDiscountFactory,
} = require("../../factories/simple-discount-factory")
@@ -235,6 +235,131 @@ describe("/store/carts", () => {
unit_price: 1000,
variant_id: "test-variant-quantity",
quantity: 1,
adjustments: [],
}),
])
})
it("adds line item to cart containing a total fixed discount", async () => {
const api = useApi()
const response = await api
.post(
"/store/carts/test-cart-w-total-fixed-discount/line-items",
{
variant_id: "test-variant-quantity",
quantity: 2,
},
{ withCredentials: true }
)
.catch((err) => console.log(err))
expect(response.data.cart.items).toEqual([
expect.objectContaining({
cart_id: "test-cart-w-total-fixed-discount",
unit_price: 1000,
variant_id: "test-variant-quantity",
quantity: 2,
adjustments: [
expect.objectContaining({
amount: 100,
discount_id: "total-fixed-100",
description: "discount",
}),
],
}),
])
})
it("adds line item to cart containing a total percentage discount", async () => {
const api = useApi()
const response = await api
.post(
"/store/carts/test-cart-w-total-percentage-discount/line-items",
{
variant_id: "test-variant-quantity",
quantity: 2,
},
{ withCredentials: true }
)
.catch((err) => console.log(err))
expect(response.data.cart.items).toEqual([
expect.objectContaining({
cart_id: "test-cart-w-total-percentage-discount",
unit_price: 1000,
variant_id: "test-variant-quantity",
quantity: 2,
adjustments: [
expect.objectContaining({
amount: 200,
discount_id: "10Percent",
description: "discount",
}),
],
}),
])
})
it("adds line item to cart containing an item fixed discount", async () => {
const api = useApi()
const response = await api
.post(
"/store/carts/test-cart-w-item-fixed-discount/line-items",
{
variant_id: "test-variant-quantity",
quantity: 2,
},
{ withCredentials: true }
)
.catch((err) => console.log(err))
expect(response.data.cart.items).toEqual([
expect.objectContaining({
cart_id: "test-cart-w-item-fixed-discount",
unit_price: 1000,
variant_id: "test-variant-quantity",
quantity: 2,
adjustments: [
expect.objectContaining({
amount: 400,
discount_id: "item-fixed-200",
description: "discount",
}),
],
}),
])
})
it("adds line item to cart containing an item percentage discount", async () => {
const api = useApi()
const response = await api
.post(
"/store/carts/test-cart-w-item-percentage-discount/line-items",
{
variant_id: "test-variant-quantity",
quantity: 2,
},
{ withCredentials: true }
)
.catch((err) => console.log(err))
expect(response.data.cart.items).toEqual([
expect.objectContaining({
cart_id: "test-cart-w-item-percentage-discount",
unit_price: 1000,
variant_id: "test-variant-quantity",
quantity: 2,
adjustments: [
expect.objectContaining({
amount: 300,
discount_id: "item-percentage-15",
description: "discount",
}),
],
}),
])
})
@@ -242,7 +367,6 @@ describe("/store/carts", () => {
it("adds line item to cart time limited sale", async () => {
const api = useApi()
// Add standard line item to cart
const response = await api
.post(
"/store/carts/test-cart/line-items",
@@ -305,7 +429,6 @@ describe("/store/carts", () => {
it("adds line item with quantity to cart with quantity discount", async () => {
const api = useApi()
// Add standard line item to cart
const response = await api
.post(
"/store/carts/test-cart/line-items",
@@ -330,7 +453,6 @@ describe("/store/carts", () => {
it("adds line item with quantity to cart with quantity discount no ceiling", async () => {
const api = useApi()
// Add standard line item to cart
const response = await api
.post(
"/store/carts/test-cart/line-items",
@@ -351,6 +473,421 @@ describe("/store/carts", () => {
}),
])
})
describe("ensures correct line item adjustment generation", () => {
const discountData = {
code: "MEDUSA185DKK",
id: "medusa-185",
rule: {
allocation: "total",
type: "fixed",
value: 185,
},
regions: ["test-region"],
}
let discountCart, discount
beforeEach(async () => {
try {
discount = await simpleDiscountFactory(
dbConnection,
discountData,
100
)
discountCart = await simpleCartFactory(
dbConnection,
{
id: "discount-cart",
customer: "test-customer",
region: "test-region",
shipping_address: {
address_1: "next door",
first_name: "lebron",
last_name: "james",
country_code: "dk",
postal_code: "100",
},
line_items: [
{
id: "test-li",
variant_id: "test-variant",
quantity: 1,
unit_price: 100,
adjustments: [
{
amount: 185,
description: "discount",
discount_id: "medusa-185",
},
],
},
],
shipping_methods: [
{
shipping_option: "test-option",
price: 1000,
},
],
},
100
)
await dbConnection.manager
.createQueryBuilder()
.relation(Cart, "discounts")
.of(discountCart)
.add(discount)
} catch (err) {
console.log(err)
}
})
afterEach(async () => {
await doAfterEach()
})
it("updates an old line item adjustment when a new line item is added to a discount cart", async () => {
const api = useApi()
const response = await api
.post(
"/store/carts/discount-cart/line-items",
{
quantity: 1,
variant_id: "test-variant-quantity",
},
{
withCredentials: true,
}
)
.catch((err) => console.log(err))
expect(response.data.cart.items.length).toEqual(2)
expect(response.data.cart.items).toEqual(
expect.arrayContaining([
expect.objectContaining({
adjustments: [
expect.objectContaining({
item_id: "test-li",
amount: 17,
discount_id: "medusa-185",
}),
],
}),
expect.objectContaining({
adjustments: [
expect.objectContaining({
amount: 168,
discount_id: "medusa-185",
}),
],
}),
])
)
})
it("updates an existing item adjustment when a line item is updated", async () => {
const api = useApi()
await simpleLineItemFactory(
dbConnection,
{
id: "line-item-2",
cart_id: discountCart.id,
variant_id: "test-variant-quantity",
unit_price: 950,
quantity: 1,
adjustments: [
{
id: "lia-2",
amount: 92,
description: "discount",
discount_id: "medusa-185",
},
],
},
100
)
const response = await api
.post(
"/store/carts/discount-cart/line-items/line-item-2",
{
quantity: 2,
},
{
withCredentials: true,
}
)
.catch((err) => console.log(err))
expect(response.data.cart.items.length).toEqual(2)
expect(response.data.cart.items).toEqual(
expect.arrayContaining([
expect.objectContaining({
adjustments: [
expect.objectContaining({
item_id: "test-li",
discount_id: "medusa-185",
amount: 9,
}),
],
}),
expect.objectContaining({
adjustments: [
expect.objectContaining({
item_id: "line-item-2",
amount: 176,
discount_id: "medusa-185",
}),
],
}),
])
)
})
it("updates an existing item adjustment when a line item is deleted from a discount cart", async () => {
const api = useApi()
await simpleLineItemFactory(
dbConnection,
{
id: "line-item-2",
cart_id: discountCart.id,
variant_id: "test-variant-quantity",
unit_price: 1000,
quantity: 1,
adjustments: [
{
id: "lia-2",
amount: 93,
description: "discount",
discount_id: "medusa-185",
},
],
},
100
)
const response = await api
.delete("/store/carts/discount-cart/line-items/test-li", {
withCredentials: true,
})
.catch((err) => console.log(err))
expect(response.data.cart.items.length).toEqual(1)
expect(response.data.cart.items).toEqual([
expect.objectContaining({
adjustments: [
expect.objectContaining({
item_id: "line-item-2",
amount: 185,
discount_id: "medusa-185",
}),
],
}),
])
})
})
})
describe("POST /store/carts/:id/line-items/:line_id", () => {
beforeEach(async () => {
try {
await cartSeeder(dbConnection)
await swapSeeder(dbConnection)
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
await doAfterEach()
})
it("updates line item of cart", async () => {
const api = useApi()
const response = await api
.post(
"/store/carts/test-cart-3/line-items/test-item3/",
{
quantity: 3,
},
{ withCredentials: true }
)
.catch((err) => console.log(err))
expect(response.data.cart.items).toEqual([
expect.objectContaining({
cart_id: "test-cart-3",
unit_price: 8000,
variant_id: "test-variant-sale-cg",
quantity: 3,
adjustments: [],
}),
])
})
it("updates line item of a cart containing a total fixed discount", async () => {
const api = useApi()
await simpleLineItemFactory(dbConnection, {
id: "test-li-disc",
allow_discounts: true,
title: "Line Item Disc",
thumbnail: "https://test.js/1234",
unit_price: 1000,
quantity: 1,
variant_id: "test-variant-quantity",
cart_id: "test-cart-w-total-fixed-discount",
})
const response = await api
.post(
`store/carts/test-cart-w-total-fixed-discount/line-items/test-li-disc`,
{
quantity: 3,
},
{ withCredentials: true }
)
.catch((err) => console.log(err))
expect(response.data.cart.items).toEqual([
expect.objectContaining({
cart_id: "test-cart-w-total-fixed-discount",
unit_price: 1000,
variant_id: "test-variant-quantity",
quantity: 3,
adjustments: [
expect.objectContaining({
amount: 100,
discount_id: "total-fixed-100",
description: "discount",
}),
],
}),
])
})
it("updates line item of a cart containing a total percentage discount", async () => {
const api = useApi()
await simpleLineItemFactory(dbConnection, {
id: "test-li-disc",
allow_discounts: true,
title: "Line Item Disc",
thumbnail: "https://test.js/1234",
unit_price: 1000,
quantity: 1,
variant_id: "test-variant-quantity",
cart_id: "test-cart-w-total-percentage-discount",
})
const response = await api
.post(
"/store/carts/test-cart-w-total-percentage-discount/line-items/test-li-disc",
{
quantity: 10,
},
{ withCredentials: true }
)
.catch((err) => console.log(err))
expect(response.data.cart.items).toEqual([
expect.objectContaining({
cart_id: "test-cart-w-total-percentage-discount",
unit_price: 1000,
variant_id: "test-variant-quantity",
quantity: 10,
adjustments: [
expect.objectContaining({
amount: 1000,
discount_id: "10Percent",
description: "discount",
}),
],
}),
])
})
it("updates line item of a cart containing an item fixed discount", async () => {
const api = useApi()
await simpleLineItemFactory(dbConnection, {
id: "test-li-disc",
allow_discounts: true,
title: "Line Item Disc",
thumbnail: "https://test.js/1234",
unit_price: 1000,
quantity: 1,
variant_id: "test-variant-quantity",
cart_id: "test-cart-w-item-fixed-discount",
})
const response = await api
.post(
"/store/carts/test-cart-w-item-fixed-discount/line-items/test-li-disc",
{
quantity: 4,
},
{ withCredentials: true }
)
.catch((err) => console.log(err))
expect(response.data.cart.items).toEqual([
expect.objectContaining({
cart_id: "test-cart-w-item-fixed-discount",
unit_price: 1000,
variant_id: "test-variant-quantity",
quantity: 4,
adjustments: [
expect.objectContaining({
amount: 800,
discount_id: "item-fixed-200",
description: "discount",
}),
],
}),
])
})
it("updates line item of a cart containing an item percentage discount", async () => {
const api = useApi()
await simpleLineItemFactory(dbConnection, {
id: "test-li-disc",
allow_discounts: true,
title: "Line Item Disc",
thumbnail: "https://test.js/1234",
unit_price: 1000,
quantity: 1,
variant_id: "test-variant-quantity",
cart_id: "test-cart-w-item-percentage-discount",
})
const response = await api
.post(
"/store/carts/test-cart-w-item-percentage-discount/line-items/test-li-disc",
{
quantity: 3,
},
{ withCredentials: true }
)
.catch((err) => console.log(err))
expect(response.data.cart.items).toEqual([
expect.objectContaining({
cart_id: "test-cart-w-item-percentage-discount",
unit_price: 1000,
variant_id: "test-variant-quantity",
quantity: 3,
adjustments: [
expect.objectContaining({
amount: 450,
discount_id: "item-percentage-15",
description: "discount",
}),
],
}),
])
})
})
describe("POST /store/carts/:id", () => {
@@ -1202,6 +1739,22 @@ describe("/store/carts", () => {
.catch((err) => console.log(err))
// Ensure that the discount is only applied to the standard item
const itemId = cartWithGiftcard.data.cart.items[0].id
expect(cartWithGiftcard.data.cart.items).toEqual(
expect.arrayContaining([
expect.objectContaining({
variant_id: "test-variant",
quantity: 1,
adjustments: [
expect.objectContaining({
discount_id: "10Percent",
amount: 100,
item_id: itemId,
}),
],
}),
])
)
expect(cartWithGiftcard.data.cart.total).toBe(1900) // 1000 (giftcard) + 900 (standard item with 10% discount)
expect(cartWithGiftcard.data.cart.discount_total).toBe(100)
expect(cartWithGiftcard.status).toEqual(200)