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:
co-authored by
Sebastian Rindom
parent
607a382b4e
commit
1cfeb5dbd8
@@ -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 () => {
|
||||
const api = useApi()
|
||||
|
||||
|
||||
@@ -1555,6 +1555,116 @@ describe("/admin/orders", () => {
|
||||
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 () => {
|
||||
const api = useApi()
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ exports[`Claims creates a replace claim 1`] = `
|
||||
Object {
|
||||
"additional_items": Array [
|
||||
Object {
|
||||
"adjustments": Array [],
|
||||
"allow_discounts": true,
|
||||
"cart_id": null,
|
||||
"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",
|
||||
quantity: 2,
|
||||
unit_price: 1000,
|
||||
adjustments: [
|
||||
{
|
||||
amount: 200,
|
||||
discount_code: "TESTCODE",
|
||||
description: "discount",
|
||||
item_id: "test-item",
|
||||
},
|
||||
],
|
||||
tax_lines: [
|
||||
{
|
||||
name: "default",
|
||||
|
||||
@@ -16,7 +16,7 @@ const { initDb, useDb } = require("../../../helpers/use-db")
|
||||
const cartSeeder = require("../../helpers/cart-seeder")
|
||||
const productSeeder = require("../../helpers/product-seeder")
|
||||
const swapSeeder = require("../../helpers/swap-seeder")
|
||||
const { simpleCartFactory } = require("../../factories")
|
||||
const { simpleCartFactory, simpleLineItemFactory } = require("../../factories")
|
||||
const {
|
||||
simpleDiscountFactory,
|
||||
} = require("../../factories/simple-discount-factory")
|
||||
@@ -235,6 +235,131 @@ describe("/store/carts", () => {
|
||||
unit_price: 1000,
|
||||
variant_id: "test-variant-quantity",
|
||||
quantity: 1,
|
||||
adjustments: [],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("adds line item to cart containing a total fixed discount", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/store/carts/test-cart-w-total-fixed-discount/line-items",
|
||||
{
|
||||
variant_id: "test-variant-quantity",
|
||||
quantity: 2,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
expect(response.data.cart.items).toEqual([
|
||||
expect.objectContaining({
|
||||
cart_id: "test-cart-w-total-fixed-discount",
|
||||
unit_price: 1000,
|
||||
variant_id: "test-variant-quantity",
|
||||
quantity: 2,
|
||||
adjustments: [
|
||||
expect.objectContaining({
|
||||
amount: 100,
|
||||
discount_id: "total-fixed-100",
|
||||
description: "discount",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("adds line item to cart containing a total percentage discount", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/store/carts/test-cart-w-total-percentage-discount/line-items",
|
||||
{
|
||||
variant_id: "test-variant-quantity",
|
||||
quantity: 2,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
expect(response.data.cart.items).toEqual([
|
||||
expect.objectContaining({
|
||||
cart_id: "test-cart-w-total-percentage-discount",
|
||||
unit_price: 1000,
|
||||
variant_id: "test-variant-quantity",
|
||||
quantity: 2,
|
||||
adjustments: [
|
||||
expect.objectContaining({
|
||||
amount: 200,
|
||||
discount_id: "10Percent",
|
||||
description: "discount",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("adds line item to cart containing an item fixed discount", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/store/carts/test-cart-w-item-fixed-discount/line-items",
|
||||
{
|
||||
variant_id: "test-variant-quantity",
|
||||
quantity: 2,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
expect(response.data.cart.items).toEqual([
|
||||
expect.objectContaining({
|
||||
cart_id: "test-cart-w-item-fixed-discount",
|
||||
unit_price: 1000,
|
||||
variant_id: "test-variant-quantity",
|
||||
quantity: 2,
|
||||
adjustments: [
|
||||
expect.objectContaining({
|
||||
amount: 400,
|
||||
discount_id: "item-fixed-200",
|
||||
description: "discount",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("adds line item to cart containing an item percentage discount", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/store/carts/test-cart-w-item-percentage-discount/line-items",
|
||||
{
|
||||
variant_id: "test-variant-quantity",
|
||||
quantity: 2,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
expect(response.data.cart.items).toEqual([
|
||||
expect.objectContaining({
|
||||
cart_id: "test-cart-w-item-percentage-discount",
|
||||
unit_price: 1000,
|
||||
variant_id: "test-variant-quantity",
|
||||
quantity: 2,
|
||||
adjustments: [
|
||||
expect.objectContaining({
|
||||
amount: 300,
|
||||
discount_id: "item-percentage-15",
|
||||
description: "discount",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
@@ -242,7 +367,6 @@ describe("/store/carts", () => {
|
||||
it("adds line item to cart time limited sale", async () => {
|
||||
const api = useApi()
|
||||
|
||||
// Add standard line item to cart
|
||||
const response = await api
|
||||
.post(
|
||||
"/store/carts/test-cart/line-items",
|
||||
@@ -305,7 +429,6 @@ describe("/store/carts", () => {
|
||||
it("adds line item with quantity to cart with quantity discount", async () => {
|
||||
const api = useApi()
|
||||
|
||||
// Add standard line item to cart
|
||||
const response = await api
|
||||
.post(
|
||||
"/store/carts/test-cart/line-items",
|
||||
@@ -330,7 +453,6 @@ describe("/store/carts", () => {
|
||||
it("adds line item with quantity to cart with quantity discount no ceiling", async () => {
|
||||
const api = useApi()
|
||||
|
||||
// Add standard line item to cart
|
||||
const response = await api
|
||||
.post(
|
||||
"/store/carts/test-cart/line-items",
|
||||
@@ -351,6 +473,421 @@ describe("/store/carts", () => {
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
describe("ensures correct line item adjustment generation", () => {
|
||||
const discountData = {
|
||||
code: "MEDUSA185DKK",
|
||||
id: "medusa-185",
|
||||
rule: {
|
||||
allocation: "total",
|
||||
type: "fixed",
|
||||
value: 185,
|
||||
},
|
||||
regions: ["test-region"],
|
||||
}
|
||||
|
||||
let discountCart, discount
|
||||
beforeEach(async () => {
|
||||
try {
|
||||
discount = await simpleDiscountFactory(
|
||||
dbConnection,
|
||||
discountData,
|
||||
100
|
||||
)
|
||||
discountCart = await simpleCartFactory(
|
||||
dbConnection,
|
||||
{
|
||||
id: "discount-cart",
|
||||
customer: "test-customer",
|
||||
region: "test-region",
|
||||
shipping_address: {
|
||||
address_1: "next door",
|
||||
first_name: "lebron",
|
||||
last_name: "james",
|
||||
country_code: "dk",
|
||||
postal_code: "100",
|
||||
},
|
||||
line_items: [
|
||||
{
|
||||
id: "test-li",
|
||||
variant_id: "test-variant",
|
||||
quantity: 1,
|
||||
unit_price: 100,
|
||||
adjustments: [
|
||||
{
|
||||
amount: 185,
|
||||
description: "discount",
|
||||
discount_id: "medusa-185",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
shipping_option: "test-option",
|
||||
price: 1000,
|
||||
},
|
||||
],
|
||||
},
|
||||
100
|
||||
)
|
||||
await dbConnection.manager
|
||||
.createQueryBuilder()
|
||||
.relation(Cart, "discounts")
|
||||
.of(discountCart)
|
||||
.add(discount)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await doAfterEach()
|
||||
})
|
||||
|
||||
it("updates an old line item adjustment when a new line item is added to a discount cart", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/store/carts/discount-cart/line-items",
|
||||
{
|
||||
quantity: 1,
|
||||
variant_id: "test-variant-quantity",
|
||||
},
|
||||
{
|
||||
withCredentials: true,
|
||||
}
|
||||
)
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
expect(response.data.cart.items.length).toEqual(2)
|
||||
expect(response.data.cart.items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
adjustments: [
|
||||
expect.objectContaining({
|
||||
item_id: "test-li",
|
||||
amount: 17,
|
||||
discount_id: "medusa-185",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
adjustments: [
|
||||
expect.objectContaining({
|
||||
amount: 168,
|
||||
discount_id: "medusa-185",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("updates an existing item adjustment when a line item is updated", async () => {
|
||||
const api = useApi()
|
||||
|
||||
await simpleLineItemFactory(
|
||||
dbConnection,
|
||||
{
|
||||
id: "line-item-2",
|
||||
cart_id: discountCart.id,
|
||||
variant_id: "test-variant-quantity",
|
||||
unit_price: 950,
|
||||
quantity: 1,
|
||||
adjustments: [
|
||||
{
|
||||
id: "lia-2",
|
||||
amount: 92,
|
||||
description: "discount",
|
||||
discount_id: "medusa-185",
|
||||
},
|
||||
],
|
||||
},
|
||||
100
|
||||
)
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/store/carts/discount-cart/line-items/line-item-2",
|
||||
{
|
||||
quantity: 2,
|
||||
},
|
||||
{
|
||||
withCredentials: true,
|
||||
}
|
||||
)
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
expect(response.data.cart.items.length).toEqual(2)
|
||||
expect(response.data.cart.items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
adjustments: [
|
||||
expect.objectContaining({
|
||||
item_id: "test-li",
|
||||
discount_id: "medusa-185",
|
||||
amount: 9,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
adjustments: [
|
||||
expect.objectContaining({
|
||||
item_id: "line-item-2",
|
||||
amount: 176,
|
||||
discount_id: "medusa-185",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("updates an existing item adjustment when a line item is deleted from a discount cart", async () => {
|
||||
const api = useApi()
|
||||
|
||||
await simpleLineItemFactory(
|
||||
dbConnection,
|
||||
{
|
||||
id: "line-item-2",
|
||||
cart_id: discountCart.id,
|
||||
variant_id: "test-variant-quantity",
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
adjustments: [
|
||||
{
|
||||
id: "lia-2",
|
||||
amount: 93,
|
||||
description: "discount",
|
||||
discount_id: "medusa-185",
|
||||
},
|
||||
],
|
||||
},
|
||||
100
|
||||
)
|
||||
|
||||
const response = await api
|
||||
.delete("/store/carts/discount-cart/line-items/test-li", {
|
||||
withCredentials: true,
|
||||
})
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
expect(response.data.cart.items.length).toEqual(1)
|
||||
expect(response.data.cart.items).toEqual([
|
||||
expect.objectContaining({
|
||||
adjustments: [
|
||||
expect.objectContaining({
|
||||
item_id: "line-item-2",
|
||||
amount: 185,
|
||||
discount_id: "medusa-185",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /store/carts/:id/line-items/:line_id", () => {
|
||||
beforeEach(async () => {
|
||||
try {
|
||||
await cartSeeder(dbConnection)
|
||||
await swapSeeder(dbConnection)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
throw err
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await doAfterEach()
|
||||
})
|
||||
|
||||
it("updates line item of cart", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/store/carts/test-cart-3/line-items/test-item3/",
|
||||
{
|
||||
quantity: 3,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
expect(response.data.cart.items).toEqual([
|
||||
expect.objectContaining({
|
||||
cart_id: "test-cart-3",
|
||||
unit_price: 8000,
|
||||
variant_id: "test-variant-sale-cg",
|
||||
quantity: 3,
|
||||
adjustments: [],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("updates line item of a cart containing a total fixed discount", async () => {
|
||||
const api = useApi()
|
||||
await simpleLineItemFactory(dbConnection, {
|
||||
id: "test-li-disc",
|
||||
allow_discounts: true,
|
||||
title: "Line Item Disc",
|
||||
thumbnail: "https://test.js/1234",
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
variant_id: "test-variant-quantity",
|
||||
cart_id: "test-cart-w-total-fixed-discount",
|
||||
})
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
`store/carts/test-cart-w-total-fixed-discount/line-items/test-li-disc`,
|
||||
{
|
||||
quantity: 3,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
expect(response.data.cart.items).toEqual([
|
||||
expect.objectContaining({
|
||||
cart_id: "test-cart-w-total-fixed-discount",
|
||||
unit_price: 1000,
|
||||
variant_id: "test-variant-quantity",
|
||||
quantity: 3,
|
||||
adjustments: [
|
||||
expect.objectContaining({
|
||||
amount: 100,
|
||||
discount_id: "total-fixed-100",
|
||||
description: "discount",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("updates line item of a cart containing a total percentage discount", async () => {
|
||||
const api = useApi()
|
||||
await simpleLineItemFactory(dbConnection, {
|
||||
id: "test-li-disc",
|
||||
allow_discounts: true,
|
||||
title: "Line Item Disc",
|
||||
thumbnail: "https://test.js/1234",
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
variant_id: "test-variant-quantity",
|
||||
cart_id: "test-cart-w-total-percentage-discount",
|
||||
})
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/store/carts/test-cart-w-total-percentage-discount/line-items/test-li-disc",
|
||||
{
|
||||
quantity: 10,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
expect(response.data.cart.items).toEqual([
|
||||
expect.objectContaining({
|
||||
cart_id: "test-cart-w-total-percentage-discount",
|
||||
unit_price: 1000,
|
||||
variant_id: "test-variant-quantity",
|
||||
quantity: 10,
|
||||
adjustments: [
|
||||
expect.objectContaining({
|
||||
amount: 1000,
|
||||
discount_id: "10Percent",
|
||||
description: "discount",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("updates line item of a cart containing an item fixed discount", async () => {
|
||||
const api = useApi()
|
||||
await simpleLineItemFactory(dbConnection, {
|
||||
id: "test-li-disc",
|
||||
allow_discounts: true,
|
||||
title: "Line Item Disc",
|
||||
thumbnail: "https://test.js/1234",
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
variant_id: "test-variant-quantity",
|
||||
cart_id: "test-cart-w-item-fixed-discount",
|
||||
})
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/store/carts/test-cart-w-item-fixed-discount/line-items/test-li-disc",
|
||||
{
|
||||
quantity: 4,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
expect(response.data.cart.items).toEqual([
|
||||
expect.objectContaining({
|
||||
cart_id: "test-cart-w-item-fixed-discount",
|
||||
unit_price: 1000,
|
||||
variant_id: "test-variant-quantity",
|
||||
quantity: 4,
|
||||
adjustments: [
|
||||
expect.objectContaining({
|
||||
amount: 800,
|
||||
discount_id: "item-fixed-200",
|
||||
description: "discount",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("updates line item of a cart containing an item percentage discount", async () => {
|
||||
const api = useApi()
|
||||
await simpleLineItemFactory(dbConnection, {
|
||||
id: "test-li-disc",
|
||||
allow_discounts: true,
|
||||
title: "Line Item Disc",
|
||||
thumbnail: "https://test.js/1234",
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
variant_id: "test-variant-quantity",
|
||||
cart_id: "test-cart-w-item-percentage-discount",
|
||||
})
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/store/carts/test-cart-w-item-percentage-discount/line-items/test-li-disc",
|
||||
{
|
||||
quantity: 3,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
expect(response.data.cart.items).toEqual([
|
||||
expect.objectContaining({
|
||||
cart_id: "test-cart-w-item-percentage-discount",
|
||||
unit_price: 1000,
|
||||
variant_id: "test-variant-quantity",
|
||||
quantity: 3,
|
||||
adjustments: [
|
||||
expect.objectContaining({
|
||||
amount: 450,
|
||||
discount_id: "item-percentage-15",
|
||||
description: "discount",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /store/carts/:id", () => {
|
||||
@@ -1202,6 +1739,22 @@ describe("/store/carts", () => {
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
// Ensure that the discount is only applied to the standard item
|
||||
const itemId = cartWithGiftcard.data.cart.items[0].id
|
||||
expect(cartWithGiftcard.data.cart.items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
variant_id: "test-variant",
|
||||
quantity: 1,
|
||||
adjustments: [
|
||||
expect.objectContaining({
|
||||
discount_id: "10Percent",
|
||||
amount: 100,
|
||||
item_id: itemId,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
)
|
||||
expect(cartWithGiftcard.data.cart.total).toBe(1900) // 1000 (giftcard) + 900 (standard item with 10% discount)
|
||||
expect(cartWithGiftcard.data.cart.discount_total).toBe(100)
|
||||
expect(cartWithGiftcard.status).toEqual(200)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Connection } from "typeorm"
|
||||
import faker from "faker"
|
||||
import { LineItem, LineItemTaxLine } from "@medusajs/medusa"
|
||||
import { LineItem, LineItemAdjustment, LineItemTaxLine } from "@medusajs/medusa"
|
||||
|
||||
type TaxLineFactoryData = {
|
||||
rate: number
|
||||
@@ -8,6 +8,11 @@ type TaxLineFactoryData = {
|
||||
name: string
|
||||
}
|
||||
|
||||
type LineItemAdjustmentFactoryData = Omit<LineItemAdjustment, "discount_id"> & {
|
||||
discount_id: string
|
||||
discount_code: string
|
||||
}
|
||||
|
||||
export type LineItemFactoryData = {
|
||||
id?: string
|
||||
cart_id?: string
|
||||
@@ -24,6 +29,7 @@ export type LineItemFactoryData = {
|
||||
shipped_quantity?: boolean
|
||||
returned_quantity?: boolean
|
||||
tax_lines?: TaxLineFactoryData[]
|
||||
adjustments: LineItemAdjustmentFactoryData[]
|
||||
}
|
||||
|
||||
export const simpleLineItemFactory = async (
|
||||
@@ -63,6 +69,7 @@ export const simpleLineItemFactory = async (
|
||||
fulfilled_quantity: data.fulfilled_quantity || null,
|
||||
shipped_quantity: data.shipped_quantity || null,
|
||||
returned_quantity: data.returned_quantity || null,
|
||||
adjustments: data.adjustments,
|
||||
})
|
||||
|
||||
const line = await manager.save(toSave)
|
||||
|
||||
@@ -101,7 +101,17 @@ export const simpleOrderFactory = async (
|
||||
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) {
|
||||
await simpleLineItemFactory(connection, { ...item, order_id: id })
|
||||
}
|
||||
|
||||
@@ -144,6 +144,69 @@ module.exports = async (connection, data = {}) => {
|
||||
tenPercent.rule = tenPercentRule
|
||||
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, {
|
||||
id: "test-discount-usage-limit",
|
||||
code: "SPENT",
|
||||
@@ -570,6 +633,74 @@ module.exports = async (connection, data = {}) => {
|
||||
|
||||
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, {
|
||||
id: "test-cart-2",
|
||||
customer_id: "some-customer",
|
||||
|
||||
@@ -173,6 +173,14 @@ module.exports = async (connection, data = {}) => {
|
||||
quantity: 1,
|
||||
variant_id: "test-variant",
|
||||
order_id: "test-order",
|
||||
adjustments: [
|
||||
{
|
||||
amount: 800,
|
||||
discount_id: "test-discount",
|
||||
description: "discount",
|
||||
item_id: "test-item",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await manager.save(li)
|
||||
|
||||
@@ -349,7 +349,7 @@ const createSwap = async (options, manager) => {
|
||||
is_disabled: false,
|
||||
rule: dRule,
|
||||
})
|
||||
await manager.save(discount)
|
||||
let discountDb = await manager.save(discount)
|
||||
|
||||
const cart = manager.create(Cart, {
|
||||
id: `${swapId}-cart`,
|
||||
@@ -396,6 +396,14 @@ const createSwap = async (options, manager) => {
|
||||
thumbnail: "https://test.js/1234",
|
||||
unit_price: 8000,
|
||||
quantity: 1,
|
||||
adjustments: [
|
||||
{
|
||||
amount: -800,
|
||||
description: "discount",
|
||||
discount_id: discountDb.id,
|
||||
item_id: `${swapId}-return-item-1`,
|
||||
},
|
||||
],
|
||||
variant_id: "test-variant",
|
||||
order_id: "order-with-swap",
|
||||
cart_id: cart.id,
|
||||
@@ -419,6 +427,7 @@ const createSwap = async (options, manager) => {
|
||||
|
||||
const return_item1 = manager.create(LineItem, {
|
||||
...li,
|
||||
is_return: true,
|
||||
unit_price: -1 * li.unit_price,
|
||||
})
|
||||
|
||||
|
||||
@@ -8,16 +8,16 @@
|
||||
"build": "babel src -d dist --extensions \".ts,.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/medusa": "1.2.1-dev-1648026403166",
|
||||
"@medusajs/medusa": "1.2.1-dev-1649181615374",
|
||||
"faker": "^5.5.3",
|
||||
"medusa-interfaces": "1.2.1-dev-1648026403166",
|
||||
"medusa-interfaces": "1.2.1-dev-1649181615374",
|
||||
"typeorm": "^0.2.31"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.12.10",
|
||||
"@babel/core": "^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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1301,10 +1301,10 @@
|
||||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@medusajs/medusa-cli@1.2.1-dev-1648026403166":
|
||||
version "1.2.1-dev-1648026403166"
|
||||
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.2.1-dev-1648026403166.tgz#1dc2ad62ba11421d43357596c61eb0b76d3bc69e"
|
||||
integrity sha512-TScUnBuxI5V5cB6gAAGeGOZU8lXYHIhozpelDcYibZqaHEIwMNs1ekzRYhfMjYowSFCMLlzCsHFUh+gqU/6WSw==
|
||||
"@medusajs/medusa-cli@1.2.1-dev-1649181615374":
|
||||
version "1.2.1-dev-1649181615374"
|
||||
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.2.1-dev-1649181615374.tgz#1ea9014e3ec9813a52457b0d6e2fc6bb64d3bfd6"
|
||||
integrity sha512-8m6Z1ZZqstZKaAaKoFS3v3IzI7BFhcBgpF+iCSRuJoXltQgzVQOAxXuPjkRoi+m1ZZ+Yi/YYEzKmNQ99vmXisQ==
|
||||
dependencies:
|
||||
"@babel/polyfill" "^7.8.7"
|
||||
"@babel/runtime" "^7.9.6"
|
||||
@@ -1322,8 +1322,8 @@
|
||||
is-valid-path "^0.1.1"
|
||||
joi-objectid "^3.0.1"
|
||||
meant "^1.0.1"
|
||||
medusa-core-utils "1.1.31-dev-1648026403166"
|
||||
medusa-telemetry "0.0.11-dev-1648026403166"
|
||||
medusa-core-utils "1.1.31-dev-1649181615374"
|
||||
medusa-telemetry "0.0.11-dev-1649181615374"
|
||||
netrc-parser "^3.1.6"
|
||||
open "^8.0.6"
|
||||
ora "^5.4.1"
|
||||
@@ -1337,13 +1337,13 @@
|
||||
winston "^3.3.3"
|
||||
yargs "^15.3.1"
|
||||
|
||||
"@medusajs/medusa@1.2.1-dev-1648026403166":
|
||||
version "1.2.1-dev-1648026403166"
|
||||
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.2.1-dev-1648026403166.tgz#f6e8fab09ac73b38ed79e15d2080e7ecad80fd99"
|
||||
integrity sha512-ufYVgcKo+bsAzSnZA3FVLmxseDwnSKkSbIdktQUSZGMJTRvN/+Wcqnjdf59oH11jqqBApJ8T77Nvd/ABasjTWw==
|
||||
"@medusajs/medusa@1.2.1-dev-1649181615374":
|
||||
version "1.2.1-dev-1649181615374"
|
||||
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.2.1-dev-1649181615374.tgz#6a62f8628b84b47a8717e9e0c276f3a9c2e376ce"
|
||||
integrity sha512-eiCGE6JqYuP7GCzTBGg5LI9U0uQ0wlsR+NuMZVEwldj+xc7qwMjBJwUA7gc58gBv6JesfMYj3VZmJComN4+7Bg==
|
||||
dependencies:
|
||||
"@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"
|
||||
awilix "^4.2.3"
|
||||
body-parser "^1.19.0"
|
||||
@@ -1367,8 +1367,8 @@
|
||||
joi "^17.3.0"
|
||||
joi-objectid "^3.0.1"
|
||||
jsonwebtoken "^8.5.1"
|
||||
medusa-core-utils "1.1.31-dev-1648026403166"
|
||||
medusa-test-utils "1.1.37-dev-1648026403166"
|
||||
medusa-core-utils "1.1.31-dev-1649181615374"
|
||||
medusa-test-utils "1.1.37-dev-1649181615374"
|
||||
morgan "^1.9.1"
|
||||
multer "^1.4.2"
|
||||
passport "^0.4.0"
|
||||
@@ -2010,10 +2010,10 @@ babel-preset-jest@^26.6.2:
|
||||
babel-plugin-jest-hoist "^26.6.2"
|
||||
babel-preset-current-node-syntax "^1.0.0"
|
||||
|
||||
babel-preset-medusa-package@1.1.19-dev-1648026403166:
|
||||
version "1.1.19-dev-1648026403166"
|
||||
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.19-dev-1648026403166.tgz#e834a06c2283d5b8709d45de1e4ff559dfd23944"
|
||||
integrity sha512-HjbSvyeLU9ez0dgBv4+SDX2/nXaskN5Bip7nF36F/fnufw/5FtJ28Lyw30VWz6i5Ci991Zmadfq6TNUZnKgNHw==
|
||||
babel-preset-medusa-package@1.1.19-dev-1649181615374:
|
||||
version "1.1.19-dev-1649181615374"
|
||||
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.19-dev-1649181615374.tgz#2f13d52fedd336ad4b4c0602b3bf4696d2d08db7"
|
||||
integrity sha512-N4XL7rTmNM2W+iRR92xvU4bKadP25lY5QR3vndxTxsLNSgcR5tLjKLO/4j7AqiFvcthbE8cF1TcdECH5aJfSuA==
|
||||
dependencies:
|
||||
"@babel/plugin-proposal-class-properties" "^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"
|
||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||
|
||||
medusa-core-utils@1.1.31-dev-1648026403166:
|
||||
version "1.1.31-dev-1648026403166"
|
||||
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.31-dev-1648026403166.tgz#0f3bde6fc0a77d3027d80a4ca5e1a1ce2426ad21"
|
||||
integrity sha512-B5eFj3dH8g/HgNd8p8ChwR4bpydl0dqyBJllyvqS1AnFpjW7BcXpO0ZPkdYrIHflQL9e2OiIq3A2Lm6ZsYCI1A==
|
||||
medusa-core-utils@1.1.31-dev-1649181615374:
|
||||
version "1.1.31-dev-1649181615374"
|
||||
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.31-dev-1649181615374.tgz#60416bba53eaba607d77ca36789aa23756b8db0f"
|
||||
integrity sha512-w5nusocZweIrAFJ6sl4hD/mN+UtNjz39IIfXukekyJByg3wpv4P+vsW3XdrFTX5OLvKVxuzjl3B2zeZUmdgSKg==
|
||||
dependencies:
|
||||
joi "^17.3.0"
|
||||
joi-objectid "^3.0.1"
|
||||
|
||||
medusa-interfaces@1.2.1-dev-1648026403166:
|
||||
version "1.2.1-dev-1648026403166"
|
||||
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.2.1-dev-1648026403166.tgz#29742128c852c967a94df1e00f0c8dacccff6b86"
|
||||
integrity sha512-VRnFIFqPeOd0TcPMlng1kIzjhT7AtJzNqIDmKDxeJeE1MqU89g6YME/yQfCPzTXKNMdwN+8Xx+E5gANNn5i1QA==
|
||||
medusa-interfaces@1.2.1-dev-1649181615374:
|
||||
version "1.2.1-dev-1649181615374"
|
||||
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.2.1-dev-1649181615374.tgz#0b664f4e3e8e61b67108a41c8f0f9dd58a947075"
|
||||
integrity sha512-JRD773nZnxjn/2oNrgb/zXn+scBoNHpW97YQYW7+LFX1JBYafzyGQW3vWTFX4X+q08ehz4dc21CgoMeYco8yvQ==
|
||||
dependencies:
|
||||
medusa-core-utils "1.1.31-dev-1648026403166"
|
||||
medusa-core-utils "1.1.31-dev-1649181615374"
|
||||
|
||||
medusa-telemetry@0.0.11-dev-1648026403166:
|
||||
version "0.0.11-dev-1648026403166"
|
||||
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.11-dev-1648026403166.tgz#dd66a411a214b953f8b6673f59c0b02dd78ec99f"
|
||||
integrity sha512-SH07C6rOQyx7bHH4/2TzmBgzgMY8fLiE7PH7dk6BGwY8bjx9w6DvF9CvhdwxFnFnzDTed/cjkE/cHNkQFkjSrQ==
|
||||
medusa-telemetry@0.0.11-dev-1649181615374:
|
||||
version "0.0.11-dev-1649181615374"
|
||||
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.11-dev-1649181615374.tgz#3f4c366ea8d0d0fdde9b289f7e771bb27adae56d"
|
||||
integrity sha512-RMJR3/qlTb1nV05RnBnX1bNOvYyeuXf4owxLlfbWzKAZirWQ5LAC2GikEGGbHGbw7UgiLgQtu4Rnmg2Uye+VcA==
|
||||
dependencies:
|
||||
axios "^0.21.1"
|
||||
axios-retry "^3.1.9"
|
||||
@@ -5186,13 +5186,13 @@ medusa-telemetry@0.0.11-dev-1648026403166:
|
||||
remove-trailing-slash "^0.1.1"
|
||||
uuid "^8.3.2"
|
||||
|
||||
medusa-test-utils@1.1.37-dev-1648026403166:
|
||||
version "1.1.37-dev-1648026403166"
|
||||
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.37-dev-1648026403166.tgz#f32f0a04d7d38f6a84e58a2937bd026767ea5d1f"
|
||||
integrity sha512-VQCuRal14tl/ZLsN6Ueh+z3FBgQEXgsLLSJxfpsrmABKTgoHONhkoe7UyXxxUozXwPWWqqtHhy80zfCIlfHF+w==
|
||||
medusa-test-utils@1.1.37-dev-1649181615374:
|
||||
version "1.1.37-dev-1649181615374"
|
||||
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.37-dev-1649181615374.tgz#079c16a791d47c52072c6f0837d0a827208bc9cc"
|
||||
integrity sha512-hj3iNZsIA01l7qAZrOgt+kT8PDkXKoW4CEL3bhVfIUEwsdv9jID7FGdTIN/7G3diioTypvrVcqRrp0uiWdgp+Q==
|
||||
dependencies:
|
||||
"@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"
|
||||
|
||||
merge-descriptors@1.0.1:
|
||||
|
||||
@@ -427,6 +427,7 @@ Object {
|
||||
"idempotency_key": null,
|
||||
"items": Array [
|
||||
Object {
|
||||
"adjustments": Array [],
|
||||
"allow_discounts": true,
|
||||
"cart_id": null,
|
||||
"claim_order_id": null,
|
||||
@@ -761,6 +762,7 @@ Object {
|
||||
"idempotency_key": null,
|
||||
"items": Array [
|
||||
Object {
|
||||
"adjustments": Array [],
|
||||
"allow_discounts": true,
|
||||
"cart_id": null,
|
||||
"claim_order_id": null,
|
||||
@@ -982,6 +984,7 @@ Object {
|
||||
"idempotency_key": null,
|
||||
"items": Array [
|
||||
Object {
|
||||
"adjustments": Array [],
|
||||
"allow_discounts": true,
|
||||
"cart_id": null,
|
||||
"claim_order_id": null,
|
||||
@@ -1248,6 +1251,7 @@ Object {
|
||||
"idempotency_key": null,
|
||||
"items": Array [
|
||||
Object {
|
||||
"adjustments": Array [],
|
||||
"allow_discounts": true,
|
||||
"cart_id": null,
|
||||
"claim_order_id": null,
|
||||
@@ -1569,6 +1573,7 @@ Object {
|
||||
"idempotency_key": null,
|
||||
"items": Array [
|
||||
Object {
|
||||
"adjustments": Array [],
|
||||
"allow_discounts": true,
|
||||
"cart_id": null,
|
||||
"claim_order_id": null,
|
||||
|
||||
@@ -107,6 +107,7 @@ describe("medusa-plugin-sendgrid", () => {
|
||||
},
|
||||
items: [
|
||||
{
|
||||
adjustments: [],
|
||||
created_at: expect.any(Date),
|
||||
updated_at: expect.any(Date),
|
||||
order_id: expect.any(String),
|
||||
@@ -227,6 +228,7 @@ describe("medusa-plugin-sendgrid", () => {
|
||||
},
|
||||
items: [
|
||||
{
|
||||
adjustments: [],
|
||||
created_at: expect.any(Date),
|
||||
updated_at: expect.any(Date),
|
||||
order_id: expect.any(String),
|
||||
@@ -285,6 +287,7 @@ describe("medusa-plugin-sendgrid", () => {
|
||||
},
|
||||
items: [
|
||||
{
|
||||
adjustments: [],
|
||||
created_at: expect.any(Date),
|
||||
updated_at: expect.any(Date),
|
||||
order_id: expect.any(String),
|
||||
|
||||
@@ -8,18 +8,18 @@
|
||||
"build": "babel src -d dist --extensions \".ts,.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/medusa": "1.1.60-dev-1642337635041",
|
||||
"@medusajs/medusa": "1.2.1-dev-1649009241281",
|
||||
"faker": "^5.5.3",
|
||||
"medusa-fulfillment-webshipper": "1.1.35-dev-1642337635041",
|
||||
"medusa-interfaces": "1.1.32-dev-1642337635041",
|
||||
"medusa-plugin-sendgrid": "1.1.36-dev-1642337635041",
|
||||
"medusa-fulfillment-webshipper": "1.2.1-dev-1649009241281",
|
||||
"medusa-interfaces": "1.2.1-dev-1649009241281",
|
||||
"medusa-plugin-sendgrid": "1.2.1-dev-1649009241281",
|
||||
"typeorm": "^0.2.31"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.12.10",
|
||||
"@babel/core": "^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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1268,10 +1268,10 @@
|
||||
"@types/yargs" "^15.0.0"
|
||||
chalk "^4.0.0"
|
||||
|
||||
"@medusajs/medusa-cli@1.1.25-dev-1642337635041":
|
||||
version "1.1.25-dev-1642337635041"
|
||||
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.1.25-dev-1642337635041.tgz#e3ca33f453446dddc4922f42b4254995de9874b5"
|
||||
integrity sha512-8pA5iBMglgrdPRSEZLvjovJczib5zk9qlQJsewlSrw/aolwnusukEE7vBfjAiUXAZKngMx6BnYoORlWSV88wdA==
|
||||
"@medusajs/medusa-cli@1.2.1-dev-1649009241281":
|
||||
version "1.2.1-dev-1649009241281"
|
||||
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.2.1-dev-1649009241281.tgz#e0cc327224af6067013e0eba1b882b3f9f0fd0e3"
|
||||
integrity sha512-3949nJfrBIguXrKDwiKjQ10XxZ06ccTShbcOUsXkIuGIMOHBZ+AuhjErCAnXS3H/JSIO93lnQKVJ8DWSkf9BGQ==
|
||||
dependencies:
|
||||
"@babel/polyfill" "^7.8.7"
|
||||
"@babel/runtime" "^7.9.6"
|
||||
@@ -1289,8 +1289,8 @@
|
||||
is-valid-path "^0.1.1"
|
||||
joi-objectid "^3.0.1"
|
||||
meant "^1.0.1"
|
||||
medusa-core-utils "1.1.31-dev-1642337635041"
|
||||
medusa-telemetry "0.0.11-dev-1642337635041"
|
||||
medusa-core-utils "1.1.31-dev-1649009241281"
|
||||
medusa-telemetry "0.0.11-dev-1649009241281"
|
||||
netrc-parser "^3.1.6"
|
||||
open "^8.0.6"
|
||||
ora "^5.4.1"
|
||||
@@ -1304,13 +1304,13 @@
|
||||
winston "^3.3.3"
|
||||
yargs "^15.3.1"
|
||||
|
||||
"@medusajs/medusa@1.1.60-dev-1642337635041":
|
||||
version "1.1.60-dev-1642337635041"
|
||||
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.1.60-dev-1642337635041.tgz#011d64b9b85a3f66e01f3755788370aa444a2e27"
|
||||
integrity sha512-MBef46mJZMSyE3+dcijMqjFZbNo7AL60mP/PwkOy4W1d/t2MZ+GOBIufLAVxVw7SaiSjAGpQDmCrUOqQrIhIdg==
|
||||
"@medusajs/medusa@1.2.1-dev-1649009241281":
|
||||
version "1.2.1-dev-1649009241281"
|
||||
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.2.1-dev-1649009241281.tgz#ed063a9abee468cd71b5d0201ea4f32dd2a59775"
|
||||
integrity sha512-uNVPZq4rVOLRRP9yTeYWPn9xsLfhh7mgRexMjRL/7QmKTQmspBL/pbjdRk0WCBSSf3o5SFiOV0/n69Oxg6Oaig==
|
||||
dependencies:
|
||||
"@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"
|
||||
awilix "^4.2.3"
|
||||
body-parser "^1.19.0"
|
||||
@@ -1334,8 +1334,8 @@
|
||||
joi "^17.3.0"
|
||||
joi-objectid "^3.0.1"
|
||||
jsonwebtoken "^8.5.1"
|
||||
medusa-core-utils "1.1.31-dev-1642337635041"
|
||||
medusa-test-utils "1.1.35-dev-1642337635041"
|
||||
medusa-core-utils "1.1.31-dev-1649009241281"
|
||||
medusa-test-utils "1.1.37-dev-1649009241281"
|
||||
morgan "^1.9.1"
|
||||
multer "^1.4.2"
|
||||
passport "^0.4.0"
|
||||
@@ -1995,10 +1995,10 @@ babel-preset-jest@^26.6.2:
|
||||
babel-plugin-jest-hoist "^26.6.2"
|
||||
babel-preset-current-node-syntax "^1.0.0"
|
||||
|
||||
babel-preset-medusa-package@1.1.19-dev-1642337635041:
|
||||
version "1.1.19-dev-1642337635041"
|
||||
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.19-dev-1642337635041.tgz#24c6f2db887f35c358270d85cc0f6b6c19f459fe"
|
||||
integrity sha512-HtrHI6oWUEMyTK9MHKzCg7hzZHgi8+uCIQyTQIY5SYFRA6kZiNl54Wzai/BY26F1j5g0sVNfqmFXdBd53GEz7g==
|
||||
babel-preset-medusa-package@1.1.19-dev-1649009241281:
|
||||
version "1.1.19-dev-1649009241281"
|
||||
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.19-dev-1649009241281.tgz#253cb95840d3e122a45e2f72e15f623afa57ae61"
|
||||
integrity sha512-UFH2A+rYUc33d2f+xEVCQOOmcm0RSSPlnTeSVV5bM8SoS4MXgekPdBO+sMjKi/bLbHJncKaapCm/HoYZ19ZJHg==
|
||||
dependencies:
|
||||
"@babel/plugin-proposal-class-properties" "^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"
|
||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||
|
||||
medusa-core-utils@1.1.31-dev-1642337635041:
|
||||
version "1.1.31-dev-1642337635041"
|
||||
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.31-dev-1642337635041.tgz#d6928f377320ab851eff1750e13e44e2e366c9cb"
|
||||
integrity sha512-m0VXTM3QwKi0oRk2Q4mqn6sK07xJgiwyz3rs/0gHTjl88B8k75XugbriFceLPEYqHs6AjP6nSNiRDqdRJqUsMg==
|
||||
medusa-core-utils@1.1.31-dev-1649009241281:
|
||||
version "1.1.31-dev-1649009241281"
|
||||
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.31-dev-1649009241281.tgz#1ff37cdd9057c5716124f250c0b4b261ef8c7cde"
|
||||
integrity sha512-nYcgpTtUSGAFG00m1rKcWUgnlmL2zu5aOYl5Fas/re+hhc1fhi+jwHUMigK0c28t71RwgtaeJFNKgQNgxli+2A==
|
||||
dependencies:
|
||||
joi "^17.3.0"
|
||||
joi-objectid "^3.0.1"
|
||||
|
||||
medusa-fulfillment-webshipper@1.1.35-dev-1642337635041:
|
||||
version "1.1.35-dev-1642337635041"
|
||||
resolved "http://localhost:4873/medusa-fulfillment-webshipper/-/medusa-fulfillment-webshipper-1.1.35-dev-1642337635041.tgz#ab4b0a23488094b39582d1c1d1c69e29713b464d"
|
||||
integrity sha512-P1gRvnJcs5Svgmvb44Qp1Oph9TqtUQsEeawhX7u+3dSz9qDnJ1lo1XB1tBttpCP9qepPe3nc0Sk7mEwlq8eM6Q==
|
||||
medusa-fulfillment-webshipper@1.2.1-dev-1649009241281:
|
||||
version "1.2.1-dev-1649009241281"
|
||||
resolved "http://localhost:4873/medusa-fulfillment-webshipper/-/medusa-fulfillment-webshipper-1.2.1-dev-1649009241281.tgz#d199209df0d3683a53829565713b857bac284282"
|
||||
integrity sha512-dtMBJ3mObQS2uGKClga66qDOGTKjOenzLOdjScurCwheQqi5a+EOyUrZSssE4oQEMMRsuY+L/MTkcLyKr4f+HA==
|
||||
dependencies:
|
||||
axios "^0.20.0"
|
||||
body-parser "^1.19.0"
|
||||
cors "^2.8.5"
|
||||
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:
|
||||
version "1.1.32-dev-1642337635041"
|
||||
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.1.32-dev-1642337635041.tgz#8179a344a41e1163502b4ee8af5b294443067f99"
|
||||
integrity sha512-I7Wp+BEgzJ2wLkGBoDe6VhAlB8lJRMAHJ7wGzWfCMiG3Dj0PX2KE2HZDT+PA5S7IIXHWh9c0n37/cHZDcIF5Hw==
|
||||
medusa-interfaces@1.2.1-dev-1649009241281:
|
||||
version "1.2.1-dev-1649009241281"
|
||||
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.2.1-dev-1649009241281.tgz#77b6c906b805e6b1fb092fb7632a7ad8ecbafec5"
|
||||
integrity sha512-tgDptu8k7aWCKBo1m8RxcxcffsVjy2e2M80rfvTd81gjC3EtGBGxDmiwNGJoaWkFq+Matq2wve2l92xVA2t8uQ==
|
||||
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:
|
||||
version "1.1.36-dev-1642337635041"
|
||||
resolved "http://localhost:4873/medusa-plugin-sendgrid/-/medusa-plugin-sendgrid-1.1.36-dev-1642337635041.tgz#ae4d1d23167daf3c45ced085f3079ed21d5e876d"
|
||||
integrity sha512-nJBq0x9h9fMsPfW4BnGzvk8yWpuk1v90zeXP85cDhFeAAWJ50o2zF7HsNcJawPfhKooAF+3kRDApR8706gUyqQ==
|
||||
medusa-plugin-sendgrid@1.2.1-dev-1649009241281:
|
||||
version "1.2.1-dev-1649009241281"
|
||||
resolved "http://localhost:4873/medusa-plugin-sendgrid/-/medusa-plugin-sendgrid-1.2.1-dev-1649009241281.tgz#362fafa54658c641fb482346c0ba6f28007bbbf3"
|
||||
integrity sha512-2C7mfwR+HZSjm/Y3TGHHr7dJAoNhwUvEVZs2dpJCEfmpbsR2f6TG3J5e+kidubQ4NeCmHdrXGPmjS4lbH1tnhQ==
|
||||
dependencies:
|
||||
"@babel/plugin-transform-classes" "^7.9.5"
|
||||
"@sendgrid/mail" "^7.1.1"
|
||||
body-parser "^1.19.0"
|
||||
express "^4.17.1"
|
||||
medusa-core-utils "1.1.31-dev-1642337635041"
|
||||
medusa-test-utils "1.1.35-dev-1642337635041"
|
||||
medusa-core-utils "1.1.31-dev-1649009241281"
|
||||
medusa-test-utils "1.1.37-dev-1649009241281"
|
||||
|
||||
medusa-telemetry@0.0.11-dev-1642337635041:
|
||||
version "0.0.11-dev-1642337635041"
|
||||
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.11-dev-1642337635041.tgz#290bb20c1712adf99ec5d2f6871b200106203bc0"
|
||||
integrity sha512-LlJVgdKAQFrp6ULW8D+Qvb/VSEgbQbKjEeoMnUwDaMMswGehsFUXsqBzs1LRo5nKmpLaPvSorw4OMvktR+RxTQ==
|
||||
medusa-telemetry@0.0.11-dev-1649009241281:
|
||||
version "0.0.11-dev-1649009241281"
|
||||
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.11-dev-1649009241281.tgz#76077de2a5b53f754f1a55701d892716be67876d"
|
||||
integrity sha512-ZEWmHZI9RByg1Tm8sOOxVCs+dkr2LCvElXqFsJfgIoTdjvGS96tfG1pbod2LHD5rcBY2aXDSXBRf0pA1xRtvsQ==
|
||||
dependencies:
|
||||
axios "^0.21.1"
|
||||
axios-retry "^3.1.9"
|
||||
@@ -5191,13 +5191,13 @@ medusa-telemetry@0.0.11-dev-1642337635041:
|
||||
remove-trailing-slash "^0.1.1"
|
||||
uuid "^8.3.2"
|
||||
|
||||
medusa-test-utils@1.1.35-dev-1642337635041:
|
||||
version "1.1.35-dev-1642337635041"
|
||||
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.35-dev-1642337635041.tgz#1daf7f6a671e2fffe43ab349d795581870104a4a"
|
||||
integrity sha512-pCpMjmo0VyYbSRu/Ga/GdirBscJtS4HK4HtfXepMrut7Yg/kV+GpW/fFtBFkiqGDBpCWYLopzZAdRw+qpdpusA==
|
||||
medusa-test-utils@1.1.37-dev-1649009241281:
|
||||
version "1.1.37-dev-1649009241281"
|
||||
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.37-dev-1649009241281.tgz#59c9ac8fddf31a410347dac5ef147758a61f13da"
|
||||
integrity sha512-+3c+2DB3UpJxyIt6LvWCR9aBtXwrABXV4GhUYW6toGl19JE2lZ9PBL3GSVmGi7etZ7Xv7tR9bwZXZURvKMr8Hg==
|
||||
dependencies:
|
||||
"@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"
|
||||
|
||||
merge-descriptors@1.0.1:
|
||||
|
||||
@@ -46,11 +46,17 @@ export default (app) => {
|
||||
return app
|
||||
}
|
||||
|
||||
export const defaultAdminDraftOrdersRelations = ["order", "cart"]
|
||||
export const defaultAdminDraftOrdersRelations = [
|
||||
"order",
|
||||
"cart",
|
||||
"cart.items",
|
||||
"cart.items.adjustments",
|
||||
]
|
||||
|
||||
export const defaultAdminDraftOrdersCartRelations = [
|
||||
"region",
|
||||
"items",
|
||||
"items.adjustments",
|
||||
"payment",
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
|
||||
@@ -5,12 +5,15 @@ import { SwapServiceMock } from "../../../../../services/__mocks__/swap"
|
||||
const defaultRelations = [
|
||||
"order",
|
||||
"additional_items",
|
||||
"additional_items.adjustments",
|
||||
"return_order",
|
||||
"fulfillments",
|
||||
"payment",
|
||||
"shipping_address",
|
||||
"shipping_methods",
|
||||
"cart",
|
||||
"cart.items",
|
||||
"cart.items.adjustments",
|
||||
]
|
||||
|
||||
const defaultFields = [
|
||||
|
||||
@@ -24,12 +24,15 @@ export default (app) => {
|
||||
export const defaultAdminSwapRelations = [
|
||||
"order",
|
||||
"additional_items",
|
||||
"additional_items.adjustments",
|
||||
"return_order",
|
||||
"fulfillments",
|
||||
"payment",
|
||||
"shipping_address",
|
||||
"shipping_methods",
|
||||
"cart",
|
||||
"cart.items",
|
||||
"cart.items.adjustments",
|
||||
]
|
||||
|
||||
export const defaultAdminSwapFields = [
|
||||
|
||||
@@ -115,6 +115,7 @@ export const defaultStoreCartRelations = [
|
||||
"gift_cards",
|
||||
"region",
|
||||
"items",
|
||||
"items.adjustments",
|
||||
"payment",
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
|
||||
@@ -14,6 +14,7 @@ export * from "./models/product-type-tax-rate"
|
||||
export * from "./models/tax-rate"
|
||||
export * from "./models/shipping-method-tax-line"
|
||||
export * from "./models/line-item-tax-line"
|
||||
export * from "./models/line-item-adjustment"
|
||||
export * from "./models/address"
|
||||
export * from "./models/cart"
|
||||
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 { ClaimOrder } from "./claim-order"
|
||||
import { ProductVariant } from "./product-variant"
|
||||
import { LineItemAdjustment } from "./line-item-adjustment"
|
||||
|
||||
@Check(`"fulfilled_quantity" <= "quantity"`)
|
||||
@Check(`"shipped_quantity" <= "fulfilled_quantity"`)
|
||||
@@ -65,6 +66,9 @@ export class LineItem {
|
||||
@OneToMany(() => LineItemTaxLine, (tl) => tl.item, { cascade: ["insert"] })
|
||||
tax_lines: LineItemTaxLine[]
|
||||
|
||||
@OneToMany(() => LineItemAdjustment, (lia) => lia.item, { cascade: ["insert"] })
|
||||
adjustments: LineItemAdjustment[]
|
||||
|
||||
@Column()
|
||||
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 })[]> {
|
||||
const qb = this.createQueryBuilder("li")
|
||||
.leftJoinAndSelect(`li.tax_lines`, "tax_lines")
|
||||
.leftJoinAndSelect(`li.adjustments`, "adjustments")
|
||||
.leftJoinAndMapOne(
|
||||
`li.return_item`,
|
||||
ReturnItem,
|
||||
|
||||
@@ -210,7 +210,7 @@ export const CartServiceMock = {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
updatePaymentSession: jest.fn().mockImplementation(data => {
|
||||
updatePaymentSession: jest.fn().mockImplementation((data) => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
authorizePayment: jest.fn().mockImplementation((id, data) => {
|
||||
@@ -223,13 +223,13 @@ export const CartServiceMock = {
|
||||
}
|
||||
return Promise.resolve(carts.testCart)
|
||||
}),
|
||||
refreshPaymentSession: jest.fn().mockImplementation(data => {
|
||||
refreshPaymentSession: jest.fn().mockImplementation((data) => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
update: jest.fn().mockImplementation(data => {
|
||||
update: jest.fn().mockImplementation((data) => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
create: jest.fn().mockImplementation(data => {
|
||||
create: jest.fn().mockImplementation((data) => {
|
||||
if (data.region_id === IdMap.getId("testRegion")) {
|
||||
return Promise.resolve(carts.regionCart)
|
||||
}
|
||||
@@ -238,7 +238,7 @@ export const CartServiceMock = {
|
||||
}
|
||||
return Promise.resolve(carts.regionCart)
|
||||
}),
|
||||
retrieve: jest.fn().mockImplementation(cartId => {
|
||||
retrieve: jest.fn().mockImplementation((cartId) => {
|
||||
if (cartId === IdMap.getId("fr-cart")) {
|
||||
return Promise.resolve(carts.frCart)
|
||||
}
|
||||
@@ -326,20 +326,20 @@ export const CartServiceMock = {
|
||||
applyDiscount: jest.fn().mockImplementation((cartId, code) => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
setPaymentSession: jest.fn().mockImplementation(cartId => {
|
||||
setPaymentSession: jest.fn().mockImplementation((cartId) => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
setPaymentSessions: jest.fn().mockImplementation(cartId => {
|
||||
setPaymentSessions: jest.fn().mockImplementation((cartId) => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
setShippingOptions: jest.fn().mockImplementation(cartId => {
|
||||
setShippingOptions: jest.fn().mockImplementation((cartId) => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
decorate: jest.fn().mockImplementation(cart => {
|
||||
decorate: jest.fn().mockImplementation((cart) => {
|
||||
cart.decorated = true
|
||||
return cart
|
||||
}),
|
||||
addShippingMethod: jest.fn().mockImplementation(cartId => {
|
||||
addShippingMethod: jest.fn().mockImplementation((cartId) => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
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(() => {
|
||||
|
||||
@@ -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 CartService from "../cart"
|
||||
import { InventoryServiceMock } from "../__mocks__/inventory"
|
||||
import { LineItemAdjustmentServiceMock } from "../__mocks__/line-item-adjustment"
|
||||
|
||||
const eventBusService = {
|
||||
emit: jest.fn(),
|
||||
@@ -347,6 +348,7 @@ describe("CartService", () => {
|
||||
findOneWithRelations: (rels, q) => {
|
||||
if (q.where.id === IdMap.getId("cartWithLine")) {
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("cartWithLine"),
|
||||
items: [
|
||||
{
|
||||
id: IdMap.getId("merger"),
|
||||
@@ -359,6 +361,7 @@ describe("CartService", () => {
|
||||
})
|
||||
}
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("emptyCart"),
|
||||
shipping_methods: [
|
||||
{
|
||||
shipping_option: {
|
||||
@@ -378,13 +381,14 @@ describe("CartService", () => {
|
||||
eventBusService,
|
||||
shippingOptionService,
|
||||
inventoryService,
|
||||
lineItemAdjustmentService: LineItemAdjustmentServiceMock,
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully creates new line item", async () => {
|
||||
it("creates a new line item and emits a created event", async () => {
|
||||
const lineItem = {
|
||||
title: "New Line",
|
||||
description: "This is a new line",
|
||||
@@ -393,7 +397,6 @@ describe("CartService", () => {
|
||||
unit_price: 123,
|
||||
quantity: 10,
|
||||
}
|
||||
|
||||
await cartService.addLineItem(IdMap.getId("emptyCart"), _.clone(lineItem))
|
||||
|
||||
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
||||
@@ -408,6 +411,15 @@ describe("CartService", () => {
|
||||
has_shipping: false,
|
||||
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 () => {
|
||||
@@ -440,6 +452,15 @@ describe("CartService", () => {
|
||||
has_shipping: false,
|
||||
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 () => {
|
||||
@@ -462,6 +483,20 @@ describe("CartService", () => {
|
||||
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 () => {
|
||||
@@ -559,6 +594,7 @@ describe("CartService", () => {
|
||||
lineItemService,
|
||||
shippingOptionService,
|
||||
eventBusService,
|
||||
lineItemAdjustmentService: LineItemAdjustmentServiceMock,
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -576,6 +612,22 @@ describe("CartService", () => {
|
||||
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).toHaveBeenCalledWith(
|
||||
"cart.updated",
|
||||
@@ -598,6 +650,22 @@ describe("CartService", () => {
|
||||
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 () => {
|
||||
@@ -666,7 +734,13 @@ describe("CartService", () => {
|
||||
|
||||
describe("updateLineItem", () => {
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
update: jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
id: IdMap.getId("existing"),
|
||||
variant_id: IdMap.getId("good"),
|
||||
quantity: 1,
|
||||
})
|
||||
),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
@@ -692,6 +766,7 @@ describe("CartService", () => {
|
||||
})
|
||||
}
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("cartWithLine"),
|
||||
items: [
|
||||
{
|
||||
id: IdMap.getId("existing"),
|
||||
@@ -709,6 +784,7 @@ describe("CartService", () => {
|
||||
lineItemService,
|
||||
eventBusService,
|
||||
inventoryService,
|
||||
lineItemAdjustmentService: LineItemAdjustmentServiceMock,
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -733,6 +809,20 @@ describe("CartService", () => {
|
||||
IdMap.getId("existing"),
|
||||
{ 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 () => {
|
||||
@@ -1599,6 +1689,18 @@ describe("CartService", () => {
|
||||
},
|
||||
],
|
||||
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") {
|
||||
@@ -1619,6 +1721,18 @@ describe("CartService", () => {
|
||||
id: IdMap.getId("cart"),
|
||||
discounts: [],
|
||||
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)
|
||||
}),
|
||||
validateDiscountForCartOrThrow: jest
|
||||
.fn()
|
||||
.mockImplementation((cart, discount) => {
|
||||
return Promise.resolve({ hasErrors: () => false })
|
||||
}),
|
||||
}
|
||||
|
||||
const cartService = new CartService({
|
||||
@@ -1764,6 +1883,7 @@ describe("CartService", () => {
|
||||
cartRepository,
|
||||
discountService,
|
||||
eventBusService,
|
||||
lineItemAdjustmentService: LineItemAdjustmentServiceMock,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -1793,6 +1913,18 @@ describe("CartService", () => {
|
||||
subtotal: 0,
|
||||
tax_total: 0,
|
||||
total: 0,
|
||||
items: [
|
||||
{
|
||||
id: "li1",
|
||||
quantity: 2,
|
||||
unit_price: 1000,
|
||||
},
|
||||
{
|
||||
id: "li2",
|
||||
quantity: 1,
|
||||
unit_price: 500,
|
||||
},
|
||||
],
|
||||
discounts: [
|
||||
{
|
||||
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 () => {
|
||||
@@ -1820,6 +1966,18 @@ describe("CartService", () => {
|
||||
subtotal: 0,
|
||||
tax_total: 0,
|
||||
total: 0,
|
||||
items: [
|
||||
{
|
||||
id: "li1",
|
||||
quantity: 2,
|
||||
unit_price: 1000,
|
||||
},
|
||||
{
|
||||
id: "li2",
|
||||
quantity: 1,
|
||||
unit_price: 500,
|
||||
},
|
||||
],
|
||||
discounts: [
|
||||
{
|
||||
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 () => {
|
||||
@@ -1860,32 +2032,16 @@ describe("CartService", () => {
|
||||
},
|
||||
},
|
||||
],
|
||||
discount_total: 0,
|
||||
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: [
|
||||
items: [
|
||||
{
|
||||
id: IdMap.getId("null-count"),
|
||||
code: "null-count",
|
||||
regions: [{ id: IdMap.getId("good") }],
|
||||
usage_count: 0,
|
||||
usage_limit: 2,
|
||||
rule: {},
|
||||
id: "li1",
|
||||
quantity: 2,
|
||||
unit_price: 1000,
|
||||
},
|
||||
{
|
||||
id: "li2",
|
||||
quantity: 1,
|
||||
unit_price: 500,
|
||||
},
|
||||
],
|
||||
discount_total: 0,
|
||||
@@ -1895,92 +2051,20 @@ describe("CartService", () => {
|
||||
total: 0,
|
||||
region_id: IdMap.getId("good"),
|
||||
})
|
||||
})
|
||||
|
||||
it("successfully applies valid discount with expiration date to cart", async () => {
|
||||
await cartService.update(IdMap.getId("fr-cart"), {
|
||||
discounts: [
|
||||
{
|
||||
code: "ValidDiscount",
|
||||
},
|
||||
],
|
||||
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledTimes(1)
|
||||
expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledWith({
|
||||
item_id: ["li1", "li2"],
|
||||
})
|
||||
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusService.emit).toHaveBeenCalledWith(
|
||||
"cart.updated",
|
||||
expect.any(Object)
|
||||
|
||||
expect(
|
||||
LineItemAdjustmentServiceMock.createAdjustments
|
||||
).toHaveBeenCalledTimes(1)
|
||||
expect(
|
||||
LineItemAdjustmentServiceMock.createAdjustments
|
||||
).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ id: IdMap.getId("cart") })
|
||||
)
|
||||
|
||||
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 () => {
|
||||
|
||||
@@ -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", () => {
|
||||
const discountConditionRepository = {
|
||||
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", () => {
|
||||
const totalsService = {
|
||||
getTotal: o => {
|
||||
getTotal: (o) => {
|
||||
return o.total || 0
|
||||
},
|
||||
getSubtotal: o => {
|
||||
getSubtotal: (o) => {
|
||||
return o.subtotal || 0
|
||||
},
|
||||
getTaxTotal: o => {
|
||||
getTaxTotal: (o) => {
|
||||
return o.tax_total || 0
|
||||
},
|
||||
getDiscountTotal: o => {
|
||||
getDiscountTotal: (o) => {
|
||||
return o.discount_total || 0
|
||||
},
|
||||
getShippingTotal: o => {
|
||||
getShippingTotal: (o) => {
|
||||
return o.shipping_total || 0
|
||||
},
|
||||
getGiftCardTotal: o => {
|
||||
getGiftCardTotal: (o) => {
|
||||
return o.gift_card_total || 0
|
||||
},
|
||||
}
|
||||
@@ -83,7 +83,7 @@ describe("DraftOrderService", () => {
|
||||
}
|
||||
|
||||
const cartService = {
|
||||
create: jest.fn().mockImplementation(data =>
|
||||
create: jest.fn().mockImplementation((data) =>
|
||||
Promise.resolve({
|
||||
id: "test-cart",
|
||||
...data,
|
||||
@@ -110,16 +110,16 @@ describe("DraftOrderService", () => {
|
||||
}
|
||||
|
||||
const addressRepository = MockRepository({
|
||||
create: addr => ({
|
||||
create: (addr) => ({
|
||||
...addr,
|
||||
}),
|
||||
})
|
||||
|
||||
const draftOrderRepository = MockRepository({
|
||||
create: d => ({
|
||||
create: (d) => ({
|
||||
...d,
|
||||
}),
|
||||
save: d => ({
|
||||
save: (d) => ({
|
||||
id: "test-draft-order",
|
||||
...d,
|
||||
}),
|
||||
@@ -170,7 +170,13 @@ describe("DraftOrderService", () => {
|
||||
"test-variant",
|
||||
"test-region",
|
||||
2,
|
||||
{ metadata: {}, unit_price: undefined }
|
||||
{
|
||||
metadata: {},
|
||||
unit_price: undefined,
|
||||
cart: expect.objectContaining({
|
||||
id: "test-cart",
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
expect(lineItemService.create).toHaveBeenCalledTimes(1)
|
||||
@@ -228,14 +234,14 @@ describe("DraftOrderService", () => {
|
||||
}
|
||||
|
||||
const draftOrderRepository = MockRepository({
|
||||
create: d => ({
|
||||
create: (d) => ({
|
||||
...d,
|
||||
}),
|
||||
save: d => ({
|
||||
save: (d) => ({
|
||||
id: "test-draft-order",
|
||||
...d,
|
||||
}),
|
||||
findOne: q => {
|
||||
findOne: (q) => {
|
||||
switch (q.where.id) {
|
||||
case "completed":
|
||||
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 SwapService from "../swap"
|
||||
import { InventoryServiceMock } from "../__mocks__/inventory"
|
||||
import { LineItemAdjustmentServiceMock } from "../__mocks__/line-item-adjustment"
|
||||
|
||||
const eventBusService = {
|
||||
emit: jest.fn(),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -166,7 +167,7 @@ describe("SwapService", () => {
|
||||
Promise.resolve({ id: "cart", items: [{ id: "test-item" }] })
|
||||
),
|
||||
update: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -178,7 +179,7 @@ describe("SwapService", () => {
|
||||
const customShippingOptionService = {
|
||||
create: jest.fn().mockReturnValue(Promise.resolve({ id: "cso-test" })),
|
||||
update: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -188,7 +189,7 @@ describe("SwapService", () => {
|
||||
update: jest.fn().mockImplementation((d) => Promise.resolve(d)),
|
||||
retrieve: () => Promise.resolve({}),
|
||||
createReturnLines: jest.fn(() => Promise.resolve()),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -200,6 +201,7 @@ describe("SwapService", () => {
|
||||
cartService,
|
||||
lineItemService,
|
||||
customShippingOptionService,
|
||||
lineItemAdjustmentService: LineItemAdjustmentServiceMock,
|
||||
})
|
||||
|
||||
it("finds swap and calls return create cart", async () => {
|
||||
@@ -219,6 +221,7 @@ describe("SwapService", () => {
|
||||
"order.claims",
|
||||
"order.claims.additional_items",
|
||||
"additional_items",
|
||||
"additional_items.variant",
|
||||
"return_order",
|
||||
"return_order.items",
|
||||
"return_order.shipping_method",
|
||||
@@ -251,6 +254,24 @@ describe("SwapService", () => {
|
||||
expect(cartService.create).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).toHaveBeenCalledWith({
|
||||
...existing,
|
||||
@@ -330,7 +351,7 @@ describe("SwapService", () => {
|
||||
const swapRepo = MockRepository()
|
||||
const returnService = {
|
||||
create: jest.fn().mockReturnValue(Promise.resolve({ id: "ret" })),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -431,7 +452,7 @@ describe("SwapService", () => {
|
||||
{ items: [{ item_id: "1234", quantity: 2 }], data: "new" },
|
||||
])
|
||||
),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -452,7 +473,7 @@ describe("SwapService", () => {
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
retrieve: () => Promise.resolve({}),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -541,7 +562,7 @@ describe("SwapService", () => {
|
||||
})
|
||||
}
|
||||
}),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -595,14 +616,14 @@ describe("SwapService", () => {
|
||||
data: "new",
|
||||
})
|
||||
}),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const eventBusService = {
|
||||
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -643,7 +664,7 @@ describe("SwapService", () => {
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
retrieve: () => Promise.resolve({}),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -655,7 +676,7 @@ describe("SwapService", () => {
|
||||
.mockReturnValue(
|
||||
Promise.resolve({ id: "cart", items: [{ id: "test-item" }] })
|
||||
),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -730,7 +751,7 @@ describe("SwapService", () => {
|
||||
|
||||
const eventBusService = {
|
||||
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -745,7 +766,7 @@ describe("SwapService", () => {
|
||||
updateShippingMethod: () => {
|
||||
return Promise.resolve()
|
||||
},
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -760,7 +781,7 @@ describe("SwapService", () => {
|
||||
update: () => {
|
||||
return Promise.resolve()
|
||||
},
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -775,14 +796,14 @@ describe("SwapService", () => {
|
||||
cancelPayment: jest.fn(() => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const inventoryService = {
|
||||
...InventoryServiceMock,
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -904,7 +925,7 @@ describe("SwapService", () => {
|
||||
describe("success", () => {
|
||||
const eventBusService = {
|
||||
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -916,7 +937,7 @@ describe("SwapService", () => {
|
||||
refundPayment: jest.fn((g) =>
|
||||
g[0].id === "good" ? Promise.resolve() : Promise.reject()
|
||||
),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -1044,7 +1065,7 @@ describe("SwapService", () => {
|
||||
|
||||
const eventBusService = {
|
||||
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -1107,7 +1128,7 @@ describe("SwapService", () => {
|
||||
|
||||
const paymentProviderService = {
|
||||
cancelPayment: jest.fn(() => Promise.resolve({})),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
@@ -55,136 +55,180 @@ 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", () => {
|
||||
const container = {
|
||||
taxProviderService: {},
|
||||
taxCalculationStrategy: {},
|
||||
}
|
||||
|
||||
// TODO: Redo tests to include new line item adjustments
|
||||
describe("getAllocationItemDiscounts", () => {
|
||||
let res
|
||||
|
||||
// describe("getAllocationItemDiscounts", () => {
|
||||
// let res
|
||||
const totalsService = new TotalsService(container)
|
||||
|
||||
// const totalsService = new TotalsService(container)
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
// beforeEach(() => {
|
||||
// jest.clearAllMocks()
|
||||
// })
|
||||
it("calculates item with percentage discount", async () => {
|
||||
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 cart = {
|
||||
// items: [
|
||||
// {
|
||||
// id: "test",
|
||||
// allow_discounts: true,
|
||||
// unit_price: 10,
|
||||
// quantity: 10,
|
||||
// variant: {
|
||||
// id: "testv",
|
||||
// product_id: "testp",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// }
|
||||
const discount = {
|
||||
rule: {
|
||||
type: "percentage",
|
||||
value: 10,
|
||||
},
|
||||
}
|
||||
|
||||
// const discount = {
|
||||
// rule: {
|
||||
// type: "percentage",
|
||||
// value: 10,
|
||||
// },
|
||||
// }
|
||||
res = totalsService.getAllocationItemDiscounts(discount, cart)
|
||||
|
||||
// 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([
|
||||
// {
|
||||
// lineItem: {
|
||||
// id: "test",
|
||||
// allow_discounts: true,
|
||||
// unit_price: 10,
|
||||
// quantity: 10,
|
||||
// variant: {
|
||||
// id: "testv",
|
||||
// product_id: "testp",
|
||||
// },
|
||||
// },
|
||||
// variant: "testv",
|
||||
// amount: 10,
|
||||
// },
|
||||
// ])
|
||||
// })
|
||||
it("calculates item with fixed discount", async () => {
|
||||
const cart = {
|
||||
items: [
|
||||
{
|
||||
id: "exists",
|
||||
allow_discounts: true,
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
id: "testv",
|
||||
product_id: "testp",
|
||||
},
|
||||
quantity: 10,
|
||||
adjustments: [{ amount: 90 }],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// it("calculates item with fixed discount", async () => {
|
||||
// const cart = {
|
||||
// items: [
|
||||
// {
|
||||
// id: "exists",
|
||||
// allow_discounts: true,
|
||||
// unit_price: 10,
|
||||
// variant: {
|
||||
// id: "testv",
|
||||
// product_id: "testp",
|
||||
// },
|
||||
// quantity: 10,
|
||||
// },
|
||||
// ],
|
||||
// }
|
||||
const discount = {
|
||||
rule: {
|
||||
type: "fixed",
|
||||
value: 9,
|
||||
// TODO: Add conditions relation
|
||||
},
|
||||
}
|
||||
|
||||
// const discount = {
|
||||
// rule: {
|
||||
// type: "fixed",
|
||||
// value: 9,
|
||||
// },
|
||||
// }
|
||||
res = totalsService.getAllocationItemDiscounts(discount, cart)
|
||||
|
||||
// res = totalsService.getAllocationItemDiscounts(discount, cart)
|
||||
expect(res).toEqual([
|
||||
{
|
||||
lineItem: {
|
||||
id: "exists",
|
||||
allow_discounts: true,
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
id: "testv",
|
||||
product_id: "testp",
|
||||
},
|
||||
quantity: 10,
|
||||
adjustments: [{ amount: 90 }],
|
||||
},
|
||||
variant: "testv",
|
||||
amount: 90,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
// expect(res).toEqual([
|
||||
// {
|
||||
// lineItem: {
|
||||
// id: "exists",
|
||||
// allow_discounts: true,
|
||||
// unit_price: 10,
|
||||
// variant: {
|
||||
// id: "testv",
|
||||
// product_id: "testp",
|
||||
// },
|
||||
// quantity: 10,
|
||||
// },
|
||||
// variant: "testv",
|
||||
// amount: 90,
|
||||
// },
|
||||
// ])
|
||||
// })
|
||||
// not relevant anymore
|
||||
// it("does not apply discount if no valid variants are provided", async () => {
|
||||
// const cart = {
|
||||
// items: [
|
||||
// {
|
||||
// id: "exists",
|
||||
// allow_discounts: true,
|
||||
// unit_price: 10,
|
||||
// variant: {
|
||||
// id: "testv",
|
||||
// product_id: "testp",
|
||||
// },
|
||||
// quantity: 10,
|
||||
// },
|
||||
// ],
|
||||
// }
|
||||
|
||||
// it("does not apply discount if no valid variants are provided", async () => {
|
||||
// const cart = {
|
||||
// items: [
|
||||
// {
|
||||
// id: "exists",
|
||||
// allow_discounts: true,
|
||||
// unit_price: 10,
|
||||
// variant: {
|
||||
// id: "testv",
|
||||
// product_id: "testp",
|
||||
// },
|
||||
// quantity: 10,
|
||||
// },
|
||||
// ],
|
||||
// }
|
||||
// const discount = {
|
||||
// rule: {
|
||||
// type: "fixed",
|
||||
// value: 9,
|
||||
// // TODO: Add conditions relation
|
||||
// },
|
||||
// }
|
||||
// res = totalsService.getAllocationItemDiscounts(discount, cart)
|
||||
|
||||
// const discount = {
|
||||
// rule: {
|
||||
// type: "fixed",
|
||||
// value: 9,
|
||||
// },
|
||||
// }
|
||||
// res = totalsService.getAllocationItemDiscounts(discount, cart)
|
||||
|
||||
// expect(res).toEqual([])
|
||||
// })
|
||||
// })
|
||||
// expect(res).toEqual([])
|
||||
// })
|
||||
})
|
||||
|
||||
describe("getDiscountTotal", () => {
|
||||
let res
|
||||
@@ -223,32 +267,36 @@ describe("TotalsService", () => {
|
||||
discountCart.discounts = []
|
||||
})
|
||||
|
||||
it("calculate total precentage discount", async () => {
|
||||
it("calculate total percentage discount", async () => {
|
||||
discountCart.discounts.push(discounts.total10Percent)
|
||||
res = totalsService.getDiscountTotal(discountCart)
|
||||
let cart = applyDiscount(discountCart, discounts.total10Percent)
|
||||
res = totalsService.getDiscountTotal(cart)
|
||||
|
||||
expect(res).toEqual(28)
|
||||
})
|
||||
|
||||
// TODO: Redo tests to include new line item adjustments
|
||||
|
||||
// it("calculate item fixed discount", async () => {
|
||||
// discountCart.discounts.push(discounts.item2Fixed)
|
||||
// res = totalsService.getDiscountTotal(discountCart)
|
||||
it("calculate item fixed discount", async () => {
|
||||
discountCart.discounts.push(discounts.item2Fixed)
|
||||
let cart = applyDiscount(discountCart, discounts.item2Fixed)
|
||||
res = totalsService.getDiscountTotal(cart)
|
||||
|
||||
// expect(res).toEqual(20)
|
||||
// })
|
||||
expect(res).toEqual(40)
|
||||
})
|
||||
|
||||
// it("calculate item percentage discount", async () => {
|
||||
// discountCart.discounts.push(discounts.item10Percent)
|
||||
// res = totalsService.getDiscountTotal(discountCart)
|
||||
it("calculate item percentage discount", async () => {
|
||||
discountCart.discounts.push(discounts.item10Percent)
|
||||
let cart = applyDiscount(discountCart, discounts.item10Percent)
|
||||
res = totalsService.getDiscountTotal(cart)
|
||||
|
||||
// expect(res).toEqual(10)
|
||||
// })
|
||||
expect(res).toEqual(28)
|
||||
})
|
||||
|
||||
it("calculate total fixed discount", async () => {
|
||||
discountCart.discounts.push(discounts.total10Fixed)
|
||||
res = totalsService.getDiscountTotal(discountCart)
|
||||
let cart = applyDiscount(discountCart, discounts.total10Fixed)
|
||||
res = totalsService.getDiscountTotal(cart)
|
||||
|
||||
expect(res).toEqual(10)
|
||||
})
|
||||
@@ -388,43 +436,45 @@ describe("TotalsService", () => {
|
||||
// expect(res).toEqual(1244)
|
||||
// })
|
||||
|
||||
// it("calculates refund with item fixed discount", async () => {
|
||||
// orderToRefund.discounts.push(discounts.item2Fixed)
|
||||
// res = totalsService.getRefundTotal(orderToRefund, [
|
||||
// {
|
||||
// id: "line2",
|
||||
// unit_price: 100,
|
||||
// allow_discounts: true,
|
||||
// variant: {
|
||||
// id: "variant",
|
||||
// product_id: "testp2",
|
||||
// },
|
||||
// quantity: 10,
|
||||
// returned_quantity: 0,
|
||||
// },
|
||||
// ])
|
||||
it("calculates refund with item fixed discount", async () => {
|
||||
orderToRefund.discounts.push(discounts.item2Fixed)
|
||||
let order = applyDiscount(orderToRefund, discounts.item2Fixed)
|
||||
res = totalsService.getRefundTotal(order, [
|
||||
{
|
||||
id: "line2",
|
||||
unit_price: 100,
|
||||
allow_discounts: true,
|
||||
variant: {
|
||||
id: "variant",
|
||||
product_id: "testp2",
|
||||
},
|
||||
quantity: 10,
|
||||
returned_quantity: 0,
|
||||
},
|
||||
])
|
||||
|
||||
// expect(res).toEqual(1225)
|
||||
// })
|
||||
expect(res).toEqual(1225)
|
||||
})
|
||||
|
||||
// it("calculates refund with item percentage discount", async () => {
|
||||
// orderToRefund.discounts.push(discounts.item10Percent)
|
||||
// res = totalsService.getRefundTotal(orderToRefund, [
|
||||
// {
|
||||
// id: "line2",
|
||||
// unit_price: 100,
|
||||
// allow_discounts: true,
|
||||
// variant: {
|
||||
// id: "variant",
|
||||
// product_id: "testp2",
|
||||
// },
|
||||
// quantity: 10,
|
||||
// returned_quantity: 0,
|
||||
// },
|
||||
// ])
|
||||
it("calculates refund with item percentage discount", async () => {
|
||||
orderToRefund.discounts.push(discounts.item10Percent)
|
||||
let order = applyDiscount(orderToRefund, discounts.item10Percent)
|
||||
res = totalsService.getRefundTotal(order, [
|
||||
{
|
||||
id: "line2",
|
||||
unit_price: 100,
|
||||
allow_discounts: true,
|
||||
variant: {
|
||||
id: "variant",
|
||||
product_id: "testp2",
|
||||
},
|
||||
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 () => {
|
||||
const work = () =>
|
||||
@@ -444,6 +494,7 @@ describe("TotalsService", () => {
|
||||
expect(work).toThrow("Line item does not exist on order")
|
||||
})
|
||||
})
|
||||
|
||||
describe("getShippingTotal", () => {
|
||||
let res
|
||||
const totalsService = new TotalsService(container)
|
||||
|
||||
@@ -21,12 +21,10 @@ import {
|
||||
LineItemUpdate,
|
||||
} from "../types/cart"
|
||||
import { FindConfig, TotalField } from "../types/common"
|
||||
import CustomShippingOptionService from "./custom-shipping-option"
|
||||
import CustomerService from "./customer"
|
||||
import DiscountService from "./discount"
|
||||
import EventBusService from "./event-bus"
|
||||
import GiftCardService from "./gift-card"
|
||||
import InventoryService from "./inventory"
|
||||
import LineItemService from "./line-item"
|
||||
import PaymentProviderService from "./payment-provider"
|
||||
import ProductService from "./product"
|
||||
@@ -35,6 +33,9 @@ import RegionService from "./region"
|
||||
import ShippingOptionService from "./shipping-option"
|
||||
import TaxProviderService from "./tax-provider"
|
||||
import TotalsService from "./totals"
|
||||
import InventoryService from "./inventory"
|
||||
import CustomShippingOptionService from "./custom-shipping-option"
|
||||
import LineItemAdjustmentService from "./line-item-adjustment"
|
||||
|
||||
type CartConstructorProps = {
|
||||
manager: EntityManager
|
||||
@@ -56,6 +57,7 @@ type CartConstructorProps = {
|
||||
totalsService: TotalsService
|
||||
inventoryService: InventoryService
|
||||
customShippingOptionService: CustomShippingOptionService
|
||||
lineItemAdjustmentService: LineItemAdjustmentService
|
||||
priceSelectionStrategy: IPriceSelectionStrategy
|
||||
}
|
||||
|
||||
@@ -92,6 +94,7 @@ class CartService extends BaseService {
|
||||
private paymentSessionRepository_: typeof PaymentSessionRepository
|
||||
private inventoryService_: InventoryService
|
||||
private customShippingOptionService_: CustomShippingOptionService
|
||||
private lineItemAdjustmentService_: LineItemAdjustmentService
|
||||
private priceSelectionStrategy_: IPriceSelectionStrategy
|
||||
|
||||
constructor({
|
||||
@@ -114,6 +117,7 @@ class CartService extends BaseService {
|
||||
paymentSessionRepository,
|
||||
inventoryService,
|
||||
customShippingOptionService,
|
||||
lineItemAdjustmentService,
|
||||
priceSelectionStrategy,
|
||||
}: CartConstructorProps) {
|
||||
super()
|
||||
@@ -137,6 +141,7 @@ class CartService extends BaseService {
|
||||
this.inventoryService_ = inventoryService
|
||||
this.customShippingOptionService_ = customShippingOptionService
|
||||
this.taxProviderService_ = taxProviderService
|
||||
this.lineItemAdjustmentService_ = lineItemAdjustmentService
|
||||
this.priceSelectionStrategy_ = priceSelectionStrategy
|
||||
}
|
||||
|
||||
@@ -165,6 +170,7 @@ class CartService extends BaseService {
|
||||
giftCardService: this.giftCardService_,
|
||||
inventoryService: this.inventoryService_,
|
||||
customShippingOptionService: this.customShippingOptionService_,
|
||||
lineItemAdjustmentService: this.lineItemAdjustmentService_,
|
||||
priceSelectionStrategy: this.priceSelectionStrategy_,
|
||||
})
|
||||
|
||||
@@ -457,7 +463,12 @@ class CartService extends BaseService {
|
||||
|
||||
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
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
@@ -512,9 +523,12 @@ class CartService extends BaseService {
|
||||
relations: [
|
||||
"shipping_methods",
|
||||
"items",
|
||||
"items.adjustments",
|
||||
"payment_sessions",
|
||||
"items.variant",
|
||||
"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_
|
||||
.withTransaction(manager)
|
||||
.emit(CartService.Events.UPDATED, result)
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
@@ -592,7 +612,7 @@ class CartService extends BaseService {
|
||||
): Promise<Cart> {
|
||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||
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
|
||||
@@ -621,8 +641,13 @@ class CartService extends BaseService {
|
||||
.withTransaction(manager)
|
||||
.update(lineItemId, lineItemUpdate)
|
||||
|
||||
const result = await this.retrieve(cartId, {
|
||||
relations: ["items", "discounts", "discounts.rule"],
|
||||
})
|
||||
|
||||
await this.refreshAdjustments_(result)
|
||||
|
||||
// Update the line item
|
||||
const result = await this.retrieve(cartId)
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(CartService.Events.UPDATED, result)
|
||||
@@ -1004,109 +1029,51 @@ class CartService extends BaseService {
|
||||
* @return the result of the update operation
|
||||
*/
|
||||
async applyDiscount(cart: Cart, discountCode: string): Promise<void> {
|
||||
const discount = await this.discountService_.retrieveByCode(discountCode, [
|
||||
"rule",
|
||||
"regions",
|
||||
])
|
||||
|
||||
if (cart.customer_id) {
|
||||
const canApply = await this.discountService_.canApplyForCustomer(
|
||||
discount.rule.id,
|
||||
cart.customer_id
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const discount = await this.discountService_.retrieveByCode(
|
||||
discountCode,
|
||||
["rule", "regions"]
|
||||
)
|
||||
|
||||
if (!canApply) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Discount is not valid for customer"
|
||||
)
|
||||
await this.discountService_.validateDiscountForCartOrThrow(cart, discount)
|
||||
|
||||
const rule = discount.rule
|
||||
|
||||
// if discount is already there, we simply resolve
|
||||
if (cart.discounts.find(({ id }) => id === discount.id)) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
const rule = discount.rule
|
||||
const toParse = [...cart.discounts, discount]
|
||||
|
||||
// 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 (cart.discounts.find(({ id }) => id === discount.id)) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const toParse = [...cart.discounts, discount]
|
||||
|
||||
let sawNotShipping = false
|
||||
const newDiscounts = toParse.map((d) => {
|
||||
const drule = d.rule
|
||||
switch (drule.type) {
|
||||
case "free_shipping":
|
||||
if (d.rule.type === rule.type) {
|
||||
return discount
|
||||
}
|
||||
return d
|
||||
default:
|
||||
if (!sawNotShipping) {
|
||||
sawNotShipping = true
|
||||
if (rule.type !== "free_shipping") {
|
||||
let sawNotShipping = false
|
||||
const newDiscounts = toParse.map((d) => {
|
||||
const drule = d.rule
|
||||
switch (drule.type) {
|
||||
case "free_shipping":
|
||||
if (d.rule.type === rule.type) {
|
||||
return discount
|
||||
}
|
||||
return d
|
||||
}
|
||||
return null
|
||||
default:
|
||||
if (!sawNotShipping) {
|
||||
sawNotShipping = true
|
||||
if (rule.type !== "free_shipping") {
|
||||
return discount
|
||||
}
|
||||
return d
|
||||
}
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
cart.discounts = newDiscounts.filter(Boolean) as Discount[]
|
||||
|
||||
// ignore if free shipping
|
||||
if (rule.type !== "free_shipping" && cart?.items) {
|
||||
await this.refreshAdjustments_(cart)
|
||||
}
|
||||
})
|
||||
|
||||
cart.discounts = newDiscounts.filter(Boolean) as Discount[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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.
|
||||
* @param cartId - the cart to delete metadata from.
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
UpdateDiscountInput,
|
||||
UpsertDiscountConditionInput,
|
||||
} from "../types/discount"
|
||||
import { isFuture, isPast } from "../utils/date-helpers"
|
||||
import { formatException, PostgresError } from "../utils/exception-formatter"
|
||||
|
||||
/**
|
||||
@@ -735,6 +736,99 @@ class DiscountService extends BaseService {
|
||||
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(
|
||||
discountRuleId: string,
|
||||
customerId: string | undefined
|
||||
|
||||
@@ -290,6 +290,7 @@ class DraftOrderService extends BaseService {
|
||||
.generate(item.variant_id, data.region_id, item.quantity, {
|
||||
metadata: item?.metadata || {},
|
||||
unit_price: item.unit_price,
|
||||
cart: createdCart,
|
||||
})
|
||||
|
||||
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,
|
||||
regionService,
|
||||
cartRepository,
|
||||
lineItemAdjustmentService,
|
||||
}) {
|
||||
super()
|
||||
|
||||
@@ -37,6 +38,8 @@ class LineItemService extends BaseService {
|
||||
|
||||
/** @private @const {CartRepository} */
|
||||
this.cartRepository_ = cartRepository
|
||||
|
||||
this.lineItemAdjustmentService_ = lineItemAdjustmentService
|
||||
}
|
||||
|
||||
withTransaction(transactionManager) {
|
||||
@@ -52,6 +55,7 @@ class LineItemService extends BaseService {
|
||||
productService: this.productService_,
|
||||
regionService: this.regionService_,
|
||||
cartRepository: this.cartRepository_,
|
||||
lineItemAdjustmentService: this.lineItemAdjustmentService_,
|
||||
})
|
||||
|
||||
cloned.transactionManager_ = transactionManager
|
||||
@@ -131,13 +135,21 @@ class LineItemService extends BaseService {
|
||||
})
|
||||
}),
|
||||
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)
|
||||
}
|
||||
|
||||
async generate(variantId, regionId, quantity, config = {}) {
|
||||
async generate(variantId, regionId, quantity, context = {}) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const variant = await this.productVariantService_
|
||||
.withTransaction(manager)
|
||||
@@ -153,14 +165,14 @@ class LineItemService extends BaseService {
|
||||
let price
|
||||
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
|
||||
// and we choose to not merge the items
|
||||
shouldMerge = false
|
||||
if (config.unit_price < 0) {
|
||||
if (context.unit_price < 0) {
|
||||
price = 0
|
||||
} else {
|
||||
price = config.unit_price
|
||||
price = context.unit_price
|
||||
}
|
||||
} else {
|
||||
price = await this.productVariantService_
|
||||
@@ -168,7 +180,7 @@ class LineItemService extends BaseService {
|
||||
.getRegionPrice(variant.id, {
|
||||
regionId: region.id,
|
||||
quantity: quantity,
|
||||
customer_id: config.customer_id,
|
||||
customer_id: context.customer_id,
|
||||
include_discount_prices: true,
|
||||
})
|
||||
}
|
||||
@@ -182,10 +194,17 @@ class LineItemService extends BaseService {
|
||||
quantity: quantity || 1,
|
||||
allow_discounts: variant.product.discountable,
|
||||
is_giftcard: variant.product.is_giftcard,
|
||||
metadata: config?.metadata || {},
|
||||
metadata: context?.metadata || {},
|
||||
should_merge: shouldMerge,
|
||||
}
|
||||
|
||||
if (context.cart) {
|
||||
const adjustments = await this.lineItemAdjustmentService_
|
||||
.withTransaction(manager)
|
||||
.generateAdjustments(context.cart, toCreate, { variant })
|
||||
toCreate.adjustments = adjustments
|
||||
}
|
||||
|
||||
return toCreate
|
||||
})
|
||||
}
|
||||
|
||||
@@ -298,12 +298,15 @@ class OrderService extends BaseService {
|
||||
const relationSet = new Set(relations)
|
||||
relationSet.add("items")
|
||||
relationSet.add("items.tax_lines")
|
||||
relationSet.add("items.adjustments")
|
||||
relationSet.add("swaps")
|
||||
relationSet.add("swaps.additional_items")
|
||||
relationSet.add("swaps.additional_items.tax_lines")
|
||||
relationSet.add("swaps.additional_items.adjustments")
|
||||
relationSet.add("claims")
|
||||
relationSet.add("claims.additional_items")
|
||||
relationSet.add("claims.additional_items.tax_lines")
|
||||
relationSet.add("claims.additional_items.adjustments")
|
||||
relationSet.add("discounts")
|
||||
relationSet.add("discounts.rule")
|
||||
relationSet.add("gift_cards")
|
||||
|
||||
@@ -33,6 +33,7 @@ class SwapService extends BaseService {
|
||||
orderService,
|
||||
inventoryService,
|
||||
customShippingOptionService,
|
||||
lineItemAdjustmentService,
|
||||
}) {
|
||||
super()
|
||||
|
||||
@@ -77,6 +78,9 @@ class SwapService extends BaseService {
|
||||
|
||||
/** @private @const {CustomShippingOptionService} */
|
||||
this.customShippingOptionService_ = customShippingOptionService
|
||||
|
||||
/** @private @const {LineItemAdjustmentService} */
|
||||
this.lineItemAdjustmentService_ = lineItemAdjustmentService
|
||||
}
|
||||
|
||||
withTransaction(transactionManager) {
|
||||
@@ -99,6 +103,7 @@ class SwapService extends BaseService {
|
||||
inventoryService: this.inventoryService_,
|
||||
fulfillmentService: this.fulfillmentService_,
|
||||
customShippingOptionService: this.customShippingOptionService_,
|
||||
lineItemAdjustmentService: this.lineItemAdjustmentService_,
|
||||
})
|
||||
|
||||
cloned.transactionManager_ = transactionManager
|
||||
@@ -549,6 +554,7 @@ class SwapService extends BaseService {
|
||||
"order.claims",
|
||||
"order.claims.additional_items",
|
||||
"additional_items",
|
||||
"additional_items.variant",
|
||||
"return_order",
|
||||
"return_order.items",
|
||||
"return_order.shipping_method",
|
||||
@@ -605,6 +611,10 @@ class SwapService extends BaseService {
|
||||
await this.lineItemService_.withTransaction(manager).update(item.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
|
||||
|
||||
@@ -200,8 +200,6 @@ class TotalsService extends BaseService {
|
||||
calculationContext
|
||||
)
|
||||
|
||||
console.log(orderLines)
|
||||
|
||||
taxLines = orderLines.filter((ol) => {
|
||||
if ("shipping_method_id" in ol) {
|
||||
return ol.shipping_method_id === shippingMethod.id
|
||||
@@ -465,6 +463,7 @@ class TotalsService extends BaseService {
|
||||
const discountAmount =
|
||||
(allocationMap[lineItem.id]?.discount?.unit_amount || 0) *
|
||||
lineItem.quantity
|
||||
|
||||
const lineSubtotal =
|
||||
lineItem.unit_price * lineItem.quantity - discountAmount
|
||||
|
||||
@@ -589,11 +588,46 @@ class TotalsService extends BaseService {
|
||||
discount: Discount,
|
||||
cart: Cart | Order
|
||||
): LineDiscount[] {
|
||||
const discounts: LineDiscount[] = []
|
||||
// TODO: Add line item adjustments
|
||||
const discounts: LineDiscount[] = cart.items.map((item) => ({
|
||||
lineItem: item,
|
||||
variant: item.variant.id,
|
||||
amount: this.getLineItemDiscountAdjustment(item, discount),
|
||||
}))
|
||||
|
||||
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.
|
||||
* @param cartOrOrder - the cart or order to get line discount allocations for
|
||||
@@ -605,10 +639,6 @@ class TotalsService extends BaseService {
|
||||
cartOrOrder: Cart | Order,
|
||||
discount: Discount
|
||||
): LineDiscountAmount[] {
|
||||
const subtotal = this.getSubtotal(cartOrOrder, {
|
||||
excludeNonDiscounts: true,
|
||||
})
|
||||
|
||||
let merged: LineItem[] = [...cartOrOrder.items]
|
||||
|
||||
// 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) => {
|
||||
const lineTotal = item.unit_price * item.quantity
|
||||
|
||||
return {
|
||||
item,
|
||||
amount: item.allow_discounts ? lineTotal * percentage : 0,
|
||||
}
|
||||
})
|
||||
} else if (allocation === "item") {
|
||||
const allocationDiscounts = this.getAllocationItemDiscounts(
|
||||
discount,
|
||||
cartOrOrder
|
||||
return merged.map((item) => {
|
||||
const adjustments = item?.adjustments || []
|
||||
const discountAdjustments = adjustments.filter(
|
||||
(adjustment) => adjustment.discount_id === discount.id
|
||||
)
|
||||
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 }))
|
||||
return {
|
||||
item,
|
||||
amount: item.allow_discounts
|
||||
? discountAdjustments.reduce(
|
||||
(total, adjustment) => total + adjustment.amount,
|
||||
0
|
||||
)
|
||||
: 0,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -865,32 +874,13 @@ class TotalsService extends BaseService {
|
||||
return 0
|
||||
}
|
||||
|
||||
const { type, allocation, value } = discount.rule
|
||||
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)
|
||||
}
|
||||
const discountTotal = this.getLineItemAdjustmentsTotal(cartOrOrder)
|
||||
|
||||
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