feat: Add DiscountConditions (#1230)

* feat: Add DiscountCondition entity + Join table per relation (#1146)

* feat: Convert DiscountService to TypeScript (#1149)

* feat: Add DiscountRepository + bulk insert and remove (#1156)

* feat: Add `conditions` to payload in `POST /discounts` and `POST /discounts/:id` (#1170)

* feat: Add DiscountRuleCondition entity

* fix relation

* fix join key

* Add discount rule condition repo

* add join table per relation

* Convert DiscountService to TypeScript

* feat: Add DiscountConditionRepository

* Add migration + remove use of valid_for

* revert changes to files, not done yet

* init work on create discount endpoint

* Add conditions to create discount endpoint

* Add conditions to update discount endpoint

* Add unique constraint to discount condition

* integration tests passing

* fix imports of models

* fix tests (excluding totals calculations)

* Fix commented code

* add unique constraint on discount condition

* Add generic way of generating retrieve configs

* Requested changes + ExactlyOne validator

* Remove isLocal flag from error handler

* Use postgres error constant

* remove commented code

* feat: Add `isValidForProduct` to check if Discount is valid for a given Product (#1172)

* feat: Add `canApplyForCustomer` to check if Discount is valid for customer groups (#1212)

* feat: Add `calculateDiscountForLineItem` (#1224)

* feat: Adds discount condition test factory (#1228)

* Remove use of valid_for

* Tests passing

* Remove valid_for form relations

* Add integration tests for applying discounts to cart
This commit is contained in:
Oliver Windall Juhl authored and GitHub committed 2022-03-24 16:47:50 +01:00
1 parent b7f699654b
commit a610805917
60 files changed
+4805 -2021

No files matched your search

+197 -120
View File
@@ -1,7 +1,5 @@
import DiscountService from "../discount"
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import { MedusaError } from "medusa-core-utils"
import { exportAllDeclaration } from "@babel/types"
import DiscountService from "../discount"
describe("DiscountService", () => {
describe("create", () => {
@@ -15,7 +13,7 @@ describe("DiscountService", () => {
id: IdMap.getId("france"),
}
},
withTransaction: function () {
withTransaction: function() {
return this
},
}
@@ -309,121 +307,6 @@ describe("DiscountService", () => {
})
})
describe("addValidProduct", () => {
const discountRepository = MockRepository({
findOne: () =>
Promise.resolve({
id: IdMap.getId("total10"),
rule: {
id: IdMap.getId("test-rule"),
valid_for: [{ id: IdMap.getId("test-product") }],
},
}),
})
const discountRuleRepository = MockRepository({})
const productService = {
retrieve: () => {
return {
id: IdMap.getId("test-product-2"),
}
},
}
const discountService = new DiscountService({
manager: MockManager,
discountRepository,
discountRuleRepository,
productService,
})
beforeEach(() => {
jest.clearAllMocks()
})
it("successfully adds a product", async () => {
await discountService.addValidProduct(
IdMap.getId("total10"),
IdMap.getId("test-product-2")
)
expect(discountRuleRepository.save).toHaveBeenCalledTimes(1)
expect(discountRuleRepository.save).toHaveBeenCalledWith({
id: IdMap.getId("test-rule"),
valid_for: [
{ id: IdMap.getId("test-product") },
{ id: IdMap.getId("test-product-2") },
],
})
})
it("successfully resolves if product already exists", async () => {
await discountService.addValidProduct(
IdMap.getId("total10"),
IdMap.getId("test-product")
)
expect(discountRuleRepository.save).toHaveBeenCalledTimes(0)
})
})
describe("removeValidVariant", () => {
const discountRepository = MockRepository({
findOne: () =>
Promise.resolve({
id: IdMap.getId("total10"),
rule: {
id: IdMap.getId("test-rule"),
valid_for: [{ id: IdMap.getId("test-product") }],
},
}),
})
const discountRuleRepository = MockRepository({})
const productService = {
retrieve: () => {
return {
id: IdMap.getId("test-product"),
}
},
}
const discountService = new DiscountService({
manager: MockManager,
discountRepository,
discountRuleRepository,
productService,
})
beforeEach(() => {
jest.clearAllMocks()
})
it("successfully removes a product", async () => {
await discountService.removeValidProduct(
IdMap.getId("total10"),
IdMap.getId("test-product")
)
expect(discountRuleRepository.save).toHaveBeenCalledTimes(1)
expect(discountRuleRepository.save).toHaveBeenCalledWith({
id: IdMap.getId("test-rule"),
valid_for: [],
})
})
it("successfully resolve if product does not exist", async () => {
await discountService.removeValidProduct(
IdMap.getId("total10"),
IdMap.getId("test-product-2")
)
expect(discountRuleRepository.save).toHaveBeenCalledTimes(0)
})
})
describe("addRegion", () => {
const discountRepository = MockRepository({
findOne: (q) => {
@@ -638,7 +521,7 @@ describe("DiscountService", () => {
expect(discountRepository.findAndCount).toHaveBeenCalledWith({
where: expect.anything(),
skip: 0,
take: 50,
take: 20,
order: { created_at: "DESC" },
})
})
@@ -658,4 +541,198 @@ describe("DiscountService", () => {
})
})
})
describe("calculateDiscountForLineItem", () => {
const discountRepository = MockRepository({
findOne: ({ where }) => {
if (where.id === "disc_percentage") {
return Promise.resolve({
code: "MEDUSA",
rule: {
type: "percentage",
allocation: "total",
value: 15,
},
})
}
if (where.id === "disc_fixed_total") {
return Promise.resolve({
code: "MEDUSA",
rule: {
type: "fixed",
allocation: "total",
value: 400,
},
})
}
return Promise.resolve({
id: "disc_fixed",
code: "MEDUSA",
rule: {
type: "fixed",
allocation: "item",
value: 200,
},
})
},
})
const totalsService = {
getSubtotal: () => {
return 1100
},
}
const discountService = new DiscountService({
manager: MockManager,
discountRepository,
totalsService,
})
beforeEach(() => {
jest.clearAllMocks()
})
it("correctly calculates fixed + item discount", async () => {
const adjustment = await discountService.calculateDiscountForLineItem(
"disc_fixed",
{
unit_price: 300,
quantity: 2,
allow_discounts: true,
}
)
expect(adjustment).toBe(400)
})
it("correctly calculates fixed + total discount", async () => {
const adjustment1 = await discountService.calculateDiscountForLineItem(
"disc_fixed_total",
{
unit_price: 400,
quantity: 2,
allow_discounts: true,
}
)
const adjustment2 = await discountService.calculateDiscountForLineItem(
"disc_fixed_total",
{
unit_price: 300,
quantity: 1,
allow_discounts: true,
}
)
expect(adjustment1).toBe(291)
expect(adjustment2).toBe(109)
})
it("returns line item amount if discount exceeds lime item price", async () => {
const adjustment = await discountService.calculateDiscountForLineItem(
"disc_fixed",
{
unit_price: 100,
quantity: 1,
allow_discounts: true,
}
)
expect(adjustment).toBe(100)
})
it("correctly calculates percentage discount", async () => {
const adjustment = await discountService.calculateDiscountForLineItem(
"disc_percentage",
{
unit_price: 400,
quantity: 2,
allow_discounts: true,
}
)
expect(adjustment).toBe(120)
})
it("returns full amount if exceeds total line item amount", async () => {
const adjustment = await discountService.calculateDiscountForLineItem(
"disc_fixed",
{
unit_price: 50,
quantity: 2,
allow_discounts: true,
}
)
expect(adjustment).toBe(100)
})
it("returns early if discounts are not allowed", async () => {
const adjustment = await discountService.calculateDiscountForLineItem(
"disc_percentage",
{
unit_price: 400,
quantity: 2,
allow_discounts: false,
}
)
expect(adjustment).toBe(0)
})
})
describe("canApplyForCustomer", () => {
const discountConditionRepository = {
canApplyForCustomer: jest
.fn()
.mockImplementation(() => Promise.resolve(true)),
}
const customerService = {
retrieve: jest.fn().mockImplementation((id) => {
if (id === "customer-no-groups") {
return Promise.resolve({ id: "customer-no-groups" })
}
if (id === "customer-with-groups") {
return Promise.resolve({
id: "customer-with-groups",
groups: [{ id: "group-1" }],
})
}
}),
}
const discountService = new DiscountService({
manager: MockManager,
discountConditionRepository,
customerService,
})
it("returns false on undefined customer id", async () => {
const res = await discountService.canApplyForCustomer("rule-1")
expect(res).toBe(false)
expect(
discountConditionRepository.canApplyForCustomer
).toHaveBeenCalledTimes(0)
})
it("returns true on customer with groups", async () => {
const res = await discountService.canApplyForCustomer(
"rule-1",
"customer-with-groups"
)
expect(res).toBe(true)
expect(
discountConditionRepository.canApplyForCustomer
).toHaveBeenCalledTimes(1)
expect(
discountConditionRepository.canApplyForCustomer
).toHaveBeenCalledWith("rule-1", "customer-with-groups")
})
})
})