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:
1 parent
607a382b4e
commit
1cfeb5dbd8
45 files changed
+3238
-529
No files matched your search
@@ -297,6 +297,62 @@ describe("/admin/draft-orders", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("creates a draft order with discount and line item", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
email: "oli@test.dk",
|
||||||
|
shipping_address: "oli-shipping",
|
||||||
|
discounts: [{ code: "TEST" }],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
variant_id: "test-variant",
|
||||||
|
quantity: 2,
|
||||||
|
metadata: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
region_id: "test-region",
|
||||||
|
customer_id: "oli-test",
|
||||||
|
shipping_methods: [
|
||||||
|
{
|
||||||
|
option_id: "test-option",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.post("/admin/draft-orders", payload, {
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
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: "test-discount",
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it("creates a draft order with created shipping address", async () => {
|
it("creates a draft order with created shipping address", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
|
|||||||
@@ -1555,6 +1555,116 @@ describe("/admin/orders", () => {
|
|||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("Given an existing discount order", () => {
|
||||||
|
describe("When a store operator attemps to create a swap form the discount order", () => {
|
||||||
|
it("Then should successfully create the swap", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
"/admin/orders/test-order/swaps",
|
||||||
|
{
|
||||||
|
return_items: [
|
||||||
|
{
|
||||||
|
item_id: "test-item",
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
additional_items: [{ variant_id: "test-variant-2", quantity: 1 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const swapCartId = response.data.order.swaps[0].cart_id
|
||||||
|
|
||||||
|
const swapCartRes = await api.get(`/store/carts/${swapCartId}`, {
|
||||||
|
headers: {
|
||||||
|
authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const cart = swapCartRes.data.cart
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(cart.items.length).toEqual(2)
|
||||||
|
expect(cart.items).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
unit_price: -8000,
|
||||||
|
adjustments: [
|
||||||
|
expect.objectContaining({
|
||||||
|
amount: -800,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
unit_price: 8000,
|
||||||
|
adjustments: [
|
||||||
|
expect.objectContaining({
|
||||||
|
amount: 800,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
expect(cart.total).toEqual(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("And given a swap cart", () => {
|
||||||
|
describe("When a line item is added to the swap cart", () => {
|
||||||
|
it("Then should not delete existing return line item adjustments", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const createSwapRes = await api.post(
|
||||||
|
"/admin/orders/test-order/swaps",
|
||||||
|
{
|
||||||
|
return_items: [
|
||||||
|
{
|
||||||
|
item_id: "test-item",
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
additional_items: [{ variant_id: "test-variant", quantity: 1 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const swapCartId = createSwapRes.data.order.swaps[0].cart_id
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/store/carts/${swapCartId}/line-items`,
|
||||||
|
{
|
||||||
|
variant_id: "test-variant-2",
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const cart = response.data.cart
|
||||||
|
const items = cart.items
|
||||||
|
const [returnItem] = items.filter((i) => i.is_return)
|
||||||
|
expect(returnItem.adjustments).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
amount: -800,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
expect(cart.total).toBe(7200)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it("creates a swap with custom shipping options", async () => {
|
it("creates a swap with custom shipping options", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ exports[`Claims creates a replace claim 1`] = `
|
|||||||
Object {
|
Object {
|
||||||
"additional_items": Array [
|
"additional_items": Array [
|
||||||
Object {
|
Object {
|
||||||
|
"adjustments": Array [],
|
||||||
"allow_discounts": true,
|
"allow_discounts": true,
|
||||||
"cart_id": null,
|
"cart_id": null,
|
||||||
"claim_order_id": StringMatching /\\^claim_\\*/,
|
"claim_order_id": StringMatching /\\^claim_\\*/,
|
||||||
|
|||||||
@@ -0,0 +1,222 @@
|
|||||||
|
const path = require("path")
|
||||||
|
const { LineItemAdjustment } = require("@medusajs/medusa")
|
||||||
|
const setupServer = require("../../../helpers/setup-server")
|
||||||
|
const { useApi } = require("../../../helpers/use-api")
|
||||||
|
const { initDb, useDb } = require("../../../helpers/use-db")
|
||||||
|
const cartSeeder = require("../../helpers/cart-seeder")
|
||||||
|
const { simpleCartFactory, simpleLineItemFactory } = require("../../factories")
|
||||||
|
const {
|
||||||
|
simpleDiscountFactory,
|
||||||
|
} = require("../../factories/simple-discount-factory")
|
||||||
|
|
||||||
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
|
describe("Line Item Adjustments", () => {
|
||||||
|
let dbConnection
|
||||||
|
let medusaProcess
|
||||||
|
|
||||||
|
const doAfterEach = async () => {
|
||||||
|
const db = useDb()
|
||||||
|
return await db.teardown()
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
|
try {
|
||||||
|
dbConnection = await initDb({ cwd })
|
||||||
|
medusaProcess = await setupServer({ cwd })
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.shutdown()
|
||||||
|
medusaProcess.kill()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Tests database constraints", () => {
|
||||||
|
let cart,
|
||||||
|
discount,
|
||||||
|
lineItemId = "line-test"
|
||||||
|
beforeEach(async () => {
|
||||||
|
try {
|
||||||
|
await cartSeeder(dbConnection)
|
||||||
|
discount = await simpleDiscountFactory(dbConnection, {
|
||||||
|
code: "MEDUSATEST",
|
||||||
|
id: "discount-test",
|
||||||
|
rule: {
|
||||||
|
value: 100,
|
||||||
|
type: "fixed",
|
||||||
|
allocation: "total",
|
||||||
|
},
|
||||||
|
regions: ["test-region"],
|
||||||
|
})
|
||||||
|
cart = await simpleCartFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
|
customer: "test-customer",
|
||||||
|
id: "cart-test",
|
||||||
|
line_items: [
|
||||||
|
{
|
||||||
|
id: lineItemId,
|
||||||
|
variant_id: "test-variant",
|
||||||
|
cart_id: "cart-test",
|
||||||
|
unit_price: 1000,
|
||||||
|
quantity: 1,
|
||||||
|
adjustments: [
|
||||||
|
{
|
||||||
|
amount: 10,
|
||||||
|
discount_id: discount.id,
|
||||||
|
description: "discount",
|
||||||
|
item_id: lineItemId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
region: "test-region",
|
||||||
|
shipping_address: {
|
||||||
|
address_1: "test",
|
||||||
|
country_code: "us",
|
||||||
|
first_name: "chris",
|
||||||
|
last_name: "rock",
|
||||||
|
postal_code: "101",
|
||||||
|
},
|
||||||
|
shipping_methods: [
|
||||||
|
{
|
||||||
|
shipping_option: "test-option",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
100
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await doAfterEach()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await doAfterEach()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Given an existing line item, a discount, and a line item adjustment for both", () => {
|
||||||
|
describe("When creating an adjustment for another line item w. same discount", () => {
|
||||||
|
test("Then should create an adjustment", async () => {
|
||||||
|
const createLineItemWithAdjustment = async () => {
|
||||||
|
return await simpleLineItemFactory(dbConnection, {
|
||||||
|
id: "line-test-2",
|
||||||
|
variant_id: "test-variant-quantity",
|
||||||
|
cart_id: "test-cart",
|
||||||
|
unit_price: 1000,
|
||||||
|
quantity: 1,
|
||||||
|
adjustments: [
|
||||||
|
{
|
||||||
|
amount: 10,
|
||||||
|
discount_id: discount.id,
|
||||||
|
description: "discount",
|
||||||
|
item_id: "line-test-2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(createLineItemWithAdjustment()).resolves.toEqual(
|
||||||
|
expect.anything()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("When creating an adjustment for another line item w. null discount", () => {
|
||||||
|
test("Then should create an adjustment", async () => {
|
||||||
|
const createAdjustmentNullDiscount = async () => {
|
||||||
|
return await dbConnection.manager.insert(LineItemAdjustment, {
|
||||||
|
id: "lia-1",
|
||||||
|
item_id: lineItemId,
|
||||||
|
amount: 35,
|
||||||
|
description: "custom discount",
|
||||||
|
discount_id: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(createAdjustmentNullDiscount()).resolves.toEqual(
|
||||||
|
expect.anything()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("When creating multiple adjustments w. a null discount_id", () => {
|
||||||
|
test("Then should create multiple adjustments", async () => {
|
||||||
|
const createAdjustmentsNullDiscount = async () => {
|
||||||
|
await dbConnection.manager.insert(LineItemAdjustment, {
|
||||||
|
id: "lia-1",
|
||||||
|
item_id: lineItemId,
|
||||||
|
amount: 35,
|
||||||
|
description: "custom discount",
|
||||||
|
discount_id: null,
|
||||||
|
})
|
||||||
|
return await dbConnection.manager.insert(LineItemAdjustment, {
|
||||||
|
id: "lia-2",
|
||||||
|
item_id: lineItemId,
|
||||||
|
amount: 100,
|
||||||
|
description: "custom discount",
|
||||||
|
discount_id: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
expect(createAdjustmentsNullDiscount()).resolves.toEqual(
|
||||||
|
expect.anything()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("When creating an adjustment w. for same line item and different discount", () => {
|
||||||
|
test("Then should create an adjustment", async () => {
|
||||||
|
const createAdjustment = async () => {
|
||||||
|
await simpleDiscountFactory(dbConnection, {
|
||||||
|
code: "ANOTHER",
|
||||||
|
id: "discount-2",
|
||||||
|
rule: {
|
||||||
|
value: 10,
|
||||||
|
type: "percentage",
|
||||||
|
allocation: "item",
|
||||||
|
},
|
||||||
|
regions: ["test-region"],
|
||||||
|
})
|
||||||
|
|
||||||
|
return await dbConnection.manager.insert(LineItemAdjustment, {
|
||||||
|
id: "lia-1",
|
||||||
|
item_id: lineItemId,
|
||||||
|
amount: 10,
|
||||||
|
description: "discount",
|
||||||
|
discount_id: "discount-2",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(createAdjustment()).resolves.toEqual(expect.anything())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("When creating an adjustment w. existing line item and discount pair", () => {
|
||||||
|
test("Then should throw a duplicate error", async () => {
|
||||||
|
const createDuplicateAdjustment = async () =>
|
||||||
|
await dbConnection.manager.insert(LineItemAdjustment, {
|
||||||
|
id: "lia-1",
|
||||||
|
item_id: lineItemId,
|
||||||
|
amount: 20,
|
||||||
|
description: "discount",
|
||||||
|
discount_id: discount.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(createDuplicateAdjustment()).rejects.toEqual(
|
||||||
|
expect.objectContaining({ code: "23505" })
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -259,6 +259,14 @@ const createReturnableOrder = async (dbConnection, options) => {
|
|||||||
variant_id: "test-variant",
|
variant_id: "test-variant",
|
||||||
quantity: 2,
|
quantity: 2,
|
||||||
unit_price: 1000,
|
unit_price: 1000,
|
||||||
|
adjustments: [
|
||||||
|
{
|
||||||
|
amount: 200,
|
||||||
|
discount_code: "TESTCODE",
|
||||||
|
description: "discount",
|
||||||
|
item_id: "test-item",
|
||||||
|
},
|
||||||
|
],
|
||||||
tax_lines: [
|
tax_lines: [
|
||||||
{
|
{
|
||||||
name: "default",
|
name: "default",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const { initDb, useDb } = require("../../../helpers/use-db")
|
|||||||
const cartSeeder = require("../../helpers/cart-seeder")
|
const cartSeeder = require("../../helpers/cart-seeder")
|
||||||
const productSeeder = require("../../helpers/product-seeder")
|
const productSeeder = require("../../helpers/product-seeder")
|
||||||
const swapSeeder = require("../../helpers/swap-seeder")
|
const swapSeeder = require("../../helpers/swap-seeder")
|
||||||
const { simpleCartFactory } = require("../../factories")
|
const { simpleCartFactory, simpleLineItemFactory } = require("../../factories")
|
||||||
const {
|
const {
|
||||||
simpleDiscountFactory,
|
simpleDiscountFactory,
|
||||||
} = require("../../factories/simple-discount-factory")
|
} = require("../../factories/simple-discount-factory")
|
||||||
@@ -235,6 +235,131 @@ describe("/store/carts", () => {
|
|||||||
unit_price: 1000,
|
unit_price: 1000,
|
||||||
variant_id: "test-variant-quantity",
|
variant_id: "test-variant-quantity",
|
||||||
quantity: 1,
|
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 () => {
|
it("adds line item to cart time limited sale", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
// Add standard line item to cart
|
|
||||||
const response = await api
|
const response = await api
|
||||||
.post(
|
.post(
|
||||||
"/store/carts/test-cart/line-items",
|
"/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 () => {
|
it("adds line item with quantity to cart with quantity discount", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
// Add standard line item to cart
|
|
||||||
const response = await api
|
const response = await api
|
||||||
.post(
|
.post(
|
||||||
"/store/carts/test-cart/line-items",
|
"/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 () => {
|
it("adds line item with quantity to cart with quantity discount no ceiling", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
// Add standard line item to cart
|
|
||||||
const response = await api
|
const response = await api
|
||||||
.post(
|
.post(
|
||||||
"/store/carts/test-cart/line-items",
|
"/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", () => {
|
describe("POST /store/carts/:id", () => {
|
||||||
@@ -1202,6 +1739,22 @@ describe("/store/carts", () => {
|
|||||||
.catch((err) => console.log(err))
|
.catch((err) => console.log(err))
|
||||||
|
|
||||||
// Ensure that the discount is only applied to the standard item
|
// 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.total).toBe(1900) // 1000 (giftcard) + 900 (standard item with 10% discount)
|
||||||
expect(cartWithGiftcard.data.cart.discount_total).toBe(100)
|
expect(cartWithGiftcard.data.cart.discount_total).toBe(100)
|
||||||
expect(cartWithGiftcard.status).toEqual(200)
|
expect(cartWithGiftcard.status).toEqual(200)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Connection } from "typeorm"
|
import { Connection } from "typeorm"
|
||||||
import faker from "faker"
|
import faker from "faker"
|
||||||
import { LineItem, LineItemTaxLine } from "@medusajs/medusa"
|
import { LineItem, LineItemAdjustment, LineItemTaxLine } from "@medusajs/medusa"
|
||||||
|
|
||||||
type TaxLineFactoryData = {
|
type TaxLineFactoryData = {
|
||||||
rate: number
|
rate: number
|
||||||
@@ -8,6 +8,11 @@ type TaxLineFactoryData = {
|
|||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LineItemAdjustmentFactoryData = Omit<LineItemAdjustment, "discount_id"> & {
|
||||||
|
discount_id: string
|
||||||
|
discount_code: string
|
||||||
|
}
|
||||||
|
|
||||||
export type LineItemFactoryData = {
|
export type LineItemFactoryData = {
|
||||||
id?: string
|
id?: string
|
||||||
cart_id?: string
|
cart_id?: string
|
||||||
@@ -24,6 +29,7 @@ export type LineItemFactoryData = {
|
|||||||
shipped_quantity?: boolean
|
shipped_quantity?: boolean
|
||||||
returned_quantity?: boolean
|
returned_quantity?: boolean
|
||||||
tax_lines?: TaxLineFactoryData[]
|
tax_lines?: TaxLineFactoryData[]
|
||||||
|
adjustments: LineItemAdjustmentFactoryData[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const simpleLineItemFactory = async (
|
export const simpleLineItemFactory = async (
|
||||||
@@ -63,6 +69,7 @@ export const simpleLineItemFactory = async (
|
|||||||
fulfilled_quantity: data.fulfilled_quantity || null,
|
fulfilled_quantity: data.fulfilled_quantity || null,
|
||||||
shipped_quantity: data.shipped_quantity || null,
|
shipped_quantity: data.shipped_quantity || null,
|
||||||
returned_quantity: data.returned_quantity || null,
|
returned_quantity: data.returned_quantity || null,
|
||||||
|
adjustments: data.adjustments,
|
||||||
})
|
})
|
||||||
|
|
||||||
const line = await manager.save(toSave)
|
const line = await manager.save(toSave)
|
||||||
|
|||||||
@@ -101,7 +101,17 @@ export const simpleOrderFactory = async (
|
|||||||
await simpleShippingMethodFactory(connection, { ...sm, order_id: order.id })
|
await simpleShippingMethodFactory(connection, { ...sm, order_id: order.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = data.line_items
|
const items = data.line_items.map((item) => {
|
||||||
|
let adjustments = item?.adjustments || []
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
adjustments: adjustments.map((adj) => ({
|
||||||
|
...adj,
|
||||||
|
discount_id: discounts.find((d) => d.code === adj?.discount_code),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
await simpleLineItemFactory(connection, { ...item, order_id: id })
|
await simpleLineItemFactory(connection, { ...item, order_id: id })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,6 +144,69 @@ module.exports = async (connection, data = {}) => {
|
|||||||
tenPercent.rule = tenPercentRule
|
tenPercent.rule = tenPercentRule
|
||||||
await manager.save(tenPercent)
|
await manager.save(tenPercent)
|
||||||
|
|
||||||
|
const totalFixed100Rule = manager.create(DiscountRule, {
|
||||||
|
id: "total-fixed-100-rule",
|
||||||
|
description: "Fixed 100 total",
|
||||||
|
type: "fixed",
|
||||||
|
value: 100,
|
||||||
|
allocation: "total",
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalFixed100 = manager.create(Discount, {
|
||||||
|
id: "total-fixed-100",
|
||||||
|
code: "FIXED100",
|
||||||
|
is_dynamic: false,
|
||||||
|
is_disabled: false,
|
||||||
|
starts_at: tenDaysAgo,
|
||||||
|
ends_at: tenDaysFromToday,
|
||||||
|
})
|
||||||
|
|
||||||
|
totalFixed100.regions = [r]
|
||||||
|
totalFixed100.rule = totalFixed100Rule
|
||||||
|
await manager.save(totalFixed100)
|
||||||
|
|
||||||
|
const itemFixed200Rule = manager.create(DiscountRule, {
|
||||||
|
id: "item-fixed-200-rule",
|
||||||
|
description: "Item 200 fixed",
|
||||||
|
type: "fixed",
|
||||||
|
value: 200,
|
||||||
|
allocation: "item",
|
||||||
|
})
|
||||||
|
|
||||||
|
const itemFixed200 = manager.create(Discount, {
|
||||||
|
id: "item-fixed-200",
|
||||||
|
code: "FIXED200",
|
||||||
|
is_dynamic: false,
|
||||||
|
is_disabled: false,
|
||||||
|
starts_at: tenDaysAgo,
|
||||||
|
ends_at: tenDaysFromToday,
|
||||||
|
})
|
||||||
|
|
||||||
|
itemFixed200.regions = [r]
|
||||||
|
itemFixed200.rule = itemFixed200Rule
|
||||||
|
await manager.save(itemFixed200)
|
||||||
|
|
||||||
|
const itemPerc15Rule = manager.create(DiscountRule, {
|
||||||
|
id: "item-percentage-15-rule",
|
||||||
|
description: "Item 15 percentage",
|
||||||
|
type: "percentage",
|
||||||
|
value: 15,
|
||||||
|
allocation: "item",
|
||||||
|
})
|
||||||
|
|
||||||
|
const itemPerc15 = manager.create(Discount, {
|
||||||
|
id: "item-percentage-15",
|
||||||
|
code: "15PERCENT",
|
||||||
|
is_dynamic: false,
|
||||||
|
is_disabled: false,
|
||||||
|
starts_at: tenDaysAgo,
|
||||||
|
ends_at: tenDaysFromToday,
|
||||||
|
})
|
||||||
|
|
||||||
|
itemPerc15.regions = [r]
|
||||||
|
itemPerc15.rule = itemPerc15Rule
|
||||||
|
await manager.save(itemPerc15)
|
||||||
|
|
||||||
const dUsageLimit = await manager.create(Discount, {
|
const dUsageLimit = await manager.create(Discount, {
|
||||||
id: "test-discount-usage-limit",
|
id: "test-discount-usage-limit",
|
||||||
code: "SPENT",
|
code: "SPENT",
|
||||||
@@ -570,6 +633,74 @@ module.exports = async (connection, data = {}) => {
|
|||||||
|
|
||||||
await manager.save(cart)
|
await manager.save(cart)
|
||||||
|
|
||||||
|
const cartWithTotalFixedDiscount = manager.create(Cart, {
|
||||||
|
id: "test-cart-w-total-fixed-discount",
|
||||||
|
customer_id: "some-customer",
|
||||||
|
email: "some-customer@email.com",
|
||||||
|
discounts: [totalFixed100],
|
||||||
|
shipping_address: {
|
||||||
|
id: "test-shipping-address",
|
||||||
|
first_name: "lebron",
|
||||||
|
country_code: "us",
|
||||||
|
},
|
||||||
|
region_id: "test-region",
|
||||||
|
currency_code: "usd",
|
||||||
|
items: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
await manager.save(cartWithTotalFixedDiscount)
|
||||||
|
|
||||||
|
const cartWithItemFixedDiscount = manager.create(Cart, {
|
||||||
|
id: "test-cart-w-item-fixed-discount",
|
||||||
|
customer_id: "some-customer",
|
||||||
|
email: "some-customer@email.com",
|
||||||
|
discounts: [itemFixed200],
|
||||||
|
shipping_address: {
|
||||||
|
id: "test-shipping-address",
|
||||||
|
first_name: "lebron",
|
||||||
|
country_code: "us",
|
||||||
|
},
|
||||||
|
region_id: "test-region",
|
||||||
|
currency_code: "usd",
|
||||||
|
items: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
await manager.save(cartWithItemFixedDiscount)
|
||||||
|
|
||||||
|
const cartWithTotalPercDiscount = manager.create(Cart, {
|
||||||
|
id: "test-cart-w-total-percentage-discount",
|
||||||
|
customer_id: "some-customer",
|
||||||
|
email: "some-customer@email.com",
|
||||||
|
discounts: [tenPercent],
|
||||||
|
shipping_address: {
|
||||||
|
id: "test-shipping-address",
|
||||||
|
first_name: "lebron",
|
||||||
|
country_code: "us",
|
||||||
|
},
|
||||||
|
region_id: "test-region",
|
||||||
|
currency_code: "usd",
|
||||||
|
items: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
await manager.save(cartWithTotalPercDiscount)
|
||||||
|
|
||||||
|
const cartWithItemPercDiscount = manager.create(Cart, {
|
||||||
|
id: "test-cart-w-item-percentage-discount",
|
||||||
|
customer_id: "some-customer",
|
||||||
|
email: "some-customer@email.com",
|
||||||
|
discounts: [itemPerc15],
|
||||||
|
shipping_address: {
|
||||||
|
id: "test-shipping-address",
|
||||||
|
first_name: "lebron",
|
||||||
|
country_code: "us",
|
||||||
|
},
|
||||||
|
region_id: "test-region",
|
||||||
|
currency_code: "usd",
|
||||||
|
items: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
await manager.save(cartWithItemPercDiscount)
|
||||||
|
|
||||||
const cart2 = manager.create(Cart, {
|
const cart2 = manager.create(Cart, {
|
||||||
id: "test-cart-2",
|
id: "test-cart-2",
|
||||||
customer_id: "some-customer",
|
customer_id: "some-customer",
|
||||||
|
|||||||
@@ -173,6 +173,14 @@ module.exports = async (connection, data = {}) => {
|
|||||||
quantity: 1,
|
quantity: 1,
|
||||||
variant_id: "test-variant",
|
variant_id: "test-variant",
|
||||||
order_id: "test-order",
|
order_id: "test-order",
|
||||||
|
adjustments: [
|
||||||
|
{
|
||||||
|
amount: 800,
|
||||||
|
discount_id: "test-discount",
|
||||||
|
description: "discount",
|
||||||
|
item_id: "test-item",
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
await manager.save(li)
|
await manager.save(li)
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ const createSwap = async (options, manager) => {
|
|||||||
is_disabled: false,
|
is_disabled: false,
|
||||||
rule: dRule,
|
rule: dRule,
|
||||||
})
|
})
|
||||||
await manager.save(discount)
|
let discountDb = await manager.save(discount)
|
||||||
|
|
||||||
const cart = manager.create(Cart, {
|
const cart = manager.create(Cart, {
|
||||||
id: `${swapId}-cart`,
|
id: `${swapId}-cart`,
|
||||||
@@ -396,6 +396,14 @@ const createSwap = async (options, manager) => {
|
|||||||
thumbnail: "https://test.js/1234",
|
thumbnail: "https://test.js/1234",
|
||||||
unit_price: 8000,
|
unit_price: 8000,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
|
adjustments: [
|
||||||
|
{
|
||||||
|
amount: -800,
|
||||||
|
description: "discount",
|
||||||
|
discount_id: discountDb.id,
|
||||||
|
item_id: `${swapId}-return-item-1`,
|
||||||
|
},
|
||||||
|
],
|
||||||
variant_id: "test-variant",
|
variant_id: "test-variant",
|
||||||
order_id: "order-with-swap",
|
order_id: "order-with-swap",
|
||||||
cart_id: cart.id,
|
cart_id: cart.id,
|
||||||
@@ -419,6 +427,7 @@ const createSwap = async (options, manager) => {
|
|||||||
|
|
||||||
const return_item1 = manager.create(LineItem, {
|
const return_item1 = manager.create(LineItem, {
|
||||||
...li,
|
...li,
|
||||||
|
is_return: true,
|
||||||
unit_price: -1 * li.unit_price,
|
unit_price: -1 * li.unit_price,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -8,16 +8,16 @@
|
|||||||
"build": "babel src -d dist --extensions \".ts,.js\""
|
"build": "babel src -d dist --extensions \".ts,.js\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@medusajs/medusa": "1.2.1-dev-1648026403166",
|
"@medusajs/medusa": "1.2.1-dev-1649181615374",
|
||||||
"faker": "^5.5.3",
|
"faker": "^5.5.3",
|
||||||
"medusa-interfaces": "1.2.1-dev-1648026403166",
|
"medusa-interfaces": "1.2.1-dev-1649181615374",
|
||||||
"typeorm": "^0.2.31"
|
"typeorm": "^0.2.31"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.12.10",
|
"@babel/cli": "^7.12.10",
|
||||||
"@babel/core": "^7.12.10",
|
"@babel/core": "^7.12.10",
|
||||||
"@babel/node": "^7.12.10",
|
"@babel/node": "^7.12.10",
|
||||||
"babel-preset-medusa-package": "1.1.19-dev-1648026403166",
|
"babel-preset-medusa-package": "1.1.19-dev-1649181615374",
|
||||||
"jest": "^26.6.3"
|
"jest": "^26.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1301,10 +1301,10 @@
|
|||||||
"@jridgewell/resolve-uri" "^3.0.3"
|
"@jridgewell/resolve-uri" "^3.0.3"
|
||||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||||
|
|
||||||
"@medusajs/medusa-cli@1.2.1-dev-1648026403166":
|
"@medusajs/medusa-cli@1.2.1-dev-1649181615374":
|
||||||
version "1.2.1-dev-1648026403166"
|
version "1.2.1-dev-1649181615374"
|
||||||
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.2.1-dev-1648026403166.tgz#1dc2ad62ba11421d43357596c61eb0b76d3bc69e"
|
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.2.1-dev-1649181615374.tgz#1ea9014e3ec9813a52457b0d6e2fc6bb64d3bfd6"
|
||||||
integrity sha512-TScUnBuxI5V5cB6gAAGeGOZU8lXYHIhozpelDcYibZqaHEIwMNs1ekzRYhfMjYowSFCMLlzCsHFUh+gqU/6WSw==
|
integrity sha512-8m6Z1ZZqstZKaAaKoFS3v3IzI7BFhcBgpF+iCSRuJoXltQgzVQOAxXuPjkRoi+m1ZZ+Yi/YYEzKmNQ99vmXisQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/polyfill" "^7.8.7"
|
"@babel/polyfill" "^7.8.7"
|
||||||
"@babel/runtime" "^7.9.6"
|
"@babel/runtime" "^7.9.6"
|
||||||
@@ -1322,8 +1322,8 @@
|
|||||||
is-valid-path "^0.1.1"
|
is-valid-path "^0.1.1"
|
||||||
joi-objectid "^3.0.1"
|
joi-objectid "^3.0.1"
|
||||||
meant "^1.0.1"
|
meant "^1.0.1"
|
||||||
medusa-core-utils "1.1.31-dev-1648026403166"
|
medusa-core-utils "1.1.31-dev-1649181615374"
|
||||||
medusa-telemetry "0.0.11-dev-1648026403166"
|
medusa-telemetry "0.0.11-dev-1649181615374"
|
||||||
netrc-parser "^3.1.6"
|
netrc-parser "^3.1.6"
|
||||||
open "^8.0.6"
|
open "^8.0.6"
|
||||||
ora "^5.4.1"
|
ora "^5.4.1"
|
||||||
@@ -1337,13 +1337,13 @@
|
|||||||
winston "^3.3.3"
|
winston "^3.3.3"
|
||||||
yargs "^15.3.1"
|
yargs "^15.3.1"
|
||||||
|
|
||||||
"@medusajs/medusa@1.2.1-dev-1648026403166":
|
"@medusajs/medusa@1.2.1-dev-1649181615374":
|
||||||
version "1.2.1-dev-1648026403166"
|
version "1.2.1-dev-1649181615374"
|
||||||
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.2.1-dev-1648026403166.tgz#f6e8fab09ac73b38ed79e15d2080e7ecad80fd99"
|
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.2.1-dev-1649181615374.tgz#6a62f8628b84b47a8717e9e0c276f3a9c2e376ce"
|
||||||
integrity sha512-ufYVgcKo+bsAzSnZA3FVLmxseDwnSKkSbIdktQUSZGMJTRvN/+Wcqnjdf59oH11jqqBApJ8T77Nvd/ABasjTWw==
|
integrity sha512-eiCGE6JqYuP7GCzTBGg5LI9U0uQ0wlsR+NuMZVEwldj+xc7qwMjBJwUA7gc58gBv6JesfMYj3VZmJComN4+7Bg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@hapi/joi" "^16.1.8"
|
"@hapi/joi" "^16.1.8"
|
||||||
"@medusajs/medusa-cli" "1.2.1-dev-1648026403166"
|
"@medusajs/medusa-cli" "1.2.1-dev-1649181615374"
|
||||||
"@types/lodash" "^4.14.168"
|
"@types/lodash" "^4.14.168"
|
||||||
awilix "^4.2.3"
|
awilix "^4.2.3"
|
||||||
body-parser "^1.19.0"
|
body-parser "^1.19.0"
|
||||||
@@ -1367,8 +1367,8 @@
|
|||||||
joi "^17.3.0"
|
joi "^17.3.0"
|
||||||
joi-objectid "^3.0.1"
|
joi-objectid "^3.0.1"
|
||||||
jsonwebtoken "^8.5.1"
|
jsonwebtoken "^8.5.1"
|
||||||
medusa-core-utils "1.1.31-dev-1648026403166"
|
medusa-core-utils "1.1.31-dev-1649181615374"
|
||||||
medusa-test-utils "1.1.37-dev-1648026403166"
|
medusa-test-utils "1.1.37-dev-1649181615374"
|
||||||
morgan "^1.9.1"
|
morgan "^1.9.1"
|
||||||
multer "^1.4.2"
|
multer "^1.4.2"
|
||||||
passport "^0.4.0"
|
passport "^0.4.0"
|
||||||
@@ -2010,10 +2010,10 @@ babel-preset-jest@^26.6.2:
|
|||||||
babel-plugin-jest-hoist "^26.6.2"
|
babel-plugin-jest-hoist "^26.6.2"
|
||||||
babel-preset-current-node-syntax "^1.0.0"
|
babel-preset-current-node-syntax "^1.0.0"
|
||||||
|
|
||||||
babel-preset-medusa-package@1.1.19-dev-1648026403166:
|
babel-preset-medusa-package@1.1.19-dev-1649181615374:
|
||||||
version "1.1.19-dev-1648026403166"
|
version "1.1.19-dev-1649181615374"
|
||||||
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.19-dev-1648026403166.tgz#e834a06c2283d5b8709d45de1e4ff559dfd23944"
|
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.19-dev-1649181615374.tgz#2f13d52fedd336ad4b4c0602b3bf4696d2d08db7"
|
||||||
integrity sha512-HjbSvyeLU9ez0dgBv4+SDX2/nXaskN5Bip7nF36F/fnufw/5FtJ28Lyw30VWz6i5Ci991Zmadfq6TNUZnKgNHw==
|
integrity sha512-N4XL7rTmNM2W+iRR92xvU4bKadP25lY5QR3vndxTxsLNSgcR5tLjKLO/4j7AqiFvcthbE8cF1TcdECH5aJfSuA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/plugin-proposal-class-properties" "^7.12.1"
|
"@babel/plugin-proposal-class-properties" "^7.12.1"
|
||||||
"@babel/plugin-proposal-decorators" "^7.12.1"
|
"@babel/plugin-proposal-decorators" "^7.12.1"
|
||||||
@@ -5156,25 +5156,25 @@ media-typer@0.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||||
|
|
||||||
medusa-core-utils@1.1.31-dev-1648026403166:
|
medusa-core-utils@1.1.31-dev-1649181615374:
|
||||||
version "1.1.31-dev-1648026403166"
|
version "1.1.31-dev-1649181615374"
|
||||||
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.31-dev-1648026403166.tgz#0f3bde6fc0a77d3027d80a4ca5e1a1ce2426ad21"
|
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.31-dev-1649181615374.tgz#60416bba53eaba607d77ca36789aa23756b8db0f"
|
||||||
integrity sha512-B5eFj3dH8g/HgNd8p8ChwR4bpydl0dqyBJllyvqS1AnFpjW7BcXpO0ZPkdYrIHflQL9e2OiIq3A2Lm6ZsYCI1A==
|
integrity sha512-w5nusocZweIrAFJ6sl4hD/mN+UtNjz39IIfXukekyJByg3wpv4P+vsW3XdrFTX5OLvKVxuzjl3B2zeZUmdgSKg==
|
||||||
dependencies:
|
dependencies:
|
||||||
joi "^17.3.0"
|
joi "^17.3.0"
|
||||||
joi-objectid "^3.0.1"
|
joi-objectid "^3.0.1"
|
||||||
|
|
||||||
medusa-interfaces@1.2.1-dev-1648026403166:
|
medusa-interfaces@1.2.1-dev-1649181615374:
|
||||||
version "1.2.1-dev-1648026403166"
|
version "1.2.1-dev-1649181615374"
|
||||||
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.2.1-dev-1648026403166.tgz#29742128c852c967a94df1e00f0c8dacccff6b86"
|
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.2.1-dev-1649181615374.tgz#0b664f4e3e8e61b67108a41c8f0f9dd58a947075"
|
||||||
integrity sha512-VRnFIFqPeOd0TcPMlng1kIzjhT7AtJzNqIDmKDxeJeE1MqU89g6YME/yQfCPzTXKNMdwN+8Xx+E5gANNn5i1QA==
|
integrity sha512-JRD773nZnxjn/2oNrgb/zXn+scBoNHpW97YQYW7+LFX1JBYafzyGQW3vWTFX4X+q08ehz4dc21CgoMeYco8yvQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
medusa-core-utils "1.1.31-dev-1648026403166"
|
medusa-core-utils "1.1.31-dev-1649181615374"
|
||||||
|
|
||||||
medusa-telemetry@0.0.11-dev-1648026403166:
|
medusa-telemetry@0.0.11-dev-1649181615374:
|
||||||
version "0.0.11-dev-1648026403166"
|
version "0.0.11-dev-1649181615374"
|
||||||
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.11-dev-1648026403166.tgz#dd66a411a214b953f8b6673f59c0b02dd78ec99f"
|
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.11-dev-1649181615374.tgz#3f4c366ea8d0d0fdde9b289f7e771bb27adae56d"
|
||||||
integrity sha512-SH07C6rOQyx7bHH4/2TzmBgzgMY8fLiE7PH7dk6BGwY8bjx9w6DvF9CvhdwxFnFnzDTed/cjkE/cHNkQFkjSrQ==
|
integrity sha512-RMJR3/qlTb1nV05RnBnX1bNOvYyeuXf4owxLlfbWzKAZirWQ5LAC2GikEGGbHGbw7UgiLgQtu4Rnmg2Uye+VcA==
|
||||||
dependencies:
|
dependencies:
|
||||||
axios "^0.21.1"
|
axios "^0.21.1"
|
||||||
axios-retry "^3.1.9"
|
axios-retry "^3.1.9"
|
||||||
@@ -5186,13 +5186,13 @@ medusa-telemetry@0.0.11-dev-1648026403166:
|
|||||||
remove-trailing-slash "^0.1.1"
|
remove-trailing-slash "^0.1.1"
|
||||||
uuid "^8.3.2"
|
uuid "^8.3.2"
|
||||||
|
|
||||||
medusa-test-utils@1.1.37-dev-1648026403166:
|
medusa-test-utils@1.1.37-dev-1649181615374:
|
||||||
version "1.1.37-dev-1648026403166"
|
version "1.1.37-dev-1649181615374"
|
||||||
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.37-dev-1648026403166.tgz#f32f0a04d7d38f6a84e58a2937bd026767ea5d1f"
|
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.37-dev-1649181615374.tgz#079c16a791d47c52072c6f0837d0a827208bc9cc"
|
||||||
integrity sha512-VQCuRal14tl/ZLsN6Ueh+z3FBgQEXgsLLSJxfpsrmABKTgoHONhkoe7UyXxxUozXwPWWqqtHhy80zfCIlfHF+w==
|
integrity sha512-hj3iNZsIA01l7qAZrOgt+kT8PDkXKoW4CEL3bhVfIUEwsdv9jID7FGdTIN/7G3diioTypvrVcqRrp0uiWdgp+Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/plugin-transform-classes" "^7.9.5"
|
"@babel/plugin-transform-classes" "^7.9.5"
|
||||||
medusa-core-utils "1.1.31-dev-1648026403166"
|
medusa-core-utils "1.1.31-dev-1649181615374"
|
||||||
randomatic "^3.1.1"
|
randomatic "^3.1.1"
|
||||||
|
|
||||||
merge-descriptors@1.0.1:
|
merge-descriptors@1.0.1:
|
||||||
|
|||||||
@@ -427,6 +427,7 @@ Object {
|
|||||||
"idempotency_key": null,
|
"idempotency_key": null,
|
||||||
"items": Array [
|
"items": Array [
|
||||||
Object {
|
Object {
|
||||||
|
"adjustments": Array [],
|
||||||
"allow_discounts": true,
|
"allow_discounts": true,
|
||||||
"cart_id": null,
|
"cart_id": null,
|
||||||
"claim_order_id": null,
|
"claim_order_id": null,
|
||||||
@@ -761,6 +762,7 @@ Object {
|
|||||||
"idempotency_key": null,
|
"idempotency_key": null,
|
||||||
"items": Array [
|
"items": Array [
|
||||||
Object {
|
Object {
|
||||||
|
"adjustments": Array [],
|
||||||
"allow_discounts": true,
|
"allow_discounts": true,
|
||||||
"cart_id": null,
|
"cart_id": null,
|
||||||
"claim_order_id": null,
|
"claim_order_id": null,
|
||||||
@@ -982,6 +984,7 @@ Object {
|
|||||||
"idempotency_key": null,
|
"idempotency_key": null,
|
||||||
"items": Array [
|
"items": Array [
|
||||||
Object {
|
Object {
|
||||||
|
"adjustments": Array [],
|
||||||
"allow_discounts": true,
|
"allow_discounts": true,
|
||||||
"cart_id": null,
|
"cart_id": null,
|
||||||
"claim_order_id": null,
|
"claim_order_id": null,
|
||||||
@@ -1248,6 +1251,7 @@ Object {
|
|||||||
"idempotency_key": null,
|
"idempotency_key": null,
|
||||||
"items": Array [
|
"items": Array [
|
||||||
Object {
|
Object {
|
||||||
|
"adjustments": Array [],
|
||||||
"allow_discounts": true,
|
"allow_discounts": true,
|
||||||
"cart_id": null,
|
"cart_id": null,
|
||||||
"claim_order_id": null,
|
"claim_order_id": null,
|
||||||
@@ -1569,6 +1573,7 @@ Object {
|
|||||||
"idempotency_key": null,
|
"idempotency_key": null,
|
||||||
"items": Array [
|
"items": Array [
|
||||||
Object {
|
Object {
|
||||||
|
"adjustments": Array [],
|
||||||
"allow_discounts": true,
|
"allow_discounts": true,
|
||||||
"cart_id": null,
|
"cart_id": null,
|
||||||
"claim_order_id": null,
|
"claim_order_id": null,
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ describe("medusa-plugin-sendgrid", () => {
|
|||||||
},
|
},
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
|
adjustments: [],
|
||||||
created_at: expect.any(Date),
|
created_at: expect.any(Date),
|
||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
order_id: expect.any(String),
|
order_id: expect.any(String),
|
||||||
@@ -227,6 +228,7 @@ describe("medusa-plugin-sendgrid", () => {
|
|||||||
},
|
},
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
|
adjustments: [],
|
||||||
created_at: expect.any(Date),
|
created_at: expect.any(Date),
|
||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
order_id: expect.any(String),
|
order_id: expect.any(String),
|
||||||
@@ -285,6 +287,7 @@ describe("medusa-plugin-sendgrid", () => {
|
|||||||
},
|
},
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
|
adjustments: [],
|
||||||
created_at: expect.any(Date),
|
created_at: expect.any(Date),
|
||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
order_id: expect.any(String),
|
order_id: expect.any(String),
|
||||||
|
|||||||
@@ -8,18 +8,18 @@
|
|||||||
"build": "babel src -d dist --extensions \".ts,.js\""
|
"build": "babel src -d dist --extensions \".ts,.js\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@medusajs/medusa": "1.1.60-dev-1642337635041",
|
"@medusajs/medusa": "1.2.1-dev-1649009241281",
|
||||||
"faker": "^5.5.3",
|
"faker": "^5.5.3",
|
||||||
"medusa-fulfillment-webshipper": "1.1.35-dev-1642337635041",
|
"medusa-fulfillment-webshipper": "1.2.1-dev-1649009241281",
|
||||||
"medusa-interfaces": "1.1.32-dev-1642337635041",
|
"medusa-interfaces": "1.2.1-dev-1649009241281",
|
||||||
"medusa-plugin-sendgrid": "1.1.36-dev-1642337635041",
|
"medusa-plugin-sendgrid": "1.2.1-dev-1649009241281",
|
||||||
"typeorm": "^0.2.31"
|
"typeorm": "^0.2.31"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.12.10",
|
"@babel/cli": "^7.12.10",
|
||||||
"@babel/core": "^7.12.10",
|
"@babel/core": "^7.12.10",
|
||||||
"@babel/node": "^7.12.10",
|
"@babel/node": "^7.12.10",
|
||||||
"babel-preset-medusa-package": "1.1.19-dev-1642337635041",
|
"babel-preset-medusa-package": "1.1.19-dev-1649009241281",
|
||||||
"jest": "^26.6.3"
|
"jest": "^26.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1268,10 +1268,10 @@
|
|||||||
"@types/yargs" "^15.0.0"
|
"@types/yargs" "^15.0.0"
|
||||||
chalk "^4.0.0"
|
chalk "^4.0.0"
|
||||||
|
|
||||||
"@medusajs/medusa-cli@1.1.25-dev-1642337635041":
|
"@medusajs/medusa-cli@1.2.1-dev-1649009241281":
|
||||||
version "1.1.25-dev-1642337635041"
|
version "1.2.1-dev-1649009241281"
|
||||||
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.1.25-dev-1642337635041.tgz#e3ca33f453446dddc4922f42b4254995de9874b5"
|
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.2.1-dev-1649009241281.tgz#e0cc327224af6067013e0eba1b882b3f9f0fd0e3"
|
||||||
integrity sha512-8pA5iBMglgrdPRSEZLvjovJczib5zk9qlQJsewlSrw/aolwnusukEE7vBfjAiUXAZKngMx6BnYoORlWSV88wdA==
|
integrity sha512-3949nJfrBIguXrKDwiKjQ10XxZ06ccTShbcOUsXkIuGIMOHBZ+AuhjErCAnXS3H/JSIO93lnQKVJ8DWSkf9BGQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/polyfill" "^7.8.7"
|
"@babel/polyfill" "^7.8.7"
|
||||||
"@babel/runtime" "^7.9.6"
|
"@babel/runtime" "^7.9.6"
|
||||||
@@ -1289,8 +1289,8 @@
|
|||||||
is-valid-path "^0.1.1"
|
is-valid-path "^0.1.1"
|
||||||
joi-objectid "^3.0.1"
|
joi-objectid "^3.0.1"
|
||||||
meant "^1.0.1"
|
meant "^1.0.1"
|
||||||
medusa-core-utils "1.1.31-dev-1642337635041"
|
medusa-core-utils "1.1.31-dev-1649009241281"
|
||||||
medusa-telemetry "0.0.11-dev-1642337635041"
|
medusa-telemetry "0.0.11-dev-1649009241281"
|
||||||
netrc-parser "^3.1.6"
|
netrc-parser "^3.1.6"
|
||||||
open "^8.0.6"
|
open "^8.0.6"
|
||||||
ora "^5.4.1"
|
ora "^5.4.1"
|
||||||
@@ -1304,13 +1304,13 @@
|
|||||||
winston "^3.3.3"
|
winston "^3.3.3"
|
||||||
yargs "^15.3.1"
|
yargs "^15.3.1"
|
||||||
|
|
||||||
"@medusajs/medusa@1.1.60-dev-1642337635041":
|
"@medusajs/medusa@1.2.1-dev-1649009241281":
|
||||||
version "1.1.60-dev-1642337635041"
|
version "1.2.1-dev-1649009241281"
|
||||||
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.1.60-dev-1642337635041.tgz#011d64b9b85a3f66e01f3755788370aa444a2e27"
|
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.2.1-dev-1649009241281.tgz#ed063a9abee468cd71b5d0201ea4f32dd2a59775"
|
||||||
integrity sha512-MBef46mJZMSyE3+dcijMqjFZbNo7AL60mP/PwkOy4W1d/t2MZ+GOBIufLAVxVw7SaiSjAGpQDmCrUOqQrIhIdg==
|
integrity sha512-uNVPZq4rVOLRRP9yTeYWPn9xsLfhh7mgRexMjRL/7QmKTQmspBL/pbjdRk0WCBSSf3o5SFiOV0/n69Oxg6Oaig==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@hapi/joi" "^16.1.8"
|
"@hapi/joi" "^16.1.8"
|
||||||
"@medusajs/medusa-cli" "1.1.25-dev-1642337635041"
|
"@medusajs/medusa-cli" "1.2.1-dev-1649009241281"
|
||||||
"@types/lodash" "^4.14.168"
|
"@types/lodash" "^4.14.168"
|
||||||
awilix "^4.2.3"
|
awilix "^4.2.3"
|
||||||
body-parser "^1.19.0"
|
body-parser "^1.19.0"
|
||||||
@@ -1334,8 +1334,8 @@
|
|||||||
joi "^17.3.0"
|
joi "^17.3.0"
|
||||||
joi-objectid "^3.0.1"
|
joi-objectid "^3.0.1"
|
||||||
jsonwebtoken "^8.5.1"
|
jsonwebtoken "^8.5.1"
|
||||||
medusa-core-utils "1.1.31-dev-1642337635041"
|
medusa-core-utils "1.1.31-dev-1649009241281"
|
||||||
medusa-test-utils "1.1.35-dev-1642337635041"
|
medusa-test-utils "1.1.37-dev-1649009241281"
|
||||||
morgan "^1.9.1"
|
morgan "^1.9.1"
|
||||||
multer "^1.4.2"
|
multer "^1.4.2"
|
||||||
passport "^0.4.0"
|
passport "^0.4.0"
|
||||||
@@ -1995,10 +1995,10 @@ babel-preset-jest@^26.6.2:
|
|||||||
babel-plugin-jest-hoist "^26.6.2"
|
babel-plugin-jest-hoist "^26.6.2"
|
||||||
babel-preset-current-node-syntax "^1.0.0"
|
babel-preset-current-node-syntax "^1.0.0"
|
||||||
|
|
||||||
babel-preset-medusa-package@1.1.19-dev-1642337635041:
|
babel-preset-medusa-package@1.1.19-dev-1649009241281:
|
||||||
version "1.1.19-dev-1642337635041"
|
version "1.1.19-dev-1649009241281"
|
||||||
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.19-dev-1642337635041.tgz#24c6f2db887f35c358270d85cc0f6b6c19f459fe"
|
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.19-dev-1649009241281.tgz#253cb95840d3e122a45e2f72e15f623afa57ae61"
|
||||||
integrity sha512-HtrHI6oWUEMyTK9MHKzCg7hzZHgi8+uCIQyTQIY5SYFRA6kZiNl54Wzai/BY26F1j5g0sVNfqmFXdBd53GEz7g==
|
integrity sha512-UFH2A+rYUc33d2f+xEVCQOOmcm0RSSPlnTeSVV5bM8SoS4MXgekPdBO+sMjKi/bLbHJncKaapCm/HoYZ19ZJHg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/plugin-proposal-class-properties" "^7.12.1"
|
"@babel/plugin-proposal-class-properties" "^7.12.1"
|
||||||
"@babel/plugin-proposal-decorators" "^7.12.1"
|
"@babel/plugin-proposal-decorators" "^7.12.1"
|
||||||
@@ -5138,48 +5138,48 @@ media-typer@0.3.0:
|
|||||||
resolved "http://localhost:4873/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
resolved "http://localhost:4873/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||||
|
|
||||||
medusa-core-utils@1.1.31-dev-1642337635041:
|
medusa-core-utils@1.1.31-dev-1649009241281:
|
||||||
version "1.1.31-dev-1642337635041"
|
version "1.1.31-dev-1649009241281"
|
||||||
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.31-dev-1642337635041.tgz#d6928f377320ab851eff1750e13e44e2e366c9cb"
|
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.31-dev-1649009241281.tgz#1ff37cdd9057c5716124f250c0b4b261ef8c7cde"
|
||||||
integrity sha512-m0VXTM3QwKi0oRk2Q4mqn6sK07xJgiwyz3rs/0gHTjl88B8k75XugbriFceLPEYqHs6AjP6nSNiRDqdRJqUsMg==
|
integrity sha512-nYcgpTtUSGAFG00m1rKcWUgnlmL2zu5aOYl5Fas/re+hhc1fhi+jwHUMigK0c28t71RwgtaeJFNKgQNgxli+2A==
|
||||||
dependencies:
|
dependencies:
|
||||||
joi "^17.3.0"
|
joi "^17.3.0"
|
||||||
joi-objectid "^3.0.1"
|
joi-objectid "^3.0.1"
|
||||||
|
|
||||||
medusa-fulfillment-webshipper@1.1.35-dev-1642337635041:
|
medusa-fulfillment-webshipper@1.2.1-dev-1649009241281:
|
||||||
version "1.1.35-dev-1642337635041"
|
version "1.2.1-dev-1649009241281"
|
||||||
resolved "http://localhost:4873/medusa-fulfillment-webshipper/-/medusa-fulfillment-webshipper-1.1.35-dev-1642337635041.tgz#ab4b0a23488094b39582d1c1d1c69e29713b464d"
|
resolved "http://localhost:4873/medusa-fulfillment-webshipper/-/medusa-fulfillment-webshipper-1.2.1-dev-1649009241281.tgz#d199209df0d3683a53829565713b857bac284282"
|
||||||
integrity sha512-P1gRvnJcs5Svgmvb44Qp1Oph9TqtUQsEeawhX7u+3dSz9qDnJ1lo1XB1tBttpCP9qepPe3nc0Sk7mEwlq8eM6Q==
|
integrity sha512-dtMBJ3mObQS2uGKClga66qDOGTKjOenzLOdjScurCwheQqi5a+EOyUrZSssE4oQEMMRsuY+L/MTkcLyKr4f+HA==
|
||||||
dependencies:
|
dependencies:
|
||||||
axios "^0.20.0"
|
axios "^0.20.0"
|
||||||
body-parser "^1.19.0"
|
body-parser "^1.19.0"
|
||||||
cors "^2.8.5"
|
cors "^2.8.5"
|
||||||
express "^4.17.1"
|
express "^4.17.1"
|
||||||
medusa-core-utils "1.1.31-dev-1642337635041"
|
medusa-core-utils "1.1.31-dev-1649009241281"
|
||||||
|
|
||||||
medusa-interfaces@1.1.32-dev-1642337635041:
|
medusa-interfaces@1.2.1-dev-1649009241281:
|
||||||
version "1.1.32-dev-1642337635041"
|
version "1.2.1-dev-1649009241281"
|
||||||
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.1.32-dev-1642337635041.tgz#8179a344a41e1163502b4ee8af5b294443067f99"
|
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.2.1-dev-1649009241281.tgz#77b6c906b805e6b1fb092fb7632a7ad8ecbafec5"
|
||||||
integrity sha512-I7Wp+BEgzJ2wLkGBoDe6VhAlB8lJRMAHJ7wGzWfCMiG3Dj0PX2KE2HZDT+PA5S7IIXHWh9c0n37/cHZDcIF5Hw==
|
integrity sha512-tgDptu8k7aWCKBo1m8RxcxcffsVjy2e2M80rfvTd81gjC3EtGBGxDmiwNGJoaWkFq+Matq2wve2l92xVA2t8uQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
medusa-core-utils "1.1.31-dev-1642337635041"
|
medusa-core-utils "1.1.31-dev-1649009241281"
|
||||||
|
|
||||||
medusa-plugin-sendgrid@1.1.36-dev-1642337635041:
|
medusa-plugin-sendgrid@1.2.1-dev-1649009241281:
|
||||||
version "1.1.36-dev-1642337635041"
|
version "1.2.1-dev-1649009241281"
|
||||||
resolved "http://localhost:4873/medusa-plugin-sendgrid/-/medusa-plugin-sendgrid-1.1.36-dev-1642337635041.tgz#ae4d1d23167daf3c45ced085f3079ed21d5e876d"
|
resolved "http://localhost:4873/medusa-plugin-sendgrid/-/medusa-plugin-sendgrid-1.2.1-dev-1649009241281.tgz#362fafa54658c641fb482346c0ba6f28007bbbf3"
|
||||||
integrity sha512-nJBq0x9h9fMsPfW4BnGzvk8yWpuk1v90zeXP85cDhFeAAWJ50o2zF7HsNcJawPfhKooAF+3kRDApR8706gUyqQ==
|
integrity sha512-2C7mfwR+HZSjm/Y3TGHHr7dJAoNhwUvEVZs2dpJCEfmpbsR2f6TG3J5e+kidubQ4NeCmHdrXGPmjS4lbH1tnhQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/plugin-transform-classes" "^7.9.5"
|
"@babel/plugin-transform-classes" "^7.9.5"
|
||||||
"@sendgrid/mail" "^7.1.1"
|
"@sendgrid/mail" "^7.1.1"
|
||||||
body-parser "^1.19.0"
|
body-parser "^1.19.0"
|
||||||
express "^4.17.1"
|
express "^4.17.1"
|
||||||
medusa-core-utils "1.1.31-dev-1642337635041"
|
medusa-core-utils "1.1.31-dev-1649009241281"
|
||||||
medusa-test-utils "1.1.35-dev-1642337635041"
|
medusa-test-utils "1.1.37-dev-1649009241281"
|
||||||
|
|
||||||
medusa-telemetry@0.0.11-dev-1642337635041:
|
medusa-telemetry@0.0.11-dev-1649009241281:
|
||||||
version "0.0.11-dev-1642337635041"
|
version "0.0.11-dev-1649009241281"
|
||||||
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.11-dev-1642337635041.tgz#290bb20c1712adf99ec5d2f6871b200106203bc0"
|
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.11-dev-1649009241281.tgz#76077de2a5b53f754f1a55701d892716be67876d"
|
||||||
integrity sha512-LlJVgdKAQFrp6ULW8D+Qvb/VSEgbQbKjEeoMnUwDaMMswGehsFUXsqBzs1LRo5nKmpLaPvSorw4OMvktR+RxTQ==
|
integrity sha512-ZEWmHZI9RByg1Tm8sOOxVCs+dkr2LCvElXqFsJfgIoTdjvGS96tfG1pbod2LHD5rcBY2aXDSXBRf0pA1xRtvsQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
axios "^0.21.1"
|
axios "^0.21.1"
|
||||||
axios-retry "^3.1.9"
|
axios-retry "^3.1.9"
|
||||||
@@ -5191,13 +5191,13 @@ medusa-telemetry@0.0.11-dev-1642337635041:
|
|||||||
remove-trailing-slash "^0.1.1"
|
remove-trailing-slash "^0.1.1"
|
||||||
uuid "^8.3.2"
|
uuid "^8.3.2"
|
||||||
|
|
||||||
medusa-test-utils@1.1.35-dev-1642337635041:
|
medusa-test-utils@1.1.37-dev-1649009241281:
|
||||||
version "1.1.35-dev-1642337635041"
|
version "1.1.37-dev-1649009241281"
|
||||||
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.35-dev-1642337635041.tgz#1daf7f6a671e2fffe43ab349d795581870104a4a"
|
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.37-dev-1649009241281.tgz#59c9ac8fddf31a410347dac5ef147758a61f13da"
|
||||||
integrity sha512-pCpMjmo0VyYbSRu/Ga/GdirBscJtS4HK4HtfXepMrut7Yg/kV+GpW/fFtBFkiqGDBpCWYLopzZAdRw+qpdpusA==
|
integrity sha512-+3c+2DB3UpJxyIt6LvWCR9aBtXwrABXV4GhUYW6toGl19JE2lZ9PBL3GSVmGi7etZ7Xv7tR9bwZXZURvKMr8Hg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/plugin-transform-classes" "^7.9.5"
|
"@babel/plugin-transform-classes" "^7.9.5"
|
||||||
medusa-core-utils "1.1.31-dev-1642337635041"
|
medusa-core-utils "1.1.31-dev-1649009241281"
|
||||||
randomatic "^3.1.1"
|
randomatic "^3.1.1"
|
||||||
|
|
||||||
merge-descriptors@1.0.1:
|
merge-descriptors@1.0.1:
|
||||||
|
|||||||
@@ -46,11 +46,17 @@ export default (app) => {
|
|||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultAdminDraftOrdersRelations = ["order", "cart"]
|
export const defaultAdminDraftOrdersRelations = [
|
||||||
|
"order",
|
||||||
|
"cart",
|
||||||
|
"cart.items",
|
||||||
|
"cart.items.adjustments",
|
||||||
|
]
|
||||||
|
|
||||||
export const defaultAdminDraftOrdersCartRelations = [
|
export const defaultAdminDraftOrdersCartRelations = [
|
||||||
"region",
|
"region",
|
||||||
"items",
|
"items",
|
||||||
|
"items.adjustments",
|
||||||
"payment",
|
"payment",
|
||||||
"shipping_address",
|
"shipping_address",
|
||||||
"billing_address",
|
"billing_address",
|
||||||
|
|||||||
@@ -5,12 +5,15 @@ import { SwapServiceMock } from "../../../../../services/__mocks__/swap"
|
|||||||
const defaultRelations = [
|
const defaultRelations = [
|
||||||
"order",
|
"order",
|
||||||
"additional_items",
|
"additional_items",
|
||||||
|
"additional_items.adjustments",
|
||||||
"return_order",
|
"return_order",
|
||||||
"fulfillments",
|
"fulfillments",
|
||||||
"payment",
|
"payment",
|
||||||
"shipping_address",
|
"shipping_address",
|
||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
"cart",
|
"cart",
|
||||||
|
"cart.items",
|
||||||
|
"cart.items.adjustments",
|
||||||
]
|
]
|
||||||
|
|
||||||
const defaultFields = [
|
const defaultFields = [
|
||||||
|
|||||||
@@ -24,12 +24,15 @@ export default (app) => {
|
|||||||
export const defaultAdminSwapRelations = [
|
export const defaultAdminSwapRelations = [
|
||||||
"order",
|
"order",
|
||||||
"additional_items",
|
"additional_items",
|
||||||
|
"additional_items.adjustments",
|
||||||
"return_order",
|
"return_order",
|
||||||
"fulfillments",
|
"fulfillments",
|
||||||
"payment",
|
"payment",
|
||||||
"shipping_address",
|
"shipping_address",
|
||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
"cart",
|
"cart",
|
||||||
|
"cart.items",
|
||||||
|
"cart.items.adjustments",
|
||||||
]
|
]
|
||||||
|
|
||||||
export const defaultAdminSwapFields = [
|
export const defaultAdminSwapFields = [
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ export const defaultStoreCartRelations = [
|
|||||||
"gift_cards",
|
"gift_cards",
|
||||||
"region",
|
"region",
|
||||||
"items",
|
"items",
|
||||||
|
"items.adjustments",
|
||||||
"payment",
|
"payment",
|
||||||
"shipping_address",
|
"shipping_address",
|
||||||
"billing_address",
|
"billing_address",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export * from "./models/product-type-tax-rate"
|
|||||||
export * from "./models/tax-rate"
|
export * from "./models/tax-rate"
|
||||||
export * from "./models/shipping-method-tax-line"
|
export * from "./models/shipping-method-tax-line"
|
||||||
export * from "./models/line-item-tax-line"
|
export * from "./models/line-item-tax-line"
|
||||||
|
export * from "./models/line-item-adjustment"
|
||||||
export * from "./models/address"
|
export * from "./models/address"
|
||||||
export * from "./models/cart"
|
export * from "./models/cart"
|
||||||
export * from "./models/claim-image"
|
export * from "./models/claim-image"
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||||
|
|
||||||
|
export class addLineItemAdjustments1648600574750 implements MigrationInterface {
|
||||||
|
name = "addLineItemAdjustments1648600574750"
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE "line_item_adjustment" ("id" character varying NOT NULL, "item_id" character varying NOT NULL, "description" character varying NOT NULL, "discount_id" character varying, "amount" integer NOT NULL, "metadata" jsonb, CONSTRAINT "PK_2b1360103753df2dc8257c2c8c3" PRIMARY KEY ("id"))`
|
||||||
|
)
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_be9aea2ccf3567007b6227da4d" ON "line_item_adjustment" ("item_id") `
|
||||||
|
)
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_2f41b20a71f30e60471d7e3769" ON "line_item_adjustment" ("discount_id") `
|
||||||
|
)
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE UNIQUE INDEX "IDX_bf701b88d2041392a288785ada" ON "line_item_adjustment" ("discount_id", "item_id") WHERE "discount_id" IS NOT NULL`
|
||||||
|
)
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "line_item_adjustment" ADD CONSTRAINT "FK_be9aea2ccf3567007b6227da4d2" FOREIGN KEY ("item_id") REFERENCES "line_item"("id") ON DELETE CASCADE ON UPDATE NO ACTION`
|
||||||
|
)
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "line_item_adjustment" ADD CONSTRAINT "FK_2f41b20a71f30e60471d7e3769c" FOREIGN KEY ("discount_id") REFERENCES "discount"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "line_item_adjustment" DROP CONSTRAINT "FK_2f41b20a71f30e60471d7e3769c"`
|
||||||
|
)
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "line_item_adjustment" DROP CONSTRAINT "FK_be9aea2ccf3567007b6227da4d2"`
|
||||||
|
)
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_bf701b88d2041392a288785ada"`)
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_2f41b20a71f30e60471d7e3769"`)
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_be9aea2ccf3567007b6227da4d"`)
|
||||||
|
await queryRunner.query(`DROP TABLE "line_item_adjustment"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
BeforeInsert,
|
||||||
|
Index,
|
||||||
|
Column,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
PrimaryColumn,
|
||||||
|
OneToOne,
|
||||||
|
Unique,
|
||||||
|
} from "typeorm"
|
||||||
|
import { ulid } from "ulid"
|
||||||
|
import { DbAwareColumn } from "../utils/db-aware-column"
|
||||||
|
import { Discount } from "./discount"
|
||||||
|
import { LineItem } from "./line-item"
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
@Index(["discount_id", "item_id"], {
|
||||||
|
unique: true,
|
||||||
|
where: `"discount_id" IS NOT NULL`,
|
||||||
|
})
|
||||||
|
export class LineItemAdjustment {
|
||||||
|
@PrimaryColumn()
|
||||||
|
id: string
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column()
|
||||||
|
item_id: string
|
||||||
|
|
||||||
|
@ManyToOne(
|
||||||
|
() => LineItem,
|
||||||
|
(li) => li.adjustments,
|
||||||
|
{ onDelete: "CASCADE" }
|
||||||
|
)
|
||||||
|
@JoinColumn({ name: "item_id" })
|
||||||
|
item: LineItem
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
description: string
|
||||||
|
|
||||||
|
@ManyToOne(() => Discount)
|
||||||
|
@JoinColumn({ name: "discount_id" })
|
||||||
|
discount: Discount
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ nullable: true })
|
||||||
|
discount_id: string
|
||||||
|
|
||||||
|
@Column({ type: "int" })
|
||||||
|
amount: number
|
||||||
|
|
||||||
|
@DbAwareColumn({ type: "jsonb", nullable: true })
|
||||||
|
metadata: any
|
||||||
|
|
||||||
|
@BeforeInsert()
|
||||||
|
private beforeInsert() {
|
||||||
|
if (this.id) return
|
||||||
|
const id = ulid()
|
||||||
|
this.id = `lia_${id}`
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import { Cart } from "./cart"
|
|||||||
import { Order } from "./order"
|
import { Order } from "./order"
|
||||||
import { ClaimOrder } from "./claim-order"
|
import { ClaimOrder } from "./claim-order"
|
||||||
import { ProductVariant } from "./product-variant"
|
import { ProductVariant } from "./product-variant"
|
||||||
|
import { LineItemAdjustment } from "./line-item-adjustment"
|
||||||
|
|
||||||
@Check(`"fulfilled_quantity" <= "quantity"`)
|
@Check(`"fulfilled_quantity" <= "quantity"`)
|
||||||
@Check(`"shipped_quantity" <= "fulfilled_quantity"`)
|
@Check(`"shipped_quantity" <= "fulfilled_quantity"`)
|
||||||
@@ -65,6 +66,9 @@ export class LineItem {
|
|||||||
@OneToMany(() => LineItemTaxLine, (tl) => tl.item, { cascade: ["insert"] })
|
@OneToMany(() => LineItemTaxLine, (tl) => tl.item, { cascade: ["insert"] })
|
||||||
tax_lines: LineItemTaxLine[]
|
tax_lines: LineItemTaxLine[]
|
||||||
|
|
||||||
|
@OneToMany(() => LineItemAdjustment, (lia) => lia.item, { cascade: ["insert"] })
|
||||||
|
adjustments: LineItemAdjustment[]
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
title: string
|
title: string
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { EntityRepository, Repository } from "typeorm"
|
||||||
|
import { LineItemAdjustment } from "../models/line-item-adjustment"
|
||||||
|
|
||||||
|
@EntityRepository(LineItemAdjustment)
|
||||||
|
export class LineItemAdjustmentRepository extends Repository<LineItemAdjustment> {}
|
||||||
|
|
||||||
@@ -17,6 +17,7 @@ export class LineItemRepository extends Repository<LineItem> {
|
|||||||
): Promise<(LineItem & { return_item: ReturnItem })[]> {
|
): Promise<(LineItem & { return_item: ReturnItem })[]> {
|
||||||
const qb = this.createQueryBuilder("li")
|
const qb = this.createQueryBuilder("li")
|
||||||
.leftJoinAndSelect(`li.tax_lines`, "tax_lines")
|
.leftJoinAndSelect(`li.tax_lines`, "tax_lines")
|
||||||
|
.leftJoinAndSelect(`li.adjustments`, "adjustments")
|
||||||
.leftJoinAndMapOne(
|
.leftJoinAndMapOne(
|
||||||
`li.return_item`,
|
`li.return_item`,
|
||||||
ReturnItem,
|
ReturnItem,
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ export const CartServiceMock = {
|
|||||||
withTransaction: function() {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
updatePaymentSession: jest.fn().mockImplementation(data => {
|
updatePaymentSession: jest.fn().mockImplementation((data) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
authorizePayment: jest.fn().mockImplementation((id, data) => {
|
authorizePayment: jest.fn().mockImplementation((id, data) => {
|
||||||
@@ -223,13 +223,13 @@ export const CartServiceMock = {
|
|||||||
}
|
}
|
||||||
return Promise.resolve(carts.testCart)
|
return Promise.resolve(carts.testCart)
|
||||||
}),
|
}),
|
||||||
refreshPaymentSession: jest.fn().mockImplementation(data => {
|
refreshPaymentSession: jest.fn().mockImplementation((data) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
update: jest.fn().mockImplementation(data => {
|
update: jest.fn().mockImplementation((data) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
create: jest.fn().mockImplementation(data => {
|
create: jest.fn().mockImplementation((data) => {
|
||||||
if (data.region_id === IdMap.getId("testRegion")) {
|
if (data.region_id === IdMap.getId("testRegion")) {
|
||||||
return Promise.resolve(carts.regionCart)
|
return Promise.resolve(carts.regionCart)
|
||||||
}
|
}
|
||||||
@@ -238,7 +238,7 @@ export const CartServiceMock = {
|
|||||||
}
|
}
|
||||||
return Promise.resolve(carts.regionCart)
|
return Promise.resolve(carts.regionCart)
|
||||||
}),
|
}),
|
||||||
retrieve: jest.fn().mockImplementation(cartId => {
|
retrieve: jest.fn().mockImplementation((cartId) => {
|
||||||
if (cartId === IdMap.getId("fr-cart")) {
|
if (cartId === IdMap.getId("fr-cart")) {
|
||||||
return Promise.resolve(carts.frCart)
|
return Promise.resolve(carts.frCart)
|
||||||
}
|
}
|
||||||
@@ -326,20 +326,20 @@ export const CartServiceMock = {
|
|||||||
applyDiscount: jest.fn().mockImplementation((cartId, code) => {
|
applyDiscount: jest.fn().mockImplementation((cartId, code) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
setPaymentSession: jest.fn().mockImplementation(cartId => {
|
setPaymentSession: jest.fn().mockImplementation((cartId) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
setPaymentSessions: jest.fn().mockImplementation(cartId => {
|
setPaymentSessions: jest.fn().mockImplementation((cartId) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
setShippingOptions: jest.fn().mockImplementation(cartId => {
|
setShippingOptions: jest.fn().mockImplementation((cartId) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
decorate: jest.fn().mockImplementation(cart => {
|
decorate: jest.fn().mockImplementation((cart) => {
|
||||||
cart.decorated = true
|
cart.decorated = true
|
||||||
return cart
|
return cart
|
||||||
}),
|
}),
|
||||||
addShippingMethod: jest.fn().mockImplementation(cartId => {
|
addShippingMethod: jest.fn().mockImplementation((cartId) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
retrieveShippingOption: jest.fn().mockImplementation((cartId, optionId) => {
|
retrieveShippingOption: jest.fn().mockImplementation((cartId, optionId) => {
|
||||||
@@ -378,6 +378,9 @@ export const CartServiceMock = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
refreshAdjustments_: jest.fn().mockImplementation((cart) => {
|
||||||
|
return Promise.resolve({})
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
const mock = jest.fn().mockImplementation(() => {
|
const mock = jest.fn().mockImplementation(() => {
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export const LineItemAdjustmentServiceMock = {
|
||||||
|
withTransaction: function() {
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
create: jest.fn().mockImplementation((data) => {
|
||||||
|
return Promise.resolve({ ...data })
|
||||||
|
}),
|
||||||
|
update: jest.fn().mockImplementation((data) => {
|
||||||
|
return Promise.resolve({ ...data })
|
||||||
|
}),
|
||||||
|
validate: jest.fn().mockImplementation((data) => {
|
||||||
|
if (data.title === "invalid lineitem") {
|
||||||
|
throw new Error(`"content" is required`)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}),
|
||||||
|
delete: jest.fn().mockImplementation((data) => {
|
||||||
|
return Promise.resolve({})
|
||||||
|
}),
|
||||||
|
createAdjustmentForLineItem: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation((cart, lineItem) => {
|
||||||
|
return Promise.resolve({
|
||||||
|
item_id: lineItem.id,
|
||||||
|
amount: 1000,
|
||||||
|
discount_id: "disc_2",
|
||||||
|
id: "lia-1",
|
||||||
|
description: "discount",
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
createAdjustments: jest.fn().mockImplementation((cart, lineItem) => {
|
||||||
|
if (lineItem) {
|
||||||
|
return Promise.resolve({
|
||||||
|
item_id: lineItem.id,
|
||||||
|
amount: 1000,
|
||||||
|
discount_id: "disc_2",
|
||||||
|
id: "lia-1",
|
||||||
|
description: "discount",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve([
|
||||||
|
{
|
||||||
|
item_id: "li-1",
|
||||||
|
amount: 200,
|
||||||
|
discount_id: "disc_2",
|
||||||
|
id: "lia-1",
|
||||||
|
description: "discount",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item_id: "li-3",
|
||||||
|
amount: 100,
|
||||||
|
discount_id: "disc_3",
|
||||||
|
id: "lia-2",
|
||||||
|
description: "discount",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const mock = jest.fn().mockImplementation(() => {
|
||||||
|
return LineItemAdjustmentServiceMock
|
||||||
|
})
|
||||||
|
|
||||||
|
export default mock
|
||||||
@@ -3,6 +3,7 @@ import { MedusaError } from "medusa-core-utils"
|
|||||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||||
import CartService from "../cart"
|
import CartService from "../cart"
|
||||||
import { InventoryServiceMock } from "../__mocks__/inventory"
|
import { InventoryServiceMock } from "../__mocks__/inventory"
|
||||||
|
import { LineItemAdjustmentServiceMock } from "../__mocks__/line-item-adjustment"
|
||||||
|
|
||||||
const eventBusService = {
|
const eventBusService = {
|
||||||
emit: jest.fn(),
|
emit: jest.fn(),
|
||||||
@@ -347,6 +348,7 @@ describe("CartService", () => {
|
|||||||
findOneWithRelations: (rels, q) => {
|
findOneWithRelations: (rels, q) => {
|
||||||
if (q.where.id === IdMap.getId("cartWithLine")) {
|
if (q.where.id === IdMap.getId("cartWithLine")) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
id: IdMap.getId("cartWithLine"),
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: IdMap.getId("merger"),
|
id: IdMap.getId("merger"),
|
||||||
@@ -359,6 +361,7 @@ describe("CartService", () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
id: IdMap.getId("emptyCart"),
|
||||||
shipping_methods: [
|
shipping_methods: [
|
||||||
{
|
{
|
||||||
shipping_option: {
|
shipping_option: {
|
||||||
@@ -378,13 +381,14 @@ describe("CartService", () => {
|
|||||||
eventBusService,
|
eventBusService,
|
||||||
shippingOptionService,
|
shippingOptionService,
|
||||||
inventoryService,
|
inventoryService,
|
||||||
|
lineItemAdjustmentService: LineItemAdjustmentServiceMock,
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully creates new line item", async () => {
|
it("creates a new line item and emits a created event", async () => {
|
||||||
const lineItem = {
|
const lineItem = {
|
||||||
title: "New Line",
|
title: "New Line",
|
||||||
description: "This is a new line",
|
description: "This is a new line",
|
||||||
@@ -393,7 +397,6 @@ describe("CartService", () => {
|
|||||||
unit_price: 123,
|
unit_price: 123,
|
||||||
quantity: 10,
|
quantity: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
await cartService.addLineItem(IdMap.getId("emptyCart"), _.clone(lineItem))
|
await cartService.addLineItem(IdMap.getId("emptyCart"), _.clone(lineItem))
|
||||||
|
|
||||||
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
||||||
@@ -408,6 +411,15 @@ describe("CartService", () => {
|
|||||||
has_shipping: false,
|
has_shipping: false,
|
||||||
cart_id: IdMap.getId("emptyCart"),
|
cart_id: IdMap.getId("emptyCart"),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ id: IdMap.getId("emptyCart") })
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully creates new line item with shipping", async () => {
|
it("successfully creates new line item with shipping", async () => {
|
||||||
@@ -440,6 +452,15 @@ describe("CartService", () => {
|
|||||||
has_shipping: false,
|
has_shipping: false,
|
||||||
cart_id: IdMap.getId("emptyCart"),
|
cart_id: IdMap.getId("emptyCart"),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ id: IdMap.getId("emptyCart") })
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully merges existing line item", async () => {
|
it("successfully merges existing line item", async () => {
|
||||||
@@ -462,6 +483,20 @@ describe("CartService", () => {
|
|||||||
quantity: 2,
|
quantity: 2,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledTimes(1)
|
||||||
|
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledWith({
|
||||||
|
item_id: [IdMap.getId("merger")],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ id: IdMap.getId("cartWithLine") })
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("throws if inventory isn't covered", async () => {
|
it("throws if inventory isn't covered", async () => {
|
||||||
@@ -559,6 +594,7 @@ describe("CartService", () => {
|
|||||||
lineItemService,
|
lineItemService,
|
||||||
shippingOptionService,
|
shippingOptionService,
|
||||||
eventBusService,
|
eventBusService,
|
||||||
|
lineItemAdjustmentService: LineItemAdjustmentServiceMock,
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -576,6 +612,22 @@ describe("CartService", () => {
|
|||||||
IdMap.getId("itemToRemove")
|
IdMap.getId("itemToRemove")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledTimes(1)
|
||||||
|
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledWith({
|
||||||
|
item_id: [IdMap.getId("itemToRemove")],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
items: [{ id: IdMap.getId("itemToRemove") }],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
||||||
expect(eventBusService.emit).toHaveBeenCalledWith(
|
expect(eventBusService.emit).toHaveBeenCalledWith(
|
||||||
"cart.updated",
|
"cart.updated",
|
||||||
@@ -598,6 +650,22 @@ describe("CartService", () => {
|
|||||||
profile_id: IdMap.getId("prevPro"),
|
profile_id: IdMap.getId("prevPro"),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledTimes(1)
|
||||||
|
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledWith({
|
||||||
|
item_id: [IdMap.getId("itemToRemove")],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
items: [expect.objectContaining({ id: IdMap.getId("itemToRemove") })],
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("resolves if line item is not in cart", async () => {
|
it("resolves if line item is not in cart", async () => {
|
||||||
@@ -666,7 +734,13 @@ describe("CartService", () => {
|
|||||||
|
|
||||||
describe("updateLineItem", () => {
|
describe("updateLineItem", () => {
|
||||||
const lineItemService = {
|
const lineItemService = {
|
||||||
update: jest.fn(),
|
update: jest.fn().mockImplementation(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
id: IdMap.getId("existing"),
|
||||||
|
variant_id: IdMap.getId("good"),
|
||||||
|
quantity: 1,
|
||||||
|
})
|
||||||
|
),
|
||||||
withTransaction: function() {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
@@ -692,6 +766,7 @@ describe("CartService", () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
id: IdMap.getId("cartWithLine"),
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: IdMap.getId("existing"),
|
id: IdMap.getId("existing"),
|
||||||
@@ -709,6 +784,7 @@ describe("CartService", () => {
|
|||||||
lineItemService,
|
lineItemService,
|
||||||
eventBusService,
|
eventBusService,
|
||||||
inventoryService,
|
inventoryService,
|
||||||
|
lineItemAdjustmentService: LineItemAdjustmentServiceMock,
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -733,6 +809,20 @@ describe("CartService", () => {
|
|||||||
IdMap.getId("existing"),
|
IdMap.getId("existing"),
|
||||||
{ quantity: 2 }
|
{ quantity: 2 }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledTimes(1)
|
||||||
|
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledWith({
|
||||||
|
item_id: [IdMap.getId("existing")],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ id: IdMap.getId("cartWithLine") })
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("throws if inventory isn't covered", async () => {
|
it("throws if inventory isn't covered", async () => {
|
||||||
@@ -1599,6 +1689,18 @@ describe("CartService", () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
region_id: IdMap.getId("good"),
|
region_id: IdMap.getId("good"),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "li1",
|
||||||
|
quantity: 2,
|
||||||
|
unit_price: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "li2",
|
||||||
|
quantity: 1,
|
||||||
|
unit_price: 500,
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (q.where.id === "with-d-and-customer") {
|
if (q.where.id === "with-d-and-customer") {
|
||||||
@@ -1619,6 +1721,18 @@ describe("CartService", () => {
|
|||||||
id: IdMap.getId("cart"),
|
id: IdMap.getId("cart"),
|
||||||
discounts: [],
|
discounts: [],
|
||||||
region_id: IdMap.getId("good"),
|
region_id: IdMap.getId("good"),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "li1",
|
||||||
|
quantity: 2,
|
||||||
|
unit_price: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "li2",
|
||||||
|
quantity: 1,
|
||||||
|
unit_price: 500,
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -1756,6 +1870,11 @@ describe("CartService", () => {
|
|||||||
}
|
}
|
||||||
return Promise.resolve(false)
|
return Promise.resolve(false)
|
||||||
}),
|
}),
|
||||||
|
validateDiscountForCartOrThrow: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation((cart, discount) => {
|
||||||
|
return Promise.resolve({ hasErrors: () => false })
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
const cartService = new CartService({
|
const cartService = new CartService({
|
||||||
@@ -1764,6 +1883,7 @@ describe("CartService", () => {
|
|||||||
cartRepository,
|
cartRepository,
|
||||||
discountService,
|
discountService,
|
||||||
eventBusService,
|
eventBusService,
|
||||||
|
lineItemAdjustmentService: LineItemAdjustmentServiceMock,
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -1793,6 +1913,18 @@ describe("CartService", () => {
|
|||||||
subtotal: 0,
|
subtotal: 0,
|
||||||
tax_total: 0,
|
tax_total: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "li1",
|
||||||
|
quantity: 2,
|
||||||
|
unit_price: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "li2",
|
||||||
|
quantity: 1,
|
||||||
|
unit_price: 500,
|
||||||
|
},
|
||||||
|
],
|
||||||
discounts: [
|
discounts: [
|
||||||
{
|
{
|
||||||
id: IdMap.getId("10off"),
|
id: IdMap.getId("10off"),
|
||||||
@@ -1804,6 +1936,20 @@ describe("CartService", () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledTimes(1)
|
||||||
|
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledWith({
|
||||||
|
item_id: ["li1", "li2"],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ id: IdMap.getId("cart") })
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully applies discount to cart and removes old one", async () => {
|
it("successfully applies discount to cart and removes old one", async () => {
|
||||||
@@ -1820,6 +1966,18 @@ describe("CartService", () => {
|
|||||||
subtotal: 0,
|
subtotal: 0,
|
||||||
tax_total: 0,
|
tax_total: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "li1",
|
||||||
|
quantity: 2,
|
||||||
|
unit_price: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "li2",
|
||||||
|
quantity: 1,
|
||||||
|
unit_price: 500,
|
||||||
|
},
|
||||||
|
],
|
||||||
discounts: [
|
discounts: [
|
||||||
{
|
{
|
||||||
id: IdMap.getId("10off"),
|
id: IdMap.getId("10off"),
|
||||||
@@ -1831,6 +1989,20 @@ describe("CartService", () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledTimes(1)
|
||||||
|
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledWith({
|
||||||
|
item_id: ["li1", "li2"],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
|
).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ id: IdMap.getId("cart") })
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully applies free shipping", async () => {
|
it("successfully applies free shipping", async () => {
|
||||||
@@ -1860,32 +2032,16 @@ describe("CartService", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
discount_total: 0,
|
items: [
|
||||||
shipping_total: 0,
|
|
||||||
subtotal: 0,
|
|
||||||
tax_total: 0,
|
|
||||||
total: 0,
|
|
||||||
region_id: IdMap.getId("good"),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("successfully applies discount with usage count null", async () => {
|
|
||||||
await cartService.update(IdMap.getId("cart"), {
|
|
||||||
discounts: [{ code: "null-count" }],
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(discountService.retrieveByCode).toHaveBeenCalledTimes(1)
|
|
||||||
expect(cartRepository.save).toHaveBeenCalledTimes(1)
|
|
||||||
expect(cartRepository.save).toHaveBeenCalledWith({
|
|
||||||
id: IdMap.getId("cart"),
|
|
||||||
discounts: [
|
|
||||||
{
|
{
|
||||||
id: IdMap.getId("null-count"),
|
id: "li1",
|
||||||
code: "null-count",
|
quantity: 2,
|
||||||
regions: [{ id: IdMap.getId("good") }],
|
unit_price: 1000,
|
||||||
usage_count: 0,
|
},
|
||||||
usage_limit: 2,
|
{
|
||||||
rule: {},
|
id: "li2",
|
||||||
|
quantity: 1,
|
||||||
|
unit_price: 500,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
discount_total: 0,
|
discount_total: 0,
|
||||||
@@ -1895,92 +2051,20 @@ describe("CartService", () => {
|
|||||||
total: 0,
|
total: 0,
|
||||||
region_id: IdMap.getId("good"),
|
region_id: IdMap.getId("good"),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledTimes(1)
|
||||||
|
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledWith({
|
||||||
|
item_id: ["li1", "li2"],
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully applies valid discount with expiration date to cart", async () => {
|
expect(
|
||||||
await cartService.update(IdMap.getId("fr-cart"), {
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
discounts: [
|
).toHaveBeenCalledTimes(1)
|
||||||
{
|
expect(
|
||||||
code: "ValidDiscount",
|
LineItemAdjustmentServiceMock.createAdjustments
|
||||||
},
|
).toHaveBeenCalledWith(
|
||||||
],
|
expect.objectContaining({ id: IdMap.getId("cart") })
|
||||||
})
|
|
||||||
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
|
||||||
expect(eventBusService.emit).toHaveBeenCalledWith(
|
|
||||||
"cart.updated",
|
|
||||||
expect.any(Object)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(cartRepository.save).toHaveBeenCalledTimes(1)
|
|
||||||
expect(cartRepository.save).toHaveBeenCalledWith({
|
|
||||||
id: IdMap.getId("cart"),
|
|
||||||
region_id: IdMap.getId("good"),
|
|
||||||
discount_total: 0,
|
|
||||||
shipping_total: 0,
|
|
||||||
subtotal: 0,
|
|
||||||
tax_total: 0,
|
|
||||||
total: 0,
|
|
||||||
discounts: [
|
|
||||||
{
|
|
||||||
id: IdMap.getId("10off"),
|
|
||||||
code: "10%OFF",
|
|
||||||
regions: [{ id: IdMap.getId("good") }],
|
|
||||||
rule: {
|
|
||||||
type: "percentage",
|
|
||||||
},
|
|
||||||
starts_at: expect.any(Date),
|
|
||||||
ends_at: expect.any(Date),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("throws if discount is applied too before it's valid", async () => {
|
|
||||||
await expect(
|
|
||||||
cartService.update(IdMap.getId("cart"), {
|
|
||||||
discounts: [{ code: "EarlyDiscount" }],
|
|
||||||
})
|
|
||||||
).rejects.toThrow("Discount is not valid yet")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("throws if expired discount is applied", async () => {
|
|
||||||
await expect(
|
|
||||||
cartService.update(IdMap.getId("cart"), {
|
|
||||||
discounts: [{ code: "ExpiredDiscount" }],
|
|
||||||
})
|
|
||||||
).rejects.toThrow("Discount is expired")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("throws if expired dynamic discount is applied", async () => {
|
|
||||||
await expect(
|
|
||||||
cartService.update(IdMap.getId("cart"), {
|
|
||||||
discounts: [{ code: "ExpiredDynamicDiscount" }],
|
|
||||||
})
|
|
||||||
).rejects.toThrow("Discount is expired")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("throws if expired dynamic discount is applied after ends_at", async () => {
|
|
||||||
await expect(
|
|
||||||
cartService.update(IdMap.getId("cart"), {
|
|
||||||
discounts: [{ code: "ExpiredDynamicDiscountEndDate" }],
|
|
||||||
})
|
|
||||||
).rejects.toThrow("Discount is expired")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("throws if discount limit is reached", async () => {
|
|
||||||
await expect(
|
|
||||||
cartService.update(IdMap.getId("cart"), {
|
|
||||||
discounts: [{ code: "limit-reached" }],
|
|
||||||
})
|
|
||||||
).rejects.toThrow("Discount has been used maximum allowed times")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("throws if discount is not available in region", async () => {
|
|
||||||
await expect(
|
|
||||||
cartService.update(IdMap.getId("cart"), {
|
|
||||||
discounts: [{ code: "US10" }],
|
|
||||||
})
|
|
||||||
).rejects.toThrow("The discount is not available in current region")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully applies discount with a check for customer applicableness", async () => {
|
it("successfully applies discount with a check for customer applicableness", async () => {
|
||||||
|
|||||||
@@ -682,6 +682,472 @@ describe("DiscountService", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("validateDiscountForCartOrThrow", () => {
|
||||||
|
const getCart = (id) => {
|
||||||
|
if (id === "with-d") {
|
||||||
|
return {
|
||||||
|
id: "cart",
|
||||||
|
discounts: [
|
||||||
|
{
|
||||||
|
code: "1234",
|
||||||
|
rule: {
|
||||||
|
type: "fixed",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "FS1234",
|
||||||
|
rule: {
|
||||||
|
type: "free_shipping",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
region_id: "good",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "li1",
|
||||||
|
quantity: 2,
|
||||||
|
unit_price: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "li2",
|
||||||
|
quantity: 1,
|
||||||
|
unit_price: 500,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (id === "with-d-and-customer") {
|
||||||
|
return {
|
||||||
|
id: "with-d-and-customer",
|
||||||
|
discounts: [
|
||||||
|
{
|
||||||
|
code: "ApplicableForCustomer",
|
||||||
|
rule: {
|
||||||
|
type: "fixed",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
region_id: "good",
|
||||||
|
customer_id: "test-customer",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: "cart",
|
||||||
|
discounts: [
|
||||||
|
{
|
||||||
|
code: "CODE",
|
||||||
|
rule: {
|
||||||
|
type: "percentage",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
region_id: "good",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "li1",
|
||||||
|
quantity: 2,
|
||||||
|
unit_price: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "li2",
|
||||||
|
quantity: 1,
|
||||||
|
unit_price: 500,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let discountService
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
discountService = new DiscountService({})
|
||||||
|
const hasReachedLimitMock = jest.fn().mockImplementation(() => false)
|
||||||
|
const isDisabledMock = jest.fn().mockImplementation(() => false)
|
||||||
|
const isValidForRegionMock = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => Promise.resolve(true))
|
||||||
|
const canApplyForCustomerMock = jest.fn().mockImplementation(() => {
|
||||||
|
return Promise.resolve(true)
|
||||||
|
})
|
||||||
|
discountService.hasReachedLimit = hasReachedLimitMock
|
||||||
|
discountService.isDisabled = isDisabledMock
|
||||||
|
discountService.canApplyForCustomer = canApplyForCustomerMock
|
||||||
|
discountService.isValidForRegion = isValidForRegionMock
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls all validation methods when cart includes a customer_id and doesn't throw", async () => {
|
||||||
|
const discount = {
|
||||||
|
id: "10off",
|
||||||
|
code: "10%OFF",
|
||||||
|
regions: [{ id: "good" }],
|
||||||
|
rule: {
|
||||||
|
id: "10off-rule",
|
||||||
|
type: "percentage",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const cart = getCart("with-d-and-customer")
|
||||||
|
const result = await discountService.validateDiscountForCartOrThrow(
|
||||||
|
cart,
|
||||||
|
discount
|
||||||
|
)
|
||||||
|
expect(result).toBeUndefined()
|
||||||
|
|
||||||
|
expect(discountService.hasReachedLimit).toHaveBeenCalledTimes(1)
|
||||||
|
expect(discountService.hasReachedLimit).toHaveBeenCalledWith(discount)
|
||||||
|
|
||||||
|
expect(discountService.isDisabled).toHaveBeenCalledTimes(1)
|
||||||
|
expect(discountService.isDisabled).toHaveBeenCalledWith(discount)
|
||||||
|
|
||||||
|
expect(discountService.isValidForRegion).toHaveBeenCalledTimes(1)
|
||||||
|
expect(discountService.isValidForRegion).toHaveBeenCalledWith(
|
||||||
|
discount,
|
||||||
|
cart.region_id
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(discountService.canApplyForCustomer).toHaveBeenCalledTimes(1)
|
||||||
|
expect(discountService.canApplyForCustomer).toHaveBeenCalledWith(
|
||||||
|
discount.rule.id,
|
||||||
|
cart.customer_id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throws when hasReachedLimit returns true", async () => {
|
||||||
|
const discount = {}
|
||||||
|
const cart = getCart("with-d-and-customer")
|
||||||
|
discountService.hasReachedLimit = jest.fn().mockImplementation(() => true)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
discountService.validateDiscountForCartOrThrow(cart, discount)
|
||||||
|
).rejects.toThrow({
|
||||||
|
message: "Discount has been used maximum allowed times",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throws when hasNotStarted returns true", async () => {
|
||||||
|
const discount = {}
|
||||||
|
const cart = getCart("with-d-and-customer")
|
||||||
|
discountService.hasNotStarted = jest.fn().mockImplementation(() => true)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
discountService.validateDiscountForCartOrThrow(cart, discount)
|
||||||
|
).rejects.toThrow({
|
||||||
|
message: "Discount is not valid yet",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throws when hasExpired returns true", async () => {
|
||||||
|
const discount = {}
|
||||||
|
const cart = getCart("with-d-and-customer")
|
||||||
|
discountService.hasExpired = jest.fn().mockImplementation(() => true)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
discountService.validateDiscountForCartOrThrow(cart, discount)
|
||||||
|
).rejects.toThrow({
|
||||||
|
message: "Discount is expired",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throws when isDisabled returns true", async () => {
|
||||||
|
const discount = {}
|
||||||
|
const cart = getCart("with-d-and-customer")
|
||||||
|
discountService.isDisabled = jest.fn().mockImplementation(() => true)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
discountService.validateDiscountForCartOrThrow(cart, discount)
|
||||||
|
).rejects.toThrow({
|
||||||
|
message: "The discount code is disabled",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throws when isValidForRegion returns false", async () => {
|
||||||
|
const discount = {}
|
||||||
|
const cart = getCart("with-d-and-customer")
|
||||||
|
discountService.isValidForRegion = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => Promise.resolve(false))
|
||||||
|
|
||||||
|
expect(
|
||||||
|
discountService.validateDiscountForCartOrThrow(cart, discount)
|
||||||
|
).rejects.toThrow({
|
||||||
|
message: "The discount is not available in current region",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throws when canApplyForCustomer returns false", async () => {
|
||||||
|
const discount = { rule: { id: "" } }
|
||||||
|
const cart = getCart("with-d-and-customer")
|
||||||
|
discountService.canApplyForCustomer = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => Promise.resolve(false))
|
||||||
|
|
||||||
|
expect(
|
||||||
|
discountService.validateDiscountForCartOrThrow(cart, discount)
|
||||||
|
).rejects.toThrow({
|
||||||
|
message: "Discount is not valid for customer",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("hasReachedLimit", () => {
|
||||||
|
const discountService = new DiscountService({})
|
||||||
|
|
||||||
|
it("returns true if discount limit is reached", () => {
|
||||||
|
const discount = {
|
||||||
|
id: "limit-reached",
|
||||||
|
code: "limit-reached",
|
||||||
|
regions: [{ id: "good" }],
|
||||||
|
rule: {},
|
||||||
|
usage_count: 2,
|
||||||
|
usage_limit: 2,
|
||||||
|
}
|
||||||
|
const hasReachedLimit = discountService.hasReachedLimit(discount)
|
||||||
|
expect(hasReachedLimit).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false if discount limit is not reached", () => {
|
||||||
|
const discount = {
|
||||||
|
id: "limit-reached",
|
||||||
|
code: "limit-reached",
|
||||||
|
regions: [{ id: "good" }],
|
||||||
|
rule: {},
|
||||||
|
usage_count: 1,
|
||||||
|
usage_limit: 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasReachedLimit = discountService.hasReachedLimit(discount)
|
||||||
|
expect(hasReachedLimit).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false if discount limit is not set", () => {
|
||||||
|
const discount = {
|
||||||
|
id: "limit-reached",
|
||||||
|
code: "limit-reached",
|
||||||
|
regions: [{ id: "good" }],
|
||||||
|
rule: {},
|
||||||
|
usage_count: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasReachedLimit = discountService.hasReachedLimit(discount)
|
||||||
|
expect(hasReachedLimit).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("isDisabled", () => {
|
||||||
|
const discountService = new DiscountService({})
|
||||||
|
|
||||||
|
it("returns false if discount not disabled", async () => {
|
||||||
|
const discount = {
|
||||||
|
id: "10off",
|
||||||
|
code: "10%OFF",
|
||||||
|
regions: [{ id: "good" }],
|
||||||
|
rule: {
|
||||||
|
id: "10off-rule",
|
||||||
|
type: "percentage",
|
||||||
|
},
|
||||||
|
is_disabled: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDisabled = discountService.isDisabled(discount)
|
||||||
|
expect(isDisabled).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true if discount is disabled", async () => {
|
||||||
|
const discount = {
|
||||||
|
id: "10off",
|
||||||
|
code: "10%OFF",
|
||||||
|
regions: [{ id: "good" }],
|
||||||
|
rule: {
|
||||||
|
id: "10off-rule",
|
||||||
|
type: "percentage",
|
||||||
|
},
|
||||||
|
is_disabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDisabled = discountService.isDisabled(discount)
|
||||||
|
expect(isDisabled).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("hasNotStarted", () => {
|
||||||
|
const discountService = new DiscountService({})
|
||||||
|
|
||||||
|
it("returns true if discount has a future starts_at date", async () => {
|
||||||
|
const discount = {
|
||||||
|
id: "10off",
|
||||||
|
code: "10%OFF",
|
||||||
|
regions: [{ id: "good" }],
|
||||||
|
rule: {
|
||||||
|
type: "percentage",
|
||||||
|
},
|
||||||
|
starts_at: getOffsetDate(1),
|
||||||
|
ends_at: getOffsetDate(10),
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasNotStarted = discountService.hasNotStarted(discount)
|
||||||
|
expect(hasNotStarted).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false if discount has a past starts_at date", async () => {
|
||||||
|
const discount = {
|
||||||
|
id: "10off",
|
||||||
|
code: "10%OFF",
|
||||||
|
regions: [{ id: "good" }],
|
||||||
|
rule: {
|
||||||
|
type: "percentage",
|
||||||
|
},
|
||||||
|
starts_at: getOffsetDate(-2),
|
||||||
|
ends_at: getOffsetDate(10),
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasNotStarted = discountService.hasNotStarted(discount)
|
||||||
|
expect(hasNotStarted).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("hasExpired", () => {
|
||||||
|
const discountService = new DiscountService({})
|
||||||
|
|
||||||
|
it("returns false if discount has a future ends_at date", async () => {
|
||||||
|
const discount = {
|
||||||
|
id: "10off",
|
||||||
|
code: "10%OFF",
|
||||||
|
regions: [{ id: "good" }],
|
||||||
|
rule: {
|
||||||
|
type: "percentage",
|
||||||
|
},
|
||||||
|
ends_at: getOffsetDate(10),
|
||||||
|
starts_at: getOffsetDate(-1),
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasExpired = discountService.hasExpired(discount)
|
||||||
|
expect(hasExpired).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true if discount has a past ends_at date", async () => {
|
||||||
|
const discount = {
|
||||||
|
id: "10off",
|
||||||
|
code: "10%OFF",
|
||||||
|
regions: [{ id: "good" }],
|
||||||
|
rule: {
|
||||||
|
type: "percentage",
|
||||||
|
},
|
||||||
|
starts_at: getOffsetDate(-10),
|
||||||
|
ends_at: getOffsetDate(-1),
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasExpired = discountService.hasExpired(discount)
|
||||||
|
expect(hasExpired).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("isValidForRegion", () => {
|
||||||
|
const retrieveMock = jest.fn().mockImplementation((id) => {
|
||||||
|
if (id === "parent-discount-us") {
|
||||||
|
return Promise.resolve({
|
||||||
|
id,
|
||||||
|
regions: [{ id: "us" }],
|
||||||
|
rule: {
|
||||||
|
id: "10off-rule",
|
||||||
|
type: "percentage",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else if (id === "parent-discount-dk") {
|
||||||
|
return Promise.resolve({
|
||||||
|
id,
|
||||||
|
regions: [{ id: "dk" }],
|
||||||
|
rule: {
|
||||||
|
id: "10off-rule",
|
||||||
|
type: "percentage",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const discountService = new DiscountService({})
|
||||||
|
discountService.retrieve = retrieveMock
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false if discount is not available in a given region", async () => {
|
||||||
|
const discount = {
|
||||||
|
id: "10off",
|
||||||
|
code: "10%OFF",
|
||||||
|
regions: [{ id: "us" }],
|
||||||
|
rule: {
|
||||||
|
id: "10off-rule",
|
||||||
|
type: "percentage",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidForRegion = await discountService.isValidForRegion(
|
||||||
|
discount,
|
||||||
|
"dk"
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(retrieveMock).toBeCalledTimes(0)
|
||||||
|
expect(isValidForRegion).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true if discount is available in a given region", async () => {
|
||||||
|
const discount = {
|
||||||
|
id: "10off",
|
||||||
|
code: "10%OFF",
|
||||||
|
regions: [{ id: "us" }],
|
||||||
|
rule: {
|
||||||
|
id: "10off-rule",
|
||||||
|
type: "percentage",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidForRegion = await discountService.isValidForRegion(
|
||||||
|
discount,
|
||||||
|
"us"
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(retrieveMock).toBeCalledTimes(0)
|
||||||
|
expect(isValidForRegion).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false if discount has a parent discount and is not available in region", async () => {
|
||||||
|
const discount = {
|
||||||
|
id: "10off",
|
||||||
|
code: "10%OFF",
|
||||||
|
parent_discount_id: "parent-discount-us",
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidForRegion = await discountService.isValidForRegion(
|
||||||
|
discount,
|
||||||
|
"dk"
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(retrieveMock).toBeCalledTimes(1)
|
||||||
|
expect(retrieveMock).toBeCalledWith(discount.parent_discount_id, {
|
||||||
|
relations: ["rule", "regions"],
|
||||||
|
})
|
||||||
|
expect(isValidForRegion).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true if discount has a parent discount and is available in region", async () => {
|
||||||
|
const discount = {
|
||||||
|
id: "10off",
|
||||||
|
code: "10%OFF",
|
||||||
|
parent_discount_id: "parent-discount-dk",
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidForRegion = await discountService.isValidForRegion(
|
||||||
|
discount,
|
||||||
|
"dk"
|
||||||
|
)
|
||||||
|
expect(retrieveMock).toBeCalledTimes(1)
|
||||||
|
expect(retrieveMock).toBeCalledWith(discount.parent_discount_id, {
|
||||||
|
relations: ["rule", "regions"],
|
||||||
|
})
|
||||||
|
expect(isValidForRegion).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("canApplyForCustomer", () => {
|
describe("canApplyForCustomer", () => {
|
||||||
const discountConditionRepository = {
|
const discountConditionRepository = {
|
||||||
canApplyForCustomer: jest
|
canApplyForCustomer: jest
|
||||||
@@ -736,3 +1202,9 @@ describe("DiscountService", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const getOffsetDate = (offset) => {
|
||||||
|
const date = new Date()
|
||||||
|
date.setDate(date.getDate() + offset)
|
||||||
|
return date
|
||||||
|
}
|
||||||
@@ -12,22 +12,22 @@ const eventBusService = {
|
|||||||
|
|
||||||
describe("DraftOrderService", () => {
|
describe("DraftOrderService", () => {
|
||||||
const totalsService = {
|
const totalsService = {
|
||||||
getTotal: o => {
|
getTotal: (o) => {
|
||||||
return o.total || 0
|
return o.total || 0
|
||||||
},
|
},
|
||||||
getSubtotal: o => {
|
getSubtotal: (o) => {
|
||||||
return o.subtotal || 0
|
return o.subtotal || 0
|
||||||
},
|
},
|
||||||
getTaxTotal: o => {
|
getTaxTotal: (o) => {
|
||||||
return o.tax_total || 0
|
return o.tax_total || 0
|
||||||
},
|
},
|
||||||
getDiscountTotal: o => {
|
getDiscountTotal: (o) => {
|
||||||
return o.discount_total || 0
|
return o.discount_total || 0
|
||||||
},
|
},
|
||||||
getShippingTotal: o => {
|
getShippingTotal: (o) => {
|
||||||
return o.shipping_total || 0
|
return o.shipping_total || 0
|
||||||
},
|
},
|
||||||
getGiftCardTotal: o => {
|
getGiftCardTotal: (o) => {
|
||||||
return o.gift_card_total || 0
|
return o.gift_card_total || 0
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,7 @@ describe("DraftOrderService", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cartService = {
|
const cartService = {
|
||||||
create: jest.fn().mockImplementation(data =>
|
create: jest.fn().mockImplementation((data) =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
id: "test-cart",
|
id: "test-cart",
|
||||||
...data,
|
...data,
|
||||||
@@ -110,16 +110,16 @@ describe("DraftOrderService", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const addressRepository = MockRepository({
|
const addressRepository = MockRepository({
|
||||||
create: addr => ({
|
create: (addr) => ({
|
||||||
...addr,
|
...addr,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const draftOrderRepository = MockRepository({
|
const draftOrderRepository = MockRepository({
|
||||||
create: d => ({
|
create: (d) => ({
|
||||||
...d,
|
...d,
|
||||||
}),
|
}),
|
||||||
save: d => ({
|
save: (d) => ({
|
||||||
id: "test-draft-order",
|
id: "test-draft-order",
|
||||||
...d,
|
...d,
|
||||||
}),
|
}),
|
||||||
@@ -170,7 +170,13 @@ describe("DraftOrderService", () => {
|
|||||||
"test-variant",
|
"test-variant",
|
||||||
"test-region",
|
"test-region",
|
||||||
2,
|
2,
|
||||||
{ metadata: {}, unit_price: undefined }
|
{
|
||||||
|
metadata: {},
|
||||||
|
unit_price: undefined,
|
||||||
|
cart: expect.objectContaining({
|
||||||
|
id: "test-cart",
|
||||||
|
}),
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(lineItemService.create).toHaveBeenCalledTimes(1)
|
expect(lineItemService.create).toHaveBeenCalledTimes(1)
|
||||||
@@ -228,14 +234,14 @@ describe("DraftOrderService", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const draftOrderRepository = MockRepository({
|
const draftOrderRepository = MockRepository({
|
||||||
create: d => ({
|
create: (d) => ({
|
||||||
...d,
|
...d,
|
||||||
}),
|
}),
|
||||||
save: d => ({
|
save: (d) => ({
|
||||||
id: "test-draft-order",
|
id: "test-draft-order",
|
||||||
...d,
|
...d,
|
||||||
}),
|
}),
|
||||||
findOne: q => {
|
findOne: (q) => {
|
||||||
switch (q.where.id) {
|
switch (q.where.id) {
|
||||||
case "completed":
|
case "completed":
|
||||||
return Promise.resolve(completedOrder)
|
return Promise.resolve(completedOrder)
|
||||||
|
|||||||
@@ -0,0 +1,314 @@
|
|||||||
|
import LineItemAdjustmentService from "../line-item-adjustment"
|
||||||
|
import { MockManager, MockRepository, IdMap } from "medusa-test-utils"
|
||||||
|
import { EventBusServiceMock } from "../__mocks__/event-bus"
|
||||||
|
import { DiscountServiceMock } from "../__mocks__/discount"
|
||||||
|
import { In } from "typeorm"
|
||||||
|
|
||||||
|
describe("LineItemAdjustmentService", () => {
|
||||||
|
describe("list", () => {
|
||||||
|
const lineItemAdjustmentRepo = MockRepository({
|
||||||
|
find: (q) => {
|
||||||
|
return Promise.resolve([
|
||||||
|
{
|
||||||
|
id: "lia-1",
|
||||||
|
description: "discount",
|
||||||
|
amount: 1000,
|
||||||
|
item: "li-1",
|
||||||
|
discount_id: "disc_1",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const lineItemAdjustmentService = new LineItemAdjustmentService({
|
||||||
|
manager: MockManager,
|
||||||
|
lineItemAdjustmentRepository: lineItemAdjustmentRepo,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls lineItemAdjustment list method", async () => {
|
||||||
|
await lineItemAdjustmentService.list(
|
||||||
|
{ item_id: "li-1" },
|
||||||
|
{
|
||||||
|
relations: ["item"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(lineItemAdjustmentRepo.find).toHaveBeenCalledTimes(1)
|
||||||
|
expect(lineItemAdjustmentRepo.find).toHaveBeenCalledWith({
|
||||||
|
where: {
|
||||||
|
item_id: "li-1",
|
||||||
|
},
|
||||||
|
relations: ["item"],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("retrieve", () => {
|
||||||
|
const lineItemAdjustmentRepo = MockRepository({
|
||||||
|
findOne: (q) => {
|
||||||
|
switch (q.where.id) {
|
||||||
|
case "lia-1":
|
||||||
|
return Promise.resolve({
|
||||||
|
id: "lia-1",
|
||||||
|
description: "discount",
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const lineItemAdjustmentService = new LineItemAdjustmentService({
|
||||||
|
manager: MockManager,
|
||||||
|
lineItemAdjustmentRepository: lineItemAdjustmentRepo,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls lineItemAdjustment retrieve method", async () => {
|
||||||
|
await lineItemAdjustmentService.retrieve("lia-1", {
|
||||||
|
relations: ["item"],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(lineItemAdjustmentRepo.findOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(lineItemAdjustmentRepo.findOne).toHaveBeenCalledWith({
|
||||||
|
where: { id: "lia-1" },
|
||||||
|
relations: ["item"],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("fails when lineItemAdjustment is not found", async () => {
|
||||||
|
await expect(
|
||||||
|
lineItemAdjustmentService.retrieve("not-existing")
|
||||||
|
).rejects.toThrow(
|
||||||
|
`Line item adjustment with id: not-existing was not found`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("create", () => {
|
||||||
|
const lineItemAdjustment = {
|
||||||
|
id: "lia-1",
|
||||||
|
amount: 2000,
|
||||||
|
description: "discount",
|
||||||
|
item_id: "li-3",
|
||||||
|
discount_id: "disc_999",
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineItemAdjustmentRepo = MockRepository({
|
||||||
|
create: (f) => Promise.resolve(lineItemAdjustment),
|
||||||
|
save: (f) => Promise.resolve(lineItemAdjustment),
|
||||||
|
})
|
||||||
|
|
||||||
|
const lineItemAdjustmentService = new LineItemAdjustmentService({
|
||||||
|
manager: MockManager,
|
||||||
|
lineItemAdjustmentRepository: lineItemAdjustmentRepo,
|
||||||
|
eventBusService: EventBusServiceMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls lineItemAdjustment create method", async () => {
|
||||||
|
await lineItemAdjustmentService.create({
|
||||||
|
amount: 2000,
|
||||||
|
description: "discount",
|
||||||
|
item_id: "li-3",
|
||||||
|
discount_id: "disc_999",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(lineItemAdjustmentRepo.create).toHaveBeenCalledTimes(1)
|
||||||
|
expect(lineItemAdjustmentRepo.create).toHaveBeenCalledWith({
|
||||||
|
amount: 2000,
|
||||||
|
description: "discount",
|
||||||
|
item_id: "li-3",
|
||||||
|
discount_id: "disc_999",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(lineItemAdjustmentRepo.save).toHaveBeenCalledTimes(1)
|
||||||
|
expect(lineItemAdjustmentRepo.save).toHaveBeenCalledWith({
|
||||||
|
id: "lia-1",
|
||||||
|
amount: 2000,
|
||||||
|
description: "discount",
|
||||||
|
item_id: "li-3",
|
||||||
|
discount_id: "disc_999",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("update", () => {
|
||||||
|
const lineItemAdjustment = { id: "lia-1" }
|
||||||
|
|
||||||
|
const lineItemAdjustmentRepo = MockRepository({
|
||||||
|
findOne: (f) => Promise.resolve(lineItemAdjustment),
|
||||||
|
save: (f) => Promise.resolve(lineItemAdjustment),
|
||||||
|
})
|
||||||
|
|
||||||
|
const lineItemAdjustmentService = new LineItemAdjustmentService({
|
||||||
|
manager: MockManager,
|
||||||
|
lineItemAdjustmentRepository: lineItemAdjustmentRepo,
|
||||||
|
eventBusService: EventBusServiceMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls lineItemAdjustment uppdate method", async () => {
|
||||||
|
await lineItemAdjustmentService.update("lia-1", {
|
||||||
|
amount: 6000,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(lineItemAdjustmentRepo.save).toHaveBeenCalledTimes(1)
|
||||||
|
expect(lineItemAdjustmentRepo.save).toHaveBeenCalledWith({
|
||||||
|
...lineItemAdjustment,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("delete", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("delete by line item adjustment id", () => {
|
||||||
|
const lineItemAdjustment = { id: "lia-1", item_id: "li-1" }
|
||||||
|
const lineItemAdjustmentRepo = MockRepository({
|
||||||
|
find: (f) => Promise.resolve(lineItemAdjustment),
|
||||||
|
})
|
||||||
|
|
||||||
|
const lineItemAdjustmentService = new LineItemAdjustmentService({
|
||||||
|
manager: MockManager,
|
||||||
|
lineItemAdjustmentRepository: lineItemAdjustmentRepo,
|
||||||
|
eventBusService: EventBusServiceMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls lineItemAdjustment delete method with the right params", async () => {
|
||||||
|
await lineItemAdjustmentService.delete("lia-1")
|
||||||
|
|
||||||
|
expect(lineItemAdjustmentRepo.find).toHaveBeenCalledTimes(1)
|
||||||
|
expect(lineItemAdjustmentRepo.find).toHaveBeenCalledWith({
|
||||||
|
where: {
|
||||||
|
id: "lia-1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(lineItemAdjustmentRepo.remove).toHaveBeenCalledTimes(1)
|
||||||
|
expect(lineItemAdjustmentRepo.remove).toHaveBeenCalledWith(
|
||||||
|
lineItemAdjustment
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("delete by item ids", () => {
|
||||||
|
const lineItemAdjustment = [
|
||||||
|
{ id: "lia-1", item: "li-1" },
|
||||||
|
{ id: "lia-2", item_id: "li-2" },
|
||||||
|
]
|
||||||
|
const lineItemAdjustmentRepo = MockRepository({
|
||||||
|
find: (f) => Promise.resolve(lineItemAdjustment),
|
||||||
|
})
|
||||||
|
|
||||||
|
const lineItemAdjustmentService = new LineItemAdjustmentService({
|
||||||
|
manager: MockManager,
|
||||||
|
lineItemAdjustmentRepository: lineItemAdjustmentRepo,
|
||||||
|
eventBusService: EventBusServiceMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls lineItemAdjustment delete method with the right query", async () => {
|
||||||
|
const query = { item_id: ["li-1", "li-2", "li-3"] }
|
||||||
|
await lineItemAdjustmentService.delete(query)
|
||||||
|
|
||||||
|
expect(lineItemAdjustmentRepo.find).toHaveBeenCalledTimes(1)
|
||||||
|
expect(lineItemAdjustmentRepo.find).toHaveBeenCalledWith({
|
||||||
|
where: {
|
||||||
|
item_id: In(query.item_id),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(lineItemAdjustmentRepo.remove).toHaveBeenCalledTimes(1)
|
||||||
|
expect(lineItemAdjustmentRepo.remove).toHaveBeenCalledWith(
|
||||||
|
lineItemAdjustment
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("createAdjustments", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
const lineItemAdjustmentRepo = MockRepository({
|
||||||
|
find: (f) => Promise.resolve(lineItemAdjustment),
|
||||||
|
})
|
||||||
|
|
||||||
|
const lineItemAdjustmentService = new LineItemAdjustmentService({
|
||||||
|
manager: MockManager,
|
||||||
|
lineItemAdjustmentRepository: lineItemAdjustmentRepo,
|
||||||
|
discountService: DiscountServiceMock,
|
||||||
|
eventBusService: EventBusServiceMock,
|
||||||
|
})
|
||||||
|
|
||||||
|
lineItemAdjustmentService.createAdjustmentForLineItem = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
item_id: "li-1",
|
||||||
|
amount: 1000,
|
||||||
|
discount_id: "disc-1",
|
||||||
|
id: "lia-1",
|
||||||
|
description: "discount",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls createAdjustmentForLineItem once when given a line item", () => {
|
||||||
|
const cart = {
|
||||||
|
id: "cart1",
|
||||||
|
discounts: ["disc-1"],
|
||||||
|
items: [{ id: "li-1" }],
|
||||||
|
},
|
||||||
|
lineItem = { id: "li-1" }
|
||||||
|
|
||||||
|
lineItemAdjustmentService.createAdjustments(cart, lineItem)
|
||||||
|
expect(
|
||||||
|
lineItemAdjustmentService.createAdjustmentForLineItem
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
lineItemAdjustmentService.createAdjustmentForLineItem
|
||||||
|
).toHaveBeenCalledWith(cart, lineItem)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls createAdjustmentForLineItem 3 times when given a cart containing 3 line items", () => {
|
||||||
|
const cart = {
|
||||||
|
id: "cart1",
|
||||||
|
discounts: ["disc-1"],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "li-2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "li-3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "li-4",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
lineItemAdjustmentService.createAdjustments(cart)
|
||||||
|
expect(
|
||||||
|
lineItemAdjustmentService.createAdjustmentForLineItem
|
||||||
|
).toHaveBeenCalledTimes(3)
|
||||||
|
expect(
|
||||||
|
lineItemAdjustmentService.createAdjustmentForLineItem
|
||||||
|
).toHaveBeenNthCalledWith(1, cart, { id: "li-2" })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -2,10 +2,11 @@ import paymentService from "medusa-interfaces/dist/payment-service"
|
|||||||
import { IdMap, MockRepository, MockManager } from "medusa-test-utils"
|
import { IdMap, MockRepository, MockManager } from "medusa-test-utils"
|
||||||
import SwapService from "../swap"
|
import SwapService from "../swap"
|
||||||
import { InventoryServiceMock } from "../__mocks__/inventory"
|
import { InventoryServiceMock } from "../__mocks__/inventory"
|
||||||
|
import { LineItemAdjustmentServiceMock } from "../__mocks__/line-item-adjustment"
|
||||||
|
|
||||||
const eventBusService = {
|
const eventBusService = {
|
||||||
emit: jest.fn(),
|
emit: jest.fn(),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -166,7 +167,7 @@ describe("SwapService", () => {
|
|||||||
Promise.resolve({ id: "cart", items: [{ id: "test-item" }] })
|
Promise.resolve({ id: "cart", items: [{ id: "test-item" }] })
|
||||||
),
|
),
|
||||||
update: jest.fn().mockReturnValue(Promise.resolve()),
|
update: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -178,7 +179,7 @@ describe("SwapService", () => {
|
|||||||
const customShippingOptionService = {
|
const customShippingOptionService = {
|
||||||
create: jest.fn().mockReturnValue(Promise.resolve({ id: "cso-test" })),
|
create: jest.fn().mockReturnValue(Promise.resolve({ id: "cso-test" })),
|
||||||
update: jest.fn().mockReturnValue(Promise.resolve()),
|
update: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -188,7 +189,7 @@ describe("SwapService", () => {
|
|||||||
update: jest.fn().mockImplementation((d) => Promise.resolve(d)),
|
update: jest.fn().mockImplementation((d) => Promise.resolve(d)),
|
||||||
retrieve: () => Promise.resolve({}),
|
retrieve: () => Promise.resolve({}),
|
||||||
createReturnLines: jest.fn(() => Promise.resolve()),
|
createReturnLines: jest.fn(() => Promise.resolve()),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -200,6 +201,7 @@ describe("SwapService", () => {
|
|||||||
cartService,
|
cartService,
|
||||||
lineItemService,
|
lineItemService,
|
||||||
customShippingOptionService,
|
customShippingOptionService,
|
||||||
|
lineItemAdjustmentService: LineItemAdjustmentServiceMock,
|
||||||
})
|
})
|
||||||
|
|
||||||
it("finds swap and calls return create cart", async () => {
|
it("finds swap and calls return create cart", async () => {
|
||||||
@@ -219,6 +221,7 @@ describe("SwapService", () => {
|
|||||||
"order.claims",
|
"order.claims",
|
||||||
"order.claims.additional_items",
|
"order.claims.additional_items",
|
||||||
"additional_items",
|
"additional_items",
|
||||||
|
"additional_items.variant",
|
||||||
"return_order",
|
"return_order",
|
||||||
"return_order.items",
|
"return_order.items",
|
||||||
"return_order.shipping_method",
|
"return_order.shipping_method",
|
||||||
@@ -251,6 +254,24 @@ describe("SwapService", () => {
|
|||||||
expect(cartService.create).toHaveBeenCalledTimes(1)
|
expect(cartService.create).toHaveBeenCalledTimes(1)
|
||||||
// expect(cartService.update).toHaveBeenCalledTimes(1)
|
// expect(cartService.update).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
expect(lineItemService.update).toHaveBeenCalledTimes(1)
|
||||||
|
expect(lineItemService.update).toHaveBeenCalledWith("test", {
|
||||||
|
cart_id: "cart",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustmentForLineItem
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
LineItemAdjustmentServiceMock.createAdjustmentForLineItem
|
||||||
|
).toHaveBeenCalledWith(
|
||||||
|
{ id: "cart" },
|
||||||
|
{
|
||||||
|
id: "test",
|
||||||
|
data: "lines",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
expect(swapRepo.save).toHaveBeenCalledTimes(1)
|
expect(swapRepo.save).toHaveBeenCalledTimes(1)
|
||||||
expect(swapRepo.save).toHaveBeenCalledWith({
|
expect(swapRepo.save).toHaveBeenCalledWith({
|
||||||
...existing,
|
...existing,
|
||||||
@@ -330,7 +351,7 @@ describe("SwapService", () => {
|
|||||||
const swapRepo = MockRepository()
|
const swapRepo = MockRepository()
|
||||||
const returnService = {
|
const returnService = {
|
||||||
create: jest.fn().mockReturnValue(Promise.resolve({ id: "ret" })),
|
create: jest.fn().mockReturnValue(Promise.resolve({ id: "ret" })),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -431,7 +452,7 @@ describe("SwapService", () => {
|
|||||||
{ items: [{ item_id: "1234", quantity: 2 }], data: "new" },
|
{ items: [{ item_id: "1234", quantity: 2 }], data: "new" },
|
||||||
])
|
])
|
||||||
),
|
),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -452,7 +473,7 @@ describe("SwapService", () => {
|
|||||||
const lineItemService = {
|
const lineItemService = {
|
||||||
update: jest.fn(),
|
update: jest.fn(),
|
||||||
retrieve: () => Promise.resolve({}),
|
retrieve: () => Promise.resolve({}),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -541,7 +562,7 @@ describe("SwapService", () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -595,14 +616,14 @@ describe("SwapService", () => {
|
|||||||
data: "new",
|
data: "new",
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventBusService = {
|
const eventBusService = {
|
||||||
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -643,7 +664,7 @@ describe("SwapService", () => {
|
|||||||
const lineItemService = {
|
const lineItemService = {
|
||||||
update: jest.fn(),
|
update: jest.fn(),
|
||||||
retrieve: () => Promise.resolve({}),
|
retrieve: () => Promise.resolve({}),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -655,7 +676,7 @@ describe("SwapService", () => {
|
|||||||
.mockReturnValue(
|
.mockReturnValue(
|
||||||
Promise.resolve({ id: "cart", items: [{ id: "test-item" }] })
|
Promise.resolve({ id: "cart", items: [{ id: "test-item" }] })
|
||||||
),
|
),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -730,7 +751,7 @@ describe("SwapService", () => {
|
|||||||
|
|
||||||
const eventBusService = {
|
const eventBusService = {
|
||||||
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -745,7 +766,7 @@ describe("SwapService", () => {
|
|||||||
updateShippingMethod: () => {
|
updateShippingMethod: () => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
},
|
},
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -760,7 +781,7 @@ describe("SwapService", () => {
|
|||||||
update: () => {
|
update: () => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
},
|
},
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -775,14 +796,14 @@ describe("SwapService", () => {
|
|||||||
cancelPayment: jest.fn(() => {
|
cancelPayment: jest.fn(() => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const inventoryService = {
|
const inventoryService = {
|
||||||
...InventoryServiceMock,
|
...InventoryServiceMock,
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -904,7 +925,7 @@ describe("SwapService", () => {
|
|||||||
describe("success", () => {
|
describe("success", () => {
|
||||||
const eventBusService = {
|
const eventBusService = {
|
||||||
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -916,7 +937,7 @@ describe("SwapService", () => {
|
|||||||
refundPayment: jest.fn((g) =>
|
refundPayment: jest.fn((g) =>
|
||||||
g[0].id === "good" ? Promise.resolve() : Promise.reject()
|
g[0].id === "good" ? Promise.resolve() : Promise.reject()
|
||||||
),
|
),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1044,7 +1065,7 @@ describe("SwapService", () => {
|
|||||||
|
|
||||||
const eventBusService = {
|
const eventBusService = {
|
||||||
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1107,7 +1128,7 @@ describe("SwapService", () => {
|
|||||||
|
|
||||||
const paymentProviderService = {
|
const paymentProviderService = {
|
||||||
cancelPayment: jest.fn(() => Promise.resolve({})),
|
cancelPayment: jest.fn(() => Promise.resolve({})),
|
||||||
withTransaction: function () {
|
withTransaction: function() {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,109 +55,152 @@ const discounts = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const applyDiscount = (cart, discount) => {
|
||||||
|
let newCart = { ...cart }
|
||||||
|
if (newCart.items) {
|
||||||
|
newCart.items = cart.items.map((item) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
adjustments: [
|
||||||
|
{
|
||||||
|
item_id: item.id,
|
||||||
|
amount: calculateAdjustment(newCart, item, discount),
|
||||||
|
description: "discount",
|
||||||
|
discount_id: discount.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return newCart
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateAdjustment = (cart, lineItem, discount) => {
|
||||||
|
let amount = discount.rule.value * lineItem.quantity
|
||||||
|
|
||||||
|
let lineItemPrice = lineItem.unit_price * lineItem.quantity
|
||||||
|
|
||||||
|
if (discount.rule.type === "fixed" && discount.rule.allocation === "total") {
|
||||||
|
let subtotal = cart.items.reduce(
|
||||||
|
(total, item) => total + item.unit_price * item.quantity,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
const nominator = Math.min(discount.rule.value, subtotal)
|
||||||
|
amount = Math.round((lineItemPrice / subtotal) * nominator)
|
||||||
|
} else if (discount.rule.type === "percentage") {
|
||||||
|
amount = Math.round((lineItemPrice * discount.rule.value) / 100)
|
||||||
|
}
|
||||||
|
return amount > lineItemPrice ? lineItemPrice : amount
|
||||||
|
}
|
||||||
|
|
||||||
describe("TotalsService", () => {
|
describe("TotalsService", () => {
|
||||||
const container = {
|
const container = {
|
||||||
taxProviderService: {},
|
taxProviderService: {},
|
||||||
taxCalculationStrategy: {},
|
taxCalculationStrategy: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Redo tests to include new line item adjustments
|
describe("getAllocationItemDiscounts", () => {
|
||||||
|
let res
|
||||||
|
|
||||||
// describe("getAllocationItemDiscounts", () => {
|
const totalsService = new TotalsService(container)
|
||||||
// let res
|
|
||||||
|
|
||||||
// const totalsService = new TotalsService(container)
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
// beforeEach(() => {
|
it("calculates item with percentage discount", async () => {
|
||||||
// jest.clearAllMocks()
|
const cart = {
|
||||||
// })
|
items: [
|
||||||
|
{
|
||||||
|
id: "test",
|
||||||
|
allow_discounts: true,
|
||||||
|
unit_price: 10,
|
||||||
|
quantity: 10,
|
||||||
|
variant: {
|
||||||
|
id: "testv",
|
||||||
|
product_id: "testp",
|
||||||
|
},
|
||||||
|
adjustments: [{ amount: 10 }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
// it("calculates item with percentage discount", async () => {
|
const discount = {
|
||||||
// const cart = {
|
rule: {
|
||||||
// items: [
|
type: "percentage",
|
||||||
// {
|
value: 10,
|
||||||
// id: "test",
|
},
|
||||||
// allow_discounts: true,
|
}
|
||||||
// unit_price: 10,
|
|
||||||
// quantity: 10,
|
|
||||||
// variant: {
|
|
||||||
// id: "testv",
|
|
||||||
// product_id: "testp",
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const discount = {
|
res = totalsService.getAllocationItemDiscounts(discount, cart)
|
||||||
// rule: {
|
|
||||||
// type: "percentage",
|
|
||||||
// value: 10,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// res = totalsService.getAllocationItemDiscounts(discount, cart)
|
expect(res).toEqual([
|
||||||
|
{
|
||||||
|
lineItem: {
|
||||||
|
id: "test",
|
||||||
|
allow_discounts: true,
|
||||||
|
unit_price: 10,
|
||||||
|
quantity: 10,
|
||||||
|
variant: {
|
||||||
|
id: "testv",
|
||||||
|
product_id: "testp",
|
||||||
|
},
|
||||||
|
adjustments: [{ amount: 10 }],
|
||||||
|
},
|
||||||
|
variant: "testv",
|
||||||
|
amount: 10,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
// expect(res).toEqual([
|
it("calculates item with fixed discount", async () => {
|
||||||
// {
|
const cart = {
|
||||||
// lineItem: {
|
items: [
|
||||||
// id: "test",
|
{
|
||||||
// allow_discounts: true,
|
id: "exists",
|
||||||
// unit_price: 10,
|
allow_discounts: true,
|
||||||
// quantity: 10,
|
unit_price: 10,
|
||||||
// variant: {
|
variant: {
|
||||||
// id: "testv",
|
id: "testv",
|
||||||
// product_id: "testp",
|
product_id: "testp",
|
||||||
// },
|
},
|
||||||
// },
|
quantity: 10,
|
||||||
// variant: "testv",
|
adjustments: [{ amount: 90 }],
|
||||||
// amount: 10,
|
},
|
||||||
// },
|
],
|
||||||
// ])
|
}
|
||||||
// })
|
|
||||||
|
|
||||||
// it("calculates item with fixed discount", async () => {
|
const discount = {
|
||||||
// const cart = {
|
rule: {
|
||||||
// items: [
|
type: "fixed",
|
||||||
// {
|
value: 9,
|
||||||
// id: "exists",
|
// TODO: Add conditions relation
|
||||||
// allow_discounts: true,
|
},
|
||||||
// unit_price: 10,
|
}
|
||||||
// variant: {
|
|
||||||
// id: "testv",
|
|
||||||
// product_id: "testp",
|
|
||||||
// },
|
|
||||||
// quantity: 10,
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const discount = {
|
res = totalsService.getAllocationItemDiscounts(discount, cart)
|
||||||
// rule: {
|
|
||||||
// type: "fixed",
|
|
||||||
// value: 9,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// res = totalsService.getAllocationItemDiscounts(discount, cart)
|
expect(res).toEqual([
|
||||||
|
{
|
||||||
// expect(res).toEqual([
|
lineItem: {
|
||||||
// {
|
id: "exists",
|
||||||
// lineItem: {
|
allow_discounts: true,
|
||||||
// id: "exists",
|
unit_price: 10,
|
||||||
// allow_discounts: true,
|
variant: {
|
||||||
// unit_price: 10,
|
id: "testv",
|
||||||
// variant: {
|
product_id: "testp",
|
||||||
// id: "testv",
|
},
|
||||||
// product_id: "testp",
|
quantity: 10,
|
||||||
// },
|
adjustments: [{ amount: 90 }],
|
||||||
// quantity: 10,
|
},
|
||||||
// },
|
variant: "testv",
|
||||||
// variant: "testv",
|
amount: 90,
|
||||||
// amount: 90,
|
},
|
||||||
// },
|
])
|
||||||
// ])
|
})
|
||||||
// })
|
|
||||||
|
|
||||||
|
// not relevant anymore
|
||||||
// it("does not apply discount if no valid variants are provided", async () => {
|
// it("does not apply discount if no valid variants are provided", async () => {
|
||||||
// const cart = {
|
// const cart = {
|
||||||
// items: [
|
// items: [
|
||||||
@@ -178,13 +221,14 @@ describe("TotalsService", () => {
|
|||||||
// rule: {
|
// rule: {
|
||||||
// type: "fixed",
|
// type: "fixed",
|
||||||
// value: 9,
|
// value: 9,
|
||||||
|
// // TODO: Add conditions relation
|
||||||
// },
|
// },
|
||||||
// }
|
// }
|
||||||
// res = totalsService.getAllocationItemDiscounts(discount, cart)
|
// res = totalsService.getAllocationItemDiscounts(discount, cart)
|
||||||
|
|
||||||
// expect(res).toEqual([])
|
// expect(res).toEqual([])
|
||||||
// })
|
// })
|
||||||
// })
|
})
|
||||||
|
|
||||||
describe("getDiscountTotal", () => {
|
describe("getDiscountTotal", () => {
|
||||||
let res
|
let res
|
||||||
@@ -223,32 +267,36 @@ describe("TotalsService", () => {
|
|||||||
discountCart.discounts = []
|
discountCart.discounts = []
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calculate total precentage discount", async () => {
|
it("calculate total percentage discount", async () => {
|
||||||
discountCart.discounts.push(discounts.total10Percent)
|
discountCart.discounts.push(discounts.total10Percent)
|
||||||
res = totalsService.getDiscountTotal(discountCart)
|
let cart = applyDiscount(discountCart, discounts.total10Percent)
|
||||||
|
res = totalsService.getDiscountTotal(cart)
|
||||||
|
|
||||||
expect(res).toEqual(28)
|
expect(res).toEqual(28)
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Redo tests to include new line item adjustments
|
// TODO: Redo tests to include new line item adjustments
|
||||||
|
|
||||||
// it("calculate item fixed discount", async () => {
|
it("calculate item fixed discount", async () => {
|
||||||
// discountCart.discounts.push(discounts.item2Fixed)
|
discountCart.discounts.push(discounts.item2Fixed)
|
||||||
// res = totalsService.getDiscountTotal(discountCart)
|
let cart = applyDiscount(discountCart, discounts.item2Fixed)
|
||||||
|
res = totalsService.getDiscountTotal(cart)
|
||||||
|
|
||||||
// expect(res).toEqual(20)
|
expect(res).toEqual(40)
|
||||||
// })
|
})
|
||||||
|
|
||||||
// it("calculate item percentage discount", async () => {
|
it("calculate item percentage discount", async () => {
|
||||||
// discountCart.discounts.push(discounts.item10Percent)
|
discountCart.discounts.push(discounts.item10Percent)
|
||||||
// res = totalsService.getDiscountTotal(discountCart)
|
let cart = applyDiscount(discountCart, discounts.item10Percent)
|
||||||
|
res = totalsService.getDiscountTotal(cart)
|
||||||
|
|
||||||
// expect(res).toEqual(10)
|
expect(res).toEqual(28)
|
||||||
// })
|
})
|
||||||
|
|
||||||
it("calculate total fixed discount", async () => {
|
it("calculate total fixed discount", async () => {
|
||||||
discountCart.discounts.push(discounts.total10Fixed)
|
discountCart.discounts.push(discounts.total10Fixed)
|
||||||
res = totalsService.getDiscountTotal(discountCart)
|
let cart = applyDiscount(discountCart, discounts.total10Fixed)
|
||||||
|
res = totalsService.getDiscountTotal(cart)
|
||||||
|
|
||||||
expect(res).toEqual(10)
|
expect(res).toEqual(10)
|
||||||
})
|
})
|
||||||
@@ -388,43 +436,45 @@ describe("TotalsService", () => {
|
|||||||
// expect(res).toEqual(1244)
|
// expect(res).toEqual(1244)
|
||||||
// })
|
// })
|
||||||
|
|
||||||
// it("calculates refund with item fixed discount", async () => {
|
it("calculates refund with item fixed discount", async () => {
|
||||||
// orderToRefund.discounts.push(discounts.item2Fixed)
|
orderToRefund.discounts.push(discounts.item2Fixed)
|
||||||
// res = totalsService.getRefundTotal(orderToRefund, [
|
let order = applyDiscount(orderToRefund, discounts.item2Fixed)
|
||||||
// {
|
res = totalsService.getRefundTotal(order, [
|
||||||
// id: "line2",
|
{
|
||||||
// unit_price: 100,
|
id: "line2",
|
||||||
// allow_discounts: true,
|
unit_price: 100,
|
||||||
// variant: {
|
allow_discounts: true,
|
||||||
// id: "variant",
|
variant: {
|
||||||
// product_id: "testp2",
|
id: "variant",
|
||||||
// },
|
product_id: "testp2",
|
||||||
// quantity: 10,
|
},
|
||||||
// returned_quantity: 0,
|
quantity: 10,
|
||||||
// },
|
returned_quantity: 0,
|
||||||
// ])
|
},
|
||||||
|
])
|
||||||
|
|
||||||
// expect(res).toEqual(1225)
|
expect(res).toEqual(1225)
|
||||||
// })
|
})
|
||||||
|
|
||||||
// it("calculates refund with item percentage discount", async () => {
|
it("calculates refund with item percentage discount", async () => {
|
||||||
// orderToRefund.discounts.push(discounts.item10Percent)
|
orderToRefund.discounts.push(discounts.item10Percent)
|
||||||
// res = totalsService.getRefundTotal(orderToRefund, [
|
let order = applyDiscount(orderToRefund, discounts.item10Percent)
|
||||||
// {
|
res = totalsService.getRefundTotal(order, [
|
||||||
// id: "line2",
|
{
|
||||||
// unit_price: 100,
|
id: "line2",
|
||||||
// allow_discounts: true,
|
unit_price: 100,
|
||||||
// variant: {
|
allow_discounts: true,
|
||||||
// id: "variant",
|
variant: {
|
||||||
// product_id: "testp2",
|
id: "variant",
|
||||||
// },
|
product_id: "testp2",
|
||||||
// quantity: 10,
|
},
|
||||||
// returned_quantity: 0,
|
quantity: 10,
|
||||||
// },
|
returned_quantity: 0,
|
||||||
// ])
|
},
|
||||||
|
])
|
||||||
|
|
||||||
// expect(res).toEqual(1125)
|
expect(res).toEqual(1125)
|
||||||
// })
|
})
|
||||||
|
|
||||||
it("throws if line items to return is not in order", async () => {
|
it("throws if line items to return is not in order", async () => {
|
||||||
const work = () =>
|
const work = () =>
|
||||||
@@ -444,6 +494,7 @@ describe("TotalsService", () => {
|
|||||||
expect(work).toThrow("Line item does not exist on order")
|
expect(work).toThrow("Line item does not exist on order")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("getShippingTotal", () => {
|
describe("getShippingTotal", () => {
|
||||||
let res
|
let res
|
||||||
const totalsService = new TotalsService(container)
|
const totalsService = new TotalsService(container)
|
||||||
|
|||||||
@@ -21,12 +21,10 @@ import {
|
|||||||
LineItemUpdate,
|
LineItemUpdate,
|
||||||
} from "../types/cart"
|
} from "../types/cart"
|
||||||
import { FindConfig, TotalField } from "../types/common"
|
import { FindConfig, TotalField } from "../types/common"
|
||||||
import CustomShippingOptionService from "./custom-shipping-option"
|
|
||||||
import CustomerService from "./customer"
|
import CustomerService from "./customer"
|
||||||
import DiscountService from "./discount"
|
import DiscountService from "./discount"
|
||||||
import EventBusService from "./event-bus"
|
import EventBusService from "./event-bus"
|
||||||
import GiftCardService from "./gift-card"
|
import GiftCardService from "./gift-card"
|
||||||
import InventoryService from "./inventory"
|
|
||||||
import LineItemService from "./line-item"
|
import LineItemService from "./line-item"
|
||||||
import PaymentProviderService from "./payment-provider"
|
import PaymentProviderService from "./payment-provider"
|
||||||
import ProductService from "./product"
|
import ProductService from "./product"
|
||||||
@@ -35,6 +33,9 @@ import RegionService from "./region"
|
|||||||
import ShippingOptionService from "./shipping-option"
|
import ShippingOptionService from "./shipping-option"
|
||||||
import TaxProviderService from "./tax-provider"
|
import TaxProviderService from "./tax-provider"
|
||||||
import TotalsService from "./totals"
|
import TotalsService from "./totals"
|
||||||
|
import InventoryService from "./inventory"
|
||||||
|
import CustomShippingOptionService from "./custom-shipping-option"
|
||||||
|
import LineItemAdjustmentService from "./line-item-adjustment"
|
||||||
|
|
||||||
type CartConstructorProps = {
|
type CartConstructorProps = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
@@ -56,6 +57,7 @@ type CartConstructorProps = {
|
|||||||
totalsService: TotalsService
|
totalsService: TotalsService
|
||||||
inventoryService: InventoryService
|
inventoryService: InventoryService
|
||||||
customShippingOptionService: CustomShippingOptionService
|
customShippingOptionService: CustomShippingOptionService
|
||||||
|
lineItemAdjustmentService: LineItemAdjustmentService
|
||||||
priceSelectionStrategy: IPriceSelectionStrategy
|
priceSelectionStrategy: IPriceSelectionStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +94,7 @@ class CartService extends BaseService {
|
|||||||
private paymentSessionRepository_: typeof PaymentSessionRepository
|
private paymentSessionRepository_: typeof PaymentSessionRepository
|
||||||
private inventoryService_: InventoryService
|
private inventoryService_: InventoryService
|
||||||
private customShippingOptionService_: CustomShippingOptionService
|
private customShippingOptionService_: CustomShippingOptionService
|
||||||
|
private lineItemAdjustmentService_: LineItemAdjustmentService
|
||||||
private priceSelectionStrategy_: IPriceSelectionStrategy
|
private priceSelectionStrategy_: IPriceSelectionStrategy
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
@@ -114,6 +117,7 @@ class CartService extends BaseService {
|
|||||||
paymentSessionRepository,
|
paymentSessionRepository,
|
||||||
inventoryService,
|
inventoryService,
|
||||||
customShippingOptionService,
|
customShippingOptionService,
|
||||||
|
lineItemAdjustmentService,
|
||||||
priceSelectionStrategy,
|
priceSelectionStrategy,
|
||||||
}: CartConstructorProps) {
|
}: CartConstructorProps) {
|
||||||
super()
|
super()
|
||||||
@@ -137,6 +141,7 @@ class CartService extends BaseService {
|
|||||||
this.inventoryService_ = inventoryService
|
this.inventoryService_ = inventoryService
|
||||||
this.customShippingOptionService_ = customShippingOptionService
|
this.customShippingOptionService_ = customShippingOptionService
|
||||||
this.taxProviderService_ = taxProviderService
|
this.taxProviderService_ = taxProviderService
|
||||||
|
this.lineItemAdjustmentService_ = lineItemAdjustmentService
|
||||||
this.priceSelectionStrategy_ = priceSelectionStrategy
|
this.priceSelectionStrategy_ = priceSelectionStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +170,7 @@ class CartService extends BaseService {
|
|||||||
giftCardService: this.giftCardService_,
|
giftCardService: this.giftCardService_,
|
||||||
inventoryService: this.inventoryService_,
|
inventoryService: this.inventoryService_,
|
||||||
customShippingOptionService: this.customShippingOptionService_,
|
customShippingOptionService: this.customShippingOptionService_,
|
||||||
|
lineItemAdjustmentService: this.lineItemAdjustmentService_,
|
||||||
priceSelectionStrategy: this.priceSelectionStrategy_,
|
priceSelectionStrategy: this.priceSelectionStrategy_,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -457,7 +463,12 @@ class CartService extends BaseService {
|
|||||||
|
|
||||||
await this.lineItemService_.withTransaction(manager).delete(lineItem.id)
|
await this.lineItemService_.withTransaction(manager).delete(lineItem.id)
|
||||||
|
|
||||||
const result = await this.retrieve(cartId)
|
const result = await this.retrieve(cartId, {
|
||||||
|
relations: ["items", "discounts", "discounts.rule"],
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.refreshAdjustments_(result)
|
||||||
|
|
||||||
// Notify subscribers
|
// Notify subscribers
|
||||||
await this.eventBus_
|
await this.eventBus_
|
||||||
.withTransaction(manager)
|
.withTransaction(manager)
|
||||||
@@ -512,9 +523,12 @@ class CartService extends BaseService {
|
|||||||
relations: [
|
relations: [
|
||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
"items",
|
"items",
|
||||||
|
"items.adjustments",
|
||||||
"payment_sessions",
|
"payment_sessions",
|
||||||
"items.variant",
|
"items.variant",
|
||||||
"items.variant.product",
|
"items.variant.product",
|
||||||
|
"discounts",
|
||||||
|
"discounts.rule",
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -570,10 +584,16 @@ class CartService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.retrieve(cartId)
|
const result = await this.retrieve(cartId, {
|
||||||
|
relations: ["items", "discounts", "discounts.rule"],
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.refreshAdjustments_(result)
|
||||||
|
|
||||||
await this.eventBus_
|
await this.eventBus_
|
||||||
.withTransaction(manager)
|
.withTransaction(manager)
|
||||||
.emit(CartService.Events.UPDATED, result)
|
.emit(CartService.Events.UPDATED, result)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -592,7 +612,7 @@ class CartService extends BaseService {
|
|||||||
): Promise<Cart> {
|
): Promise<Cart> {
|
||||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
const cart = await this.retrieve(cartId, {
|
const cart = await this.retrieve(cartId, {
|
||||||
relations: ["items", "payment_sessions"],
|
relations: ["items", "items.adjustments", "payment_sessions"],
|
||||||
})
|
})
|
||||||
|
|
||||||
// Ensure that the line item exists in the cart
|
// Ensure that the line item exists in the cart
|
||||||
@@ -621,8 +641,13 @@ class CartService extends BaseService {
|
|||||||
.withTransaction(manager)
|
.withTransaction(manager)
|
||||||
.update(lineItemId, lineItemUpdate)
|
.update(lineItemId, lineItemUpdate)
|
||||||
|
|
||||||
|
const result = await this.retrieve(cartId, {
|
||||||
|
relations: ["items", "discounts", "discounts.rule"],
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.refreshAdjustments_(result)
|
||||||
|
|
||||||
// Update the line item
|
// Update the line item
|
||||||
const result = await this.retrieve(cartId)
|
|
||||||
await this.eventBus_
|
await this.eventBus_
|
||||||
.withTransaction(manager)
|
.withTransaction(manager)
|
||||||
.emit(CartService.Events.UPDATED, result)
|
.emit(CartService.Events.UPDATED, result)
|
||||||
@@ -1004,80 +1029,16 @@ class CartService extends BaseService {
|
|||||||
* @return the result of the update operation
|
* @return the result of the update operation
|
||||||
*/
|
*/
|
||||||
async applyDiscount(cart: Cart, discountCode: string): Promise<void> {
|
async applyDiscount(cart: Cart, discountCode: string): Promise<void> {
|
||||||
const discount = await this.discountService_.retrieveByCode(discountCode, [
|
return this.atomicPhase_(async (manager) => {
|
||||||
"rule",
|
const discount = await this.discountService_.retrieveByCode(
|
||||||
"regions",
|
discountCode,
|
||||||
])
|
["rule", "regions"]
|
||||||
|
|
||||||
if (cart.customer_id) {
|
|
||||||
const canApply = await this.discountService_.canApplyForCustomer(
|
|
||||||
discount.rule.id,
|
|
||||||
cart.customer_id
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!canApply) {
|
await this.discountService_.validateDiscountForCartOrThrow(cart, discount)
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"Discount is not valid for customer"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rule = discount.rule
|
const rule = discount.rule
|
||||||
|
|
||||||
// if limit is set and reached, we make an early exit
|
|
||||||
if (discount.usage_limit) {
|
|
||||||
discount.usage_count = discount.usage_count || 0
|
|
||||||
|
|
||||||
if (discount.usage_count >= discount.usage_limit) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"Discount has been used maximum allowed times"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const today = new Date()
|
|
||||||
if (discount.starts_at > today) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"Discount is not valid yet"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discount.ends_at && discount.ends_at < today) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"Discount is expired"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let regions = discount.regions
|
|
||||||
if (discount.parent_discount_id) {
|
|
||||||
const parent = await this.discountService_.retrieve(
|
|
||||||
discount.parent_discount_id,
|
|
||||||
{
|
|
||||||
relations: ["rule", "regions"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
regions = parent.regions
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discount.is_disabled) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"The discount code is disabled"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!regions.find(({ id }) => id === cart.region_id)) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_DATA,
|
|
||||||
"The discount is not available in current region"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if discount is already there, we simply resolve
|
// if discount is already there, we simply resolve
|
||||||
if (cart.discounts.find(({ id }) => id === discount.id)) {
|
if (cart.discounts.find(({ id }) => id === discount.id)) {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
@@ -1107,6 +1068,12 @@ class CartService extends BaseService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
cart.discounts = newDiscounts.filter(Boolean) as Discount[]
|
cart.discounts = newDiscounts.filter(Boolean) as Discount[]
|
||||||
|
|
||||||
|
// ignore if free shipping
|
||||||
|
if (rule.type !== "free_shipping" && cart?.items) {
|
||||||
|
await this.refreshAdjustments_(cart)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1888,6 +1855,23 @@ class CartService extends BaseService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async refreshAdjustments_(cart: Cart): Promise<void> {
|
||||||
|
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
const nonReturnLines = cart.items.filter((item) => !item.is_return)
|
||||||
|
const nonReturnLineIDs = nonReturnLines.map((i) => i.id)
|
||||||
|
|
||||||
|
// delete all old non return line item adjustments
|
||||||
|
await this.lineItemAdjustmentService_.withTransaction(manager).delete({
|
||||||
|
item_id: nonReturnLineIDs,
|
||||||
|
})
|
||||||
|
|
||||||
|
// potentially create/update line item adjustments
|
||||||
|
await this.lineItemAdjustmentService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.createAdjustments(cart)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dedicated method to delete metadata for a cart.
|
* Dedicated method to delete metadata for a cart.
|
||||||
* @param cartId - the cart to delete metadata from.
|
* @param cartId - the cart to delete metadata from.
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
UpdateDiscountInput,
|
UpdateDiscountInput,
|
||||||
UpsertDiscountConditionInput,
|
UpsertDiscountConditionInput,
|
||||||
} from "../types/discount"
|
} from "../types/discount"
|
||||||
|
import { isFuture, isPast } from "../utils/date-helpers"
|
||||||
import { formatException, PostgresError } from "../utils/exception-formatter"
|
import { formatException, PostgresError } from "../utils/exception-formatter"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -735,6 +736,99 @@ class DiscountService extends BaseService {
|
|||||||
return adjustment >= fullItemPrice ? fullItemPrice : adjustment
|
return adjustment >= fullItemPrice ? fullItemPrice : adjustment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async validateDiscountForCartOrThrow(
|
||||||
|
cart: Cart,
|
||||||
|
discount: Discount
|
||||||
|
): Promise<void> {
|
||||||
|
if (this.hasReachedLimit(discount)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Discount has been used maximum allowed times"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasNotStarted(discount)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Discount is not valid yet"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasExpired(discount)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Discount is expired"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isDisabled(discount)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"The discount code is disabled"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidForRegion = await this.isValidForRegion(
|
||||||
|
discount,
|
||||||
|
cart.region_id
|
||||||
|
)
|
||||||
|
if (!isValidForRegion) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"The discount is not available in current region"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cart.customer_id) {
|
||||||
|
const canApplyForCustomer = await this.canApplyForCustomer(
|
||||||
|
discount.rule.id,
|
||||||
|
cart.customer_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!canApplyForCustomer) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Discount is not valid for customer"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasReachedLimit(discount: Discount): boolean {
|
||||||
|
const count = discount.usage_count || 0
|
||||||
|
const limit = discount.usage_limit
|
||||||
|
return !!limit && count >= limit
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNotStarted(discount: Discount): boolean {
|
||||||
|
return isFuture(discount.starts_at)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasExpired(discount: Discount): boolean {
|
||||||
|
return discount.ends_at && isPast(discount.ends_at)
|
||||||
|
}
|
||||||
|
|
||||||
|
isDisabled(discount: Discount): boolean {
|
||||||
|
return discount.is_disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
async isValidForRegion(
|
||||||
|
discount: Discount,
|
||||||
|
region_id: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
let regions = discount.regions
|
||||||
|
|
||||||
|
if (discount.parent_discount_id) {
|
||||||
|
const parent = await this.retrieve(discount.parent_discount_id, {
|
||||||
|
relations: ["rule", "regions"],
|
||||||
|
})
|
||||||
|
|
||||||
|
regions = parent.regions
|
||||||
|
}
|
||||||
|
|
||||||
|
return regions.find(({ id }) => id === region_id) !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
async canApplyForCustomer(
|
async canApplyForCustomer(
|
||||||
discountRuleId: string,
|
discountRuleId: string,
|
||||||
customerId: string | undefined
|
customerId: string | undefined
|
||||||
|
|||||||
@@ -290,6 +290,7 @@ class DraftOrderService extends BaseService {
|
|||||||
.generate(item.variant_id, data.region_id, item.quantity, {
|
.generate(item.variant_id, data.region_id, item.quantity, {
|
||||||
metadata: item?.metadata || {},
|
metadata: item?.metadata || {},
|
||||||
unit_price: item.unit_price,
|
unit_price: item.unit_price,
|
||||||
|
cart: createdCart,
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.lineItemService_.withTransaction(manager).create({
|
await this.lineItemService_.withTransaction(manager).create({
|
||||||
|
|||||||
@@ -0,0 +1,303 @@
|
|||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { BaseService } from "medusa-interfaces"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
import { LineItemAdjustmentRepository } from "../repositories/line-item-adjustment"
|
||||||
|
import { FindConfig } from "../types/common"
|
||||||
|
import { LineItemAdjustment } from "../models/line-item-adjustment"
|
||||||
|
import { FilterableLineItemAdjustmentProps } from "../types/line-item-adjustment"
|
||||||
|
import { LineItem } from "../models/line-item"
|
||||||
|
import { Cart } from "../models/cart"
|
||||||
|
import DiscountService from "./discount"
|
||||||
|
import { ProductVariant } from "../models/product-variant"
|
||||||
|
|
||||||
|
type LineItemAdjustmentServiceProps = {
|
||||||
|
manager: EntityManager
|
||||||
|
lineItemAdjustmentRepository: typeof LineItemAdjustmentRepository
|
||||||
|
discountService: DiscountService
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdjustmentContext = {
|
||||||
|
variant: ProductVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeneratedAdjustment = Omit<LineItem, "id" | "item_id">
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides layer to manipulate line item adjustments.
|
||||||
|
* @extends BaseService
|
||||||
|
*/
|
||||||
|
class LineItemAdjustmentService extends BaseService {
|
||||||
|
private manager_: EntityManager
|
||||||
|
private lineItemAdjustmentRepo_: typeof LineItemAdjustmentRepository
|
||||||
|
private discountService: DiscountService
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
manager,
|
||||||
|
lineItemAdjustmentRepository,
|
||||||
|
discountService,
|
||||||
|
}: LineItemAdjustmentServiceProps) {
|
||||||
|
super()
|
||||||
|
this.manager_ = manager
|
||||||
|
this.lineItemAdjustmentRepo_ = lineItemAdjustmentRepository
|
||||||
|
this.discountService = discountService
|
||||||
|
}
|
||||||
|
|
||||||
|
withTransaction(
|
||||||
|
transactionManager: EntityManager
|
||||||
|
): LineItemAdjustmentService {
|
||||||
|
if (!transactionManager) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloned = new LineItemAdjustmentService({
|
||||||
|
manager: transactionManager,
|
||||||
|
lineItemAdjustmentRepository: this.lineItemAdjustmentRepo_,
|
||||||
|
discountService: this.discountService,
|
||||||
|
})
|
||||||
|
|
||||||
|
cloned.transactionManager_ = transactionManager
|
||||||
|
|
||||||
|
return cloned
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a line item adjustment by id.
|
||||||
|
* @param id - the id of the line item adjustment to retrieve
|
||||||
|
* @param config - the config to retrieve the line item adjustment by
|
||||||
|
* @return the line item adjustment.
|
||||||
|
*/
|
||||||
|
async retrieve(
|
||||||
|
id: string,
|
||||||
|
config: FindConfig<LineItemAdjustment> = {}
|
||||||
|
): Promise<LineItemAdjustment> {
|
||||||
|
const lineItemAdjustmentRepo: LineItemAdjustmentRepository =
|
||||||
|
this.manager_.getCustomRepository(this.lineItemAdjustmentRepo_)
|
||||||
|
|
||||||
|
const query = this.buildQuery_({ id }, config)
|
||||||
|
const lineItemAdjustment = await lineItemAdjustmentRepo.findOne(query)
|
||||||
|
|
||||||
|
if (!lineItemAdjustment) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Line item adjustment with id: ${id} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lineItemAdjustment
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a line item adjustment
|
||||||
|
* @param data - the line item adjustment to create
|
||||||
|
* @return line item adjustment
|
||||||
|
*/
|
||||||
|
async create(data: Partial<LineItemAdjustment>): Promise<LineItemAdjustment> {
|
||||||
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
const lineItemAdjustmentRepo: LineItemAdjustmentRepository =
|
||||||
|
manager.getCustomRepository(this.lineItemAdjustmentRepo_)
|
||||||
|
|
||||||
|
const lineItemAdjustment = await lineItemAdjustmentRepo.create(data)
|
||||||
|
|
||||||
|
return await lineItemAdjustmentRepo.save(lineItemAdjustment)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a line item adjustment
|
||||||
|
* @param id - the line item adjustment id to update
|
||||||
|
* @param data - the line item adjustment to create
|
||||||
|
* @return line item adjustment
|
||||||
|
*/
|
||||||
|
async update(
|
||||||
|
id: string,
|
||||||
|
data: Partial<LineItemAdjustment>
|
||||||
|
): Promise<LineItemAdjustment> {
|
||||||
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
const lineItemAdjustmentRepo: LineItemAdjustmentRepository =
|
||||||
|
manager.getCustomRepository(this.lineItemAdjustmentRepo_)
|
||||||
|
|
||||||
|
const lineItemAdjustment = await this.retrieve(id)
|
||||||
|
|
||||||
|
const { metadata, ...rest } = data
|
||||||
|
|
||||||
|
if (metadata) {
|
||||||
|
lineItemAdjustment.metadata = this.setMetadata_(
|
||||||
|
lineItemAdjustment,
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(rest)) {
|
||||||
|
lineItemAdjustment[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await lineItemAdjustmentRepo.save(lineItemAdjustment)
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists line item adjustments
|
||||||
|
* @param selector - the query object for find
|
||||||
|
* @param config - the config to be used for find
|
||||||
|
* @return the result of the find operation
|
||||||
|
*/
|
||||||
|
async list(
|
||||||
|
selector: FilterableLineItemAdjustmentProps = {},
|
||||||
|
config: FindConfig<LineItemAdjustment> = { skip: 0, take: 20 }
|
||||||
|
): Promise<LineItemAdjustment[]> {
|
||||||
|
const lineItemAdjustmentRepo = this.manager_.getCustomRepository(
|
||||||
|
this.lineItemAdjustmentRepo_
|
||||||
|
)
|
||||||
|
|
||||||
|
const query = this.buildQuery_(selector, config)
|
||||||
|
return await lineItemAdjustmentRepo.find(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes line item adjustments matching a selector
|
||||||
|
* @param selectorOrId - the query object for find or the line item adjustment id
|
||||||
|
* @return the result of the delete operation
|
||||||
|
*/
|
||||||
|
async delete(
|
||||||
|
selectorOrId: string | FilterableLineItemAdjustmentProps
|
||||||
|
): Promise<void> {
|
||||||
|
return this.atomicPhase_(async (manager) => {
|
||||||
|
const lineItemAdjustmentRepo: LineItemAdjustmentRepository =
|
||||||
|
manager.getCustomRepository(this.lineItemAdjustmentRepo_)
|
||||||
|
|
||||||
|
if (typeof selectorOrId === "string") {
|
||||||
|
return await this.delete({ id: selectorOrId })
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = this.buildQuery_(selectorOrId)
|
||||||
|
|
||||||
|
const lineItemAdjustments = await lineItemAdjustmentRepo.find(query)
|
||||||
|
|
||||||
|
await lineItemAdjustmentRepo.remove(lineItemAdjustments)
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates adjustment for a line item
|
||||||
|
* @param cart - the cart object holding discounts
|
||||||
|
* @param generatedLineItem - the line item for which a line item adjustment might be created
|
||||||
|
* @param context - the line item for which a line item adjustment might be created
|
||||||
|
* @return a line item adjustment or undefined if no adjustment was created
|
||||||
|
*/
|
||||||
|
async generateAdjustments(
|
||||||
|
cart: Cart,
|
||||||
|
generatedLineItem: LineItem,
|
||||||
|
context: AdjustmentContext
|
||||||
|
): Promise<GeneratedAdjustment[]> {
|
||||||
|
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 ||
|
||||||
|
!cart?.discounts?.length
|
||||||
|
) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const [discount] = cart.discounts.filter(
|
||||||
|
(d) => d.rule.type !== "free_shipping"
|
||||||
|
)
|
||||||
|
|
||||||
|
// if no discount is applied to the cart then return
|
||||||
|
if (!discount) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineItemProduct = context.variant.product_id
|
||||||
|
|
||||||
|
const isValid = await this.discountService
|
||||||
|
.withTransaction(manager)
|
||||||
|
.validateDiscountForProduct(discount.rule_id, lineItemProduct)
|
||||||
|
|
||||||
|
// if discount is not valid for line item, then do nothing
|
||||||
|
if (!isValid) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = await this.discountService.calculateDiscountForLineItem(
|
||||||
|
discount.id,
|
||||||
|
generatedLineItem,
|
||||||
|
cart
|
||||||
|
)
|
||||||
|
|
||||||
|
// if discounted amount is 0, then do nothing
|
||||||
|
if (amount === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const adjustments = [
|
||||||
|
{
|
||||||
|
amount,
|
||||||
|
discount_id: discount.id,
|
||||||
|
description: "discount",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return adjustments
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates adjustment for a line item
|
||||||
|
* @param cart - the cart object holding discounts
|
||||||
|
* @param lineItem - the line item for which a line item adjustment might be created
|
||||||
|
* @return a line item adjustment or undefined if no adjustment was created
|
||||||
|
*/
|
||||||
|
async createAdjustmentForLineItem(
|
||||||
|
cart: Cart,
|
||||||
|
lineItem: LineItem
|
||||||
|
): Promise<LineItemAdjustment[]> {
|
||||||
|
const adjustments = await this.generateAdjustments(cart, lineItem, {
|
||||||
|
variant: lineItem.variant,
|
||||||
|
})
|
||||||
|
|
||||||
|
const createdAdjustments: LineItemAdjustment[] = []
|
||||||
|
for (const adjustment of adjustments) {
|
||||||
|
const created = await this.create({
|
||||||
|
item_id: lineItem.id,
|
||||||
|
...adjustment,
|
||||||
|
})
|
||||||
|
|
||||||
|
createdAdjustments.push(created)
|
||||||
|
}
|
||||||
|
|
||||||
|
return createdAdjustments
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates adjustment for a line item
|
||||||
|
* @param cart - the cart object holding discounts
|
||||||
|
* @param lineItem - the line item for which a line item adjustment might be created
|
||||||
|
* @return if a lineItem was given, returns a line item adjustment or undefined if no adjustment was created
|
||||||
|
* otherwise returns an array of line item adjustments for each line item in the cart
|
||||||
|
*/
|
||||||
|
async createAdjustments(
|
||||||
|
cart: Cart,
|
||||||
|
lineItem?: LineItem
|
||||||
|
): Promise<LineItemAdjustment[] | LineItemAdjustment[][]> {
|
||||||
|
if (lineItem) {
|
||||||
|
return await this.createAdjustmentForLineItem(cart, lineItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cart.items) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Promise.all(
|
||||||
|
cart.items.map((li) => this.createAdjustmentForLineItem(cart, li))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LineItemAdjustmentService
|
||||||
@@ -14,6 +14,7 @@ class LineItemService extends BaseService {
|
|||||||
productService,
|
productService,
|
||||||
regionService,
|
regionService,
|
||||||
cartRepository,
|
cartRepository,
|
||||||
|
lineItemAdjustmentService,
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
@@ -37,6 +38,8 @@ class LineItemService extends BaseService {
|
|||||||
|
|
||||||
/** @private @const {CartRepository} */
|
/** @private @const {CartRepository} */
|
||||||
this.cartRepository_ = cartRepository
|
this.cartRepository_ = cartRepository
|
||||||
|
|
||||||
|
this.lineItemAdjustmentService_ = lineItemAdjustmentService
|
||||||
}
|
}
|
||||||
|
|
||||||
withTransaction(transactionManager) {
|
withTransaction(transactionManager) {
|
||||||
@@ -52,6 +55,7 @@ class LineItemService extends BaseService {
|
|||||||
productService: this.productService_,
|
productService: this.productService_,
|
||||||
regionService: this.regionService_,
|
regionService: this.regionService_,
|
||||||
cartRepository: this.cartRepository_,
|
cartRepository: this.cartRepository_,
|
||||||
|
lineItemAdjustmentService: this.lineItemAdjustmentService_,
|
||||||
})
|
})
|
||||||
|
|
||||||
cloned.transactionManager_ = transactionManager
|
cloned.transactionManager_ = transactionManager
|
||||||
@@ -131,13 +135,21 @@ class LineItemService extends BaseService {
|
|||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
metadata: i.metadata,
|
metadata: i.metadata,
|
||||||
|
adjustments: i.adjustments.map((adjustment) => {
|
||||||
|
return {
|
||||||
|
amount: -1 * adjustment.amount,
|
||||||
|
description: adjustment.description,
|
||||||
|
discount_id: adjustment.discount_id,
|
||||||
|
metadata: adjustment.metadata,
|
||||||
|
}
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
return await lineItemRepo.save(toCreate)
|
return await lineItemRepo.save(toCreate)
|
||||||
}
|
}
|
||||||
|
|
||||||
async generate(variantId, regionId, quantity, config = {}) {
|
async generate(variantId, regionId, quantity, context = {}) {
|
||||||
return this.atomicPhase_(async (manager) => {
|
return this.atomicPhase_(async (manager) => {
|
||||||
const variant = await this.productVariantService_
|
const variant = await this.productVariantService_
|
||||||
.withTransaction(manager)
|
.withTransaction(manager)
|
||||||
@@ -153,14 +165,14 @@ class LineItemService extends BaseService {
|
|||||||
let price
|
let price
|
||||||
let shouldMerge = true
|
let shouldMerge = true
|
||||||
|
|
||||||
if (config.unit_price !== undefined && config.unit_price !== null) {
|
if (context.unit_price !== undefined && context.unit_price !== null) {
|
||||||
// if custom unit_price, we ensure positive values
|
// if custom unit_price, we ensure positive values
|
||||||
// and we choose to not merge the items
|
// and we choose to not merge the items
|
||||||
shouldMerge = false
|
shouldMerge = false
|
||||||
if (config.unit_price < 0) {
|
if (context.unit_price < 0) {
|
||||||
price = 0
|
price = 0
|
||||||
} else {
|
} else {
|
||||||
price = config.unit_price
|
price = context.unit_price
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
price = await this.productVariantService_
|
price = await this.productVariantService_
|
||||||
@@ -168,7 +180,7 @@ class LineItemService extends BaseService {
|
|||||||
.getRegionPrice(variant.id, {
|
.getRegionPrice(variant.id, {
|
||||||
regionId: region.id,
|
regionId: region.id,
|
||||||
quantity: quantity,
|
quantity: quantity,
|
||||||
customer_id: config.customer_id,
|
customer_id: context.customer_id,
|
||||||
include_discount_prices: true,
|
include_discount_prices: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -182,10 +194,17 @@ class LineItemService extends BaseService {
|
|||||||
quantity: quantity || 1,
|
quantity: quantity || 1,
|
||||||
allow_discounts: variant.product.discountable,
|
allow_discounts: variant.product.discountable,
|
||||||
is_giftcard: variant.product.is_giftcard,
|
is_giftcard: variant.product.is_giftcard,
|
||||||
metadata: config?.metadata || {},
|
metadata: context?.metadata || {},
|
||||||
should_merge: shouldMerge,
|
should_merge: shouldMerge,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.cart) {
|
||||||
|
const adjustments = await this.lineItemAdjustmentService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.generateAdjustments(context.cart, toCreate, { variant })
|
||||||
|
toCreate.adjustments = adjustments
|
||||||
|
}
|
||||||
|
|
||||||
return toCreate
|
return toCreate
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -298,12 +298,15 @@ class OrderService extends BaseService {
|
|||||||
const relationSet = new Set(relations)
|
const relationSet = new Set(relations)
|
||||||
relationSet.add("items")
|
relationSet.add("items")
|
||||||
relationSet.add("items.tax_lines")
|
relationSet.add("items.tax_lines")
|
||||||
|
relationSet.add("items.adjustments")
|
||||||
relationSet.add("swaps")
|
relationSet.add("swaps")
|
||||||
relationSet.add("swaps.additional_items")
|
relationSet.add("swaps.additional_items")
|
||||||
relationSet.add("swaps.additional_items.tax_lines")
|
relationSet.add("swaps.additional_items.tax_lines")
|
||||||
|
relationSet.add("swaps.additional_items.adjustments")
|
||||||
relationSet.add("claims")
|
relationSet.add("claims")
|
||||||
relationSet.add("claims.additional_items")
|
relationSet.add("claims.additional_items")
|
||||||
relationSet.add("claims.additional_items.tax_lines")
|
relationSet.add("claims.additional_items.tax_lines")
|
||||||
|
relationSet.add("claims.additional_items.adjustments")
|
||||||
relationSet.add("discounts")
|
relationSet.add("discounts")
|
||||||
relationSet.add("discounts.rule")
|
relationSet.add("discounts.rule")
|
||||||
relationSet.add("gift_cards")
|
relationSet.add("gift_cards")
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class SwapService extends BaseService {
|
|||||||
orderService,
|
orderService,
|
||||||
inventoryService,
|
inventoryService,
|
||||||
customShippingOptionService,
|
customShippingOptionService,
|
||||||
|
lineItemAdjustmentService,
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
@@ -77,6 +78,9 @@ class SwapService extends BaseService {
|
|||||||
|
|
||||||
/** @private @const {CustomShippingOptionService} */
|
/** @private @const {CustomShippingOptionService} */
|
||||||
this.customShippingOptionService_ = customShippingOptionService
|
this.customShippingOptionService_ = customShippingOptionService
|
||||||
|
|
||||||
|
/** @private @const {LineItemAdjustmentService} */
|
||||||
|
this.lineItemAdjustmentService_ = lineItemAdjustmentService
|
||||||
}
|
}
|
||||||
|
|
||||||
withTransaction(transactionManager) {
|
withTransaction(transactionManager) {
|
||||||
@@ -99,6 +103,7 @@ class SwapService extends BaseService {
|
|||||||
inventoryService: this.inventoryService_,
|
inventoryService: this.inventoryService_,
|
||||||
fulfillmentService: this.fulfillmentService_,
|
fulfillmentService: this.fulfillmentService_,
|
||||||
customShippingOptionService: this.customShippingOptionService_,
|
customShippingOptionService: this.customShippingOptionService_,
|
||||||
|
lineItemAdjustmentService: this.lineItemAdjustmentService_,
|
||||||
})
|
})
|
||||||
|
|
||||||
cloned.transactionManager_ = transactionManager
|
cloned.transactionManager_ = transactionManager
|
||||||
@@ -549,6 +554,7 @@ class SwapService extends BaseService {
|
|||||||
"order.claims",
|
"order.claims",
|
||||||
"order.claims.additional_items",
|
"order.claims.additional_items",
|
||||||
"additional_items",
|
"additional_items",
|
||||||
|
"additional_items.variant",
|
||||||
"return_order",
|
"return_order",
|
||||||
"return_order.items",
|
"return_order.items",
|
||||||
"return_order.shipping_method",
|
"return_order.shipping_method",
|
||||||
@@ -605,6 +611,10 @@ class SwapService extends BaseService {
|
|||||||
await this.lineItemService_.withTransaction(manager).update(item.id, {
|
await this.lineItemService_.withTransaction(manager).update(item.id, {
|
||||||
cart_id: cart.id,
|
cart_id: cart.id,
|
||||||
})
|
})
|
||||||
|
// we generate adjustments in case the cart has any discounts that should be applied to the additional items
|
||||||
|
await this.lineItemAdjustmentService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.createAdjustmentForLineItem(cart, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the swap has a return shipping method the price has to be added to
|
// If the swap has a return shipping method the price has to be added to
|
||||||
|
|||||||
@@ -200,8 +200,6 @@ class TotalsService extends BaseService {
|
|||||||
calculationContext
|
calculationContext
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log(orderLines)
|
|
||||||
|
|
||||||
taxLines = orderLines.filter((ol) => {
|
taxLines = orderLines.filter((ol) => {
|
||||||
if ("shipping_method_id" in ol) {
|
if ("shipping_method_id" in ol) {
|
||||||
return ol.shipping_method_id === shippingMethod.id
|
return ol.shipping_method_id === shippingMethod.id
|
||||||
@@ -465,6 +463,7 @@ class TotalsService extends BaseService {
|
|||||||
const discountAmount =
|
const discountAmount =
|
||||||
(allocationMap[lineItem.id]?.discount?.unit_amount || 0) *
|
(allocationMap[lineItem.id]?.discount?.unit_amount || 0) *
|
||||||
lineItem.quantity
|
lineItem.quantity
|
||||||
|
|
||||||
const lineSubtotal =
|
const lineSubtotal =
|
||||||
lineItem.unit_price * lineItem.quantity - discountAmount
|
lineItem.unit_price * lineItem.quantity - discountAmount
|
||||||
|
|
||||||
@@ -589,11 +588,46 @@ class TotalsService extends BaseService {
|
|||||||
discount: Discount,
|
discount: Discount,
|
||||||
cart: Cart | Order
|
cart: Cart | Order
|
||||||
): LineDiscount[] {
|
): LineDiscount[] {
|
||||||
const discounts: LineDiscount[] = []
|
const discounts: LineDiscount[] = cart.items.map((item) => ({
|
||||||
// TODO: Add line item adjustments
|
lineItem: item,
|
||||||
|
variant: item.variant.id,
|
||||||
|
amount: this.getLineItemDiscountAdjustment(item, discount),
|
||||||
|
}))
|
||||||
|
|
||||||
return discounts
|
return discounts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLineItemDiscountAdjustment(
|
||||||
|
lineItem: LineItem,
|
||||||
|
discount: Discount
|
||||||
|
): number {
|
||||||
|
const matchingDiscount = lineItem.adjustments.find(
|
||||||
|
(adjustment) => adjustment.discount_id === discount.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!matchingDiscount) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchingDiscount.amount
|
||||||
|
}
|
||||||
|
|
||||||
|
getLineItemAdjustmentsTotal(cartOrOrder: Cart | Order): number {
|
||||||
|
if (!cartOrOrder?.items?.length) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return cartOrOrder.items.reduce(
|
||||||
|
(total, item) =>
|
||||||
|
total +
|
||||||
|
item.adjustments?.reduce(
|
||||||
|
(total, adjustment) => total + adjustment.amount,
|
||||||
|
0
|
||||||
|
) || 0,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the discount amount allocated to the line items of an order.
|
* Returns the discount amount allocated to the line items of an order.
|
||||||
* @param cartOrOrder - the cart or order to get line discount allocations for
|
* @param cartOrOrder - the cart or order to get line discount allocations for
|
||||||
@@ -605,10 +639,6 @@ class TotalsService extends BaseService {
|
|||||||
cartOrOrder: Cart | Order,
|
cartOrOrder: Cart | Order,
|
||||||
discount: Discount
|
discount: Discount
|
||||||
): LineDiscountAmount[] {
|
): LineDiscountAmount[] {
|
||||||
const subtotal = this.getSubtotal(cartOrOrder, {
|
|
||||||
excludeNonDiscounts: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
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
|
||||||
@@ -624,43 +654,22 @@ class TotalsService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { type, allocation, value } = discount.rule
|
|
||||||
if (allocation === "total") {
|
|
||||||
let percentage = 0
|
|
||||||
if (type === "percentage") {
|
|
||||||
percentage = value / 100
|
|
||||||
} else if (type === "fixed") {
|
|
||||||
// If the fixed discount exceeds the subtotal we should
|
|
||||||
// calculate a 100% discount
|
|
||||||
const nominator = Math.min(value, subtotal)
|
|
||||||
percentage = nominator / subtotal
|
|
||||||
}
|
|
||||||
|
|
||||||
return merged.map((item) => {
|
return merged.map((item) => {
|
||||||
const lineTotal = item.unit_price * item.quantity
|
const adjustments = item?.adjustments || []
|
||||||
|
const discountAdjustments = adjustments.filter(
|
||||||
|
(adjustment) => adjustment.discount_id === discount.id
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
item,
|
item,
|
||||||
amount: item.allow_discounts ? lineTotal * percentage : 0,
|
amount: item.allow_discounts
|
||||||
|
? discountAdjustments.reduce(
|
||||||
|
(total, adjustment) => total + adjustment.amount,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
: 0,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else if (allocation === "item") {
|
|
||||||
const allocationDiscounts = this.getAllocationItemDiscounts(
|
|
||||||
discount,
|
|
||||||
cartOrOrder
|
|
||||||
)
|
|
||||||
return merged.map((item) => {
|
|
||||||
const discounted = allocationDiscounts.find(
|
|
||||||
(a) => a.lineItem.id === item.id
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
item,
|
|
||||||
amount: discounted ? discounted.amount : 0,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return merged.map((i) => ({ item: i, amount: 0 }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -865,32 +874,13 @@ class TotalsService extends BaseService {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const { type, allocation, value } = discount.rule
|
const discountTotal = this.getLineItemAdjustmentsTotal(cartOrOrder)
|
||||||
let toReturn = 0
|
|
||||||
|
|
||||||
if (type === "percentage" && allocation === "total") {
|
|
||||||
toReturn = (subtotal / 100) * value
|
|
||||||
} else if (type === "percentage" && allocation === "item") {
|
|
||||||
const itemPercentageDiscounts = this.getAllocationItemDiscounts(
|
|
||||||
discount,
|
|
||||||
cartOrOrder
|
|
||||||
)
|
|
||||||
toReturn = _.sumBy(itemPercentageDiscounts, (d) => d.amount)
|
|
||||||
} else if (type === "fixed" && allocation === "total") {
|
|
||||||
toReturn = value
|
|
||||||
} else if (type === "fixed" && allocation === "item") {
|
|
||||||
const itemFixedDiscounts = this.getAllocationItemDiscounts(
|
|
||||||
discount,
|
|
||||||
cartOrOrder
|
|
||||||
)
|
|
||||||
toReturn = _.sumBy(itemFixedDiscounts, (d) => d.amount)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subtotal < 0) {
|
if (subtotal < 0) {
|
||||||
return this.rounded(Math.max(subtotal, toReturn))
|
return this.rounded(Math.max(subtotal, discountTotal))
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.rounded(Math.min(subtotal, toReturn))
|
return this.rounded(Math.min(subtotal, discountTotal))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { ValidateNested } from "class-validator"
|
||||||
|
import { IsType } from "../utils/validators/is-type"
|
||||||
|
import { DateComparisonOperator, StringComparisonOperator } from "./common"
|
||||||
|
|
||||||
|
export class FilterableLineItemAdjustmentProps {
|
||||||
|
@ValidateNested()
|
||||||
|
@IsType([String, [String], StringComparisonOperator])
|
||||||
|
id?: string | string[] | StringComparisonOperator
|
||||||
|
|
||||||
|
@ValidateNested()
|
||||||
|
@IsType([String, [String]])
|
||||||
|
item_id?: string | string[]
|
||||||
|
|
||||||
|
@ValidateNested()
|
||||||
|
@IsType([String, [String]])
|
||||||
|
description?: string | string[]
|
||||||
|
|
||||||
|
@ValidateNested()
|
||||||
|
@IsType([String, [String]])
|
||||||
|
resource_id?: string | string[]
|
||||||
|
|
||||||
|
@IsType([DateComparisonOperator])
|
||||||
|
created_at?: DateComparisonOperator
|
||||||
|
|
||||||
|
@IsType([DateComparisonOperator])
|
||||||
|
updated_at?: DateComparisonOperator
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { isDate } from "lodash"
|
||||||
|
|
||||||
|
export const isPast = (date: Date | null) => {
|
||||||
|
const now = new Date()
|
||||||
|
return isDate(date) && now > date
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isFuture = (date: Date | null) => {
|
||||||
|
const now = new Date()
|
||||||
|
return isDate(date) && date > now
|
||||||
|
}
|
||||||
Reference in new issue
Block a user