feat(medusa): Performance improvements of Carts domain (#2648)
**What** I have created a new method on the cart service which is `addLineItems`, allowing a user to add one or multiple items in an optimized way. Also updated the `generate` method from the line item service which now also accept a object data or a collection of data which. Various places have been optimized and cache support has been added to the price selection strategy. The overall optimization allows to reach another 9000% improvement in the response time as a median (Creating a cart with 6 items): | | Min (ms) | Median (ms) | Max (ms) | Median Improvement (%) |---|:-:|---|---|---| | Before optimisation | 1200 | 9999 | 12698 | N/A | After optimisation | 63 | 252 | 500 | 39x | After re optimisation | 56 | 82 | 399 | 121x | After including addressed feedback | 65 | 202 | 495 | 49x FIXES CORE-722
This commit is contained in:
1 parent
3b2c929408
commit
42d9c7222b
71 files changed
+1204
-439
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Feat(medusa): Optimize the cart creation with time line and therefore the response time
|
||||||
@@ -29,7 +29,7 @@ describe("/admin/collections", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd, verbose: true })
|
medusaProcess = await setupServer({ cwd })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ describe("/admin/draft-orders", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd, verbose: true })
|
medusaProcess = await setupServer({ cwd })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/draft-orders", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ describe("[MEDUSA_FF_ORDER_EDITING] /admin/order-edits", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_ORDER_EDITING: true },
|
env: { MEDUSA_FF_ORDER_EDITING: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ const adminSeeder = require("../../../helpers/admin-seeder")
|
|||||||
const {
|
const {
|
||||||
simpleRegionFactory,
|
simpleRegionFactory,
|
||||||
simpleShippingOptionFactory,
|
simpleShippingOptionFactory,
|
||||||
simpleOrderFactory
|
simpleOrderFactory,
|
||||||
} = require("../../../factories");
|
} = require("../../../factories")
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
@@ -24,7 +24,6 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/orders", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
@@ -50,7 +49,7 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/orders", () => {
|
|||||||
country_code: "us",
|
country_code: "us",
|
||||||
}
|
}
|
||||||
const region = await simpleRegionFactory(dbConnection, {
|
const region = await simpleRegionFactory(dbConnection, {
|
||||||
id: "test-region"
|
id: "test-region",
|
||||||
})
|
})
|
||||||
order = await simpleOrderFactory(dbConnection, {
|
order = await simpleOrderFactory(dbConnection, {
|
||||||
id: "test-order",
|
id: "test-order",
|
||||||
@@ -58,16 +57,19 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/orders", () => {
|
|||||||
shipping_address: shippingAddress,
|
shipping_address: shippingAddress,
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
})
|
})
|
||||||
includesTaxShippingOption = await simpleShippingOptionFactory(dbConnection, {
|
includesTaxShippingOption = await simpleShippingOptionFactory(
|
||||||
includes_tax: true,
|
dbConnection,
|
||||||
region_id: region.id
|
{
|
||||||
})
|
includes_tax: true,
|
||||||
|
region_id: region.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async() => {
|
afterEach(async () => {
|
||||||
const db = useDb()
|
const db = useDb()
|
||||||
return await db.teardown()
|
return await db.teardown()
|
||||||
})
|
})
|
||||||
@@ -76,26 +78,27 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/orders", () => {
|
|||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
const orderWithShippingMethodRes = await api.post(
|
const orderWithShippingMethodRes = await api.post(
|
||||||
`/admin/orders/${order.id}/shipping-methods`,
|
`/admin/orders/${order.id}/shipping-methods`,
|
||||||
{
|
{
|
||||||
option_id: includesTaxShippingOption.id,
|
option_id: includesTaxShippingOption.id,
|
||||||
price: 10,
|
price: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test_token",
|
||||||
},
|
},
|
||||||
{
|
}
|
||||||
headers: {
|
|
||||||
Authorization: "Bearer test_token",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(orderWithShippingMethodRes.status).toEqual(200)
|
expect(orderWithShippingMethodRes.status).toEqual(200)
|
||||||
expect(orderWithShippingMethodRes.data.order.shipping_methods)
|
expect(orderWithShippingMethodRes.data.order.shipping_methods).toEqual(
|
||||||
.toEqual(expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
shipping_option_id: includesTaxShippingOption.id,
|
shipping_option_id: includesTaxShippingOption.id,
|
||||||
includes_tax: true,
|
includes_tax: true,
|
||||||
})
|
}),
|
||||||
]))
|
])
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -33,7 +33,7 @@ describe("/admin/orders", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd, verbose: false })
|
medusaProcess = await setupServer({ cwd })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ describe("[MEDUSA_FF_ORDER_EDITING] /admin/payment-collections", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_ORDER_EDITING: true },
|
env: { MEDUSA_FF_ORDER_EDITING: true },
|
||||||
verbose: true,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ describe("[MEDUSA_FF_ORDER_EDITING] /admin/payment", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_ORDER_EDITING: true },
|
env: { MEDUSA_FF_ORDER_EDITING: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
|
|||||||
@@ -1415,7 +1415,6 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/price-lists", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ describe("/admin/products", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd, verbose: false })
|
medusaProcess = await setupServer({
|
||||||
|
cwd,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ describe("[MEDUSA_FF_PUBLISHABLE_API_KEYS] Publishable API keys", () => {
|
|||||||
MEDUSA_FF_PUBLISHABLE_API_KEYS: true,
|
MEDUSA_FF_PUBLISHABLE_API_KEYS: true,
|
||||||
MEDUSA_FF_SALES_CHANNELS: true,
|
MEDUSA_FF_SALES_CHANNELS: true,
|
||||||
},
|
},
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ const path = require("path")
|
|||||||
const { Region } = require("@medusajs/medusa")
|
const { Region } = require("@medusajs/medusa")
|
||||||
|
|
||||||
const setupServer = require("../../../helpers/setup-server")
|
const setupServer = require("../../../helpers/setup-server")
|
||||||
const startServerWithEnvironment = require("../../../helpers/start-server-with-environment").default
|
const startServerWithEnvironment =
|
||||||
|
require("../../../helpers/start-server-with-environment").default
|
||||||
const { useApi } = require("../../../helpers/use-api")
|
const { useApi } = require("../../../helpers/use-api")
|
||||||
const { initDb, useDb } = require("../../../helpers/use-db")
|
const { initDb, useDb } = require("../../../helpers/use-db")
|
||||||
const adminSeeder = require("../../helpers/admin-seeder")
|
const adminSeeder = require("../../helpers/admin-seeder")
|
||||||
const { simpleRegionFactory } = require("../../factories");
|
const { simpleRegionFactory } = require("../../factories")
|
||||||
|
|
||||||
const adminReqConfig = {
|
const adminReqConfig = {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -304,7 +305,6 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/regions", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
@@ -364,7 +364,7 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/regions", () => {
|
|||||||
name: "region-including-taxes",
|
name: "region-including-taxes",
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
});
|
})
|
||||||
|
|
||||||
it("should allow to update a region that includes tax", async function () {
|
it("should allow to update a region that includes tax", async function () {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
@@ -376,17 +376,19 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/regions", () => {
|
|||||||
|
|
||||||
expect(response.data.region.includes_tax).toBe(false)
|
expect(response.data.region.includes_tax).toBe(false)
|
||||||
|
|
||||||
response = await api.post(
|
response = await api
|
||||||
`/admin/regions/${region1TaxInclusiveId}`,
|
.post(
|
||||||
{
|
`/admin/regions/${region1TaxInclusiveId}`,
|
||||||
includes_tax: true,
|
{
|
||||||
},
|
includes_tax: true,
|
||||||
adminReqConfig,
|
},
|
||||||
).catch((err) => {
|
adminReqConfig
|
||||||
console.log(err)
|
)
|
||||||
})
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.data.region.includes_tax).toBe(true)
|
expect(response.data.region.includes_tax).toBe(true)
|
||||||
});
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -15,7 +15,7 @@ describe("/admin/return-reasons", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd, verbose: false })
|
medusaProcess = await setupServer({ cwd })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ describe("sales channels", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_SALES_CHANNELS: true },
|
env: { MEDUSA_FF_SALES_CHANNELS: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
const path = require("path")
|
const path = require("path")
|
||||||
const {
|
const { ShippingProfile } = require("@medusajs/medusa")
|
||||||
ShippingProfile,
|
|
||||||
} = require("@medusajs/medusa")
|
|
||||||
|
|
||||||
const setupServer = require("../../../helpers/setup-server")
|
const setupServer = require("../../../helpers/setup-server")
|
||||||
const startServerWithEnvironment = require("../../../helpers/start-server-with-environment").default
|
const startServerWithEnvironment =
|
||||||
|
require("../../../helpers/start-server-with-environment").default
|
||||||
const { useApi } = require("../../../helpers/use-api")
|
const { useApi } = require("../../../helpers/use-api")
|
||||||
const { initDb, useDb } = require("../../../helpers/use-db")
|
const { initDb, useDb } = require("../../../helpers/use-db")
|
||||||
const adminSeeder = require("../../helpers/admin-seeder")
|
const adminSeeder = require("../../helpers/admin-seeder")
|
||||||
const shippingOptionSeeder = require("../../helpers/shipping-option-seeder")
|
const shippingOptionSeeder = require("../../helpers/shipping-option-seeder")
|
||||||
const { simpleShippingOptionFactory, simpleRegionFactory } = require("../../factories")
|
const {
|
||||||
|
simpleShippingOptionFactory,
|
||||||
|
simpleRegionFactory,
|
||||||
|
} = require("../../factories")
|
||||||
|
|
||||||
const adminReqConfig = {
|
const adminReqConfig = {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -475,7 +477,6 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/shipping-options", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
@@ -517,9 +518,12 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/shipping-options", () => {
|
|||||||
it("should creates a shipping option that includes tax", async () => {
|
it("should creates a shipping option that includes tax", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
const defaultProfile = await dbConnection.manager.findOne(ShippingProfile, {
|
const defaultProfile = await dbConnection.manager.findOne(
|
||||||
type: "default",
|
ShippingProfile,
|
||||||
})
|
{
|
||||||
|
type: "default",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
name: "Test option",
|
name: "Test option",
|
||||||
@@ -551,7 +555,10 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/shipping-options", () => {
|
|||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
let response = await api
|
let response = await api
|
||||||
.get(`/admin/shipping-options/${shippingOptionIncludesTaxId}`, adminReqConfig)
|
.get(
|
||||||
|
`/admin/shipping-options/${shippingOptionIncludesTaxId}`,
|
||||||
|
adminReqConfig
|
||||||
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
})
|
})
|
||||||
@@ -573,7 +580,11 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/shipping-options", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
response = await api
|
response = await api
|
||||||
.post(`/admin/shipping-options/${shippingOptionIncludesTaxId}`, payload, adminReqConfig)
|
.post(
|
||||||
|
`/admin/shipping-options/${shippingOptionIncludesTaxId}`,
|
||||||
|
payload,
|
||||||
|
adminReqConfig
|
||||||
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -363,7 +363,6 @@ describe("/admin/swaps", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ describe("/admin/users", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd, verbose: false })
|
medusaProcess = await setupServer({ cwd })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const path = require("path")
|
const path = require("path")
|
||||||
const fs = require("fs/promises")
|
const fs = require("fs/promises")
|
||||||
import { sep, resolve } from "path"
|
import { resolve, sep } from "path"
|
||||||
|
|
||||||
const setupServer = require("../../../../helpers/setup-server")
|
const setupServer = require("../../../../helpers/setup-server")
|
||||||
const { useApi } = require("../../../../helpers/use-api")
|
const { useApi } = require("../../../../helpers/use-api")
|
||||||
@@ -31,7 +31,6 @@ describe("Batchjob with type order-export", () => {
|
|||||||
cwd,
|
cwd,
|
||||||
redisUrl: "redis://127.0.0.1:6379",
|
redisUrl: "redis://127.0.0.1:6379",
|
||||||
uploadDir: __dirname,
|
uploadDir: __dirname,
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ describe("Price list import batch job", () => {
|
|||||||
cwd,
|
cwd,
|
||||||
redisUrl: "redis://127.0.0.1:6379",
|
redisUrl: "redis://127.0.0.1:6379",
|
||||||
uploadDir: __dirname,
|
uploadDir: __dirname,
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const adminReqConfig = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
jest.setTimeout(100000000)
|
jest.setTimeout(180000)
|
||||||
|
|
||||||
describe("Batch job of product-export type", () => {
|
describe("Batch job of product-export type", () => {
|
||||||
let medusaProcess
|
let medusaProcess
|
||||||
@@ -31,7 +31,6 @@ describe("Batch job of product-export type", () => {
|
|||||||
cwd,
|
cwd,
|
||||||
redisUrl: "redis://127.0.0.1:6379",
|
redisUrl: "redis://127.0.0.1:6379",
|
||||||
uploadDir: __dirname,
|
uploadDir: __dirname,
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ describe("Product import - Sales Channel", () => {
|
|||||||
env: { MEDUSA_FF_SALES_CHANNELS: true },
|
env: { MEDUSA_FF_SALES_CHANNELS: true },
|
||||||
redisUrl: "redis://127.0.0.1:6379",
|
redisUrl: "redis://127.0.0.1:6379",
|
||||||
uploadDir: __dirname,
|
uploadDir: __dirname,
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ describe("Product import batch job", () => {
|
|||||||
cwd,
|
cwd,
|
||||||
redisUrl: "redis://127.0.0.1:6379",
|
redisUrl: "redis://127.0.0.1:6379",
|
||||||
uploadDir: __dirname,
|
uploadDir: __dirname,
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ describe("Line Item - Sales Channel", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_SALES_CHANNELS: true },
|
env: { MEDUSA_FF_SALES_CHANNELS: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ describe("tax inclusive prices", () => {
|
|||||||
const [process, conn] = await startServerWithEnvironment({
|
const [process, conn] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = conn // await initDb({ cwd })
|
dbConnection = conn // await initDb({ cwd })
|
||||||
medusaProcess = process // await setupServer({ cwd })
|
medusaProcess = process // await setupServer({ cwd })
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /store/carts", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ describe("/store/carts", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd, verbose: false })
|
medusaProcess = await setupServer({ cwd })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ describe("[MEDUSA_FF_SALES_CHANNELS] /store/carts", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_SALES_CHANNELS: true },
|
env: { MEDUSA_FF_SALES_CHANNELS: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /store/carts", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
@@ -261,6 +260,25 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /store/carts", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("with a cart with full tax inclusive variant pricing", () => {
|
describe("with a cart with full tax inclusive variant pricing", () => {
|
||||||
|
const variantId1 = IdMap.getId("test-variant-1-tax-inclusive")
|
||||||
|
const variantId2 = IdMap.getId("test-variant-2-tax-inclusive")
|
||||||
|
const productId1 = IdMap.getId("test-product-1-tax-inclusive")
|
||||||
|
const productId2 = IdMap.getId("test-product-2-tax-inclusive")
|
||||||
|
|
||||||
|
const createCartPayload = {
|
||||||
|
region_id: regionId,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
variant_id: variantId1,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant_id: variantId2,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await simpleRegionFactory(dbConnection, regionData)
|
await simpleRegionFactory(dbConnection, regionData)
|
||||||
await simpleProductFactory(
|
await simpleProductFactory(
|
||||||
@@ -319,6 +337,25 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /store/carts", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("with a cart mixing tax inclusive and exclusive variant pricing", () => {
|
describe("with a cart mixing tax inclusive and exclusive variant pricing", () => {
|
||||||
|
const variantId1 = IdMap.getId("test-variant-1-mixed-tax-inclusive")
|
||||||
|
const variantId2 = IdMap.getId("test-variant-2-mixed-tax-inclusive")
|
||||||
|
const productId1 = IdMap.getId("test-product-1-mixed-tax-inclusive")
|
||||||
|
const productId2 = IdMap.getId("test-product-2-mixed-tax-inclusive")
|
||||||
|
|
||||||
|
const createCartPayload = {
|
||||||
|
region_id: regionId,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
variant_id: variantId1,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant_id: variantId2,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await simpleRegionFactory(dbConnection, regionData)
|
await simpleRegionFactory(dbConnection, regionData)
|
||||||
await simpleProductFactory(
|
await simpleProductFactory(
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ describe("/store/collections", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd, verbose: true })
|
medusaProcess = await setupServer({ cwd })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ describe("/store/customers", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd, verbose: false })
|
medusaProcess = await setupServer({ cwd })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ describe("[MEDUSA_FF_ORDER_EDITING] /store/order-edits", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_ORDER_EDITING: true },
|
env: { MEDUSA_FF_ORDER_EDITING: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ describe("/store/carts", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd, verbose: false })
|
medusaProcess = await setupServer({ cwd })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ describe("[MEDUSA_FF_ORDER_EDITING] /store/payment-collections", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_ORDER_EDITING: true },
|
env: { MEDUSA_FF_ORDER_EDITING: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ describe("/store/variants", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd, verbose: false })
|
medusaProcess = await setupServer({ cwd })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ describe("sales channels", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_SALES_CHANNELS: true },
|
env: { MEDUSA_FF_SALES_CHANNELS: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ describe("Automatic Cart Taxes", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd, verbose: false })
|
medusaProcess = await setupServer({ cwd })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ describe("Manual Cart Taxes", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd, verbose: false })
|
medusaProcess = await setupServer({ cwd })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING]: Order Taxes", () => {
|
|||||||
const [process, connection] = await startServerWithEnvironment({
|
const [process, connection] = await startServerWithEnvironment({
|
||||||
cwd,
|
cwd,
|
||||||
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
||||||
verbose: false,
|
|
||||||
})
|
})
|
||||||
dbConnection = connection
|
dbConnection = connection
|
||||||
medusaProcess = process
|
medusaProcess = process
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
PriceList,
|
CustomerGroup,
|
||||||
MoneyAmount,
|
MoneyAmount,
|
||||||
PriceListType,
|
PriceList,
|
||||||
PriceListStatus,
|
PriceListStatus,
|
||||||
|
PriceListType,
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import faker from "faker"
|
import faker from "faker"
|
||||||
import { Connection } from "typeorm"
|
import { Connection } from "typeorm"
|
||||||
@@ -41,7 +42,7 @@ export const simplePriceListFactory = async (
|
|||||||
|
|
||||||
const listId = data.id || `simple-price-list-${Math.random() * 1000}`
|
const listId = data.id || `simple-price-list-${Math.random() * 1000}`
|
||||||
|
|
||||||
let customerGroups = []
|
let customerGroups: CustomerGroup[] = []
|
||||||
if (typeof data.customer_groups !== "undefined") {
|
if (typeof data.customer_groups !== "undefined") {
|
||||||
customerGroups = await Promise.all(
|
customerGroups = await Promise.all(
|
||||||
data.customer_groups.map((group) =>
|
data.customer_groups.map((group) =>
|
||||||
|
|||||||
@@ -8,16 +8,16 @@
|
|||||||
"build": "babel src -d dist --extensions \".ts,.js\""
|
"build": "babel src -d dist --extensions \".ts,.js\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@medusajs/medusa": "1.6.5-dev-1669708431707",
|
"@medusajs/medusa": "1.6.5-dev-1670340951307",
|
||||||
"faker": "^5.5.3",
|
"faker": "^5.5.3",
|
||||||
"medusa-interfaces": "1.3.3-dev-1669708431707",
|
"medusa-interfaces": "1.3.3-dev-1670340951307",
|
||||||
"typeorm": "^0.2.31"
|
"typeorm": "^0.2.31"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.12.10",
|
"@babel/cli": "^7.12.10",
|
||||||
"@babel/core": "^7.12.10",
|
"@babel/core": "^7.12.10",
|
||||||
"@babel/node": "^7.12.10",
|
"@babel/node": "^7.12.10",
|
||||||
"babel-preset-medusa-package": "1.1.19-dev-1669708431707",
|
"babel-preset-medusa-package": "1.1.19-dev-1670340951307",
|
||||||
"jest": "^26.6.3"
|
"jest": "^26.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1775,9 +1775,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@medusajs/medusa-cli@npm:1.3.5-dev-1669708431707":
|
"@medusajs/medusa-cli@npm:1.3.5-dev-1670340951307":
|
||||||
version: 1.3.5-dev-1669708431707
|
version: 1.3.5-dev-1670340951307
|
||||||
resolution: "@medusajs/medusa-cli@npm:1.3.5-dev-1669708431707"
|
resolution: "@medusajs/medusa-cli@npm:1.3.5-dev-1670340951307"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/polyfill": ^7.8.7
|
"@babel/polyfill": ^7.8.7
|
||||||
"@babel/runtime": ^7.9.6
|
"@babel/runtime": ^7.9.6
|
||||||
@@ -1793,8 +1793,8 @@ __metadata:
|
|||||||
inquirer: ^8.0.0
|
inquirer: ^8.0.0
|
||||||
is-valid-path: ^0.1.1
|
is-valid-path: ^0.1.1
|
||||||
meant: ^1.0.1
|
meant: ^1.0.1
|
||||||
medusa-core-utils: 1.1.35-dev-1669708431707
|
medusa-core-utils: 1.1.35-dev-1670340951307
|
||||||
medusa-telemetry: 0.0.15-dev-1669708431707
|
medusa-telemetry: 0.0.15-dev-1670340951307
|
||||||
netrc-parser: ^3.1.6
|
netrc-parser: ^3.1.6
|
||||||
open: ^8.0.6
|
open: ^8.0.6
|
||||||
ora: ^5.4.1
|
ora: ^5.4.1
|
||||||
@@ -1809,15 +1809,15 @@ __metadata:
|
|||||||
yargs: ^15.3.1
|
yargs: ^15.3.1
|
||||||
bin:
|
bin:
|
||||||
medusa: cli.js
|
medusa: cli.js
|
||||||
checksum: 779d98b21775542534466272d8fe415620024846aba435b45ae9956eab3fc7c627f85540ac0ac7a5282a0ab15d2c04c2dd99e72ca071b46f55da20f34effb69a
|
checksum: 7588d1b5ef91b720c36379db278da61b308403f30807d4c470418f1072203238b2820ce14d8b8ddf154b7f61f6ae79afc081ad9e481311e83cfc44546026ba01
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@medusajs/medusa@npm:1.6.5-dev-1669708431707":
|
"@medusajs/medusa@npm:1.6.5-dev-1670340951307":
|
||||||
version: 1.6.5-dev-1669708431707
|
version: 1.6.5-dev-1670340951307
|
||||||
resolution: "@medusajs/medusa@npm:1.6.5-dev-1669708431707"
|
resolution: "@medusajs/medusa@npm:1.6.5-dev-1670340951307"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@medusajs/medusa-cli": 1.3.5-dev-1669708431707
|
"@medusajs/medusa-cli": 1.3.5-dev-1670340951307
|
||||||
"@types/ioredis": ^4.28.10
|
"@types/ioredis": ^4.28.10
|
||||||
"@types/lodash": ^4.14.168
|
"@types/lodash": ^4.14.168
|
||||||
awilix: ^8.0.0
|
awilix: ^8.0.0
|
||||||
@@ -1839,8 +1839,8 @@ __metadata:
|
|||||||
ioredis-mock: ^5.6.0
|
ioredis-mock: ^5.6.0
|
||||||
iso8601-duration: ^1.3.0
|
iso8601-duration: ^1.3.0
|
||||||
jsonwebtoken: ^8.5.1
|
jsonwebtoken: ^8.5.1
|
||||||
medusa-core-utils: 1.1.35-dev-1669708431707
|
medusa-core-utils: 1.1.35-dev-1670340951307
|
||||||
medusa-test-utils: 1.1.37-dev-1669708431707
|
medusa-test-utils: 1.1.37-dev-1670340951307
|
||||||
morgan: ^1.9.1
|
morgan: ^1.9.1
|
||||||
multer: ^1.4.2
|
multer: ^1.4.2
|
||||||
node-schedule: ^2.1.0
|
node-schedule: ^2.1.0
|
||||||
@@ -1865,7 +1865,7 @@ __metadata:
|
|||||||
typeorm: 0.2.x
|
typeorm: 0.2.x
|
||||||
bin:
|
bin:
|
||||||
medusa: cli.js
|
medusa: cli.js
|
||||||
checksum: 7a7ec5ba7971112e74652791cff5eb8bfde640158618b300289d67bd753859c8312256fb2aa93f3523d2a4399f6d8b6c106e03e253f9a9518405b1224043299d
|
checksum: 274637b6e19db96f5cc561837b254bf2f5a1fea62ecc5258739413b48e568d7b3bdd0d486b32009d7e3ee27d684bbd3451943e6a42d092624882331139befee3
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -2446,11 +2446,11 @@ __metadata:
|
|||||||
"@babel/cli": ^7.12.10
|
"@babel/cli": ^7.12.10
|
||||||
"@babel/core": ^7.12.10
|
"@babel/core": ^7.12.10
|
||||||
"@babel/node": ^7.12.10
|
"@babel/node": ^7.12.10
|
||||||
"@medusajs/medusa": 1.6.5-dev-1669708431707
|
"@medusajs/medusa": 1.6.5-dev-1670340951307
|
||||||
babel-preset-medusa-package: 1.1.19-dev-1669708431707
|
babel-preset-medusa-package: 1.1.19-dev-1670340951307
|
||||||
faker: ^5.5.3
|
faker: ^5.5.3
|
||||||
jest: ^26.6.3
|
jest: ^26.6.3
|
||||||
medusa-interfaces: 1.3.3-dev-1669708431707
|
medusa-interfaces: 1.3.3-dev-1670340951307
|
||||||
typeorm: ^0.2.31
|
typeorm: ^0.2.31
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
@@ -2757,9 +2757,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"babel-preset-medusa-package@npm:1.1.19-dev-1669708431707":
|
"babel-preset-medusa-package@npm:1.1.19-dev-1670340951307":
|
||||||
version: 1.1.19-dev-1669708431707
|
version: 1.1.19-dev-1670340951307
|
||||||
resolution: "babel-preset-medusa-package@npm:1.1.19-dev-1669708431707"
|
resolution: "babel-preset-medusa-package@npm:1.1.19-dev-1670340951307"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/plugin-proposal-class-properties": ^7.12.1
|
"@babel/plugin-proposal-class-properties": ^7.12.1
|
||||||
"@babel/plugin-proposal-decorators": ^7.12.1
|
"@babel/plugin-proposal-decorators": ^7.12.1
|
||||||
@@ -2773,7 +2773,7 @@ __metadata:
|
|||||||
core-js: ^3.7.0
|
core-js: ^3.7.0
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@babel/core": ^7.11.6
|
"@babel/core": ^7.11.6
|
||||||
checksum: 2b01b0754da0a4bec26abcb6c94d91d7c2fd06bf9d58c23dac9266dc8c7cb470a6a8874d1564af84b068684d34028fb0288c7eae5f271a16cd1570ccaf1aa413
|
checksum: b091e66d23c22e26a55f8039810f73910a3860af5838904aada35e6066d3d1f9c85e5897e2879388452fd424663e676a4e19ad960167a1d001105e2d797715bc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -6919,29 +6919,29 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"medusa-core-utils@npm:1.1.35-dev-1669708431707":
|
"medusa-core-utils@npm:1.1.35-dev-1670340951307":
|
||||||
version: 1.1.35-dev-1669708431707
|
version: 1.1.35-dev-1670340951307
|
||||||
resolution: "medusa-core-utils@npm:1.1.35-dev-1669708431707"
|
resolution: "medusa-core-utils@npm:1.1.35-dev-1670340951307"
|
||||||
dependencies:
|
dependencies:
|
||||||
joi: ^17.3.0
|
joi: ^17.3.0
|
||||||
joi-objectid: ^3.0.1
|
joi-objectid: ^3.0.1
|
||||||
checksum: ac797ee8b9a165a6e90e11fbe9312bcfcaaa4271a9ef79b2cb659b053697cbee80580b3aae9bead7e2b738a864df30f150b01d9598fceb8262d6d11496a68ab4
|
checksum: 9304c0426dec41f33973dd122491bd3025a65eabc94616de7f63b492b558e71234fc0e02af3a13cea014b8a4bead91053e0416e107cc0b74657af9782d5c6023
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"medusa-interfaces@npm:1.3.3-dev-1669708431707":
|
"medusa-interfaces@npm:1.3.3-dev-1670340951307":
|
||||||
version: 1.3.3-dev-1669708431707
|
version: 1.3.3-dev-1670340951307
|
||||||
resolution: "medusa-interfaces@npm:1.3.3-dev-1669708431707"
|
resolution: "medusa-interfaces@npm:1.3.3-dev-1670340951307"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
medusa-core-utils: ^1.1.31
|
medusa-core-utils: ^1.1.31
|
||||||
typeorm: 0.x
|
typeorm: 0.x
|
||||||
checksum: edad068df3783072f178cac3adfa646e8886a55bf07409addec4ab18eab8f8e09e9d5ac34c1e06c65cd111330f003325c72f9dc8585348d20382a1dacf3d3536
|
checksum: 9eda49c0cb3922f3e1109ad231389b54ad49f730444108fd10876beab9f7845c47b965dcfe32e7169a9a4845edd143cfad96187c5a5814cbd05f45852d56c130
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"medusa-telemetry@npm:0.0.15-dev-1669708431707":
|
"medusa-telemetry@npm:0.0.15-dev-1670340951307":
|
||||||
version: 0.0.15-dev-1669708431707
|
version: 0.0.15-dev-1670340951307
|
||||||
resolution: "medusa-telemetry@npm:0.0.15-dev-1669708431707"
|
resolution: "medusa-telemetry@npm:0.0.15-dev-1670340951307"
|
||||||
dependencies:
|
dependencies:
|
||||||
axios: ^0.21.1
|
axios: ^0.21.1
|
||||||
axios-retry: ^3.1.9
|
axios-retry: ^3.1.9
|
||||||
@@ -6952,18 +6952,18 @@ __metadata:
|
|||||||
is-docker: ^2.2.1
|
is-docker: ^2.2.1
|
||||||
remove-trailing-slash: ^0.1.1
|
remove-trailing-slash: ^0.1.1
|
||||||
uuid: ^8.3.2
|
uuid: ^8.3.2
|
||||||
checksum: 0116c6d4d70811290ba423868cbd5fc8600cf66c81942c0fb69eab41910e783f6f90b8d401e95f2847e4aa0fc74dbcd5115e30cd9758be2f01b4577d934fcb2c
|
checksum: 15f3b7d1d639b3024d964792455a13f820fc596d7f01583f44de9803d44f129e39b201a00dab94dc43b67f31b4852835e96fd58363b55a71d6979856be58df75
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"medusa-test-utils@npm:1.1.37-dev-1669708431707":
|
"medusa-test-utils@npm:1.1.37-dev-1670340951307":
|
||||||
version: 1.1.37-dev-1669708431707
|
version: 1.1.37-dev-1670340951307
|
||||||
resolution: "medusa-test-utils@npm:1.1.37-dev-1669708431707"
|
resolution: "medusa-test-utils@npm:1.1.37-dev-1670340951307"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/plugin-transform-classes": ^7.9.5
|
"@babel/plugin-transform-classes": ^7.9.5
|
||||||
medusa-core-utils: 1.1.35-dev-1669708431707
|
medusa-core-utils: 1.1.35-dev-1670340951307
|
||||||
randomatic: ^3.1.1
|
randomatic: ^3.1.1
|
||||||
checksum: b89c99be68369aae6f72c395eaec11f06c64415ff6b1e9a8616fd2e14e68a1f3cfb58e7722f48057c0da7da5d1dcb260ecaa49bd89c241a55d38767b2307600b
|
checksum: 8e96932f28e402c9561f6ea6c13a5d5ac8b5b83dc38a5e41d4ad5bcac73aa35886fe3b6d55809692594b468fc7be6e89477b6cd3d694be23f6a5abac7cb61c62
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ module.exports = ({ cwd, redisUrl, uploadDir, verbose, env }) => {
|
|||||||
const workerId = parseInt(process.env.JEST_WORKER_ID || "1")
|
const workerId = parseInt(process.env.JEST_WORKER_ID || "1")
|
||||||
const redisUrlWithDatabase = `${redisUrl}/${workerId - 1}`
|
const redisUrlWithDatabase = `${redisUrl}/${workerId - 1}`
|
||||||
|
|
||||||
|
verbose = verbose ?? false
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const medusaProcess = spawn("node", [path.resolve(serverPath)], {
|
const medusaProcess = spawn("node", [path.resolve(serverPath)], {
|
||||||
cwd,
|
cwd,
|
||||||
@@ -21,6 +23,7 @@ module.exports = ({ cwd, redisUrl, uploadDir, verbose, env }) => {
|
|||||||
COOKIE_SECRET: "test",
|
COOKIE_SECRET: "test",
|
||||||
REDIS_URL: redisUrl ? redisUrlWithDatabase : undefined, // If provided, will use a real instance, otherwise a fake instance
|
REDIS_URL: redisUrl ? redisUrlWithDatabase : undefined, // If provided, will use a real instance, otherwise a fake instance
|
||||||
UPLOAD_DIR: uploadDir, // If provided, will be used for the fake local file service
|
UPLOAD_DIR: uploadDir, // If provided, will be used for the fake local file service
|
||||||
|
CACHE_TTL: 0, // By default the cache service is disabled and 0 means that none of the cache key/value will be stored.
|
||||||
...env,
|
...env,
|
||||||
},
|
},
|
||||||
stdio: verbose
|
stdio: verbose
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@
|
|||||||
"jest": "jest",
|
"jest": "jest",
|
||||||
"test": "turbo run test",
|
"test": "turbo run test",
|
||||||
"test:integration": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js",
|
"test:integration": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js",
|
||||||
"test:integration:api": "NODE_ENV=test jest --detectOpenHandles --forceExit --runInBand --bail --config=integration-tests/jest.config.js --projects=integration-tests/api",
|
"test:integration:api": "NODE_ENV=test jest --detectOpenHandles --forceExit --runInBand --bail --config=integration-tests/jest.config.js --projects=integration-tests/api -- integration-tests/api/__tests__/store/cart/cart.js",
|
||||||
"test:integration:plugins": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js --projects=integration-tests/plugins",
|
"test:integration:plugins": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js --projects=integration-tests/plugins",
|
||||||
"test:fixtures": "NODE_ENV=test jest --config=docs-util/jest.config.js --runInBand --bail",
|
"test:fixtures": "NODE_ENV=test jest --config=docs-util/jest.config.js --runInBand --bail",
|
||||||
"openapi:generate": "node ./scripts/build-openapi.js",
|
"openapi:generate": "node ./scripts/build-openapi.js",
|
||||||
|
|||||||
@@ -98,19 +98,24 @@ describe("POST /store/carts", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls line item generate", () => {
|
it("calls line item generate", () => {
|
||||||
|
expect(CartServiceMock.addOrUpdateLineItems).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
expect(LineItemServiceMock.generate).toHaveBeenCalledWith(
|
expect(LineItemServiceMock.generate).toHaveBeenCalledWith(
|
||||||
IdMap.getId("testVariant"),
|
[
|
||||||
IdMap.getId("testRegion"),
|
{
|
||||||
3,
|
variantId: IdMap.getId("testVariant"),
|
||||||
{ customer_id: undefined }
|
quantity: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variantId: IdMap.getId("testVariant1"),
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
region_id: IdMap.getId("testRegion"),
|
||||||
|
customer_id: undefined,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
expect(LineItemServiceMock.generate).toHaveBeenCalledWith(
|
|
||||||
IdMap.getId("testVariant1"),
|
|
||||||
IdMap.getId("testRegion"),
|
|
||||||
1,
|
|
||||||
{ customer_id: undefined }
|
|
||||||
)
|
|
||||||
expect(CartServiceMock.addLineItem).toHaveBeenCalledTimes(2)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns cart", () => {
|
it("returns cart", () => {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
RegionService,
|
RegionService,
|
||||||
} from "../../../../services"
|
} from "../../../../services"
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
import { Cart } from "../../../../models"
|
import { Cart, LineItem } from "../../../../models"
|
||||||
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
||||||
import { FlagRouter } from "../../../../utils/flag-router"
|
import { FlagRouter } from "../../../../utils/flag-router"
|
||||||
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
||||||
@@ -179,24 +179,30 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
let cart: Cart
|
let cart: Cart
|
||||||
await entityManager.transaction(async (manager) => {
|
await entityManager.transaction(async (manager) => {
|
||||||
cart = await cartService.withTransaction(manager).create(toCreate)
|
const cartServiceTx = cartService.withTransaction(manager)
|
||||||
|
const lineItemServiceTx = lineItemService.withTransaction(manager)
|
||||||
|
|
||||||
if (validated.items) {
|
cart = await cartServiceTx.create(toCreate)
|
||||||
await Promise.all(
|
|
||||||
validated.items.map(async (i) => {
|
if (validated.items?.length) {
|
||||||
const lineItem = await lineItemService
|
const generateInputData = validated.items.map((item) => {
|
||||||
.withTransaction(manager)
|
return {
|
||||||
.generate(i.variant_id, regionId, i.quantity, {
|
variantId: item.variant_id,
|
||||||
customer_id: req.user?.customer_id,
|
quantity: item.quantity,
|
||||||
})
|
}
|
||||||
return await cartService
|
})
|
||||||
.withTransaction(manager)
|
const generatedLineItems: LineItem[] = await lineItemServiceTx.generate(
|
||||||
.addLineItem(cart.id, lineItem, {
|
generateInputData,
|
||||||
validateSalesChannels:
|
{
|
||||||
featureFlagRouter.isFeatureEnabled("sales_channels"),
|
region_id: regionId,
|
||||||
})
|
customer_id: req.user?.customer_id,
|
||||||
})
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await cartServiceTx.addOrUpdateLineItems(cart.id, generatedLineItems, {
|
||||||
|
validateSalesChannels:
|
||||||
|
featureFlagRouter.isFeatureEnabled("sales_channels"),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ export * from "./models/base-entity"
|
|||||||
export * from "./models/soft-deletable-entity"
|
export * from "./models/soft-deletable-entity"
|
||||||
export * from "./search-service"
|
export * from "./search-service"
|
||||||
export * from "./payment-service"
|
export * from "./payment-service"
|
||||||
|
export * from "./services"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export interface ICacheService {
|
||||||
|
get<T>(key: string): Promise<T | null>
|
||||||
|
|
||||||
|
set(key: string, data: unknown, ttl?: number): Promise<void>
|
||||||
|
|
||||||
|
invalidate(key: string): Promise<void>
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./cache"
|
||||||
@@ -172,11 +172,19 @@ export class MoneyAmountRepository extends Repository<MoneyAmount> {
|
|||||||
}
|
}
|
||||||
if (region_id || currency_code) {
|
if (region_id || currency_code) {
|
||||||
qb.andWhere(
|
qb.andWhere(
|
||||||
new Brackets((qb) =>
|
new Brackets((qb) => {
|
||||||
qb
|
if (region_id && !currency_code) {
|
||||||
.where({ region_id: region_id })
|
qb.where({ region_id: region_id })
|
||||||
.orWhere({ currency_code: currency_code })
|
}
|
||||||
)
|
if (!region_id && currency_code) {
|
||||||
|
qb.where({ currency_code: currency_code })
|
||||||
|
}
|
||||||
|
if (currency_code && region_id) {
|
||||||
|
qb.where({ region_id: region_id }).orWhere({
|
||||||
|
currency_code: currency_code,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
)
|
)
|
||||||
} else if (!customer_id && !include_discount_prices) {
|
} else if (!customer_id && !include_discount_prices) {
|
||||||
qb.andWhere("price_list.id IS null")
|
qb.andWhere("price_list.id IS null")
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export const cacheServiceMock = {
|
||||||
|
set: jest.fn().mockImplementation(async () => void 0),
|
||||||
|
get: jest.fn().mockImplementation(async () => null),
|
||||||
|
invalidate: jest.fn().mockImplementation(async () => void 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
const mock = jest.fn().mockImplementation(() => {
|
||||||
|
return cacheServiceMock
|
||||||
|
})
|
||||||
|
|
||||||
|
export default mock
|
||||||
@@ -319,6 +319,9 @@ export const CartServiceMock = {
|
|||||||
addLineItem: jest.fn().mockImplementation((cartId, lineItem) => {
|
addLineItem: jest.fn().mockImplementation((cartId, lineItem) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
|
addOrUpdateLineItems: jest.fn().mockImplementation((cartId, lineItem) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
setPaymentMethod: jest.fn().mockImplementation((cartId, method) => {
|
setPaymentMethod: jest.fn().mockImplementation((cartId, method) => {
|
||||||
if (method.provider_id === "default_provider") {
|
if (method.provider_id === "default_provider") {
|
||||||
return Promise.resolve(carts.cartWithPaySessions)
|
return Promise.resolve(carts.cartWithPaySessions)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||||
import { FlagRouter } from "../../utils/flag-router"
|
import { FlagRouter } from "../../utils/flag-router"
|
||||||
import DiscountService from "../discount"
|
import DiscountService from "../discount"
|
||||||
|
import { TotalsServiceMock } from "../__mocks__/totals"
|
||||||
|
import { newTotalsServiceMock } from "../__mocks__/new-totals"
|
||||||
|
|
||||||
const featureFlagRouter = new FlagRouter({})
|
const featureFlagRouter = new FlagRouter({})
|
||||||
|
|
||||||
@@ -601,15 +603,28 @@ describe("DiscountService", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const totalsService = {
|
const totalsService = {
|
||||||
getSubtotal: () => {
|
...TotalsServiceMock,
|
||||||
|
getSubtotal: async () => {
|
||||||
return 1100
|
return 1100
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newTotalsService = {
|
||||||
|
...newTotalsServiceMock,
|
||||||
|
getLineItemTotals: async () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
subtotal: 1100,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const discountService = new DiscountService({
|
const discountService = new DiscountService({
|
||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
discountRepository,
|
discountRepository,
|
||||||
totalsService,
|
totalsService,
|
||||||
|
newTotalsService,
|
||||||
featureFlagRouter,
|
featureFlagRouter,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -631,21 +646,31 @@ describe("DiscountService", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("correctly calculates fixed + total discount", async () => {
|
it("correctly calculates fixed + total discount", async () => {
|
||||||
|
let item = {
|
||||||
|
unit_price: 400,
|
||||||
|
quantity: 2,
|
||||||
|
allow_discounts: true,
|
||||||
|
}
|
||||||
|
|
||||||
const adjustment1 = await discountService.calculateDiscountForLineItem(
|
const adjustment1 = await discountService.calculateDiscountForLineItem(
|
||||||
"disc_fixed_total",
|
"disc_fixed_total",
|
||||||
|
item,
|
||||||
{
|
{
|
||||||
unit_price: 400,
|
items: [item],
|
||||||
quantity: 2,
|
|
||||||
allow_discounts: true,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
item = {
|
||||||
|
unit_price: 300,
|
||||||
|
quantity: 1,
|
||||||
|
allow_discounts: true,
|
||||||
|
}
|
||||||
|
|
||||||
const adjustment2 = await discountService.calculateDiscountForLineItem(
|
const adjustment2 = await discountService.calculateDiscountForLineItem(
|
||||||
"disc_fixed_total",
|
"disc_fixed_total",
|
||||||
|
item,
|
||||||
{
|
{
|
||||||
unit_price: 300,
|
items: [item],
|
||||||
quantity: 1,
|
|
||||||
allow_discounts: true,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -32,9 +32,7 @@ import { RegionServiceMock } from "../__mocks__/region"
|
|||||||
}
|
}
|
||||||
|
|
||||||
const productVariantService = {
|
const productVariantService = {
|
||||||
withTransaction: function () {
|
...ProductVariantServiceMock,
|
||||||
return this
|
|
||||||
},
|
|
||||||
retrieve: (query) => {
|
retrieve: (query) => {
|
||||||
if (query === IdMap.getId("test-giftcard")) {
|
if (query === IdMap.getId("test-giftcard")) {
|
||||||
return {
|
return {
|
||||||
@@ -58,15 +56,25 @@ import { RegionServiceMock } from "../__mocks__/region"
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
getRegionPrice: () => 100,
|
getRegionPrice: () => 100,
|
||||||
|
list: jest.fn().mockImplementation(async (selector) => {
|
||||||
|
return (selector.id || []).map((id) => ({
|
||||||
|
id,
|
||||||
|
title: "Test variant",
|
||||||
|
product: {
|
||||||
|
title: "Test product",
|
||||||
|
thumbnail: "",
|
||||||
|
discountable: false,
|
||||||
|
is_giftcard: true,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
const pricingService = {
|
const pricingService = {
|
||||||
withTransaction: function () {
|
...PricingServiceMock,
|
||||||
return this
|
getProductVariantsPricing: () => {
|
||||||
},
|
|
||||||
getProductVariantPricingById: () => {
|
|
||||||
return {
|
return {
|
||||||
calculated_price: 100,
|
[IdMap.getId("test-giftcard")]: { calculated_price: 100 },
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getProductVariantPricing: () => {
|
getProductVariantPricing: () => {
|
||||||
@@ -106,15 +114,17 @@ import { RegionServiceMock } from "../__mocks__/region"
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(lineItemRepository.create).toHaveBeenCalledTimes(1)
|
expect(lineItemRepository.create).toHaveBeenCalledTimes(1)
|
||||||
expect(lineItemRepository.create).toHaveBeenCalledWith({
|
expect(lineItemRepository.create).toHaveBeenCalledWith([
|
||||||
variant_id: IdMap.getId("test-variant"),
|
{
|
||||||
cart_id: IdMap.getId("test-cart"),
|
variant_id: IdMap.getId("test-variant"),
|
||||||
title: "Test product",
|
cart_id: IdMap.getId("test-cart"),
|
||||||
description: "Test variant",
|
title: "Test product",
|
||||||
thumbnail: "",
|
description: "Test variant",
|
||||||
unit_price: 100,
|
thumbnail: "",
|
||||||
quantity: 1,
|
unit_price: 100,
|
||||||
})
|
quantity: 1,
|
||||||
|
},
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully create a line item with price and quantity", async () => {
|
it("successfully create a line item with price and quantity", async () => {
|
||||||
@@ -126,12 +136,14 @@ import { RegionServiceMock } from "../__mocks__/region"
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(lineItemRepository.create).toHaveBeenCalledTimes(1)
|
expect(lineItemRepository.create).toHaveBeenCalledTimes(1)
|
||||||
expect(lineItemRepository.create).toHaveBeenCalledWith({
|
expect(lineItemRepository.create).toHaveBeenCalledWith([
|
||||||
variant_id: IdMap.getId("test-variant"),
|
{
|
||||||
cart_id: IdMap.getId("test-cart"),
|
variant_id: IdMap.getId("test-variant"),
|
||||||
unit_price: 50,
|
cart_id: IdMap.getId("test-cart"),
|
||||||
quantity: 2,
|
unit_price: 50,
|
||||||
})
|
quantity: 2,
|
||||||
|
},
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully create a line item giftcard", async () => {
|
it("successfully create a line item giftcard", async () => {
|
||||||
@@ -147,8 +159,7 @@ import { RegionServiceMock } from "../__mocks__/region"
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(lineItemRepository.create).toHaveBeenCalledTimes(2)
|
expect(lineItemRepository.create).toHaveBeenCalledTimes(2)
|
||||||
expect(lineItemRepository.create).toHaveBeenNthCalledWith(
|
expect(lineItemRepository.create).toHaveBeenNthCalledWith(2, [
|
||||||
2,
|
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
allow_discounts: false,
|
allow_discounts: false,
|
||||||
variant_id: IdMap.getId("test-giftcard"),
|
variant_id: IdMap.getId("test-giftcard"),
|
||||||
@@ -161,8 +172,8 @@ import { RegionServiceMock } from "../__mocks__/region"
|
|||||||
is_giftcard: true,
|
is_giftcard: true,
|
||||||
should_merge: true,
|
should_merge: true,
|
||||||
metadata: {},
|
metadata: {},
|
||||||
})
|
}),
|
||||||
)
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -190,6 +201,7 @@ import { RegionServiceMock } from "../__mocks__/region"
|
|||||||
const lineItemService = new LineItemService({
|
const lineItemService = new LineItemService({
|
||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
lineItemRepository,
|
lineItemRepository,
|
||||||
|
productVariantService: ProductVariantServiceMock,
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -329,9 +341,7 @@ describe("LineItemService", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const productVariantService = {
|
const productVariantService = {
|
||||||
withTransaction: function () {
|
...ProductVariantServiceMock,
|
||||||
return this
|
|
||||||
},
|
|
||||||
retrieve: (query) => {
|
retrieve: (query) => {
|
||||||
if (query === IdMap.getId("test-giftcard")) {
|
if (query === IdMap.getId("test-giftcard")) {
|
||||||
return {
|
return {
|
||||||
@@ -355,16 +365,26 @@ describe("LineItemService", () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
getRegionPrice: () => 100,
|
getRegionPrice: () => 100,
|
||||||
|
list: jest.fn().mockImplementation(async (selector) => {
|
||||||
|
return (selector.id || []).map((id) => ({
|
||||||
|
id,
|
||||||
|
title: "Test variant",
|
||||||
|
product: {
|
||||||
|
title: "Test product",
|
||||||
|
thumbnail: "",
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
const pricingService = {
|
const pricingService = {
|
||||||
withTransaction: function () {
|
...PricingServiceMock,
|
||||||
return this
|
getProductVariantsPricing: () => {
|
||||||
},
|
|
||||||
getProductVariantPricingById: () => {
|
|
||||||
return {
|
return {
|
||||||
calculated_price: 100,
|
[IdMap.getId("test-variant")]: {
|
||||||
calculated_price_includes_tax: true,
|
calculated_price: 100,
|
||||||
|
calculated_price_includes_tax: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getProductVariantPricing: () => {
|
getProductVariantPricing: () => {
|
||||||
@@ -423,6 +443,41 @@ describe("LineItemService", () => {
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("successfully create a line item with tax inclusive set to true by passing an object", async () => {
|
||||||
|
await lineItemService.generate(
|
||||||
|
{
|
||||||
|
variantId: IdMap.getId("test-variant"),
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
region_id: IdMap.getId("test-region"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(lineItemRepository.create).toHaveBeenCalledTimes(1)
|
||||||
|
expect(lineItemRepository.create).toHaveBeenCalledWith({
|
||||||
|
unit_price: 100,
|
||||||
|
title: "Test product",
|
||||||
|
description: "Test variant",
|
||||||
|
thumbnail: "",
|
||||||
|
variant_id: IdMap.getId("test-variant"),
|
||||||
|
quantity: 1,
|
||||||
|
allow_discounts: undefined,
|
||||||
|
is_giftcard: undefined,
|
||||||
|
metadata: {},
|
||||||
|
should_merge: true,
|
||||||
|
includes_tax: true,
|
||||||
|
variant: expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
product: expect.objectContaining({
|
||||||
|
thumbnail: "",
|
||||||
|
title: "Test product",
|
||||||
|
}),
|
||||||
|
title: "Test variant",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
describe("generate", () => {
|
describe("generate", () => {
|
||||||
const lineItemRepository = MockRepository({
|
const lineItemRepository = MockRepository({
|
||||||
@@ -448,9 +503,7 @@ describe("LineItemService", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const productVariantService = {
|
const productVariantService = {
|
||||||
withTransaction: function () {
|
...ProductVariantServiceMock,
|
||||||
return this
|
|
||||||
},
|
|
||||||
retrieve: (query) => {
|
retrieve: (query) => {
|
||||||
if (query === IdMap.getId("test-giftcard")) {
|
if (query === IdMap.getId("test-giftcard")) {
|
||||||
return {
|
return {
|
||||||
@@ -464,26 +517,30 @@ describe("LineItemService", () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
id: IdMap.getId("test-variant"),
|
|
||||||
title: "Test variant",
|
|
||||||
product: {
|
|
||||||
title: "Test product",
|
|
||||||
thumbnail: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
getRegionPrice: () => 100,
|
getRegionPrice: () => 100,
|
||||||
|
list: jest.fn().mockImplementation(async (selector) => {
|
||||||
|
return (selector.id || []).map((id) => {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
title: "Test variant",
|
||||||
|
product: {
|
||||||
|
title: "Test product",
|
||||||
|
thumbnail: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
const pricingService = {
|
const pricingService = {
|
||||||
withTransaction: function () {
|
...PricingServiceMock,
|
||||||
return this
|
getProductVariantsPricing: () => {
|
||||||
},
|
|
||||||
getProductVariantPricingById: () => {
|
|
||||||
return {
|
return {
|
||||||
calculated_price: 100,
|
[IdMap.getId("test-variant")]: {
|
||||||
calculated_price_includes_tax: false,
|
calculated_price: 100,
|
||||||
|
calculated_price_includes_tax: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getProductVariantPricing: () => {
|
getProductVariantPricing: () => {
|
||||||
@@ -542,6 +599,41 @@ describe("LineItemService", () => {
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("successfully create a line item with tax inclusive set to false by passing an object", async () => {
|
||||||
|
await lineItemService.generate(
|
||||||
|
{
|
||||||
|
variantId: IdMap.getId("test-variant"),
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
region_id: IdMap.getId("test-region"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(lineItemRepository.create).toHaveBeenCalledTimes(1)
|
||||||
|
expect(lineItemRepository.create).toHaveBeenCalledWith({
|
||||||
|
unit_price: 100,
|
||||||
|
title: "Test product",
|
||||||
|
description: "Test variant",
|
||||||
|
thumbnail: "",
|
||||||
|
variant_id: IdMap.getId("test-variant"),
|
||||||
|
quantity: 1,
|
||||||
|
allow_discounts: undefined,
|
||||||
|
is_giftcard: undefined,
|
||||||
|
metadata: {},
|
||||||
|
should_merge: true,
|
||||||
|
includes_tax: false,
|
||||||
|
variant: expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
product: expect.objectContaining({
|
||||||
|
thumbnail: "",
|
||||||
|
title: "Test product",
|
||||||
|
}),
|
||||||
|
title: "Test variant",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("clone", () => {
|
describe("clone", () => {
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { Redis } from "ioredis"
|
||||||
|
import { ICacheService } from "../interfaces"
|
||||||
|
|
||||||
|
const DEFAULT_CACHE_TIME = 30 // 30 seconds
|
||||||
|
const EXPIRY_MODE = "EX" // "EX" stands for an expiry time in second
|
||||||
|
|
||||||
|
export default class CacheService implements ICacheService {
|
||||||
|
protected readonly redis_: Redis
|
||||||
|
|
||||||
|
constructor({ redisClient }) {
|
||||||
|
this.redis_ = redisClient
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a key/value pair to the cache.
|
||||||
|
* It is also possible to manage the ttl through environment variable using CACHE_TTL. If the ttl is 0 it will
|
||||||
|
* act like the value should not be cached at all.
|
||||||
|
* @param key
|
||||||
|
* @param data
|
||||||
|
* @param ttl
|
||||||
|
*/
|
||||||
|
async set(
|
||||||
|
key: string,
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
ttl: number = DEFAULT_CACHE_TIME
|
||||||
|
): Promise<void> {
|
||||||
|
ttl = Number(process.env.CACHE_TTL ?? ttl)
|
||||||
|
if (ttl === 0) {
|
||||||
|
// No need to call redis set without expiry time
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.redis_.set(key, JSON.stringify(data), EXPIRY_MODE, ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a cached value belonging to the given key.
|
||||||
|
* @param cacheKey
|
||||||
|
*/
|
||||||
|
async get<T>(cacheKey: string): Promise<T | null> {
|
||||||
|
try {
|
||||||
|
const cached = await this.redis_.get(cacheKey)
|
||||||
|
if (cached) {
|
||||||
|
return JSON.parse(cached)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
await this.redis_.del(cacheKey)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate cache for a specific key. a key can be either a specific key or more global such as "ps:*".
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
async invalidate(key: string): Promise<void> {
|
||||||
|
await this.redis_.del()
|
||||||
|
const keys = await this.redis_.keys(key)
|
||||||
|
const pipeline = this.redis_.pipeline()
|
||||||
|
|
||||||
|
keys.forEach(function (key) {
|
||||||
|
pipeline.del(key)
|
||||||
|
})
|
||||||
|
|
||||||
|
await pipeline.exec()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
FilterableCartProps,
|
FilterableCartProps,
|
||||||
isCart,
|
isCart,
|
||||||
LineItemUpdate,
|
LineItemUpdate,
|
||||||
|
LineItemValidateData,
|
||||||
} from "../types/cart"
|
} from "../types/cart"
|
||||||
import {
|
import {
|
||||||
AddressPayload,
|
AddressPayload,
|
||||||
@@ -331,7 +332,7 @@ class CartService extends TransactionBaseService {
|
|||||||
rawCart.email = customer.email
|
rawCart.email = customer.email
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data.region_id) {
|
if (!data.region_id && !data.region) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.INVALID_DATA,
|
MedusaError.Types.INVALID_DATA,
|
||||||
`A region_id must be provided when creating a cart`
|
`A region_id must be provided when creating a cart`
|
||||||
@@ -339,11 +340,13 @@ class CartService extends TransactionBaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rawCart.region_id = data.region_id
|
rawCart.region_id = data.region_id
|
||||||
const region = await this.regionService_
|
const region = data.region
|
||||||
.withTransaction(transactionManager)
|
? data.region
|
||||||
.retrieve(data.region_id, {
|
: await this.regionService_
|
||||||
relations: ["countries"],
|
.withTransaction(transactionManager)
|
||||||
})
|
.retrieve(data.region_id!, {
|
||||||
|
relations: ["countries"],
|
||||||
|
})
|
||||||
const regCountries = region.countries.map(({ iso_2 }) => iso_2)
|
const regCountries = region.countries.map(({ iso_2 }) => iso_2)
|
||||||
|
|
||||||
if (!data.shipping_address && !data.shipping_address_id) {
|
if (!data.shipping_address && !data.shipping_address_id) {
|
||||||
@@ -555,15 +558,17 @@ class CartService extends TransactionBaseService {
|
|||||||
*/
|
*/
|
||||||
protected async validateLineItem(
|
protected async validateLineItem(
|
||||||
{ sales_channel_id }: { sales_channel_id: string | null },
|
{ sales_channel_id }: { sales_channel_id: string | null },
|
||||||
lineItem: LineItem
|
lineItem: LineItemValidateData
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (!sales_channel_id) {
|
if (!sales_channel_id) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineItemVariant = await this.productVariantService_
|
const lineItemVariant = lineItem.variant?.product_id
|
||||||
.withTransaction(this.manager_)
|
? lineItem.variant
|
||||||
.retrieve(lineItem.variant_id)
|
: await this.productVariantService_
|
||||||
|
.withTransaction(this.manager_)
|
||||||
|
.retrieve(lineItem.variant_id, { select: ["id", "product_id"] })
|
||||||
|
|
||||||
return !!(
|
return !!(
|
||||||
await this.productService_
|
await this.productService_
|
||||||
@@ -583,6 +588,7 @@ class CartService extends TransactionBaseService {
|
|||||||
* validateSalesChannels - should check if product belongs to the same sales chanel as cart
|
* validateSalesChannels - should check if product belongs to the same sales chanel as cart
|
||||||
* (if cart has associated sales channel)
|
* (if cart has associated sales channel)
|
||||||
* @return the result of the update operation
|
* @return the result of the update operation
|
||||||
|
* @deprecated Use {@link addOrUpdateLineItems} instead.
|
||||||
*/
|
*/
|
||||||
async addLineItem(
|
async addLineItem(
|
||||||
cartId: string,
|
cartId: string,
|
||||||
@@ -659,7 +665,166 @@ class CartService extends TransactionBaseService {
|
|||||||
{ cart_id: cartId, has_shipping: true },
|
{ cart_id: cartId, has_shipping: true },
|
||||||
{ has_shipping: false }
|
{ has_shipping: false }
|
||||||
)
|
)
|
||||||
.catch(() => void 0)
|
.catch((err: Error | MedusaError) => {
|
||||||
|
// We only want to catch the errors related to not found items since we don't care if there is not item to update
|
||||||
|
if ("type" in err && err.type === MedusaError.Types.NOT_FOUND) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
|
||||||
|
cart = await this.retrieve(cart.id, {
|
||||||
|
relations: ["items", "discounts", "discounts.rule", "region"],
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.refreshAdjustments_(cart)
|
||||||
|
|
||||||
|
await this.eventBus_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.emit(CartService.Events.UPDATED, { id: cart.id })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds or update one or multiple line items to the cart. It also update all existing items in the cart
|
||||||
|
* to have has_shipping to false. Finally, the adjustments will be updated.
|
||||||
|
* @param cartId - the id of the cart that we will add to
|
||||||
|
* @param lineItems - the line items to add.
|
||||||
|
* @param config
|
||||||
|
* validateSalesChannels - should check if product belongs to the same sales chanel as cart
|
||||||
|
* (if cart has associated sales channel)
|
||||||
|
* @return the result of the update operation
|
||||||
|
*/
|
||||||
|
async addOrUpdateLineItems(
|
||||||
|
cartId: string,
|
||||||
|
lineItems: LineItem | LineItem[],
|
||||||
|
config = { validateSalesChannels: true }
|
||||||
|
): Promise<void> {
|
||||||
|
const items: LineItem[] = Array.isArray(lineItems) ? lineItems : [lineItems]
|
||||||
|
|
||||||
|
const select: (keyof Cart)[] = ["id"]
|
||||||
|
|
||||||
|
if (this.featureFlagRouter_.isFeatureEnabled("sales_channels")) {
|
||||||
|
select.push("sales_channel_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.atomicPhase_(
|
||||||
|
async (transactionManager: EntityManager) => {
|
||||||
|
let cart = await this.retrieve(cartId, { select })
|
||||||
|
|
||||||
|
if (this.featureFlagRouter_.isFeatureEnabled("sales_channels")) {
|
||||||
|
if (config.validateSalesChannels) {
|
||||||
|
const areValid = await Promise.all(
|
||||||
|
items.map(async (item) => {
|
||||||
|
return await this.validateLineItem(cart, item)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const invalidProducts = areValid
|
||||||
|
.map((valid, index) => {
|
||||||
|
return !valid ? { title: items[index].title } : undefined
|
||||||
|
})
|
||||||
|
.filter((v): v is { title: string } => !!v)
|
||||||
|
|
||||||
|
if (invalidProducts.length) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`The products [${invalidProducts
|
||||||
|
.map((item) => item.title)
|
||||||
|
.join(
|
||||||
|
" - "
|
||||||
|
)}] must belongs to the sales channel on which the cart has been created.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineItemServiceTx =
|
||||||
|
this.lineItemService_.withTransaction(transactionManager)
|
||||||
|
const inventoryServiceTx =
|
||||||
|
this.inventoryService_.withTransaction(transactionManager)
|
||||||
|
|
||||||
|
const existingItems = await lineItemServiceTx.list(
|
||||||
|
{
|
||||||
|
cart_id: cart.id,
|
||||||
|
variant_id: In([items.map((item) => item.variant_id)]),
|
||||||
|
should_merge: true,
|
||||||
|
},
|
||||||
|
{ select: ["id", "metadata", "quantity"] }
|
||||||
|
)
|
||||||
|
|
||||||
|
const existingItemsVariantMap = new Map()
|
||||||
|
existingItems.forEach((item) => {
|
||||||
|
existingItemsVariantMap.set(item.variant_id, item)
|
||||||
|
})
|
||||||
|
|
||||||
|
const lineItemsToCreate: LineItem[] = []
|
||||||
|
const lineItemsToUpdate: { [id: string]: LineItem }[] = []
|
||||||
|
for (const item of items) {
|
||||||
|
let currentItem: LineItem | undefined
|
||||||
|
|
||||||
|
const existingItem = existingItemsVariantMap.get(item.variant_id)
|
||||||
|
if (item.should_merge) {
|
||||||
|
if (existingItem && isEqual(existingItem.metadata, item.metadata)) {
|
||||||
|
currentItem = existingItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If content matches one of the line items currently in the cart we can
|
||||||
|
// simply update the quantity of the existing line item
|
||||||
|
item.quantity = currentItem
|
||||||
|
? (currentItem.quantity += item.quantity)
|
||||||
|
: item.quantity
|
||||||
|
|
||||||
|
await inventoryServiceTx.confirmInventory(
|
||||||
|
item.variant_id,
|
||||||
|
item.quantity
|
||||||
|
)
|
||||||
|
|
||||||
|
if (currentItem) {
|
||||||
|
lineItemsToUpdate[currentItem.id] = {
|
||||||
|
quantity: item.quantity,
|
||||||
|
has_shipping: false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Since the variant is eager loaded, we are removing it before the line item is being created.
|
||||||
|
delete (item as Partial<LineItem>).variant
|
||||||
|
item.has_shipping = false
|
||||||
|
item.cart_id = cart.id
|
||||||
|
lineItemsToCreate.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemKeysToUpdate = Object.keys(lineItemsToUpdate)
|
||||||
|
|
||||||
|
// Update all items that needs to be updated
|
||||||
|
if (itemKeysToUpdate.length) {
|
||||||
|
await Promise.all(
|
||||||
|
itemKeysToUpdate.map(async (id) => {
|
||||||
|
return await lineItemServiceTx.update(id, lineItemsToUpdate[id])
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create all items that needs to be created
|
||||||
|
await lineItemServiceTx.create(lineItemsToCreate)
|
||||||
|
|
||||||
|
await lineItemServiceTx
|
||||||
|
.update(
|
||||||
|
{
|
||||||
|
cart_id: cartId,
|
||||||
|
has_shipping: true,
|
||||||
|
},
|
||||||
|
{ has_shipping: false }
|
||||||
|
)
|
||||||
|
.catch((err: Error | MedusaError) => {
|
||||||
|
// We only want to catch the errors related to not found items since we don't care if there is not item to update
|
||||||
|
if ("type" in err && err.type === MedusaError.Types.NOT_FOUND) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
|
||||||
cart = await this.retrieve(cart.id, {
|
cart = await this.retrieve(cart.id, {
|
||||||
relations: ["items", "discounts", "discounts.rule", "region"],
|
relations: ["items", "discounts", "discounts.rule", "region"],
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import { isFuture, isPast } from "../utils/date-helpers"
|
|||||||
import { FlagRouter } from "../utils/flag-router"
|
import { FlagRouter } from "../utils/flag-router"
|
||||||
import CustomerService from "./customer"
|
import CustomerService from "./customer"
|
||||||
import DiscountConditionService from "./discount-condition"
|
import DiscountConditionService from "./discount-condition"
|
||||||
|
import { CalculationContextData } from "../types/totals"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides layer to manipulate discounts.
|
* Provides layer to manipulate discounts.
|
||||||
@@ -573,7 +574,7 @@ class DiscountService extends TransactionBaseService {
|
|||||||
async calculateDiscountForLineItem(
|
async calculateDiscountForLineItem(
|
||||||
discountId: string,
|
discountId: string,
|
||||||
lineItem: LineItem,
|
lineItem: LineItem,
|
||||||
cart: Cart
|
calculationContextData: CalculationContextData
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
return await this.atomicPhase_(async (transactionManager) => {
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
let adjustment = 0
|
let adjustment = 0
|
||||||
@@ -586,6 +587,12 @@ class DiscountService extends TransactionBaseService {
|
|||||||
|
|
||||||
const { type, value, allocation } = discount.rule
|
const { type, value, allocation } = discount.rule
|
||||||
|
|
||||||
|
const calculationContext = await this.totalsService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.getCalculationContext(calculationContextData, {
|
||||||
|
exclude_shipping: true,
|
||||||
|
})
|
||||||
|
|
||||||
let fullItemPrice = lineItem.unit_price * lineItem.quantity
|
let fullItemPrice = lineItem.unit_price * lineItem.quantity
|
||||||
if (
|
if (
|
||||||
this.featureFlagRouter_.isFeatureEnabled(
|
this.featureFlagRouter_.isFeatureEnabled(
|
||||||
@@ -593,11 +600,6 @@ class DiscountService extends TransactionBaseService {
|
|||||||
) &&
|
) &&
|
||||||
lineItem.includes_tax
|
lineItem.includes_tax
|
||||||
) {
|
) {
|
||||||
const calculationContext = await this.totalsService_
|
|
||||||
.withTransaction(transactionManager)
|
|
||||||
.getCalculationContext(cart, {
|
|
||||||
exclude_shipping: true,
|
|
||||||
})
|
|
||||||
const lineItemTotals = await this.newTotalsService_
|
const lineItemTotals = await this.newTotalsService_
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.getLineItemTotals([lineItem], {
|
.getLineItemTotals([lineItem], {
|
||||||
@@ -616,15 +618,26 @@ class DiscountService extends TransactionBaseService {
|
|||||||
// when a fixed discount should be applied to the total,
|
// when a fixed discount should be applied to the total,
|
||||||
// we create line adjustments for each item with an amount
|
// we create line adjustments for each item with an amount
|
||||||
// relative to the subtotal
|
// relative to the subtotal
|
||||||
const subtotal = await this.totalsService_.getSubtotal(cart, {
|
const discountedItems = calculationContextData.items.filter(
|
||||||
excludeNonDiscounts: true,
|
(item) => item.allow_discounts
|
||||||
})
|
)
|
||||||
|
const totals = await this.newTotalsService_.getLineItemTotals(
|
||||||
|
discountedItems,
|
||||||
|
{
|
||||||
|
calculationContext,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const subtotal = Object.values(totals).reduce((subtotal, total) => {
|
||||||
|
subtotal += total.subtotal
|
||||||
|
return subtotal
|
||||||
|
}, 0)
|
||||||
const nominator = Math.min(value, subtotal)
|
const nominator = Math.min(value, subtotal)
|
||||||
const totalItemPercentage = fullItemPrice / subtotal
|
const totalItemPercentage = fullItemPrice / subtotal
|
||||||
adjustment = Math.round(nominator * totalItemPercentage)
|
adjustment = Math.round(nominator * totalItemPercentage)
|
||||||
} else {
|
} else {
|
||||||
adjustment = value * lineItem.quantity
|
adjustment = value * lineItem.quantity
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the amount of the discount exceeds the total price of the item,
|
// if the amount of the discount exceeds the total price of the item,
|
||||||
// we return the total item price, else the fixed amount
|
// we return the total item price, else the fixed amount
|
||||||
return adjustment >= fullItemPrice ? fullItemPrice : adjustment
|
return adjustment >= fullItemPrice ? fullItemPrice : adjustment
|
||||||
|
|||||||
@@ -55,28 +55,28 @@ export default class EventBusService {
|
|||||||
config: ConfigModule,
|
config: ConfigModule,
|
||||||
singleton = true
|
singleton = true
|
||||||
) {
|
) {
|
||||||
const opts = {
|
|
||||||
createClient: (type: string): Redis.Redis => {
|
|
||||||
switch (type) {
|
|
||||||
case "client":
|
|
||||||
return redisClient
|
|
||||||
case "subscriber":
|
|
||||||
return redisSubscriber
|
|
||||||
default:
|
|
||||||
if (config.projectConfig.redis_url) {
|
|
||||||
return new Redis(config.projectConfig.redis_url)
|
|
||||||
}
|
|
||||||
return redisClient
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
this.config_ = config
|
this.config_ = config
|
||||||
this.manager_ = manager
|
this.manager_ = manager
|
||||||
this.logger_ = logger
|
this.logger_ = logger
|
||||||
this.stagedJobRepository_ = stagedJobRepository
|
this.stagedJobRepository_ = stagedJobRepository
|
||||||
|
|
||||||
if (singleton) {
|
if (singleton) {
|
||||||
|
const opts = {
|
||||||
|
createClient: (type: string): Redis.Redis => {
|
||||||
|
switch (type) {
|
||||||
|
case "client":
|
||||||
|
return redisClient
|
||||||
|
case "subscriber":
|
||||||
|
return redisSubscriber
|
||||||
|
default:
|
||||||
|
if (config.projectConfig.redis_url) {
|
||||||
|
return new Redis(config.projectConfig.redis_url)
|
||||||
|
}
|
||||||
|
return redisClient
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
this.observers_ = new Map()
|
this.observers_ = new Map()
|
||||||
this.queue_ = new Bull(`${this.constructor.name}:queue`, opts)
|
this.queue_ = new Bull(`${this.constructor.name}:queue`, opts)
|
||||||
this.cronHandlers_ = new Map()
|
this.cronHandlers_ = new Map()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export { default as AnalyticsConfigService } from "./analytics-config"
|
|||||||
export { default as AuthService } from "./auth"
|
export { default as AuthService } from "./auth"
|
||||||
export { default as BatchJobService } from "./batch-job"
|
export { default as BatchJobService } from "./batch-job"
|
||||||
export { default as CartService } from "./cart"
|
export { default as CartService } from "./cart"
|
||||||
|
export { default as CacheService } from "./cache"
|
||||||
export { default as ClaimService } from "./claim"
|
export { default as ClaimService } from "./claim"
|
||||||
export { default as ClaimItemService } from "./claim-item"
|
export { default as ClaimItemService } from "./claim-item"
|
||||||
export { default as CurrencyService } from "./currency"
|
export { default as CurrencyService } from "./currency"
|
||||||
@@ -35,7 +36,7 @@ export { default as ProductService } from "./product"
|
|||||||
export { default as ProductCollectionService } from "./product-collection"
|
export { default as ProductCollectionService } from "./product-collection"
|
||||||
export { default as ProductTypeService } from "./product-type"
|
export { default as ProductTypeService } from "./product-type"
|
||||||
export { default as ProductVariantService } from "./product-variant"
|
export { default as ProductVariantService } from "./product-variant"
|
||||||
import { default as PublishableApiKey } from "./publishable-api-key"
|
|
||||||
export { default as RegionService } from "./region"
|
export { default as RegionService } from "./region"
|
||||||
export { default as ReturnService } from "./return"
|
export { default as ReturnService } from "./return"
|
||||||
export { default as ReturnReasonService } from "./return-reason"
|
export { default as ReturnReasonService } from "./return-reason"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { TransactionBaseService } from "../interfaces"
|
|||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import ProductVariantService from "./product-variant"
|
import ProductVariantService from "./product-variant"
|
||||||
import { ProductVariant } from "../models"
|
import { ProductVariant } from "../models"
|
||||||
|
import { isDefined } from "../utils"
|
||||||
|
|
||||||
type InventoryServiceProps = {
|
type InventoryServiceProps = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
@@ -62,22 +63,29 @@ class InventoryService extends TransactionBaseService {
|
|||||||
* @return true if the inventory covers the quantity
|
* @return true if the inventory covers the quantity
|
||||||
*/
|
*/
|
||||||
async confirmInventory(
|
async confirmInventory(
|
||||||
variantId: string | undefined | null,
|
variantId: string | null | undefined,
|
||||||
quantity: number
|
quantity: number
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
// if variantId is undefined then confirm inventory as it
|
// if variantId is undefined then confirm inventory as it
|
||||||
// is a custom item that is not managed
|
// is a custom item that is not managed
|
||||||
if (typeof variantId === "undefined" || variantId === null) {
|
if (!isDefined(variantId) || variantId === null) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const variant = await this.productVariantService_
|
const variant = await this.productVariantService_
|
||||||
.withTransaction(this.manager_)
|
.withTransaction(this.manager_)
|
||||||
.retrieve(variantId)
|
.retrieve(variantId, {
|
||||||
|
select: [
|
||||||
|
"id",
|
||||||
|
"inventory_quantity",
|
||||||
|
"allow_backorder",
|
||||||
|
"manage_inventory",
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
const { inventory_quantity, allow_backorder, manage_inventory } = variant
|
const { inventory_quantity, allow_backorder, manage_inventory } = variant
|
||||||
const isCovered =
|
const isCovered =
|
||||||
!manage_inventory || allow_backorder || inventory_quantity >= quantity
|
!manage_inventory || allow_backorder || inventory_quantity >= quantity
|
||||||
|
|
||||||
if (!isCovered) {
|
if (!isCovered) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
|||||||
@@ -1,19 +1,14 @@
|
|||||||
import { MedusaError } from "medusa-core-utils"
|
import { MedusaError } from "medusa-core-utils"
|
||||||
import { EntityManager, In } from "typeorm"
|
import { EntityManager, In } from "typeorm"
|
||||||
|
|
||||||
import {
|
import { Cart, DiscountRuleType, LineItem, LineItemAdjustment } from "../models"
|
||||||
Cart,
|
|
||||||
DiscountRuleType,
|
|
||||||
LineItem,
|
|
||||||
LineItemAdjustment,
|
|
||||||
ProductVariant,
|
|
||||||
} from "../models"
|
|
||||||
import { LineItemAdjustmentRepository } from "../repositories/line-item-adjustment"
|
import { LineItemAdjustmentRepository } from "../repositories/line-item-adjustment"
|
||||||
import { FindConfig } from "../types/common"
|
import { FindConfig } from "../types/common"
|
||||||
import { FilterableLineItemAdjustmentProps } from "../types/line-item-adjustment"
|
import { FilterableLineItemAdjustmentProps } from "../types/line-item-adjustment"
|
||||||
import DiscountService from "./discount"
|
import DiscountService from "./discount"
|
||||||
import { TransactionBaseService } from "../interfaces"
|
import { TransactionBaseService } from "../interfaces"
|
||||||
import { buildQuery, setMetadata } from "../utils"
|
import { buildQuery, setMetadata } from "../utils"
|
||||||
|
import { CalculationContextData } from "../types/totals"
|
||||||
|
|
||||||
type LineItemAdjustmentServiceProps = {
|
type LineItemAdjustmentServiceProps = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
@@ -22,7 +17,7 @@ type LineItemAdjustmentServiceProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AdjustmentContext = {
|
type AdjustmentContext = {
|
||||||
variant: ProductVariant
|
variant: { product_id: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
type GeneratedAdjustment = {
|
type GeneratedAdjustment = {
|
||||||
@@ -174,13 +169,13 @@ class LineItemAdjustmentService extends TransactionBaseService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates adjustment for a line item
|
* Creates adjustment for a line item
|
||||||
* @param cart - the cart object holding discounts
|
* @param calculationContextData - the calculationContextData object holding discounts
|
||||||
* @param generatedLineItem - the line item for which a line item adjustment might be created
|
* @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
|
* @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
|
* @return a line item adjustment or undefined if no adjustment was created
|
||||||
*/
|
*/
|
||||||
async generateAdjustments(
|
async generateAdjustments(
|
||||||
cart: Cart,
|
calculationContextData: CalculationContextData,
|
||||||
generatedLineItem: LineItem,
|
generatedLineItem: LineItem,
|
||||||
context: AdjustmentContext
|
context: AdjustmentContext
|
||||||
): Promise<GeneratedAdjustment[]> {
|
): Promise<GeneratedAdjustment[]> {
|
||||||
@@ -196,12 +191,12 @@ class LineItemAdjustmentService extends TransactionBaseService {
|
|||||||
if (
|
if (
|
||||||
!lineItem.allow_discounts ||
|
!lineItem.allow_discounts ||
|
||||||
lineItem.is_return ||
|
lineItem.is_return ||
|
||||||
!cart?.discounts?.length
|
!calculationContextData?.discounts?.length
|
||||||
) {
|
) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const [discount] = cart.discounts.filter(
|
const [discount] = calculationContextData.discounts.filter(
|
||||||
(d) => d.rule.type !== DiscountRuleType.FREE_SHIPPING
|
(d) => d.rule.type !== DiscountRuleType.FREE_SHIPPING
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -226,7 +221,7 @@ class LineItemAdjustmentService extends TransactionBaseService {
|
|||||||
const amount = await this.discountService.calculateDiscountForLineItem(
|
const amount = await this.discountService.calculateDiscountForLineItem(
|
||||||
discount.id,
|
discount.id,
|
||||||
lineItem,
|
lineItem,
|
||||||
cart
|
calculationContextData
|
||||||
)
|
)
|
||||||
|
|
||||||
// if discounted amount is 0, then do nothing
|
// if discounted amount is 0, then do nothing
|
||||||
@@ -234,15 +229,13 @@ class LineItemAdjustmentService extends TransactionBaseService {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const adjustments = [
|
return [
|
||||||
{
|
{
|
||||||
amount,
|
amount,
|
||||||
discount_id: discount.id,
|
discount_id: discount.id,
|
||||||
description: "discount",
|
description: "discount",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return adjustments
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ import { DeepPartial } from "typeorm/common/DeepPartial"
|
|||||||
import { CartRepository } from "../repositories/cart"
|
import { CartRepository } from "../repositories/cart"
|
||||||
import { LineItemRepository } from "../repositories/line-item"
|
import { LineItemRepository } from "../repositories/line-item"
|
||||||
import { LineItemTaxLineRepository } from "../repositories/line-item-tax-line"
|
import { LineItemTaxLineRepository } from "../repositories/line-item-tax-line"
|
||||||
import { Cart, LineItem, LineItemAdjustment, LineItemTaxLine } from "../models"
|
import {
|
||||||
|
LineItem,
|
||||||
|
LineItemAdjustment,
|
||||||
|
LineItemTaxLine,
|
||||||
|
ProductVariant,
|
||||||
|
} from "../models"
|
||||||
import { FindConfig, Selector } from "../types/common"
|
import { FindConfig, Selector } from "../types/common"
|
||||||
import { FlagRouter } from "../utils/flag-router"
|
import { FlagRouter } from "../utils/flag-router"
|
||||||
import LineItemAdjustmentService from "./line-item-adjustment"
|
import LineItemAdjustmentService from "./line-item-adjustment"
|
||||||
@@ -18,8 +23,10 @@ import {
|
|||||||
RegionService,
|
RegionService,
|
||||||
TaxProviderService,
|
TaxProviderService,
|
||||||
} from "./index"
|
} from "./index"
|
||||||
import { buildQuery, setMetadata } from "../utils"
|
import { buildQuery, isString, setMetadata } from "../utils"
|
||||||
import { TransactionBaseService } from "../interfaces"
|
import { TransactionBaseService } from "../interfaces"
|
||||||
|
import { GenerateInputData, GenerateLineItemContext } from "../types/line-item"
|
||||||
|
import { ProductVariantPricing } from "../types/pricing"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
@@ -178,115 +185,205 @@ class LineItemService extends TransactionBaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async generate(
|
/**
|
||||||
variantId: string,
|
* Generate a single or multiple line item without persisting the data into the db
|
||||||
regionId: string,
|
* @param variantIdOrData
|
||||||
quantity: number,
|
* @param regionIdOrContext
|
||||||
context: {
|
* @param quantity
|
||||||
unit_price?: number
|
* @param context
|
||||||
includes_tax?: boolean
|
*/
|
||||||
metadata?: Record<string, unknown>
|
async generate<
|
||||||
customer_id?: string
|
T = string | GenerateInputData | GenerateInputData[],
|
||||||
order_edit_id?: string
|
TResult = T extends string
|
||||||
cart?: Cart
|
? LineItem
|
||||||
} = {}
|
: T extends LineItem
|
||||||
): Promise<LineItem> {
|
? LineItem
|
||||||
|
: LineItem[]
|
||||||
|
>(
|
||||||
|
variantIdOrData: string | T,
|
||||||
|
regionIdOrContext: T extends string ? string : GenerateLineItemContext,
|
||||||
|
quantity?: number,
|
||||||
|
context: GenerateLineItemContext = {}
|
||||||
|
): Promise<TResult> {
|
||||||
return await this.atomicPhase_(
|
return await this.atomicPhase_(
|
||||||
async (transactionManager: EntityManager) => {
|
async (transactionManager: EntityManager) => {
|
||||||
const [variant, region] = await Promise.all([
|
this.validateGenerateArguments(
|
||||||
this.productVariantService_
|
variantIdOrData,
|
||||||
.withTransaction(transactionManager)
|
regionIdOrContext,
|
||||||
.retrieve(variantId, {
|
quantity
|
||||||
relations: ["product"],
|
|
||||||
}),
|
|
||||||
this.regionService_
|
|
||||||
.withTransaction(transactionManager)
|
|
||||||
.retrieve(regionId),
|
|
||||||
])
|
|
||||||
|
|
||||||
let unit_price = Number(context.unit_price) < 0 ? 0 : context.unit_price
|
|
||||||
|
|
||||||
let unitPriceIncludesTax = false
|
|
||||||
|
|
||||||
let shouldMerge = false
|
|
||||||
|
|
||||||
if (context.unit_price === undefined || context.unit_price === null) {
|
|
||||||
shouldMerge = true
|
|
||||||
const variantPricing = await this.pricingService_
|
|
||||||
.withTransaction(transactionManager)
|
|
||||||
.getProductVariantPricingById(variant.id, {
|
|
||||||
region_id: region.id,
|
|
||||||
quantity: quantity,
|
|
||||||
customer_id: context?.customer_id,
|
|
||||||
include_discount_prices: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
unitPriceIncludesTax = !!variantPricing.calculated_price_includes_tax
|
|
||||||
|
|
||||||
unit_price = variantPricing.calculated_price ?? undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawLineItem: Partial<LineItem> = {
|
|
||||||
unit_price: unit_price,
|
|
||||||
title: variant.product.title,
|
|
||||||
description: variant.title,
|
|
||||||
thumbnail: variant.product.thumbnail,
|
|
||||||
variant_id: variant.id,
|
|
||||||
quantity: quantity || 1,
|
|
||||||
allow_discounts: variant.product.discountable,
|
|
||||||
is_giftcard: variant.product.is_giftcard,
|
|
||||||
metadata: context?.metadata || {},
|
|
||||||
should_merge: shouldMerge,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.featureFlagRouter_.isFeatureEnabled(
|
|
||||||
TaxInclusivePricingFeatureFlag.key
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
rawLineItem.includes_tax = unitPriceIncludesTax
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.featureFlagRouter_.isFeatureEnabled(OrderEditingFeatureFlag.key)
|
|
||||||
) {
|
|
||||||
rawLineItem.order_edit_id = context.order_edit_id || null
|
|
||||||
}
|
|
||||||
|
|
||||||
const lineItemRepo = transactionManager.getCustomRepository(
|
|
||||||
this.lineItemRepository_
|
|
||||||
)
|
)
|
||||||
const lineItem = lineItemRepo.create({
|
|
||||||
...rawLineItem,
|
|
||||||
variant,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (context.cart) {
|
const data = isString(variantIdOrData)
|
||||||
const adjustments = await this.lineItemAdjustmentService_
|
? {
|
||||||
.withTransaction(transactionManager)
|
variantId: variantIdOrData,
|
||||||
.generateAdjustments(context.cart, lineItem, { variant })
|
quantity: quantity as number,
|
||||||
lineItem.adjustments = adjustments as unknown as LineItemAdjustment[]
|
}
|
||||||
|
: variantIdOrData
|
||||||
|
const resolvedContext = isString(variantIdOrData)
|
||||||
|
? context
|
||||||
|
: (regionIdOrContext as GenerateLineItemContext)
|
||||||
|
const regionId = (
|
||||||
|
isString(variantIdOrData)
|
||||||
|
? regionIdOrContext
|
||||||
|
: resolvedContext.region_id
|
||||||
|
) as string
|
||||||
|
|
||||||
|
const resolvedData = (
|
||||||
|
Array.isArray(data) ? data : [data]
|
||||||
|
) as GenerateInputData[]
|
||||||
|
|
||||||
|
const variants = await this.productVariantService_.list(
|
||||||
|
{
|
||||||
|
id: resolvedData.map((d) => d.variantId),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relations: ["product"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const variantsMap = new Map<string, ProductVariant>()
|
||||||
|
const variantIdsToCalculatePricingFor: string[] = []
|
||||||
|
|
||||||
|
for (const variant of variants) {
|
||||||
|
variantsMap.set(variant.id, variant)
|
||||||
|
if (resolvedContext.unit_price == null) {
|
||||||
|
variantIdsToCalculatePricingFor.push(variant.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lineItem
|
const variantsPricing = await this.pricingService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.getProductVariantsPricing(variantIdsToCalculatePricingFor, {
|
||||||
|
region_id: regionId,
|
||||||
|
quantity: quantity,
|
||||||
|
customer_id: context?.customer_id,
|
||||||
|
include_discount_prices: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const generatedItems: LineItem[] = []
|
||||||
|
|
||||||
|
for (const variantData of resolvedData) {
|
||||||
|
const variant = variantsMap.get(
|
||||||
|
variantData.variantId
|
||||||
|
) as ProductVariant
|
||||||
|
const variantPricing = variantsPricing[variantData.variantId]
|
||||||
|
|
||||||
|
const lineItem = await this.generateLineItem(
|
||||||
|
variant,
|
||||||
|
variantData.quantity,
|
||||||
|
{
|
||||||
|
...resolvedContext,
|
||||||
|
variantPricing,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (resolvedContext.cart) {
|
||||||
|
const adjustments = await this.lineItemAdjustmentService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.generateAdjustments(resolvedContext.cart, lineItem, { variant })
|
||||||
|
lineItem.adjustments =
|
||||||
|
adjustments as unknown as LineItemAdjustment[]
|
||||||
|
}
|
||||||
|
|
||||||
|
generatedItems.push(lineItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Array.isArray(data)
|
||||||
|
? generatedItems
|
||||||
|
: generatedItems[0]) as unknown as TResult
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async generateLineItem(
|
||||||
|
variant: {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
product_id: string
|
||||||
|
product: {
|
||||||
|
title: string
|
||||||
|
thumbnail: string | null
|
||||||
|
discountable: boolean
|
||||||
|
is_giftcard: boolean
|
||||||
|
}
|
||||||
|
},
|
||||||
|
quantity: number,
|
||||||
|
context: GenerateLineItemContext & {
|
||||||
|
variantPricing: ProductVariantPricing
|
||||||
|
}
|
||||||
|
): Promise<LineItem> {
|
||||||
|
const transactionManager = this.transactionManager_ ?? this.manager_
|
||||||
|
|
||||||
|
let unit_price = Number(context.unit_price) < 0 ? 0 : context.unit_price
|
||||||
|
let unitPriceIncludesTax = false
|
||||||
|
let shouldMerge = false
|
||||||
|
|
||||||
|
if (context.unit_price == null) {
|
||||||
|
shouldMerge = true
|
||||||
|
|
||||||
|
unitPriceIncludesTax =
|
||||||
|
!!context.variantPricing?.calculated_price_includes_tax
|
||||||
|
unit_price = context.variantPricing?.calculated_price ?? undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawLineItem: Partial<LineItem> = {
|
||||||
|
unit_price: unit_price,
|
||||||
|
title: variant.product.title,
|
||||||
|
description: variant.title,
|
||||||
|
thumbnail: variant.product.thumbnail,
|
||||||
|
variant_id: variant.id,
|
||||||
|
quantity: quantity || 1,
|
||||||
|
allow_discounts: variant.product.discountable,
|
||||||
|
is_giftcard: variant.product.is_giftcard,
|
||||||
|
metadata: context?.metadata || {},
|
||||||
|
should_merge: shouldMerge,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.featureFlagRouter_.isFeatureEnabled(
|
||||||
|
TaxInclusivePricingFeatureFlag.key
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
rawLineItem.includes_tax = unitPriceIncludesTax
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.featureFlagRouter_.isFeatureEnabled(OrderEditingFeatureFlag.key)) {
|
||||||
|
rawLineItem.order_edit_id = context.order_edit_id || null
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineItemRepo = transactionManager.getCustomRepository(
|
||||||
|
this.lineItemRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const lineItem = lineItemRepo.create(rawLineItem)
|
||||||
|
lineItem.variant = variant as ProductVariant
|
||||||
|
|
||||||
|
return lineItem
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a line item
|
* Create a line item
|
||||||
* @param data - the line item object to create
|
* @param data - the line item object to create
|
||||||
* @return the created line item
|
* @return the created line item
|
||||||
*/
|
*/
|
||||||
async create(data: Partial<LineItem>): Promise<LineItem> {
|
async create<
|
||||||
|
T = LineItem | LineItem[],
|
||||||
|
TResult = T extends LineItem ? LineItem : LineItem[]
|
||||||
|
>(data: T): Promise<TResult> {
|
||||||
return await this.atomicPhase_(
|
return await this.atomicPhase_(
|
||||||
async (transactionManager: EntityManager) => {
|
async (transactionManager: EntityManager) => {
|
||||||
const lineItemRepository = transactionManager.getCustomRepository(
|
const lineItemRepository = transactionManager.getCustomRepository(
|
||||||
this.lineItemRepository_
|
this.lineItemRepository_
|
||||||
)
|
)
|
||||||
|
|
||||||
const item = lineItemRepository.create(data)
|
const data_ = Array.isArray(data) ? data : [data]
|
||||||
return await lineItemRepository.save(item)
|
|
||||||
|
const items = lineItemRepository.create(data_)
|
||||||
|
const lineItems = await lineItemRepository.save(items)
|
||||||
|
|
||||||
|
return (Array.isArray(data)
|
||||||
|
? lineItems
|
||||||
|
: lineItems[0]) as unknown as TResult
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -326,14 +423,8 @@ class LineItemService extends TransactionBaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lineItems = lineItems.map((item) => {
|
lineItems = lineItems.map((item) => {
|
||||||
const lineItemMetadata = metadata
|
item.metadata = metadata ? setMetadata(item, metadata) : item.metadata
|
||||||
? setMetadata(item, metadata)
|
return Object.assign(item, rest)
|
||||||
: item.metadata
|
|
||||||
|
|
||||||
return Object.assign(item, {
|
|
||||||
...rest,
|
|
||||||
metadata: lineItemMetadata,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return await lineItemRepository.save(lineItems)
|
return await lineItemRepository.save(lineItems)
|
||||||
@@ -464,6 +555,37 @@ class LineItemService extends TransactionBaseService {
|
|||||||
return await lineItemRepository.save(clonedLineItemEntities)
|
return await lineItemRepository.save(clonedLineItemEntities)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected validateGenerateArguments<
|
||||||
|
T = string | GenerateInputData | GenerateInputData[],
|
||||||
|
TResult = T extends string
|
||||||
|
? LineItem
|
||||||
|
: T extends LineItem
|
||||||
|
? LineItem
|
||||||
|
: LineItem[]
|
||||||
|
>(
|
||||||
|
variantIdOrData: string | T,
|
||||||
|
regionIdOrContext: T extends string ? string : GenerateLineItemContext,
|
||||||
|
quantity?: number
|
||||||
|
): void | never {
|
||||||
|
if (isString(variantIdOrData)) {
|
||||||
|
if (!quantity || !regionIdOrContext || !isString(regionIdOrContext)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.UNEXPECTED_STATE,
|
||||||
|
"The generate method has been called with a variant id but one of the argument quantity or regionId is missing. Please, provide the variantId, quantity and regionId."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const resolvedContext = regionIdOrContext as GenerateLineItemContext
|
||||||
|
|
||||||
|
if (!resolvedContext.region_id) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.UNEXPECTED_STATE,
|
||||||
|
"The generate method has been called with the data but the context is missing either region_id or region. Please provide at least one of region or region_id."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LineItemService
|
export default LineItemService
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
PriceSelectionContext,
|
PriceSelectionContext,
|
||||||
} from "../interfaces/price-selection-strategy"
|
} from "../interfaces/price-selection-strategy"
|
||||||
import TaxInclusivePricingFeatureFlag from "../loaders/feature-flags/tax-inclusive-pricing"
|
import TaxInclusivePricingFeatureFlag from "../loaders/feature-flags/tax-inclusive-pricing"
|
||||||
import { Product, ProductVariant, ShippingOption } from "../models"
|
import { Product, ProductVariant, Region, ShippingOption } from "../models"
|
||||||
import {
|
import {
|
||||||
PricedProduct,
|
PricedProduct,
|
||||||
PricedShippingOption,
|
PricedShippingOption,
|
||||||
@@ -73,8 +73,9 @@ class PricingService extends TransactionBaseService {
|
|||||||
let taxRate: number | null = null
|
let taxRate: number | null = null
|
||||||
let currencyCode = context.currency_code
|
let currencyCode = context.currency_code
|
||||||
|
|
||||||
|
let region: Region
|
||||||
if (context.region_id) {
|
if (context.region_id) {
|
||||||
const region = await this.regionService
|
region = await this.regionService
|
||||||
.withTransaction(this.manager_)
|
.withTransaction(this.manager_)
|
||||||
.retrieve(context.region_id, {
|
.retrieve(context.region_id, {
|
||||||
select: ["id", "currency_code", "automatic_taxes", "tax_rate"],
|
select: ["id", "currency_code", "automatic_taxes", "tax_rate"],
|
||||||
@@ -247,6 +248,7 @@ class PricingService extends TransactionBaseService {
|
|||||||
* @param variantId - the id of the variant to get prices for
|
* @param variantId - the id of the variant to get prices for
|
||||||
* @param context - the price selection context to use
|
* @param context - the price selection context to use
|
||||||
* @return The product variant prices
|
* @return The product variant prices
|
||||||
|
* @deprecated Use {@link getProductVariantsPricing} instead.
|
||||||
*/
|
*/
|
||||||
async getProductVariantPricingById(
|
async getProductVariantPricingById(
|
||||||
variantId: string,
|
variantId: string,
|
||||||
@@ -267,6 +269,7 @@ class PricingService extends TransactionBaseService {
|
|||||||
const { product_id } = await this.productVariantService
|
const { product_id } = await this.productVariantService
|
||||||
.withTransaction(this.manager_)
|
.withTransaction(this.manager_)
|
||||||
.retrieve(variantId, { select: ["id", "product_id"] })
|
.retrieve(variantId, { select: ["id", "product_id"] })
|
||||||
|
|
||||||
productRates = await this.taxProviderService
|
productRates = await this.taxProviderService
|
||||||
.withTransaction(this.manager_)
|
.withTransaction(this.manager_)
|
||||||
.getRegionRatesForProduct(product_id, {
|
.getRegionRatesForProduct(product_id, {
|
||||||
@@ -282,6 +285,69 @@ class PricingService extends TransactionBaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the prices for a collection of variants.
|
||||||
|
* @param variantIds - the id of the variants to get the prices for
|
||||||
|
* @param context - the price selection context to use
|
||||||
|
* @return The product variant prices
|
||||||
|
*/
|
||||||
|
async getProductVariantsPricing<
|
||||||
|
T = string | string[],
|
||||||
|
TOutput = T extends string
|
||||||
|
? ProductVariantPricing
|
||||||
|
: { [variant_id: string]: ProductVariantPricing }
|
||||||
|
>(
|
||||||
|
variantIds: T,
|
||||||
|
context: PriceSelectionContext | PricingContext
|
||||||
|
): Promise<TOutput> {
|
||||||
|
let pricingContext: PricingContext
|
||||||
|
if ("automatic_taxes" in context) {
|
||||||
|
pricingContext = context
|
||||||
|
} else {
|
||||||
|
pricingContext = await this.collectPricingContext(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ids = (
|
||||||
|
Array.isArray(variantIds) ? variantIds : [variantIds]
|
||||||
|
) as string[]
|
||||||
|
|
||||||
|
const variants = await this.productVariantService
|
||||||
|
.withTransaction(this.manager_)
|
||||||
|
.list({ id: ids }, { select: ["id", "product_id"] })
|
||||||
|
|
||||||
|
const variantsMap = new Map(
|
||||||
|
variants.map((variant) => {
|
||||||
|
return [variant.id, variant]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const pricingResult: { [variant_id: string]: ProductVariantPricing } = {}
|
||||||
|
for (const variantId of ids) {
|
||||||
|
const { id, product_id } = variantsMap.get(variantId)!
|
||||||
|
|
||||||
|
let productRates: TaxServiceRate[] = []
|
||||||
|
|
||||||
|
if (pricingContext.price_selection.region_id) {
|
||||||
|
productRates = await this.taxProviderService
|
||||||
|
.withTransaction(this.manager_)
|
||||||
|
.getRegionRatesForProduct(product_id, {
|
||||||
|
id: pricingContext.price_selection.region_id,
|
||||||
|
tax_rate: pricingContext.tax_rate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pricingResult[id] = await this.getProductVariantPricing_(
|
||||||
|
id,
|
||||||
|
productRates,
|
||||||
|
pricingContext
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (!Array.isArray(variantIds)
|
||||||
|
? Object.values(pricingResult)[0]
|
||||||
|
: pricingResult) as unknown as TOutput
|
||||||
|
}
|
||||||
|
|
||||||
private async getProductPricing_(
|
private async getProductPricing_(
|
||||||
productId: string,
|
productId: string,
|
||||||
variants: ProductVariant[],
|
variants: ProductVariant[],
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
} from "../interfaces"
|
} from "../interfaces"
|
||||||
import {
|
import {
|
||||||
Cart,
|
Cart,
|
||||||
|
ClaimOrder,
|
||||||
Discount,
|
Discount,
|
||||||
DiscountRuleType,
|
DiscountRuleType,
|
||||||
LineItem,
|
LineItem,
|
||||||
@@ -13,10 +14,12 @@ import {
|
|||||||
Order,
|
Order,
|
||||||
ShippingMethod,
|
ShippingMethod,
|
||||||
ShippingMethodTaxLine,
|
ShippingMethodTaxLine,
|
||||||
|
Swap,
|
||||||
} from "../models"
|
} from "../models"
|
||||||
import { isCart } from "../types/cart"
|
import { isCart } from "../types/cart"
|
||||||
import { isOrder } from "../types/orders"
|
import { isOrder } from "../types/orders"
|
||||||
import {
|
import {
|
||||||
|
CalculationContextData,
|
||||||
LineAllocationsMap,
|
LineAllocationsMap,
|
||||||
LineDiscount,
|
LineDiscount,
|
||||||
LineDiscountAmount,
|
LineDiscountAmount,
|
||||||
@@ -429,7 +432,12 @@ class TotalsService extends TransactionBaseService {
|
|||||||
* @return the allocation map for the line items in the cart or order.
|
* @return the allocation map for the line items in the cart or order.
|
||||||
*/
|
*/
|
||||||
async getAllocationMap(
|
async getAllocationMap(
|
||||||
orderOrCart: Cart | Order,
|
orderOrCart: {
|
||||||
|
discounts?: Discount[]
|
||||||
|
items: LineItem[]
|
||||||
|
swaps?: Swap[]
|
||||||
|
claims?: ClaimOrder[]
|
||||||
|
},
|
||||||
options: AllocationMapOptions = {}
|
options: AllocationMapOptions = {}
|
||||||
): Promise<LineAllocationsMap> {
|
): Promise<LineAllocationsMap> {
|
||||||
const allocationMap: LineAllocationsMap = {}
|
const allocationMap: LineAllocationsMap = {}
|
||||||
@@ -700,19 +708,23 @@ class TotalsService extends TransactionBaseService {
|
|||||||
* order
|
* order
|
||||||
*/
|
*/
|
||||||
getLineDiscounts(
|
getLineDiscounts(
|
||||||
cartOrOrder: Cart | Order,
|
cartOrOrder: {
|
||||||
|
items: LineItem[]
|
||||||
|
swaps?: Swap[]
|
||||||
|
claims?: ClaimOrder[]
|
||||||
|
},
|
||||||
discount: Discount
|
discount: Discount
|
||||||
): LineDiscountAmount[] {
|
): LineDiscountAmount[] {
|
||||||
let merged: LineItem[] = [...(cartOrOrder.items ?? [])]
|
let merged: LineItem[] = [...(cartOrOrder.items ?? [])]
|
||||||
|
|
||||||
// merge items from order with items from order swaps
|
// merge items from order with items from order swaps
|
||||||
if ("swaps" in cartOrOrder && cartOrOrder.swaps.length) {
|
if ("swaps" in cartOrOrder && cartOrOrder.swaps?.length) {
|
||||||
for (const s of cartOrOrder.swaps) {
|
for (const s of cartOrOrder.swaps) {
|
||||||
merged = [...merged, ...s.additional_items]
|
merged = [...merged, ...s.additional_items]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("claims" in cartOrOrder && cartOrOrder.claims.length) {
|
if ("claims" in cartOrOrder && cartOrOrder.claims?.length) {
|
||||||
for (const c of cartOrOrder.claims) {
|
for (const c of cartOrOrder.claims) {
|
||||||
merged = [...merged, ...c.additional_items]
|
merged = [...merged, ...c.additional_items]
|
||||||
}
|
}
|
||||||
@@ -1051,15 +1063,15 @@ class TotalsService extends TransactionBaseService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares the calculation context for a tax total calculation.
|
* Prepares the calculation context for a tax total calculation.
|
||||||
* @param cartOrOrder - the cart or order to get the calculation context for
|
* @param calculationContextData - the calculationContextData to get the calculation context for
|
||||||
* @param options - options to gather context by
|
* @param options - options to gather context by
|
||||||
* @return the tax calculation context
|
* @return the tax calculation context
|
||||||
*/
|
*/
|
||||||
async getCalculationContext(
|
async getCalculationContext(
|
||||||
cartOrOrder: Cart | Order,
|
calculationContextData: CalculationContextData,
|
||||||
options: CalculationContextOptions = {}
|
options: CalculationContextOptions = {}
|
||||||
): Promise<TaxCalculationContext> {
|
): Promise<TaxCalculationContext> {
|
||||||
const allocationMap = await this.getAllocationMap(cartOrOrder, {
|
const allocationMap = await this.getAllocationMap(calculationContextData, {
|
||||||
exclude_gift_cards: options.exclude_gift_cards,
|
exclude_gift_cards: options.exclude_gift_cards,
|
||||||
exclude_discounts: options.exclude_discounts,
|
exclude_discounts: options.exclude_discounts,
|
||||||
})
|
})
|
||||||
@@ -1067,14 +1079,14 @@ class TotalsService extends TransactionBaseService {
|
|||||||
let shippingMethods: ShippingMethod[] = []
|
let shippingMethods: ShippingMethod[] = []
|
||||||
// Default to include shipping methods
|
// Default to include shipping methods
|
||||||
if (!options.exclude_shipping) {
|
if (!options.exclude_shipping) {
|
||||||
shippingMethods = cartOrOrder.shipping_methods || []
|
shippingMethods = calculationContextData.shipping_methods || []
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
shipping_address: cartOrOrder.shipping_address,
|
shipping_address: calculationContextData.shipping_address,
|
||||||
shipping_methods: shippingMethods,
|
shipping_methods: shippingMethods,
|
||||||
customer: cartOrOrder.customer,
|
customer: calculationContextData.customer,
|
||||||
region: cartOrOrder.region,
|
region: calculationContextData.region,
|
||||||
is_return: options.is_return ?? false,
|
is_return: options.is_return ?? false,
|
||||||
allocation_map: allocationMap,
|
allocation_map: allocationMap,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import TaxInclusivePricingFeatureFlag from "../../loaders/feature-flags/tax-inclusive-pricing"
|
import TaxInclusivePricingFeatureFlag from "../../loaders/feature-flags/tax-inclusive-pricing"
|
||||||
import { FlagRouter } from "../../utils/flag-router"
|
import { FlagRouter } from "../../utils/flag-router"
|
||||||
import PriceSelectionStrategy from "../price-selection"
|
import PriceSelectionStrategy from "../price-selection"
|
||||||
|
import { cacheServiceMock } from "../../services/__mocks__/cache"
|
||||||
|
|
||||||
const executeTest =
|
const executeTest =
|
||||||
(flagValue) =>
|
(flagValue) =>
|
||||||
@@ -226,6 +227,7 @@ const executeTest =
|
|||||||
manager: mockEntityManager,
|
manager: mockEntityManager,
|
||||||
moneyAmountRepository: mockMoneyAmountRepository,
|
moneyAmountRepository: mockMoneyAmountRepository,
|
||||||
featureFlagRouter,
|
featureFlagRouter,
|
||||||
|
cacheService: cacheServiceMock,
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -211,13 +211,19 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy {
|
|||||||
|
|
||||||
return await this.atomicPhase_(
|
return await this.atomicPhase_(
|
||||||
async (transactionManager) => {
|
async (transactionManager) => {
|
||||||
let batchJob = (await this.batchJobService_
|
const productServiceTx =
|
||||||
.withTransaction(transactionManager)
|
this.productService_.withTransaction(transactionManager)
|
||||||
.retrieve(batchJobId)) as ProductExportBatchJob
|
const batchJobServiceTx =
|
||||||
|
this.batchJobService_.withTransaction(transactionManager)
|
||||||
|
const fileServiceTx =
|
||||||
|
this.fileService_.withTransaction(transactionManager)
|
||||||
|
|
||||||
const { writeStream, fileKey, promise } = await this.fileService_
|
let batchJob = (await batchJobServiceTx.retrieve(
|
||||||
.withTransaction(transactionManager)
|
batchJobId
|
||||||
.getUploadStreamDescriptor({
|
)) as ProductExportBatchJob
|
||||||
|
|
||||||
|
const { writeStream, fileKey, promise } =
|
||||||
|
await fileServiceTx.getUploadStreamDescriptor({
|
||||||
name: `exports/products/product-export-${Date.now()}`,
|
name: `exports/products/product-export-${Date.now()}`,
|
||||||
ext: "csv",
|
ext: "csv",
|
||||||
})
|
})
|
||||||
@@ -226,14 +232,12 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy {
|
|||||||
writeStream.write(header)
|
writeStream.write(header)
|
||||||
approximateFileSize += Buffer.from(header).byteLength
|
approximateFileSize += Buffer.from(header).byteLength
|
||||||
|
|
||||||
await this.batchJobService_
|
await batchJobServiceTx.update(batchJobId, {
|
||||||
.withTransaction(transactionManager)
|
result: {
|
||||||
.update(batchJobId, {
|
file_key: fileKey,
|
||||||
result: {
|
file_size: approximateFileSize,
|
||||||
file_key: fileKey,
|
},
|
||||||
file_size: approximateFileSize,
|
})
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
advancementCount =
|
advancementCount =
|
||||||
batchJob.result?.advancement_count ?? advancementCount
|
batchJob.result?.advancement_count ?? advancementCount
|
||||||
@@ -241,26 +245,25 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy {
|
|||||||
limit = batchJob.context?.list_config?.take ?? limit
|
limit = batchJob.context?.list_config?.take ?? limit
|
||||||
|
|
||||||
const { list_config = {}, filterable_fields = {} } = batchJob.context
|
const { list_config = {}, filterable_fields = {} } = batchJob.context
|
||||||
const [productList, count] = await this.productService_
|
const [productList, count] = await productServiceTx.listAndCount(
|
||||||
.withTransaction(transactionManager)
|
filterable_fields,
|
||||||
.listAndCount(filterable_fields, {
|
{
|
||||||
...list_config,
|
...list_config,
|
||||||
skip: offset,
|
skip: offset,
|
||||||
take: Math.min(batchJob.context.batch_size ?? Infinity, limit),
|
take: Math.min(batchJob.context.batch_size ?? Infinity, limit),
|
||||||
} as FindProductConfig)
|
} as FindProductConfig
|
||||||
|
)
|
||||||
|
|
||||||
productCount = batchJob.context?.batch_size ?? count
|
productCount = batchJob.context?.batch_size ?? count
|
||||||
let products: Product[] = productList
|
let products: Product[] = productList
|
||||||
|
|
||||||
while (offset < productCount) {
|
while (offset < productCount) {
|
||||||
if (!products?.length) {
|
if (!products?.length) {
|
||||||
products = await this.productService_
|
products = await productServiceTx.list(filterable_fields, {
|
||||||
.withTransaction(transactionManager)
|
...list_config,
|
||||||
.list(filterable_fields, {
|
skip: offset,
|
||||||
...list_config,
|
take: Math.min(productCount - offset, limit),
|
||||||
skip: offset,
|
} as FindProductConfig)
|
||||||
take: Math.min(productCount - offset, limit),
|
|
||||||
} as FindProductConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
products.forEach((product: Product) => {
|
products.forEach((product: Product) => {
|
||||||
@@ -275,16 +278,14 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy {
|
|||||||
offset += products.length
|
offset += products.length
|
||||||
products = []
|
products = []
|
||||||
|
|
||||||
batchJob = (await this.batchJobService_
|
batchJob = (await batchJobServiceTx.update(batchJobId, {
|
||||||
.withTransaction(transactionManager)
|
result: {
|
||||||
.update(batchJobId, {
|
file_size: approximateFileSize,
|
||||||
result: {
|
count: productCount,
|
||||||
file_size: approximateFileSize,
|
advancement_count: advancementCount,
|
||||||
count: productCount,
|
progress: advancementCount / productCount,
|
||||||
advancement_count: advancementCount,
|
},
|
||||||
progress: advancementCount / productCount,
|
})) as ProductExportBatchJob
|
||||||
},
|
|
||||||
})) as ProductExportBatchJob
|
|
||||||
|
|
||||||
if (batchJob.status === BatchJobStatus.CANCELED) {
|
if (batchJob.status === BatchJobStatus.CANCELED) {
|
||||||
writeStream.end()
|
writeStream.end()
|
||||||
|
|||||||
@@ -1,27 +1,36 @@
|
|||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import {
|
import {
|
||||||
AbstractPriceSelectionStrategy,
|
AbstractPriceSelectionStrategy,
|
||||||
|
ICacheService,
|
||||||
IPriceSelectionStrategy,
|
IPriceSelectionStrategy,
|
||||||
PriceSelectionContext,
|
PriceSelectionContext,
|
||||||
PriceSelectionResult,
|
PriceSelectionResult,
|
||||||
PriceType,
|
PriceType,
|
||||||
} from "../interfaces/price-selection-strategy"
|
} from "../interfaces"
|
||||||
import TaxInclusivePricingFeatureFlag from "../loaders/feature-flags/tax-inclusive-pricing"
|
import TaxInclusivePricingFeatureFlag from "../loaders/feature-flags/tax-inclusive-pricing"
|
||||||
import { MoneyAmountRepository } from "../repositories/money-amount"
|
import { MoneyAmountRepository } from "../repositories/money-amount"
|
||||||
import { TaxServiceRate } from "../types/tax-service"
|
import { TaxServiceRate } from "../types/tax-service"
|
||||||
import { FlagRouter } from "../utils/flag-router"
|
import { FlagRouter } from "../utils/flag-router"
|
||||||
import { isDefined } from "../utils/is-defined"
|
import { isDefined } from "../utils"
|
||||||
|
|
||||||
class PriceSelectionStrategy extends AbstractPriceSelectionStrategy {
|
class PriceSelectionStrategy extends AbstractPriceSelectionStrategy {
|
||||||
private moneyAmountRepository_: typeof MoneyAmountRepository
|
protected manager_: EntityManager
|
||||||
private featureFlagRouter_: FlagRouter
|
|
||||||
private manager_: EntityManager
|
|
||||||
|
|
||||||
constructor({ manager, featureFlagRouter, moneyAmountRepository }) {
|
protected readonly featureFlagRouter_: FlagRouter
|
||||||
|
protected moneyAmountRepository_: typeof MoneyAmountRepository
|
||||||
|
protected cacheService_: ICacheService
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
manager,
|
||||||
|
featureFlagRouter,
|
||||||
|
moneyAmountRepository,
|
||||||
|
cacheService,
|
||||||
|
}) {
|
||||||
super()
|
super()
|
||||||
this.manager_ = manager
|
this.manager_ = manager
|
||||||
this.moneyAmountRepository_ = moneyAmountRepository
|
this.moneyAmountRepository_ = moneyAmountRepository
|
||||||
this.featureFlagRouter_ = featureFlagRouter
|
this.featureFlagRouter_ = featureFlagRouter
|
||||||
|
this.cacheService_ = cacheService
|
||||||
}
|
}
|
||||||
|
|
||||||
withTransaction(manager: EntityManager): IPriceSelectionStrategy {
|
withTransaction(manager: EntityManager): IPriceSelectionStrategy {
|
||||||
@@ -33,6 +42,7 @@ class PriceSelectionStrategy extends AbstractPriceSelectionStrategy {
|
|||||||
manager: manager,
|
manager: manager,
|
||||||
moneyAmountRepository: this.moneyAmountRepository_,
|
moneyAmountRepository: this.moneyAmountRepository_,
|
||||||
featureFlagRouter: this.featureFlagRouter_,
|
featureFlagRouter: this.featureFlagRouter_,
|
||||||
|
cacheService: this.cacheService_,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,14 +50,30 @@ class PriceSelectionStrategy extends AbstractPriceSelectionStrategy {
|
|||||||
variant_id: string,
|
variant_id: string,
|
||||||
context: PriceSelectionContext
|
context: PriceSelectionContext
|
||||||
): Promise<PriceSelectionResult> {
|
): Promise<PriceSelectionResult> {
|
||||||
|
// TODO: Refactor using the cache decorators when it will be finished
|
||||||
|
const cacheKey = this.getCacheKey(variant_id, context)
|
||||||
|
const cached = await this.cacheService_
|
||||||
|
.get<PriceSelectionResult>(cacheKey)
|
||||||
|
.catch(() => void 0)
|
||||||
|
if (cached) {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
|
||||||
|
let result
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.featureFlagRouter_.isFeatureEnabled(
|
this.featureFlagRouter_.isFeatureEnabled(
|
||||||
TaxInclusivePricingFeatureFlag.key
|
TaxInclusivePricingFeatureFlag.key
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return this.calculateVariantPrice_new(variant_id, context)
|
result = await this.calculateVariantPrice_new(variant_id, context)
|
||||||
|
} else {
|
||||||
|
result = await this.calculateVariantPrice_old(variant_id, context)
|
||||||
}
|
}
|
||||||
return this.calculateVariantPrice_old(variant_id, context)
|
|
||||||
|
await this.cacheService_.set(cacheKey, result)
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private async calculateVariantPrice_new(
|
private async calculateVariantPrice_new(
|
||||||
@@ -213,6 +239,21 @@ class PriceSelectionStrategy extends AbstractPriceSelectionStrategy {
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getCacheKey(
|
||||||
|
variantId: string,
|
||||||
|
context: PriceSelectionContext
|
||||||
|
): string {
|
||||||
|
const taxRate =
|
||||||
|
context.tax_rates?.reduce(
|
||||||
|
(accRate: number, nextTaxRate: TaxServiceRate) => {
|
||||||
|
return accRate + (nextTaxRate.rate || 0) / 100
|
||||||
|
},
|
||||||
|
0
|
||||||
|
) || 0
|
||||||
|
|
||||||
|
return `ps:${variantId}:${context.region_id}:${context.currency_code}:${context.customer_id}:${context.quantity}:${context.include_discount_prices}:${taxRate}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isValidAmount = (
|
const isValidAmount = (
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import {
|
||||||
|
CacheService,
|
||||||
|
EventBusService,
|
||||||
|
ProductVariantService,
|
||||||
|
} from "../services"
|
||||||
|
|
||||||
|
type ProductVariantUpdatedEventData = {
|
||||||
|
id: string
|
||||||
|
product_id: string
|
||||||
|
fields: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
class PricingSubscriber {
|
||||||
|
protected readonly eventBus_: EventBusService
|
||||||
|
protected readonly cacheService_: CacheService
|
||||||
|
|
||||||
|
constructor({ eventBusService, cacheService }) {
|
||||||
|
this.eventBus_ = eventBusService
|
||||||
|
this.cacheService_ = cacheService
|
||||||
|
|
||||||
|
this.eventBus_.subscribe(
|
||||||
|
ProductVariantService.Events.UPDATED,
|
||||||
|
async (data) => {
|
||||||
|
const { id, fields } = data as ProductVariantUpdatedEventData
|
||||||
|
if (fields.includes("prices")) {
|
||||||
|
await this.cacheService_.invalidate(`ps:${id}*`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PricingSubscriber
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
import { ValidateNested } from "class-validator"
|
import { ValidateNested } from "class-validator"
|
||||||
import { IsType } from "../utils/validators/is-type"
|
import { IsType } from "../utils/validators/is-type"
|
||||||
import { Cart, CartType } from "../models/cart"
|
import { Cart, CartType } from "../models/cart"
|
||||||
import {
|
import { AddressPayload, DateComparisonOperator, StringComparisonOperator } from "./common"
|
||||||
AddressPayload,
|
import { Region } from "../models"
|
||||||
DateComparisonOperator,
|
|
||||||
StringComparisonOperator,
|
|
||||||
} from "./common"
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export function isCart(object: any): object is Cart {
|
export function isCart(object: any): object is Cart {
|
||||||
@@ -34,6 +31,11 @@ export type LineItemUpdate = {
|
|||||||
variant_id?: string
|
variant_id?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type LineItemValidateData = {
|
||||||
|
variant?: { product_id: string };
|
||||||
|
variant_id: string
|
||||||
|
}
|
||||||
|
|
||||||
class GiftCard {
|
class GiftCard {
|
||||||
code: string
|
code: string
|
||||||
}
|
}
|
||||||
@@ -44,6 +46,7 @@ class Discount {
|
|||||||
|
|
||||||
export type CartCreateProps = {
|
export type CartCreateProps = {
|
||||||
region_id?: string
|
region_id?: string
|
||||||
|
region?: Region
|
||||||
email?: string
|
email?: string
|
||||||
billing_address_id?: string
|
billing_address_id?: string
|
||||||
billing_address?: Partial<AddressPayload>
|
billing_address?: Partial<AddressPayload>
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { CalculationContextData } from "./totals"
|
||||||
|
|
||||||
|
export type GenerateInputData = {
|
||||||
|
variantId: string
|
||||||
|
quantity: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GenerateLineItemContext = {
|
||||||
|
region_id?: string
|
||||||
|
unit_price?: number
|
||||||
|
includes_tax?: boolean
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
customer_id?: string
|
||||||
|
order_edit_id?: string
|
||||||
|
cart?: CalculationContextData
|
||||||
|
}
|
||||||
@@ -1,4 +1,24 @@
|
|||||||
import { LineItem } from "../models"
|
import {
|
||||||
|
Address,
|
||||||
|
ClaimOrder,
|
||||||
|
Customer,
|
||||||
|
Discount,
|
||||||
|
LineItem,
|
||||||
|
Region,
|
||||||
|
ShippingMethod,
|
||||||
|
Swap,
|
||||||
|
} from "../models"
|
||||||
|
|
||||||
|
export type CalculationContextData = {
|
||||||
|
discounts: Discount[]
|
||||||
|
items: LineItem[]
|
||||||
|
customer: Customer
|
||||||
|
region: Region
|
||||||
|
shipping_address: Address | null
|
||||||
|
swaps?: Swap[]
|
||||||
|
claims?: ClaimOrder[]
|
||||||
|
shipping_methods?: ShippingMethod[]
|
||||||
|
}
|
||||||
|
|
||||||
/** The amount of a gift card allocated to a line item */
|
/** The amount of a gift card allocated to a line item */
|
||||||
export type GiftCardAllocation = {
|
export type GiftCardAllocation = {
|
||||||
|
|||||||
Reference in new issue
Block a user