feat(medusa): Continue create product workflow changes (#4473)
This commit is contained in:
1 parent
edf93d972d
commit
d2a8cf0378
103 files changed
+1845
-510
No files matched your search
@@ -43,7 +43,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
@@ -157,7 +159,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
|
|||||||
@@ -12,10 +12,7 @@ const {
|
|||||||
DiscountConditionOperator,
|
DiscountConditionOperator,
|
||||||
} = require("@medusajs/medusa")
|
} = require("@medusajs/medusa")
|
||||||
const { IdMap } = require("medusa-test-utils")
|
const { IdMap } = require("medusa-test-utils")
|
||||||
const {
|
const { simpleDiscountFactory } = require("../../../factories")
|
||||||
simpleDiscountFactory,
|
|
||||||
simpleSalesChannelFactory,
|
|
||||||
} = require("../../../factories")
|
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
|
|||||||
@@ -1879,7 +1879,7 @@ describe("/admin/discounts", () => {
|
|||||||
const cond = discount.data.discount.rule.conditions[0]
|
const cond = discount.data.discount.rule.conditions[0]
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/admin/discounts/test-discount/conditions/${cond.id}?expand=rule,rule.conditions,rule.conditions.products`,
|
`/admin/discounts/test-discount/conditions/${cond.id}?expand=rule.conditions.products.profiles`,
|
||||||
{
|
{
|
||||||
products: [prod2.id],
|
products: [prod2.id],
|
||||||
},
|
},
|
||||||
@@ -1911,6 +1911,8 @@ describe("/admin/discounts", () => {
|
|||||||
created_at: expect.any(String),
|
created_at: expect.any(String),
|
||||||
updated_at: expect.any(String),
|
updated_at: expect.any(String),
|
||||||
profile_id: expect.any(String),
|
profile_id: expect.any(String),
|
||||||
|
profiles: expect.any(Array),
|
||||||
|
profile: expect.any(Object),
|
||||||
type_id: expect.any(String),
|
type_id: expect.any(String),
|
||||||
id: "test-product",
|
id: "test-product",
|
||||||
},
|
},
|
||||||
@@ -2047,7 +2049,7 @@ describe("/admin/discounts", () => {
|
|||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
const discountCondition = await api.get(
|
const discountCondition = await api.get(
|
||||||
"/admin/discounts/test-discount/conditions/test-condition?expand=products&fields=id,type",
|
"/admin/discounts/test-discount/conditions/test-condition?expand=products.profiles&fields=id,type",
|
||||||
adminReqConfig
|
adminReqConfig
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2061,6 +2063,8 @@ describe("/admin/discounts", () => {
|
|||||||
{
|
{
|
||||||
id: "test-product",
|
id: "test-product",
|
||||||
profile_id: expect.any(String),
|
profile_id: expect.any(String),
|
||||||
|
profiles: expect.any(Array),
|
||||||
|
profile: expect.any(Object),
|
||||||
type_id: expect.any(String),
|
type_id: expect.any(String),
|
||||||
created_at: expect.any(String),
|
created_at: expect.any(String),
|
||||||
updated_at: expect.any(String),
|
updated_at: expect.any(String),
|
||||||
@@ -2353,7 +2357,7 @@ describe("/admin/discounts", () => {
|
|||||||
const cond = discount.data.discount.rule.conditions[0]
|
const cond = discount.data.discount.rule.conditions[0]
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/admin/discounts/test-discount/conditions/${cond.id}/batch?expand=rule,rule.conditions,rule.conditions.products`,
|
`/admin/discounts/test-discount/conditions/${cond.id}/batch?expand=rule.conditions.products.profiles`,
|
||||||
{
|
{
|
||||||
resources: [{ id: prod2.id }, { id: prod3.id }],
|
resources: [{ id: prod2.id }, { id: prod3.id }],
|
||||||
},
|
},
|
||||||
@@ -2490,7 +2494,7 @@ describe("/admin/discounts", () => {
|
|||||||
const cond = discount.data.discount.rule.conditions[0]
|
const cond = discount.data.discount.rule.conditions[0]
|
||||||
|
|
||||||
const response = await api.delete(
|
const response = await api.delete(
|
||||||
`/admin/discounts/test-discount/conditions/${cond.id}/batch?expand=rule,rule.conditions,rule.conditions.products`,
|
`/admin/discounts/test-discount/conditions/${cond.id}/batch?expand=rule.conditions.products.profiles`,
|
||||||
{
|
{
|
||||||
...adminReqConfig,
|
...adminReqConfig,
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -26,9 +26,6 @@ const { IdMap } = require("medusa-test-utils")
|
|||||||
|
|
||||||
jest.setTimeout(50000)
|
jest.setTimeout(50000)
|
||||||
|
|
||||||
const testProductId = "test-product"
|
|
||||||
const testProduct1Id = "test-product1"
|
|
||||||
const testProductFilteringId1 = "test-product_filtering_1"
|
|
||||||
const adminHeaders = {
|
const adminHeaders = {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: "Bearer test_token",
|
Authorization: "Bearer test_token",
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ describe("/admin/shipping-options", () => {
|
|||||||
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("/admin/shipping-profiles", () => {
|
|||||||
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 () => {
|
||||||
|
|||||||
@@ -222,6 +222,7 @@ describe("/store/products", () => {
|
|||||||
"tags",
|
"tags",
|
||||||
"collection",
|
"collection",
|
||||||
"type",
|
"type",
|
||||||
|
"profiles",
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -992,7 +993,12 @@ describe("/store/products", () => {
|
|||||||
it("response contains only fields defined with `fields` param", async () => {
|
it("response contains only fields defined with `fields` param", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
const fields = allowedStoreProductsFields
|
// profile_id is not a column in the products table, so it should be ignored as it
|
||||||
|
// will be rejected by typeorm as invalid, though, it is an entity property
|
||||||
|
// that we want to return, so it part of the allowedStoreProductsFields
|
||||||
|
const fields = allowedStoreProductsFields.filter(
|
||||||
|
(f) => f !== "profile_id"
|
||||||
|
)
|
||||||
|
|
||||||
const response = await api.get(
|
const response = await api.get(
|
||||||
`/store/products/test-product?fields=${fields.join(",")}`
|
`/store/products/test-product?fields=${fields.join(",")}`
|
||||||
|
|||||||
@@ -91,8 +91,11 @@ export const simpleProductFactory = async (
|
|||||||
discountable: !data.is_giftcard,
|
discountable: !data.is_giftcard,
|
||||||
tags: [] as ProductTag[],
|
tags: [] as ProductTag[],
|
||||||
profile_id: data.is_giftcard ? gcProfile?.id : defaultProfile?.id,
|
profile_id: data.is_giftcard ? gcProfile?.id : defaultProfile?.id,
|
||||||
|
profiles: [
|
||||||
|
{ id: data.is_giftcard ? gcProfile?.id : defaultProfile?.id },
|
||||||
|
] as ShippingProfile[],
|
||||||
metadata: data.metadata || null,
|
metadata: data.metadata || null,
|
||||||
} as Product
|
} as unknown as Product
|
||||||
|
|
||||||
if (typeof data.tags !== "undefined") {
|
if (typeof data.tags !== "undefined") {
|
||||||
for (let i = 0; i < data.tags.length; i++) {
|
for (let i = 0; i < data.tags.length; i++) {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const simpleProductVariantFactory = async (
|
|||||||
const options = data.options || [{ option_id: "test-option", value: "Large" }]
|
const options = data.options || [{ option_id: "test-option", value: "Large" }]
|
||||||
for (const o of options) {
|
for (const o of options) {
|
||||||
await manager.insert(ProductOptionValue, {
|
await manager.insert(ProductOptionValue, {
|
||||||
id: `${o.value}-${o.option_id ?? Math.random()}`,
|
id: `${variant.id}-${o.option_id ?? Math.random()}`,
|
||||||
value: o.value,
|
value: o.value,
|
||||||
variant_id: id,
|
variant_id: id,
|
||||||
option_id: o.option_id,
|
option_id: o.option_id,
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ export const simpleShippingOptionFactory = async (
|
|||||||
const defaultProfile = await manager.findOne(ShippingProfile, {
|
const defaultProfile = await manager.findOne(ShippingProfile, {
|
||||||
where: {
|
where: {
|
||||||
type: ShippingProfileType.DEFAULT,
|
type: ShippingProfileType.DEFAULT,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const gcProfile = await manager.findOne(ShippingProfile, {
|
const gcProfile = await manager.findOne(ShippingProfile, {
|
||||||
where: {
|
where: {
|
||||||
type: ShippingProfileType.GIFT_CARD,
|
type: ShippingProfileType.GIFT_CARD,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
let region_id = data.region_id
|
let region_id = data.region_id
|
||||||
@@ -62,7 +62,7 @@ export const simpleShippingOptionFactory = async (
|
|||||||
is_return: data.is_return ?? false,
|
is_return: data.is_return ?? false,
|
||||||
region_id: region_id,
|
region_id: region_id,
|
||||||
provider_id: "test-ful",
|
provider_id: "test-ful",
|
||||||
profile_id: data.is_giftcard ? gcProfile.id : defaultProfile.id,
|
profile_id: data.is_giftcard ? gcProfile?.id : defaultProfile?.id,
|
||||||
price_type: data.price_type ?? ShippingOptionPriceType.FLAT_RATE,
|
price_type: data.price_type ?? ShippingOptionPriceType.FLAT_RATE,
|
||||||
data: data.data ?? {},
|
data: data.data ?? {},
|
||||||
requirements: (data.requirements || []) as ShippingOptionRequirement[],
|
requirements: (data.requirements || []) as ShippingOptionRequirement[],
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
handle: "test-product-reg",
|
handle: "test-product-reg",
|
||||||
title: "Multi Reg Test product",
|
title: "Multi Reg Test product",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description",
|
description: "test-product-description",
|
||||||
status: "published",
|
status: "published",
|
||||||
collection_id: "test-collection",
|
collection_id: "test-collection",
|
||||||
|
|||||||
@@ -444,14 +444,16 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
|
|
||||||
await manager.save(priceList1)
|
await manager.save(priceList1)
|
||||||
|
|
||||||
await manager.insert(Product, {
|
const giftCardProduct = manager.create(Product, {
|
||||||
id: "giftcard-product",
|
id: "giftcard-product",
|
||||||
title: "Giftcard",
|
title: "Giftcard",
|
||||||
is_giftcard: true,
|
is_giftcard: true,
|
||||||
discountable: false,
|
discountable: false,
|
||||||
profile_id: gcProfile.id,
|
profile_id: gcProfile.id,
|
||||||
|
profiles: [{ id: gcProfile.id }],
|
||||||
options: [{ id: "denom", title: "Denomination" }],
|
options: [{ id: "denom", title: "Denomination" }],
|
||||||
})
|
})
|
||||||
|
await manager.save(Product, giftCardProduct)
|
||||||
|
|
||||||
await manager.insert(ProductVariant, {
|
await manager.insert(ProductVariant, {
|
||||||
id: "giftcard-denom",
|
id: "giftcard-denom",
|
||||||
@@ -466,12 +468,14 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
await manager.insert(Product, {
|
const testProduct = manager.create(Product, {
|
||||||
id: "test-product",
|
id: "test-product",
|
||||||
title: "test product",
|
title: "test product",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
options: [{ id: "test-option", title: "Size" }],
|
options: [{ id: "test-option", title: "Size" }],
|
||||||
})
|
})
|
||||||
|
await manager.save(Product, testProduct)
|
||||||
|
|
||||||
await manager.insert(ProductVariant, {
|
await manager.insert(ProductVariant, {
|
||||||
id: "test-variant-quantity",
|
id: "test-variant-quantity",
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
id: "test-product",
|
id: "test-product",
|
||||||
title: "test product",
|
title: "test product",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
options: [{ id: "test-option", title: "Size" }],
|
options: [{ id: "test-option", title: "Size" }],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -52,6 +53,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
id: "test-product-2",
|
id: "test-product-2",
|
||||||
title: "test product 2",
|
title: "test product 2",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
options: [{ id: "test-option-color", title: "Color" }],
|
options: [{ id: "test-option-color", title: "Color" }],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
id: "test-product",
|
id: "test-product",
|
||||||
title: "test product",
|
title: "test product",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
options: [{ id: "test-option", title: "Size" }],
|
options: [{ id: "test-option", title: "Size" }],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -238,6 +238,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
handle: "test-product",
|
handle: "test-product",
|
||||||
title: "Test product",
|
title: "Test product",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description1",
|
description: "test-product-description1",
|
||||||
collection_id: "test-collection",
|
collection_id: "test-collection",
|
||||||
tags: [],
|
tags: [],
|
||||||
@@ -305,6 +306,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
handle: "test-product-quantity",
|
handle: "test-product-quantity",
|
||||||
title: "Test product",
|
title: "Test product",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description1",
|
description: "test-product-description1",
|
||||||
collection_id: "test-collection",
|
collection_id: "test-collection",
|
||||||
tags: [],
|
tags: [],
|
||||||
@@ -413,6 +415,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
handle: "test-product-sale",
|
handle: "test-product-sale",
|
||||||
title: "Test product sale",
|
title: "Test product sale",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description1",
|
description: "test-product-description1",
|
||||||
collection_id: "test-collection",
|
collection_id: "test-collection",
|
||||||
tags: [],
|
tags: [],
|
||||||
@@ -480,6 +483,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
handle: "test-product-sale-overlap",
|
handle: "test-product-sale-overlap",
|
||||||
title: "Test product sale",
|
title: "Test product sale",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description1",
|
description: "test-product-description1",
|
||||||
collection_id: "test-collection",
|
collection_id: "test-collection",
|
||||||
tags: [],
|
tags: [],
|
||||||
@@ -540,6 +544,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
handle: "test-product-multi-region",
|
handle: "test-product-multi-region",
|
||||||
title: "Test product",
|
title: "Test product",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-multi-region-description1",
|
description: "test-product-multi-region-description1",
|
||||||
collection_id: "test-collection",
|
collection_id: "test-collection",
|
||||||
tags: [],
|
tags: [],
|
||||||
@@ -606,6 +611,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
handle: "test-product-quantity-customer",
|
handle: "test-product-quantity-customer",
|
||||||
title: "Test product",
|
title: "Test product",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description1",
|
description: "test-product-description1",
|
||||||
collection_id: "test-collection",
|
collection_id: "test-collection",
|
||||||
tags: [],
|
tags: [],
|
||||||
@@ -713,6 +719,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
handle: "test-product-sale-customer",
|
handle: "test-product-sale-customer",
|
||||||
title: "Test product sale",
|
title: "Test product sale",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description1",
|
description: "test-product-description1",
|
||||||
collection_id: "test-collection",
|
collection_id: "test-collection",
|
||||||
tags: [],
|
tags: [],
|
||||||
@@ -781,6 +788,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
handle: "test-product-sale-customer-quantity",
|
handle: "test-product-sale-customer-quantity",
|
||||||
title: "Test product sale",
|
title: "Test product sale",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description1",
|
description: "test-product-description1",
|
||||||
collection_id: "test-collection",
|
collection_id: "test-collection",
|
||||||
tags: [],
|
tags: [],
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
handle: "test-product",
|
handle: "test-product",
|
||||||
title: "Test product",
|
title: "Test product",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description",
|
description: "test-product-description",
|
||||||
collection_id: "test-collection",
|
collection_id: "test-collection",
|
||||||
type: { id: "test-type", value: "test-type" },
|
type: { id: "test-type", value: "test-type" },
|
||||||
@@ -215,6 +216,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
handle: "test-product1",
|
handle: "test-product1",
|
||||||
title: "Test product1",
|
title: "Test product1",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description1",
|
description: "test-product-description1",
|
||||||
collection_id: "test-collection",
|
collection_id: "test-collection",
|
||||||
type: { id: "test-type", value: "test-type" },
|
type: { id: "test-type", value: "test-type" },
|
||||||
@@ -283,6 +285,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
handle: "test-product_filtering_1",
|
handle: "test-product_filtering_1",
|
||||||
title: "Test product filtering 1",
|
title: "Test product filtering 1",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description",
|
description: "test-product-description",
|
||||||
type: { id: "test-type", value: "test-type" },
|
type: { id: "test-type", value: "test-type" },
|
||||||
collection_id: "test-collection1",
|
collection_id: "test-collection1",
|
||||||
@@ -297,6 +300,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
handle: "test-product_filtering_2",
|
handle: "test-product_filtering_2",
|
||||||
title: "Test product filtering 2",
|
title: "Test product filtering 2",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description",
|
description: "test-product-description",
|
||||||
type: { id: "test-type", value: "test-type" },
|
type: { id: "test-type", value: "test-type" },
|
||||||
collection_id: "test-collection2",
|
collection_id: "test-collection2",
|
||||||
@@ -311,6 +315,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
handle: "test-product_filtering_3",
|
handle: "test-product_filtering_3",
|
||||||
title: "Test product filtering 3",
|
title: "Test product filtering 3",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description",
|
description: "test-product-description",
|
||||||
type: { id: "test-type", value: "test-type" },
|
type: { id: "test-type", value: "test-type" },
|
||||||
collection_id: "test-collection1",
|
collection_id: "test-collection1",
|
||||||
@@ -325,6 +330,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
handle: "test-product_filtering_4",
|
handle: "test-product_filtering_4",
|
||||||
title: "Test product filtering 4",
|
title: "Test product filtering 4",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description",
|
description: "test-product-description",
|
||||||
status: "proposed",
|
status: "proposed",
|
||||||
deleted_at: new Date().toISOString(),
|
deleted_at: new Date().toISOString(),
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ module.exports = async (dataSource, defaultSalesChannel) => {
|
|||||||
handle: "test-product",
|
handle: "test-product",
|
||||||
title: "Test product",
|
title: "Test product",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description",
|
description: "test-product-description",
|
||||||
status: "published",
|
status: "published",
|
||||||
collection_id: "test-collection",
|
collection_id: "test-collection",
|
||||||
@@ -323,6 +324,7 @@ module.exports = async (dataSource, defaultSalesChannel) => {
|
|||||||
handle: "test-product_filtering_1",
|
handle: "test-product_filtering_1",
|
||||||
title: "Test product filtering 1",
|
title: "Test product filtering 1",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description",
|
description: "test-product-description",
|
||||||
type: { id: "test-type", value: "test-type" },
|
type: { id: "test-type", value: "test-type" },
|
||||||
collection_id: "test-collection1",
|
collection_id: "test-collection1",
|
||||||
@@ -338,6 +340,7 @@ module.exports = async (dataSource, defaultSalesChannel) => {
|
|||||||
handle: "test-product_filtering_2",
|
handle: "test-product_filtering_2",
|
||||||
title: "Test product filtering 2",
|
title: "Test product filtering 2",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description",
|
description: "test-product-description",
|
||||||
type: { id: "test-type", value: "test-type" },
|
type: { id: "test-type", value: "test-type" },
|
||||||
collection_id: "test-collection2",
|
collection_id: "test-collection2",
|
||||||
@@ -353,6 +356,7 @@ module.exports = async (dataSource, defaultSalesChannel) => {
|
|||||||
handle: "test-product_filtering_3",
|
handle: "test-product_filtering_3",
|
||||||
title: "Test product filtering 3",
|
title: "Test product filtering 3",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description",
|
description: "test-product-description",
|
||||||
type: { id: "test-type", value: "test-type" },
|
type: { id: "test-type", value: "test-type" },
|
||||||
collection_id: "test-collection1",
|
collection_id: "test-collection1",
|
||||||
@@ -369,6 +373,7 @@ module.exports = async (dataSource, defaultSalesChannel) => {
|
|||||||
is_giftcard: true,
|
is_giftcard: true,
|
||||||
title: "giftcard",
|
title: "giftcard",
|
||||||
profile_id: defaultProfile.id,
|
profile_id: defaultProfile.id,
|
||||||
|
profiles: [{ id: defaultProfile.id }],
|
||||||
description: "test-product-description",
|
description: "test-product-description",
|
||||||
type: { id: "test-type", value: "test-type" },
|
type: { id: "test-type", value: "test-type" },
|
||||||
status: "published",
|
status: "published",
|
||||||
|
|||||||
+30
@@ -83,7 +83,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
@@ -240,7 +242,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
@@ -395,7 +399,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
@@ -515,7 +521,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
@@ -693,7 +701,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
@@ -861,7 +871,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
@@ -1112,7 +1124,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
@@ -1361,7 +1375,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
@@ -1570,7 +1586,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
@@ -1690,7 +1708,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
@@ -1868,7 +1888,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
@@ -2030,7 +2052,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
@@ -2139,7 +2163,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
@@ -2255,7 +2281,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
@@ -2372,7 +2400,9 @@ Object {
|
|||||||
"metadata": null,
|
"metadata": null,
|
||||||
"mid_code": null,
|
"mid_code": null,
|
||||||
"origin_country": null,
|
"origin_country": null,
|
||||||
|
"profile": Any<Object>,
|
||||||
"profile_id": Any<String>,
|
"profile_id": Any<String>,
|
||||||
|
"profiles": Any<Array>,
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"subtitle": null,
|
"subtitle": null,
|
||||||
"thumbnail": null,
|
"thumbnail": null,
|
||||||
|
|||||||
@@ -120,6 +120,8 @@ describe("medusa-plugin-sendgrid", () => {
|
|||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
product: {
|
product: {
|
||||||
profile_id: expect.any(String),
|
profile_id: expect.any(String),
|
||||||
|
profile: expect.any(Object),
|
||||||
|
profiles: expect.any(Array),
|
||||||
created_at: expect.any(Date),
|
created_at: expect.any(Date),
|
||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
},
|
},
|
||||||
@@ -242,6 +244,8 @@ describe("medusa-plugin-sendgrid", () => {
|
|||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
product: {
|
product: {
|
||||||
profile_id: expect.any(String),
|
profile_id: expect.any(String),
|
||||||
|
profile: expect.any(Object),
|
||||||
|
profiles: expect.any(Array),
|
||||||
created_at: expect.any(Date),
|
created_at: expect.any(Date),
|
||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
},
|
},
|
||||||
@@ -303,6 +307,8 @@ describe("medusa-plugin-sendgrid", () => {
|
|||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
product: {
|
product: {
|
||||||
profile_id: expect.any(String),
|
profile_id: expect.any(String),
|
||||||
|
profile: expect.any(Object),
|
||||||
|
profiles: expect.any(Array),
|
||||||
created_at: expect.any(Date),
|
created_at: expect.any(Date),
|
||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
},
|
},
|
||||||
@@ -485,6 +491,8 @@ describe("medusa-plugin-sendgrid", () => {
|
|||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
product: {
|
product: {
|
||||||
profile_id: expect.any(String),
|
profile_id: expect.any(String),
|
||||||
|
profile: expect.any(Object),
|
||||||
|
profiles: expect.any(Array),
|
||||||
created_at: expect.any(Date),
|
created_at: expect.any(Date),
|
||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
},
|
},
|
||||||
@@ -592,6 +600,8 @@ describe("medusa-plugin-sendgrid", () => {
|
|||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
product: {
|
product: {
|
||||||
profile_id: expect.any(String),
|
profile_id: expect.any(String),
|
||||||
|
profile: expect.any(Object),
|
||||||
|
profiles: expect.any(Array),
|
||||||
created_at: expect.any(Date),
|
created_at: expect.any(Date),
|
||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
},
|
},
|
||||||
@@ -780,6 +790,8 @@ const getReturnSnap = (received = false) => {
|
|||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
product: {
|
product: {
|
||||||
profile_id: expect.any(String),
|
profile_id: expect.any(String),
|
||||||
|
profile: expect.any(Object),
|
||||||
|
profiles: expect.any(Array),
|
||||||
created_at: expect.any(Date),
|
created_at: expect.any(Date),
|
||||||
updated_at: expect.any(Date),
|
updated_at: expect.any(Date),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,351 @@
|
|||||||
|
import path from "path"
|
||||||
|
import { bootstrapApp } from "../../../../environment-helpers/bootstrap-app"
|
||||||
|
import { setPort, useApi } from "../../../../environment-helpers/use-api"
|
||||||
|
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||||
|
|
||||||
|
import adminSeeder from "../../../../helpers/admin-seeder"
|
||||||
|
import productSeeder from "../../../../helpers/product-seeder"
|
||||||
|
|
||||||
|
import { simpleSalesChannelFactory } from "../../../../factories"
|
||||||
|
import { AxiosInstance } from "axios"
|
||||||
|
|
||||||
|
jest.setTimeout(50000)
|
||||||
|
|
||||||
|
const adminHeaders = {
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("/admin/products", () => {
|
||||||
|
let medusaProcess
|
||||||
|
let dbConnection
|
||||||
|
let express
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||||
|
dbConnection = await initDb({ cwd } as any)
|
||||||
|
const { app, port } = await bootstrapApp({ cwd })
|
||||||
|
setPort(port)
|
||||||
|
express = app.listen(port, () => {
|
||||||
|
process.send?.(port)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.shutdown()
|
||||||
|
|
||||||
|
medusaProcess.kill()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("POST /admin/products", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await productSeeder(dbConnection)
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
|
||||||
|
await simpleSalesChannelFactory(dbConnection, {
|
||||||
|
name: "Default channel",
|
||||||
|
id: "default-channel",
|
||||||
|
is_default: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create a product", async () => {
|
||||||
|
const api = useApi()! as AxiosInstance
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
title: "Test",
|
||||||
|
description: "test-product-description",
|
||||||
|
type: { value: "test-type" },
|
||||||
|
images: ["test-image.png", "test-image-2.png"],
|
||||||
|
collection_id: "test-collection",
|
||||||
|
tags: [{ value: "123" }, { value: "456" }],
|
||||||
|
options: [{ title: "size" }, { title: "color" }],
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
title: "Test variant",
|
||||||
|
inventory_quantity: 10,
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency_code: "eur",
|
||||||
|
amount: 45,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency_code: "dkk",
|
||||||
|
amount: 30,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
options: [{ value: "large" }, { value: "green" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.post("/admin/products", payload, adminHeaders)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response?.status).toEqual(200)
|
||||||
|
expect(response?.data.product).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.stringMatching(/^prod_*/),
|
||||||
|
title: "Test",
|
||||||
|
discountable: true,
|
||||||
|
is_giftcard: false,
|
||||||
|
handle: "test",
|
||||||
|
status: "draft",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
profile_id: expect.stringMatching(/^sp_*/),
|
||||||
|
images: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
url: "test-image.png",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
url: "test-image-2.png",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
thumbnail: "test-image.png",
|
||||||
|
tags: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
value: "123",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
value: "456",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
type: expect.objectContaining({
|
||||||
|
value: "test-type",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
}),
|
||||||
|
collection: expect.objectContaining({
|
||||||
|
id: "test-collection",
|
||||||
|
title: "Test collection",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
}),
|
||||||
|
options: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.stringMatching(/^opt_*/),
|
||||||
|
product_id: expect.stringMatching(/^prod_*/),
|
||||||
|
title: "size",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.stringMatching(/^opt_*/),
|
||||||
|
product_id: expect.stringMatching(/^prod_*/),
|
||||||
|
title: "color",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
variants: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.stringMatching(/^variant_*/),
|
||||||
|
product_id: expect.stringMatching(/^prod_*/),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
created_at: expect.any(String),
|
||||||
|
title: "Test variant",
|
||||||
|
prices: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.stringMatching(/^ma_*/),
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 100,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
variant_id: expect.stringMatching(/^variant_*/),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.stringMatching(/^ma_*/),
|
||||||
|
currency_code: "eur",
|
||||||
|
amount: 45,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
variant_id: expect.stringMatching(/^variant_*/),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.stringMatching(/^ma_*/),
|
||||||
|
currency_code: "dkk",
|
||||||
|
amount: 30,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
variant_id: expect.stringMatching(/^variant_*/),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
options: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
value: "large",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
variant_id: expect.stringMatching(/^variant_*/),
|
||||||
|
option_id: expect.stringMatching(/^opt_*/),
|
||||||
|
id: expect.stringMatching(/^optval_*/),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
value: "green",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
variant_id: expect.stringMatching(/^variant_*/),
|
||||||
|
option_id: expect.stringMatching(/^opt_*/),
|
||||||
|
id: expect.stringMatching(/^optval_*/),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create a product that is not discountable", async () => {
|
||||||
|
const api = useApi()! as AxiosInstance
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
title: "Test",
|
||||||
|
discountable: false,
|
||||||
|
description: "test-product-description",
|
||||||
|
type: { value: "test-type" },
|
||||||
|
images: ["test-image.png", "test-image-2.png"],
|
||||||
|
collection_id: "test-collection",
|
||||||
|
tags: [{ value: "123" }, { value: "456" }],
|
||||||
|
options: [{ title: "size" }, { title: "color" }],
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
title: "Test variant",
|
||||||
|
inventory_quantity: 10,
|
||||||
|
prices: [{ currency_code: "usd", amount: 100 }],
|
||||||
|
options: [{ value: "large" }, { value: "green" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.post("/admin/products", payload, adminHeaders)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response?.status).toEqual(200)
|
||||||
|
expect(response?.data.product).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
discountable: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should sets the variant ranks when creating a product", async () => {
|
||||||
|
const api = useApi()! as AxiosInstance
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
title: "Test product - 1",
|
||||||
|
description: "test-product-description 1",
|
||||||
|
type: { value: "test-type 1" },
|
||||||
|
images: ["test-image.png", "test-image-2.png"],
|
||||||
|
collection_id: "test-collection",
|
||||||
|
tags: [{ value: "123" }, { value: "456" }],
|
||||||
|
options: [{ title: "size" }, { title: "color" }],
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
title: "Test variant 1",
|
||||||
|
inventory_quantity: 10,
|
||||||
|
prices: [{ currency_code: "usd", amount: 100 }],
|
||||||
|
options: [{ value: "large" }, { value: "green" }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Test variant 2",
|
||||||
|
inventory_quantity: 10,
|
||||||
|
prices: [{ currency_code: "usd", amount: 100 }],
|
||||||
|
options: [{ value: "large" }, { value: "green" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const creationResponse = await api
|
||||||
|
.post("/admin/products", payload, adminHeaders)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(creationResponse?.status).toEqual(200)
|
||||||
|
|
||||||
|
const productId = creationResponse?.data.product.id
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.get(`/admin/products/${productId}`, adminHeaders)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response?.data.product).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
title: "Test product - 1",
|
||||||
|
variants: [
|
||||||
|
expect.objectContaining({
|
||||||
|
title: "Test variant 1",
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
title: "Test variant 2",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create a giftcard", async () => {
|
||||||
|
const api = useApi()! as AxiosInstance
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
title: "Test Giftcard",
|
||||||
|
is_giftcard: true,
|
||||||
|
description: "test-giftcard-description",
|
||||||
|
options: [{ title: "Denominations" }],
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
title: "Test variant",
|
||||||
|
prices: [{ currency_code: "usd", amount: 100 }],
|
||||||
|
options: [{ value: "100" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.post("/admin/products", payload, adminHeaders)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response?.status).toEqual(200)
|
||||||
|
|
||||||
|
expect(response?.data.product).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
title: "Test Giftcard",
|
||||||
|
discountable: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -2,6 +2,7 @@ const DB_HOST = process.env.DB_HOST
|
|||||||
const DB_USERNAME = process.env.DB_USERNAME
|
const DB_USERNAME = process.env.DB_USERNAME
|
||||||
const DB_PASSWORD = process.env.DB_PASSWORD
|
const DB_PASSWORD = process.env.DB_PASSWORD
|
||||||
const DB_NAME = process.env.DB_TEMP_NAME
|
const DB_NAME = process.env.DB_TEMP_NAME
|
||||||
|
const DB_URL = `postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}/${DB_NAME}`
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -23,7 +24,7 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
projectConfig: {
|
projectConfig: {
|
||||||
// redis_url: REDIS_URL,
|
// redis_url: REDIS_URL,
|
||||||
database_url: `postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}/${DB_NAME}`,
|
database_url: DB_URL,
|
||||||
database_type: "postgres",
|
database_type: "postgres",
|
||||||
jwt_secret: "test",
|
jwt_secret: "test",
|
||||||
cookie_secret: "test",
|
cookie_secret: "test",
|
||||||
@@ -44,5 +45,16 @@ module.exports = {
|
|||||||
resolve: "@medusajs/cache-inmemory",
|
resolve: "@medusajs/cache-inmemory",
|
||||||
options: { ttl: 5 },
|
options: { ttl: 5 },
|
||||||
},
|
},
|
||||||
|
productModuleService: {
|
||||||
|
scope: "internal",
|
||||||
|
resources: "isolated",
|
||||||
|
resolve: "@medusajs/product",
|
||||||
|
options: {
|
||||||
|
database: {
|
||||||
|
clientUrl: DB_URL,
|
||||||
|
debug: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"@medusajs/cache-inmemory": "workspace:*",
|
"@medusajs/cache-inmemory": "workspace:*",
|
||||||
"@medusajs/event-bus-local": "workspace:*",
|
"@medusajs/event-bus-local": "workspace:*",
|
||||||
"@medusajs/medusa": "workspace:*",
|
"@medusajs/medusa": "workspace:*",
|
||||||
|
"@medusajs/product": "workspace:^",
|
||||||
"faker": "^5.5.3",
|
"faker": "^5.5.3",
|
||||||
"medusa-fulfillment-webshipper": "workspace:*",
|
"medusa-fulfillment-webshipper": "workspace:*",
|
||||||
"medusa-interfaces": "workspace:*",
|
"medusa-interfaces": "workspace:*",
|
||||||
|
|||||||
@@ -16,19 +16,12 @@ const orderRelations = [
|
|||||||
"returns.shipping_method",
|
"returns.shipping_method",
|
||||||
"returns.shipping_method.tax_lines",
|
"returns.shipping_method.tax_lines",
|
||||||
"refunds",
|
"refunds",
|
||||||
"claims",
|
|
||||||
"claims.claim_items",
|
|
||||||
"claims.claim_items.item",
|
"claims.claim_items.item",
|
||||||
"claims.fulfillments",
|
"claims.fulfillments",
|
||||||
"claims.return_order",
|
"claims.return_order",
|
||||||
"claims.additional_items",
|
"claims.additional_items.variant.product.profiles",
|
||||||
"claims.additional_items.variant",
|
|
||||||
"claims.additional_items.variant.product",
|
|
||||||
"swaps",
|
|
||||||
"swaps.return_order",
|
"swaps.return_order",
|
||||||
"swaps.additional_items",
|
"swaps.additional_items.variant.product.profiles",
|
||||||
"swaps.additional_items.variant",
|
|
||||||
"swaps.additional_items.variant.product",
|
|
||||||
"swaps.fulfillments",
|
"swaps.fulfillments",
|
||||||
"returnable_items",
|
"returnable_items",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type { Cart } from "./Cart"
|
|||||||
import type { Discount } from "./Discount"
|
import type { Discount } from "./Discount"
|
||||||
import type { DraftOrder } from "./DraftOrder"
|
import type { DraftOrder } from "./DraftOrder"
|
||||||
import type { LineItem } from "./LineItem"
|
import type { LineItem } from "./LineItem"
|
||||||
|
import type { Product } from "./Product"
|
||||||
import type { ProductVariant } from "./ProductVariant"
|
import type { ProductVariant } from "./ProductVariant"
|
||||||
import type { Region } from "./Region"
|
import type { Region } from "./Region"
|
||||||
import type { ShippingMethod } from "./ShippingMethod"
|
import type { ShippingMethod } from "./ShippingMethod"
|
||||||
@@ -58,7 +59,12 @@ export interface AdminDraftOrdersRes {
|
|||||||
| "variant"
|
| "variant"
|
||||||
>,
|
>,
|
||||||
{
|
{
|
||||||
variant: SetRelation<ProductVariant, "product">
|
variant: Merge<
|
||||||
|
SetRelation<ProductVariant, "product">,
|
||||||
|
{
|
||||||
|
product: SetRelation<Product, "profiles">
|
||||||
|
}
|
||||||
|
>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type { Fulfillment } from "./Fulfillment"
|
|||||||
import type { GiftCardTransaction } from "./GiftCardTransaction"
|
import type { GiftCardTransaction } from "./GiftCardTransaction"
|
||||||
import type { LineItem } from "./LineItem"
|
import type { LineItem } from "./LineItem"
|
||||||
import type { Order } from "./Order"
|
import type { Order } from "./Order"
|
||||||
|
import type { Product } from "./Product"
|
||||||
import type { ProductVariant } from "./ProductVariant"
|
import type { ProductVariant } from "./ProductVariant"
|
||||||
import type { Region } from "./Region"
|
import type { Region } from "./Region"
|
||||||
import type { Return } from "./Return"
|
import type { Return } from "./Return"
|
||||||
@@ -112,7 +113,12 @@ export interface AdminOrdersListRes {
|
|||||||
| "variant"
|
| "variant"
|
||||||
>,
|
>,
|
||||||
{
|
{
|
||||||
variant: SetRelation<ProductVariant, "product">
|
variant: Merge<
|
||||||
|
SetRelation<ProductVariant, "product">,
|
||||||
|
{
|
||||||
|
product: SetRelation<Product, "profiles">
|
||||||
|
}
|
||||||
|
>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type { Fulfillment } from "./Fulfillment"
|
|||||||
import type { GiftCardTransaction } from "./GiftCardTransaction"
|
import type { GiftCardTransaction } from "./GiftCardTransaction"
|
||||||
import type { LineItem } from "./LineItem"
|
import type { LineItem } from "./LineItem"
|
||||||
import type { Order } from "./Order"
|
import type { Order } from "./Order"
|
||||||
|
import type { Product } from "./Product"
|
||||||
import type { ProductVariant } from "./ProductVariant"
|
import type { ProductVariant } from "./ProductVariant"
|
||||||
import type { Region } from "./Region"
|
import type { Region } from "./Region"
|
||||||
import type { Return } from "./Return"
|
import type { Return } from "./Return"
|
||||||
@@ -109,7 +110,12 @@ export interface AdminOrdersRes {
|
|||||||
| "variant"
|
| "variant"
|
||||||
>,
|
>,
|
||||||
{
|
{
|
||||||
variant: SetRelation<ProductVariant, "product">
|
variant: Merge<
|
||||||
|
SetRelation<ProductVariant, "product">,
|
||||||
|
{
|
||||||
|
product: SetRelation<Product, "profiles">
|
||||||
|
}
|
||||||
|
>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ export interface Product {
|
|||||||
* Available if the relation `profile` is expanded.
|
* Available if the relation `profile` is expanded.
|
||||||
*/
|
*/
|
||||||
profile?: ShippingProfile | null
|
profile?: ShippingProfile | null
|
||||||
|
/**
|
||||||
|
* Available if the relation `profiles` is expanded.
|
||||||
|
*/
|
||||||
|
profiles?: Array<ShippingProfile> | null
|
||||||
/**
|
/**
|
||||||
* The weight of the Product Variant. May be used in shipping rate calculations.
|
* The weight of the Product Variant. May be used in shipping rate calculations.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { SetRelation, Merge } from "../core/ModelUtils"
|
|||||||
import type { Cart } from "./Cart"
|
import type { Cart } from "./Cart"
|
||||||
import type { Discount } from "./Discount"
|
import type { Discount } from "./Discount"
|
||||||
import type { LineItem } from "./LineItem"
|
import type { LineItem } from "./LineItem"
|
||||||
|
import type { Product } from "./Product"
|
||||||
import type { ProductVariant } from "./ProductVariant"
|
import type { ProductVariant } from "./ProductVariant"
|
||||||
import type { Region } from "./Region"
|
import type { Region } from "./Region"
|
||||||
import type { ShippingMethod } from "./ShippingMethod"
|
import type { ShippingMethod } from "./ShippingMethod"
|
||||||
@@ -54,7 +55,12 @@ export interface StoreCartsRes {
|
|||||||
| "tax_lines"
|
| "tax_lines"
|
||||||
>,
|
>,
|
||||||
{
|
{
|
||||||
variant: SetRelation<ProductVariant, "product">
|
variant: Merge<
|
||||||
|
SetRelation<ProductVariant, "product">,
|
||||||
|
{
|
||||||
|
product: SetRelation<Product, "profiles">
|
||||||
|
}
|
||||||
|
>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type { Fulfillment } from "./Fulfillment"
|
|||||||
import type { GiftCardTransaction } from "./GiftCardTransaction"
|
import type { GiftCardTransaction } from "./GiftCardTransaction"
|
||||||
import type { LineItem } from "./LineItem"
|
import type { LineItem } from "./LineItem"
|
||||||
import type { Order } from "./Order"
|
import type { Order } from "./Order"
|
||||||
|
import type { Product } from "./Product"
|
||||||
import type { ProductVariant } from "./ProductVariant"
|
import type { ProductVariant } from "./ProductVariant"
|
||||||
import type { Region } from "./Region"
|
import type { Region } from "./Region"
|
||||||
import type { ShippingMethod } from "./ShippingMethod"
|
import type { ShippingMethod } from "./ShippingMethod"
|
||||||
@@ -63,7 +64,12 @@ export interface StoreCustomersListOrdersRes {
|
|||||||
| "tax_lines"
|
| "tax_lines"
|
||||||
>,
|
>,
|
||||||
{
|
{
|
||||||
variant: SetRelation<ProductVariant, "product">
|
variant: Merge<
|
||||||
|
SetRelation<ProductVariant, "product">,
|
||||||
|
{
|
||||||
|
product: SetRelation<Product, "profiles">
|
||||||
|
}
|
||||||
|
>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type { Fulfillment } from "./Fulfillment"
|
|||||||
import type { GiftCardTransaction } from "./GiftCardTransaction"
|
import type { GiftCardTransaction } from "./GiftCardTransaction"
|
||||||
import type { LineItem } from "./LineItem"
|
import type { LineItem } from "./LineItem"
|
||||||
import type { Order } from "./Order"
|
import type { Order } from "./Order"
|
||||||
|
import type { Product } from "./Product"
|
||||||
import type { ProductVariant } from "./ProductVariant"
|
import type { ProductVariant } from "./ProductVariant"
|
||||||
import type { Region } from "./Region"
|
import type { Region } from "./Region"
|
||||||
import type { ShippingMethod } from "./ShippingMethod"
|
import type { ShippingMethod } from "./ShippingMethod"
|
||||||
@@ -62,7 +63,12 @@ export interface StoreOrdersRes {
|
|||||||
| "tax_lines"
|
| "tax_lines"
|
||||||
>,
|
>,
|
||||||
{
|
{
|
||||||
variant: SetRelation<ProductVariant, "product">
|
variant: Merge<
|
||||||
|
SetRelation<ProductVariant, "product">,
|
||||||
|
{
|
||||||
|
product: SetRelation<Product, "profiles">
|
||||||
|
}
|
||||||
|
>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ export default async (req, res) => {
|
|||||||
"region",
|
"region",
|
||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
"shipping_methods.shipping_option",
|
"shipping_methods.shipping_option",
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
const shippingOptions = await shippingProfileService.fetchCartOptions(cart)
|
const shippingOptions = await shippingProfileService.fetchCartOptions(cart)
|
||||||
|
|||||||
@@ -264,6 +264,7 @@ class SendGridService extends NotificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let status
|
let status
|
||||||
|
|
||||||
await SendGrid.send(sendOptions)
|
await SendGrid.send(sendOptions)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
status = "sent"
|
status = "sent"
|
||||||
@@ -613,11 +614,8 @@ class SendGridService extends NotificationService {
|
|||||||
// Fetch the return request
|
// Fetch the return request
|
||||||
const returnRequest = await this.returnService_.retrieve(return_id, {
|
const returnRequest = await this.returnService_.retrieve(return_id, {
|
||||||
relations: [
|
relations: [
|
||||||
"items",
|
|
||||||
"items.item",
|
|
||||||
"items.item.tax_lines",
|
"items.item.tax_lines",
|
||||||
"items.item.variant",
|
"items.item.variant.product.profiles",
|
||||||
"items.item.variant.product",
|
|
||||||
"shipping_method",
|
"shipping_method",
|
||||||
"shipping_method.tax_lines",
|
"shipping_method.tax_lines",
|
||||||
"shipping_method.shipping_option",
|
"shipping_method.shipping_option",
|
||||||
@@ -628,7 +626,9 @@ class SendGridService extends NotificationService {
|
|||||||
{
|
{
|
||||||
id: returnRequest.items.map(({ item_id }) => item_id),
|
id: returnRequest.items.map(({ item_id }) => item_id),
|
||||||
},
|
},
|
||||||
{ relations: ["tax_lines", "variant", "variant.product"] }
|
{
|
||||||
|
relations: ["tax_lines", "variant", "variant.product.profiles"],
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fetch the order
|
// Fetch the order
|
||||||
@@ -774,7 +774,7 @@ class SendGridService extends NotificationService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const cart = await this.cartService_.retrieve(swap.cart_id, {
|
const cart = await this.cartService_.retrieve(swap.cart_id, {
|
||||||
relations: ["items", "items.variant", "items.variant.product"],
|
relations: ["items.variant.product.profiles"],
|
||||||
select: [
|
select: [
|
||||||
"total",
|
"total",
|
||||||
"tax_total",
|
"tax_total",
|
||||||
@@ -855,8 +855,7 @@ class SendGridService extends NotificationService {
|
|||||||
})
|
})
|
||||||
const swap = await this.swapService_.retrieve(id, {
|
const swap = await this.swapService_.retrieve(id, {
|
||||||
relations: [
|
relations: [
|
||||||
"additional_items",
|
"additional_items.variant.product.profiles",
|
||||||
"additional_items.variant.product",
|
|
||||||
"additional_items.tax_lines",
|
"additional_items.tax_lines",
|
||||||
"return_order",
|
"return_order",
|
||||||
"return_order.items",
|
"return_order.items",
|
||||||
@@ -873,7 +872,7 @@ class SendGridService extends NotificationService {
|
|||||||
id: returnRequest.items.map(({ item_id }) => item_id),
|
id: returnRequest.items.map(({ item_id }) => item_id),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
relations: ["tax_lines", "variant", "variant.product"],
|
relations: ["tax_lines", "variant.product.profiles"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -893,9 +892,7 @@ class SendGridService extends NotificationService {
|
|||||||
const order = await this.orderService_.retrieve(swap.order_id, {
|
const order = await this.orderService_.retrieve(swap.order_id, {
|
||||||
select: ["total"],
|
select: ["total"],
|
||||||
relations: [
|
relations: [
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
"items.tax_lines",
|
"items.tax_lines",
|
||||||
"discounts",
|
"discounts",
|
||||||
"discounts.rule",
|
"discounts.rule",
|
||||||
@@ -915,7 +912,7 @@ class SendGridService extends NotificationService {
|
|||||||
"shipping_total",
|
"shipping_total",
|
||||||
"subtotal",
|
"subtotal",
|
||||||
],
|
],
|
||||||
relations: ["items", "items.variant", "items.variant.product"],
|
relations: ["items.variant.product.profiles"],
|
||||||
})
|
})
|
||||||
const currencyCode = order.currency_code.toUpperCase()
|
const currencyCode = order.currency_code.toUpperCase()
|
||||||
|
|
||||||
@@ -997,9 +994,7 @@ class SendGridService extends NotificationService {
|
|||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
"shipping_methods.shipping_option",
|
"shipping_methods.shipping_option",
|
||||||
"shipping_methods.tax_lines",
|
"shipping_methods.tax_lines",
|
||||||
"additional_items",
|
"additional_items.variant.product.profiles",
|
||||||
"additional_items.variant",
|
|
||||||
"additional_items.variant.product",
|
|
||||||
"additional_items.tax_lines",
|
"additional_items.tax_lines",
|
||||||
"return_order",
|
"return_order",
|
||||||
"return_order.items",
|
"return_order.items",
|
||||||
@@ -1011,14 +1006,11 @@ class SendGridService extends NotificationService {
|
|||||||
"region",
|
"region",
|
||||||
"items",
|
"items",
|
||||||
"items.tax_lines",
|
"items.tax_lines",
|
||||||
"items.variant",
|
"items.variant.product.profiles",
|
||||||
"items.variant.product",
|
|
||||||
"discounts",
|
"discounts",
|
||||||
"discounts.rule",
|
"discounts.rule",
|
||||||
"swaps",
|
"swaps",
|
||||||
"swaps.additional_items",
|
"swaps.additional_items.variant.product.profiles",
|
||||||
"swaps.additional_items.variant",
|
|
||||||
"swaps.additional_items.variant.product",
|
|
||||||
"swaps.additional_items.tax_lines",
|
"swaps.additional_items.tax_lines",
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -1031,7 +1023,7 @@ class SendGridService extends NotificationService {
|
|||||||
"shipping_total",
|
"shipping_total",
|
||||||
"subtotal",
|
"subtotal",
|
||||||
],
|
],
|
||||||
relations: ["items", "items.variant", "items.variant.product"],
|
relations: ["items.variant.product.profiles"],
|
||||||
})
|
})
|
||||||
|
|
||||||
const returnRequest = swap.return_order
|
const returnRequest = swap.return_order
|
||||||
@@ -1040,7 +1032,7 @@ class SendGridService extends NotificationService {
|
|||||||
id: returnRequest.items.map(({ item_id }) => item_id),
|
id: returnRequest.items.map(({ item_id }) => item_id),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
relations: ["tax_lines", "variant", "variant.product"],
|
relations: ["tax_lines", "variant.product.profiles"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1147,10 +1139,7 @@ class SendGridService extends NotificationService {
|
|||||||
async claimShipmentCreatedData({ id, fulfillment_id }) {
|
async claimShipmentCreatedData({ id, fulfillment_id }) {
|
||||||
const claim = await this.claimService_.retrieve(id, {
|
const claim = await this.claimService_.retrieve(id, {
|
||||||
relations: [
|
relations: [
|
||||||
"order",
|
"order.items.variant.product.profiles",
|
||||||
"order.items",
|
|
||||||
"order.items.variant",
|
|
||||||
"order.items.variant.product",
|
|
||||||
"order.shipping_address",
|
"order.shipping_address",
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const defaultAdminCollectionsFields = [
|
|||||||
"created_at",
|
"created_at",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
]
|
]
|
||||||
export const defaultAdminCollectionsRelations = ["products"]
|
export const defaultAdminCollectionsRelations = ["products.profiles"]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @schema AdminCollectionsListRes
|
* @schema AdminCollectionsListRes
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ describe("POST /admin/discounts/:discount_id/regions/:region_id", () => {
|
|||||||
"metadata",
|
"metadata",
|
||||||
"valid_duration",
|
"valid_duration",
|
||||||
],
|
],
|
||||||
relations: ["rule", "parent_discount", "regions", "rule.conditions"],
|
relations: ["parent_discount", "regions", "rule.conditions"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -20,12 +20,7 @@ const defaultFields = [
|
|||||||
"valid_duration",
|
"valid_duration",
|
||||||
]
|
]
|
||||||
|
|
||||||
const defaultRelations = [
|
const defaultRelations = ["parent_discount", "regions", "rule.conditions"]
|
||||||
"rule",
|
|
||||||
"parent_discount",
|
|
||||||
"regions",
|
|
||||||
"rule.conditions",
|
|
||||||
]
|
|
||||||
|
|
||||||
describe("GET /admin/discounts/:discount_id", () => {
|
describe("GET /admin/discounts/:discount_id", () => {
|
||||||
describe("successful retrieval", () => {
|
describe("successful retrieval", () => {
|
||||||
|
|||||||
@@ -20,12 +20,7 @@ const defaultFields = [
|
|||||||
"valid_duration",
|
"valid_duration",
|
||||||
]
|
]
|
||||||
|
|
||||||
const defaultRelations = [
|
const defaultRelations = ["parent_discount", "regions", "rule.conditions"]
|
||||||
"rule",
|
|
||||||
"parent_discount",
|
|
||||||
"regions",
|
|
||||||
"rule.conditions",
|
|
||||||
]
|
|
||||||
|
|
||||||
describe("DELETE /admin/discounts/:discount_id/regions/region_id", () => {
|
describe("DELETE /admin/discounts/:discount_id/regions/region_id", () => {
|
||||||
describe("successful removal", () => {
|
describe("successful removal", () => {
|
||||||
|
|||||||
@@ -209,7 +209,6 @@ export const defaultAdminDiscountsFields: (keyof Discount)[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export const defaultAdminDiscountsRelations = [
|
export const defaultAdminDiscountsRelations = [
|
||||||
"rule",
|
|
||||||
"parent_discount",
|
"parent_discount",
|
||||||
"regions",
|
"regions",
|
||||||
"rule.conditions",
|
"rule.conditions",
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ export type AdminPostDraftOrdersDraftOrderRegisterPaymentRes = {
|
|||||||
* - cart.items.tax_lines
|
* - cart.items.tax_lines
|
||||||
* - cart.items.variant
|
* - cart.items.variant
|
||||||
* - cart.items.variant.product
|
* - cart.items.variant.product
|
||||||
|
* - cart.items.variant.product.profiles
|
||||||
* - cart.region
|
* - cart.region
|
||||||
* - cart.region.tax_rates
|
* - cart.region.tax_rates
|
||||||
* - cart.shipping_address
|
* - cart.shipping_address
|
||||||
|
|||||||
@@ -543,6 +543,7 @@ export default (app, featureFlagRouter: FlagRouter) => {
|
|||||||
* - items.tax_lines
|
* - items.tax_lines
|
||||||
* - items.variant
|
* - items.variant
|
||||||
* - items.variant.product
|
* - items.variant.product
|
||||||
|
* - items.variant.product.profiles
|
||||||
* - refunds
|
* - refunds
|
||||||
* - region
|
* - region
|
||||||
* - shipping_methods
|
* - shipping_methods
|
||||||
@@ -659,6 +660,7 @@ export type AdminOrdersRes = {
|
|||||||
* - items.tax_lines
|
* - items.tax_lines
|
||||||
* - items.variant
|
* - items.variant
|
||||||
* - items.variant.product
|
* - items.variant.product
|
||||||
|
* - items.variant.product.profiles
|
||||||
* - refunds
|
* - refunds
|
||||||
* - region
|
* - region
|
||||||
* - shipping_methods
|
* - shipping_methods
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ describe("GET /admin/products/:id", () => {
|
|||||||
"is_giftcard",
|
"is_giftcard",
|
||||||
"discountable",
|
"discountable",
|
||||||
"thumbnail",
|
"thumbnail",
|
||||||
"profile_id",
|
|
||||||
"collection_id",
|
"collection_id",
|
||||||
"type_id",
|
"type_id",
|
||||||
"weight",
|
"weight",
|
||||||
@@ -60,6 +59,7 @@ describe("GET /admin/products/:id", () => {
|
|||||||
"variants",
|
"variants",
|
||||||
"variants.prices",
|
"variants.prices",
|
||||||
"variants.options",
|
"variants.options",
|
||||||
|
"profiles",
|
||||||
"images",
|
"images",
|
||||||
"options",
|
"options",
|
||||||
"tags",
|
"tags",
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import {
|
|||||||
createVariantsTransaction,
|
createVariantsTransaction,
|
||||||
revertVariantTransaction,
|
revertVariantTransaction,
|
||||||
} from "./transaction/create-product-variant"
|
} from "./transaction/create-product-variant"
|
||||||
|
import { createProductsWorkflow } from "../../../../workflows/admin/create-products"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /admin/products
|
* @oas [post] /admin/products
|
||||||
@@ -129,6 +130,19 @@ export default async (req, res) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const entityManager: EntityManager = req.scope.resolve("manager")
|
const entityManager: EntityManager = req.scope.resolve("manager")
|
||||||
|
const productModuleService = req.scope.resolve("productModuleService")
|
||||||
|
|
||||||
|
if (productModuleService) {
|
||||||
|
const products = await createProductsWorkflow(
|
||||||
|
{
|
||||||
|
container: req.scope,
|
||||||
|
manager: entityManager,
|
||||||
|
},
|
||||||
|
[validated]
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.json({ product: products[0] })
|
||||||
|
}
|
||||||
|
|
||||||
const product = await entityManager.transaction(async (manager) => {
|
const product = await entityManager.transaction(async (manager) => {
|
||||||
const { variants } = validated
|
const { variants } = validated
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ export const defaultAdminProductRelations = [
|
|||||||
"variants",
|
"variants",
|
||||||
"variants.prices",
|
"variants.prices",
|
||||||
"variants.options",
|
"variants.options",
|
||||||
|
"profiles",
|
||||||
"images",
|
"images",
|
||||||
"options",
|
"options",
|
||||||
"tags",
|
"tags",
|
||||||
@@ -119,7 +120,6 @@ export const defaultAdminProductFields: (keyof Product)[] = [
|
|||||||
"is_giftcard",
|
"is_giftcard",
|
||||||
"discountable",
|
"discountable",
|
||||||
"thumbnail",
|
"thumbnail",
|
||||||
"profile_id",
|
|
||||||
"collection_id",
|
"collection_id",
|
||||||
"type_id",
|
"type_id",
|
||||||
"weight",
|
"weight",
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ const defaultFields = [
|
|||||||
"metadata",
|
"metadata",
|
||||||
]
|
]
|
||||||
|
|
||||||
const defaultRelations = ["products", "shipping_options"]
|
const defaultRelations = ["products.profiles", "shipping_options"]
|
||||||
|
|
||||||
describe("GET /admin/shipping-profiles/:profile_id", () => {
|
describe("GET /admin/shipping-profiles/:profile_id", () => {
|
||||||
describe("successful retrieval", () => {
|
describe("successful retrieval", () => {
|
||||||
|
|||||||
@@ -41,8 +41,10 @@ export const defaultAdminShippingProfilesFields: (keyof ShippingProfile)[] = [
|
|||||||
"metadata",
|
"metadata",
|
||||||
]
|
]
|
||||||
|
|
||||||
export const defaultAdminShippingProfilesRelations: (keyof ShippingProfile)[] =
|
export const defaultAdminShippingProfilesRelations: string[] = [
|
||||||
["products", "shipping_options"]
|
"products.profiles",
|
||||||
|
"shipping_options",
|
||||||
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @schema AdminDeleteShippingProfileRes
|
* @schema AdminDeleteShippingProfileRes
|
||||||
|
|||||||
@@ -262,6 +262,7 @@ export const defaultStoreCartRelations = [
|
|||||||
* - items
|
* - items
|
||||||
* - items.variant
|
* - items.variant
|
||||||
* - items.variant.product
|
* - items.variant.product
|
||||||
|
* - items.variant.product.profiles
|
||||||
* - items.tax_lines
|
* - items.tax_lines
|
||||||
* - items.adjustments
|
* - items.adjustments
|
||||||
* - gift_cards
|
* - gift_cards
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ export type StoreCustomersResetPasswordRes = {
|
|||||||
* - items.tax_lines
|
* - items.tax_lines
|
||||||
* - items.variant
|
* - items.variant
|
||||||
* - items.variant.product
|
* - items.variant.product
|
||||||
|
* - items.variant.product.profiles
|
||||||
* - refunds
|
* - refunds
|
||||||
* - region
|
* - region
|
||||||
* - shipping_address
|
* - shipping_address
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ export const allowedStoreOrdersFields = [
|
|||||||
* - items.tax_lines
|
* - items.tax_lines
|
||||||
* - items.variant
|
* - items.variant
|
||||||
* - items.variant.product
|
* - items.variant.product
|
||||||
|
* - items.variant.product.profiles
|
||||||
* - refunds
|
* - refunds
|
||||||
* - region
|
* - region
|
||||||
* - shipping_methods
|
* - shipping_methods
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export const defaultStoreProductsRelations = [
|
|||||||
"tags",
|
"tags",
|
||||||
"collection",
|
"collection",
|
||||||
"type",
|
"type",
|
||||||
|
"profiles",
|
||||||
]
|
]
|
||||||
|
|
||||||
export const defaultStoreProductsFields: (keyof Product)[] = [
|
export const defaultStoreProductsFields: (keyof Product)[] = [
|
||||||
@@ -78,7 +79,6 @@ export const defaultStoreProductsFields: (keyof Product)[] = [
|
|||||||
"is_giftcard",
|
"is_giftcard",
|
||||||
"discountable",
|
"discountable",
|
||||||
"thumbnail",
|
"thumbnail",
|
||||||
"profile_id",
|
|
||||||
"collection_id",
|
"collection_id",
|
||||||
"type_id",
|
"type_id",
|
||||||
"weight",
|
"weight",
|
||||||
@@ -97,7 +97,10 @@ export const defaultStoreProductsFields: (keyof Product)[] = [
|
|||||||
|
|
||||||
export const allowedStoreProductsFields = [
|
export const allowedStoreProductsFields = [
|
||||||
...defaultStoreProductsFields,
|
...defaultStoreProductsFields,
|
||||||
// TODO: order prop validation
|
// profile_id is not a column in the products table, so it should be ignored as it
|
||||||
|
// will be rejected by typeorm as invalid, though, it is an entity property
|
||||||
|
// that we want to return, so it part of the allowedStoreProductsFields
|
||||||
|
"profile_id",
|
||||||
"variants.title",
|
"variants.title",
|
||||||
"variants.prices.amount",
|
"variants.prices.amount",
|
||||||
]
|
]
|
||||||
|
|||||||
+1
-4
@@ -21,10 +21,7 @@ describe("GET /store/shipping-options", () => {
|
|||||||
it("calls CartService retrieve", () => {
|
it("calls CartService retrieve", () => {
|
||||||
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledTimes(1)
|
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledTimes(1)
|
||||||
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledWith(
|
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledWith(
|
||||||
IdMap.getId("emptyCart"),
|
IdMap.getId("emptyCart")
|
||||||
{
|
|
||||||
relations: ["items.variant", "items.variant.product"],
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -53,12 +53,8 @@ export default async (req, res) => {
|
|||||||
"shippingProfileService"
|
"shippingProfileService"
|
||||||
)
|
)
|
||||||
|
|
||||||
const cart = await cartService.retrieveWithTotals(cart_id, {
|
const cart = await cartService.retrieveWithTotals(cart_id)
|
||||||
relations: ["items.variant", "items.variant.product"],
|
|
||||||
})
|
|
||||||
|
|
||||||
const options = await shippingProfileService.fetchCartOptions(cart)
|
const options = await shippingProfileService.fetchCartOptions(cart)
|
||||||
|
|
||||||
const data = await pricingService.setShippingOptionPrices(options, {
|
const data = await pricingService.setShippingOptionPrices(options, {
|
||||||
cart_id,
|
cart_id,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -139,13 +139,9 @@ export default async (req, res) => {
|
|||||||
.retrieve(swapDto.order_id, {
|
.retrieve(swapDto.order_id, {
|
||||||
select: ["refunded_total", "total"],
|
select: ["refunded_total", "total"],
|
||||||
relations: [
|
relations: [
|
||||||
"items",
|
|
||||||
"items.variant",
|
"items.variant",
|
||||||
"items.tax_lines",
|
"items.tax_lines",
|
||||||
"swaps",
|
"swaps.additional_items.variant.product.profiles",
|
||||||
"swaps.additional_items",
|
|
||||||
"swaps.additional_items.variant",
|
|
||||||
"swaps.additional_items.variant.product",
|
|
||||||
"swaps.additional_items.tax_lines",
|
"swaps.additional_items.tax_lines",
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||||
|
|
||||||
|
export class addTableProductShippingProfile1680857773273
|
||||||
|
implements MigrationInterface
|
||||||
|
{
|
||||||
|
name = "addTableProductShippingProfile1680857773273"
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`
|
||||||
|
CREATE TABLE IF NOT EXISTS "product_shipping_profile"
|
||||||
|
(
|
||||||
|
"profile_id" text NOT NULL,
|
||||||
|
"product_id" text NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO "product_shipping_profile" ("profile_id", "product_id")
|
||||||
|
SELECT "profile_id", "id" FROM "product";
|
||||||
|
|
||||||
|
ALTER TABLE "product" DROP COLUMN IF EXISTS "profile_id";
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "idx_product_shipping_profile_profile_id_product_id_unique" ON "product_shipping_profile" ("profile_id", "product_id");
|
||||||
|
CREATE INDEX IF NOT EXISTS "idx_product_shipping_profile_product_id" ON "product_shipping_profile" ("product_id");
|
||||||
|
CREATE INDEX IF NOT EXISTS "idx_product_shipping_profile_profile_id" ON "product_shipping_profile" ("profile_id");
|
||||||
|
DROP INDEX IF EXISTS "IDX_80823b7ae866dc5acae2dac6d2";
|
||||||
|
`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
DROP INDEX IF EXISTS "idx_product_shipping_profile_profile_id_product_id_unique";
|
||||||
|
DROP INDEX IF EXISTS "idx_product_shipping_profile_product_id";
|
||||||
|
DROP INDEX IF EXISTS "idx_product_shipping_profile_profile_id";
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS "product_shipping_profile";
|
||||||
|
|
||||||
|
ALTER TABLE "product" ADD COLUMN IF NOT EXISTS "profile_id";
|
||||||
|
|
||||||
|
UPDATE "product" SET "profile_id" = "product_shipping_profile"."profile_id"
|
||||||
|
FROM "product_shipping_profile"
|
||||||
|
WHERE "product"."id" = "product_shipping_profile"."product_id";
|
||||||
|
|
||||||
|
ALTER TABLE "product" ALTER COLUMN profile_id SET NOT NULL;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS "IDX_80823b7ae866dc5acae2dac6d2" ON "product" ("profile_id");
|
||||||
|
ALTER TABLE "product" ADD CONSTRAINT "FK_80823b7ae866dc5acae2dac6d2c" FOREIGN KEY ("profile_id") REFERENCES "shipping_profile"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||||
|
|
||||||
|
export class dropProductIdFkSalesChannels1680857773273
|
||||||
|
implements MigrationInterface
|
||||||
|
{
|
||||||
|
name = "dropProductIdFkSalesChannels1680857773273"
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
alter table if exists "product_sales_channel" drop constraint if exists "FK_5a4d5e1e60f97633547821ec8d6";
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE if exists "product_sales_channel" ADD CONSTRAINT if not exists "FK_5a4d5e1e60f97633547821ec8d6" FOREIGN KEY ("product_id") REFERENCES "product"("id") ON DELETE cascade ON UPDATE NO ACTION;
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||||
|
|
||||||
|
export class dropVariantIdFkMoneyAmount1680857773273
|
||||||
|
implements MigrationInterface
|
||||||
|
{
|
||||||
|
name = "dropVariantIdFkMoneyAmount1680857773273"
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`
|
||||||
|
alter table if exists "money_amount" drop constraint if exists "FK_17a06d728e4cfbc5bd2ddb70af0";
|
||||||
|
`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE if exists "money_amount" ADD CONSTRAINT if not exists "FK_17a06d728e4cfbc5bd2ddb70af0" FOREIGN KEY ("variant_id") REFERENCES "product_variant"("id") ON DELETE cascade ON UPDATE NO ACTION;
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
|
AfterLoad,
|
||||||
BeforeInsert,
|
BeforeInsert,
|
||||||
|
BeforeUpdate,
|
||||||
Column,
|
Column,
|
||||||
Entity,
|
Entity,
|
||||||
Index,
|
Index,
|
||||||
@@ -23,7 +25,7 @@ import { SalesChannel } from "./sales-channel"
|
|||||||
import { ShippingProfile } from "./shipping-profile"
|
import { ShippingProfile } from "./shipping-profile"
|
||||||
import { SoftDeletableEntity } from "../interfaces/models/soft-deletable-entity"
|
import { SoftDeletableEntity } from "../interfaces/models/soft-deletable-entity"
|
||||||
import _ from "lodash"
|
import _ from "lodash"
|
||||||
import { generateEntityId } from "../utils/generate-entity-id"
|
import { generateEntityId } from "../utils"
|
||||||
|
|
||||||
export enum ProductStatus {
|
export enum ProductStatus {
|
||||||
DRAFT = "draft",
|
DRAFT = "draft",
|
||||||
@@ -92,14 +94,26 @@ export class Product extends SoftDeletableEntity {
|
|||||||
})
|
})
|
||||||
categories: ProductCategory[]
|
categories: ProductCategory[]
|
||||||
|
|
||||||
@Index()
|
|
||||||
@Column()
|
|
||||||
profile_id: string
|
profile_id: string
|
||||||
|
|
||||||
@ManyToOne(() => ShippingProfile)
|
|
||||||
@JoinColumn({ name: "profile_id" })
|
|
||||||
profile: ShippingProfile
|
profile: ShippingProfile
|
||||||
|
|
||||||
|
@ManyToMany(() => ShippingProfile, {
|
||||||
|
cascade: ["remove", "soft-remove"],
|
||||||
|
})
|
||||||
|
@JoinTable({
|
||||||
|
name: "product_shipping_profile",
|
||||||
|
joinColumn: {
|
||||||
|
name: "product_id",
|
||||||
|
referencedColumnName: "id",
|
||||||
|
},
|
||||||
|
inverseJoinColumn: {
|
||||||
|
name: "profile_id",
|
||||||
|
referencedColumnName: "id",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
profiles: ShippingProfile[]
|
||||||
|
|
||||||
@Column({ type: "int", nullable: true })
|
@Column({ type: "int", nullable: true })
|
||||||
weight: number | null
|
weight: number | null
|
||||||
|
|
||||||
@@ -185,6 +199,25 @@ export class Product extends SoftDeletableEntity {
|
|||||||
if (!this.handle) {
|
if (!this.handle) {
|
||||||
this.handle = _.kebabCase(this.title)
|
this.handle = _.kebabCase(this.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.profile_id) {
|
||||||
|
this.profiles = [{ id: this.profile_id }] as ShippingProfile[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeUpdate()
|
||||||
|
private beforeUpdate(): void {
|
||||||
|
if (this.profile_id) {
|
||||||
|
this.profiles = [{ id: this.profile_id }] as ShippingProfile[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterLoad()
|
||||||
|
private afterLoad(): void {
|
||||||
|
if (this.profiles) {
|
||||||
|
this.profile = this.profiles[this.profiles.length - 1]!
|
||||||
|
this.profile_id = this.profile?.id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,6 +321,12 @@ export class Product extends SoftDeletableEntity {
|
|||||||
* description: Available if the relation `profile` is expanded.
|
* description: Available if the relation `profile` is expanded.
|
||||||
* nullable: true
|
* nullable: true
|
||||||
* $ref: "#/components/schemas/ShippingProfile"
|
* $ref: "#/components/schemas/ShippingProfile"
|
||||||
|
* profiles:
|
||||||
|
* description: Available if the relation `profiles` is expanded.
|
||||||
|
* nullable: true
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* $ref: "#/components/schemas/ShippingProfile"
|
||||||
* weight:
|
* weight:
|
||||||
* description: The weight of the Product Variant. May be used in shipping rate calculations.
|
* description: The weight of the Product Variant. May be used in shipping rate calculations.
|
||||||
* nullable: true
|
* nullable: true
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
import { BeforeInsert, Column, Entity, OneToMany } from "typeorm"
|
import {
|
||||||
|
BeforeInsert,
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
JoinTable,
|
||||||
|
ManyToMany,
|
||||||
|
OneToMany,
|
||||||
|
} from "typeorm"
|
||||||
|
|
||||||
import { DbAwareColumn } from "../utils/db-aware-column"
|
import { DbAwareColumn, generateEntityId } from "../utils"
|
||||||
import { Product } from "./product"
|
import { Product } from "./product"
|
||||||
import { ShippingOption } from "./shipping-option"
|
import { ShippingOption } from "./shipping-option"
|
||||||
import { SoftDeletableEntity } from "../interfaces/models/soft-deletable-entity"
|
import { SoftDeletableEntity } from "../interfaces"
|
||||||
import { generateEntityId } from "../utils/generate-entity-id"
|
|
||||||
|
|
||||||
export enum ShippingProfileType {
|
export enum ShippingProfileType {
|
||||||
DEFAULT = "default",
|
DEFAULT = "default",
|
||||||
@@ -20,7 +26,18 @@ export class ShippingProfile extends SoftDeletableEntity {
|
|||||||
@DbAwareColumn({ type: "enum", enum: ShippingProfileType })
|
@DbAwareColumn({ type: "enum", enum: ShippingProfileType })
|
||||||
type: ShippingProfileType
|
type: ShippingProfileType
|
||||||
|
|
||||||
@OneToMany(() => Product, (product) => product.profile)
|
@ManyToMany(() => Product)
|
||||||
|
@JoinTable({
|
||||||
|
name: "product_shipping_profile",
|
||||||
|
joinColumn: {
|
||||||
|
name: "profile_id",
|
||||||
|
referencedColumnName: "id",
|
||||||
|
},
|
||||||
|
inverseJoinColumn: {
|
||||||
|
name: "product_id",
|
||||||
|
referencedColumnName: "id",
|
||||||
|
},
|
||||||
|
})
|
||||||
products: Product[]
|
products: Product[]
|
||||||
|
|
||||||
@OneToMany(() => ShippingOption, (so) => so.profile)
|
@OneToMany(() => ShippingOption, (so) => so.profile)
|
||||||
|
|||||||
@@ -825,25 +825,6 @@ export const ProductRepository = dataSource.getRepository(Product).extend({
|
|||||||
|
|
||||||
return queryBuilder
|
return queryBuilder
|
||||||
},*/
|
},*/
|
||||||
|
|
||||||
/**
|
|
||||||
* Upserts shipping profile for products
|
|
||||||
* @param productIds IDs of products to update
|
|
||||||
* @param shippingProfileId ID of shipping profile to assign to products
|
|
||||||
* @returns updated products
|
|
||||||
*/
|
|
||||||
async upsertShippingProfile(
|
|
||||||
productIds: string[],
|
|
||||||
shippingProfileId: string
|
|
||||||
): Promise<Product[]> {
|
|
||||||
await this.createQueryBuilder()
|
|
||||||
.update(Product)
|
|
||||||
.set({ profile_id: shippingProfileId })
|
|
||||||
.where({ id: In(productIds) })
|
|
||||||
.execute()
|
|
||||||
|
|
||||||
return await this.findByIds(productIds)
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export default ProductRepository
|
export default ProductRepository
|
||||||
@@ -859,7 +859,9 @@ describe("CartService", () => {
|
|||||||
gift_cards: true,
|
gift_cards: true,
|
||||||
items: {
|
items: {
|
||||||
variant: {
|
variant: {
|
||||||
product: true,
|
product: {
|
||||||
|
profiles: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
payment_sessions: true,
|
payment_sessions: true,
|
||||||
|
|||||||
@@ -46,18 +46,20 @@ describe("StoreService", () => {
|
|||||||
it("successfully retrieve store", async () => {
|
it("successfully retrieve store", async () => {
|
||||||
await storeService.retrieve().catch(() => void 0)
|
await storeService.retrieve().catch(() => void 0)
|
||||||
|
|
||||||
expect(storeRepository.findOne).toHaveBeenCalledTimes(1)
|
expect(storeRepository.find).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("update", () => {
|
describe("update", () => {
|
||||||
const storeRepository = MockRepository({
|
const storeRepository = MockRepository({
|
||||||
findOne: () =>
|
find: () =>
|
||||||
Promise.resolve({
|
Promise.resolve([
|
||||||
|
{
|
||||||
id: IdMap.getId("store"),
|
id: IdMap.getId("store"),
|
||||||
name: "Medusa",
|
name: "Medusa",
|
||||||
default_currency_code: "usd",
|
default_currency_code: "usd",
|
||||||
}),
|
},
|
||||||
|
]),
|
||||||
})
|
})
|
||||||
|
|
||||||
const currencyRepository = MockRepository({})
|
const currencyRepository = MockRepository({})
|
||||||
@@ -77,7 +79,7 @@ describe("StoreService", () => {
|
|||||||
name: "Medusa Commerce",
|
name: "Medusa Commerce",
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(storeRepository.findOne).toHaveBeenCalledTimes(1)
|
expect(storeRepository.find).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
expect(storeRepository.save).toHaveBeenCalledTimes(1)
|
expect(storeRepository.save).toHaveBeenCalledTimes(1)
|
||||||
expect(storeRepository.save).toHaveBeenCalledWith({
|
expect(storeRepository.save).toHaveBeenCalledWith({
|
||||||
@@ -94,18 +96,20 @@ describe("StoreService", () => {
|
|||||||
})
|
})
|
||||||
).rejects.toThrow("Currency with code 1cd does not exist")
|
).rejects.toThrow("Currency with code 1cd does not exist")
|
||||||
|
|
||||||
expect(storeRepository.findOne).toHaveBeenCalledTimes(1)
|
expect(storeRepository.find).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("addCurrency", () => {
|
describe("addCurrency", () => {
|
||||||
const storeRepository = MockRepository({
|
const storeRepository = MockRepository({
|
||||||
findOne: () =>
|
find: () =>
|
||||||
Promise.resolve({
|
Promise.resolve([
|
||||||
|
{
|
||||||
id: IdMap.getId("store"),
|
id: IdMap.getId("store"),
|
||||||
name: "Medusa",
|
name: "Medusa",
|
||||||
currencies: [{ code: "dkk" }],
|
currencies: [{ code: "dkk" }],
|
||||||
}),
|
},
|
||||||
|
]),
|
||||||
})
|
})
|
||||||
|
|
||||||
const currencyRepository = MockRepository({
|
const currencyRepository = MockRepository({
|
||||||
@@ -134,7 +138,7 @@ describe("StoreService", () => {
|
|||||||
it("successfully adds currency", async () => {
|
it("successfully adds currency", async () => {
|
||||||
await storeService.addCurrency("sek")
|
await storeService.addCurrency("sek")
|
||||||
|
|
||||||
expect(storeRepository.findOne).toHaveBeenCalledTimes(1)
|
expect(storeRepository.find).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
expect(storeRepository.save).toHaveBeenCalledTimes(1)
|
expect(storeRepository.save).toHaveBeenCalledTimes(1)
|
||||||
expect(storeRepository.save).toHaveBeenCalledWith({
|
expect(storeRepository.save).toHaveBeenCalledWith({
|
||||||
@@ -155,18 +159,20 @@ describe("StoreService", () => {
|
|||||||
"Currency already added"
|
"Currency already added"
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(storeRepository.findOne).toHaveBeenCalledTimes(1)
|
expect(storeRepository.find).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("removeCurrency", () => {
|
describe("removeCurrency", () => {
|
||||||
const storeRepository = MockRepository({
|
const storeRepository = MockRepository({
|
||||||
findOne: () =>
|
find: () =>
|
||||||
Promise.resolve({
|
Promise.resolve([
|
||||||
|
{
|
||||||
id: IdMap.getId("store"),
|
id: IdMap.getId("store"),
|
||||||
name: "Medusa",
|
name: "Medusa",
|
||||||
currencies: [{ code: "dkk" }],
|
currencies: [{ code: "dkk" }],
|
||||||
}),
|
},
|
||||||
|
]),
|
||||||
})
|
})
|
||||||
|
|
||||||
const currencyRepository = MockRepository({
|
const currencyRepository = MockRepository({
|
||||||
@@ -195,7 +201,7 @@ describe("StoreService", () => {
|
|||||||
it("successfully removes currency", async () => {
|
it("successfully removes currency", async () => {
|
||||||
await storeService.removeCurrency("dkk")
|
await storeService.removeCurrency("dkk")
|
||||||
|
|
||||||
expect(storeRepository.findOne).toHaveBeenCalledTimes(1)
|
expect(storeRepository.find).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
expect(storeRepository.save).toHaveBeenCalledTimes(1)
|
expect(storeRepository.save).toHaveBeenCalledTimes(1)
|
||||||
expect(storeRepository.save).toHaveBeenCalledWith({
|
expect(storeRepository.save).toHaveBeenCalledWith({
|
||||||
|
|||||||
@@ -280,7 +280,9 @@ describe("SwapService", () => {
|
|||||||
},
|
},
|
||||||
items: {
|
items: {
|
||||||
variant: {
|
variant: {
|
||||||
product: true,
|
product: {
|
||||||
|
profiles: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
swaps: {
|
swaps: {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const eventBusService = {
|
|||||||
describe("UserService", () => {
|
describe("UserService", () => {
|
||||||
describe("retrieve", () => {
|
describe("retrieve", () => {
|
||||||
const userRepository = MockRepository({
|
const userRepository = MockRepository({
|
||||||
findOne: () => Promise.resolve({ id: IdMap.getId("ironman") }),
|
find: () => Promise.resolve([{ id: IdMap.getId("ironman") }]),
|
||||||
})
|
})
|
||||||
const userService = new UserService({
|
const userService = new UserService({
|
||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
@@ -25,8 +25,8 @@ describe("UserService", () => {
|
|||||||
it("successfully retrieves a user", async () => {
|
it("successfully retrieves a user", async () => {
|
||||||
const result = await userService.retrieve(IdMap.getId("ironman"))
|
const result = await userService.retrieve(IdMap.getId("ironman"))
|
||||||
|
|
||||||
expect(userRepository.findOne).toHaveBeenCalledTimes(1)
|
expect(userRepository.find).toHaveBeenCalledTimes(1)
|
||||||
expect(userRepository.findOne).toHaveBeenCalledWith({
|
expect(userRepository.find).toHaveBeenCalledWith({
|
||||||
where: { id: IdMap.getId("ironman") },
|
where: { id: IdMap.getId("ironman") },
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ describe("UserService", () => {
|
|||||||
|
|
||||||
describe("update", () => {
|
describe("update", () => {
|
||||||
const userRepository = MockRepository({
|
const userRepository = MockRepository({
|
||||||
findOne: () => Promise.resolve({ id: IdMap.getId("ironman") }),
|
find: () => Promise.resolve([{ id: IdMap.getId("ironman") }]),
|
||||||
})
|
})
|
||||||
const userService = new UserService({
|
const userService = new UserService({
|
||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
@@ -159,8 +159,8 @@ describe("UserService", () => {
|
|||||||
|
|
||||||
describe("generateResetPasswordToken", () => {
|
describe("generateResetPasswordToken", () => {
|
||||||
const userRepository = MockRepository({
|
const userRepository = MockRepository({
|
||||||
findOne: () =>
|
find: () =>
|
||||||
Promise.resolve({ id: IdMap.getId("ironman"), password_hash: "lol" }),
|
Promise.resolve([{ id: IdMap.getId("ironman"), password_hash: "lol" }]),
|
||||||
})
|
})
|
||||||
|
|
||||||
const userService = new UserService({
|
const userService = new UserService({
|
||||||
|
|||||||
@@ -480,12 +480,7 @@ class CartService extends TransactionBaseService {
|
|||||||
return await this.atomicPhase_(
|
return await this.atomicPhase_(
|
||||||
async (transactionManager: EntityManager) => {
|
async (transactionManager: EntityManager) => {
|
||||||
const cart = await this.retrieve(cartId, {
|
const cart = await this.retrieve(cartId, {
|
||||||
relations: [
|
relations: ["items.variant.product.profiles", "payment_sessions"],
|
||||||
"items",
|
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
"payment_sessions",
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const lineItem = cart.items.find((item) => item.id === lineItemId)
|
const lineItem = cart.items.find((item) => item.id === lineItemId)
|
||||||
@@ -518,9 +513,7 @@ class CartService extends TransactionBaseService {
|
|||||||
|
|
||||||
const result = await this.retrieve(cartId, {
|
const result = await this.retrieve(cartId, {
|
||||||
relations: [
|
relations: [
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
"discounts",
|
"discounts",
|
||||||
"discounts.rule",
|
"discounts.rule",
|
||||||
"region",
|
"region",
|
||||||
@@ -719,9 +712,7 @@ class CartService extends TransactionBaseService {
|
|||||||
|
|
||||||
cart = await this.retrieve(cart.id, {
|
cart = await this.retrieve(cart.id, {
|
||||||
relations: [
|
relations: [
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
"discounts",
|
"discounts",
|
||||||
"discounts.rule",
|
"discounts.rule",
|
||||||
"region",
|
"region",
|
||||||
@@ -899,9 +890,7 @@ class CartService extends TransactionBaseService {
|
|||||||
|
|
||||||
cart = await this.retrieve(cart.id, {
|
cart = await this.retrieve(cart.id, {
|
||||||
relations: [
|
relations: [
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
"discounts",
|
"discounts",
|
||||||
"discounts.rule",
|
"discounts.rule",
|
||||||
"region",
|
"region",
|
||||||
@@ -978,9 +967,7 @@ class CartService extends TransactionBaseService {
|
|||||||
|
|
||||||
const updatedCart = await this.retrieve(cartId, {
|
const updatedCart = await this.retrieve(cartId, {
|
||||||
relations: [
|
relations: [
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
"discounts",
|
"discounts",
|
||||||
"discounts.rule",
|
"discounts.rule",
|
||||||
"region",
|
"region",
|
||||||
@@ -1054,9 +1041,7 @@ class CartService extends TransactionBaseService {
|
|||||||
async (transactionManager: EntityManager) => {
|
async (transactionManager: EntityManager) => {
|
||||||
const cartRepo = transactionManager.withRepository(this.cartRepository_)
|
const cartRepo = transactionManager.withRepository(this.cartRepository_)
|
||||||
const relations = [
|
const relations = [
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
"shipping_methods.shipping_option",
|
"shipping_methods.shipping_option",
|
||||||
"shipping_address",
|
"shipping_address",
|
||||||
@@ -1076,7 +1061,7 @@ class CartService extends TransactionBaseService {
|
|||||||
) &&
|
) &&
|
||||||
data.sales_channel_id
|
data.sales_channel_id
|
||||||
) {
|
) {
|
||||||
relations.push("items.variant", "items.variant.product")
|
relations.push("items.variant.product.profiles")
|
||||||
}
|
}
|
||||||
|
|
||||||
const cart = await this.retrieve(cartId, {
|
const cart = await this.retrieve(cartId, {
|
||||||
@@ -1523,9 +1508,7 @@ class CartService extends TransactionBaseService {
|
|||||||
async (transactionManager: EntityManager) => {
|
async (transactionManager: EntityManager) => {
|
||||||
const cart = await this.retrieve(cartId, {
|
const cart = await this.retrieve(cartId, {
|
||||||
relations: [
|
relations: [
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
"region",
|
"region",
|
||||||
"discounts",
|
"discounts",
|
||||||
"discounts.rule",
|
"discounts.rule",
|
||||||
@@ -1622,12 +1605,7 @@ class CartService extends TransactionBaseService {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const cart = await this.retrieveWithTotals(cartId, {
|
const cart = await this.retrieveWithTotals(cartId, {
|
||||||
relations: [
|
relations: ["payment_sessions", "items.variant.product.profiles"],
|
||||||
"payment_sessions",
|
|
||||||
"items",
|
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// If cart total is 0, we don't perform anything payment related
|
// If cart total is 0, we don't perform anything payment related
|
||||||
@@ -1697,9 +1675,7 @@ class CartService extends TransactionBaseService {
|
|||||||
|
|
||||||
const cart = await this.retrieveWithTotals(cartId, {
|
const cart = await this.retrieveWithTotals(cartId, {
|
||||||
relations: [
|
relations: [
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
"customer",
|
"customer",
|
||||||
"region",
|
"region",
|
||||||
"region.payment_providers",
|
"region.payment_providers",
|
||||||
@@ -1823,9 +1799,7 @@ class CartService extends TransactionBaseService {
|
|||||||
cartId,
|
cartId,
|
||||||
{
|
{
|
||||||
relations: [
|
relations: [
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
"items.adjustments",
|
"items.adjustments",
|
||||||
"discounts",
|
"discounts",
|
||||||
"discounts.rule",
|
"discounts.rule",
|
||||||
@@ -2107,9 +2081,7 @@ class CartService extends TransactionBaseService {
|
|||||||
relations: [
|
relations: [
|
||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
"shipping_methods.shipping_option",
|
"shipping_methods.shipping_option",
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
"payment_sessions",
|
"payment_sessions",
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -2292,7 +2264,7 @@ class CartService extends TransactionBaseService {
|
|||||||
cart.items = await lineItemServiceTx.list(
|
cart.items = await lineItemServiceTx.list(
|
||||||
{ id: cart.items.map((i) => i.id) },
|
{ id: cart.items.map((i) => i.id) },
|
||||||
{
|
{
|
||||||
relations: ["variant", "variant.product"],
|
relations: ["variant.product.profiles"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -2448,9 +2420,7 @@ class CartService extends TransactionBaseService {
|
|||||||
async (transactionManager: EntityManager) => {
|
async (transactionManager: EntityManager) => {
|
||||||
const cart = await this.retrieve(cartId, {
|
const cart = await this.retrieve(cartId, {
|
||||||
relations: [
|
relations: [
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
"discounts",
|
"discounts",
|
||||||
"discounts.rule",
|
"discounts.rule",
|
||||||
"payment_sessions",
|
"payment_sessions",
|
||||||
@@ -2537,9 +2507,7 @@ class CartService extends TransactionBaseService {
|
|||||||
"discounts",
|
"discounts",
|
||||||
"discounts.rule",
|
"discounts.rule",
|
||||||
"gift_cards",
|
"gift_cards",
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
"items.adjustments",
|
"items.adjustments",
|
||||||
"region",
|
"region",
|
||||||
"region.tax_rates",
|
"region.tax_rates",
|
||||||
@@ -2809,9 +2777,7 @@ class CartService extends TransactionBaseService {
|
|||||||
private getTotalsRelations(config: FindConfig<Cart>): string[] {
|
private getTotalsRelations(config: FindConfig<Cart>): string[] {
|
||||||
const relationSet = new Set(config.relations)
|
const relationSet = new Set(config.relations)
|
||||||
|
|
||||||
relationSet.add("items")
|
relationSet.add("items.variant.product.profiles")
|
||||||
relationSet.add("items.variant")
|
|
||||||
relationSet.add("items.variant.product")
|
|
||||||
relationSet.add("items.tax_lines")
|
relationSet.add("items.tax_lines")
|
||||||
relationSet.add("items.adjustments")
|
relationSet.add("items.adjustments")
|
||||||
relationSet.add("gift_cards")
|
relationSet.add("gift_cards")
|
||||||
|
|||||||
@@ -430,7 +430,7 @@ export default class ClaimService extends TransactionBaseService {
|
|||||||
id: result.additional_items.map((i) => i.id),
|
id: result.additional_items.map((i) => i.id),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
relations: ["variant", "variant.product"],
|
relations: ["variant.product.profiles"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -525,10 +525,8 @@ export default class ClaimService extends TransactionBaseService {
|
|||||||
async (transactionManager: EntityManager) => {
|
async (transactionManager: EntityManager) => {
|
||||||
const claim = await this.retrieve(id, {
|
const claim = await this.retrieve(id, {
|
||||||
relations: [
|
relations: [
|
||||||
"additional_items",
|
|
||||||
"additional_items.tax_lines",
|
"additional_items.tax_lines",
|
||||||
"additional_items.variant",
|
"additional_items.variant.product.profiles",
|
||||||
"additional_items.variant.product",
|
|
||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
"shipping_methods.shipping_option",
|
"shipping_methods.shipping_option",
|
||||||
"shipping_methods.tax_lines",
|
"shipping_methods.tax_lines",
|
||||||
|
|||||||
@@ -384,9 +384,7 @@ class DraftOrderService extends TransactionBaseService {
|
|||||||
relations: [
|
relations: [
|
||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
"shipping_methods.shipping_option",
|
"shipping_methods.shipping_option",
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
"payment_sessions",
|
"payment_sessions",
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -575,7 +575,7 @@ export default class OrderEditService extends TransactionBaseService {
|
|||||||
|
|
||||||
let lineItem = await lineItemServiceTx.create(lineItemData)
|
let lineItem = await lineItemServiceTx.create(lineItemData)
|
||||||
lineItem = await lineItemServiceTx.retrieve(lineItem.id, {
|
lineItem = await lineItemServiceTx.retrieve(lineItem.id, {
|
||||||
relations: ["variant", "variant.product"],
|
relations: ["variant.product.profiles"],
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.refreshAdjustments(orderEditId)
|
await this.refreshAdjustments(orderEditId)
|
||||||
|
|||||||
@@ -342,11 +342,9 @@ class OrderService extends TransactionBaseService {
|
|||||||
const totalsToSelect = select.filter((v) => totalFields.includes(v))
|
const totalsToSelect = select.filter((v) => totalFields.includes(v))
|
||||||
if (totalsToSelect.length > 0) {
|
if (totalsToSelect.length > 0) {
|
||||||
const relationSet = new Set(relations)
|
const relationSet = new Set(relations)
|
||||||
relationSet.add("items")
|
|
||||||
relationSet.add("items.tax_lines")
|
relationSet.add("items.tax_lines")
|
||||||
relationSet.add("items.adjustments")
|
relationSet.add("items.adjustments")
|
||||||
relationSet.add("items.variant")
|
relationSet.add("items.variant.product.profiles")
|
||||||
relationSet.add("items.variant.product")
|
|
||||||
relationSet.add("swaps")
|
relationSet.add("swaps")
|
||||||
relationSet.add("swaps.additional_items")
|
relationSet.add("swaps.additional_items")
|
||||||
relationSet.add("swaps.additional_items.tax_lines")
|
relationSet.add("swaps.additional_items.tax_lines")
|
||||||
@@ -1034,9 +1032,7 @@ class OrderService extends TransactionBaseService {
|
|||||||
relations: [
|
relations: [
|
||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
"shipping_methods.shipping_option",
|
"shipping_methods.shipping_option",
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
const { shipping_methods } = order
|
const { shipping_methods } = order
|
||||||
@@ -1403,10 +1399,8 @@ class OrderService extends TransactionBaseService {
|
|||||||
"billing_address",
|
"billing_address",
|
||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
"shipping_methods.shipping_option",
|
"shipping_methods.shipping_option",
|
||||||
"items",
|
|
||||||
"items.adjustments",
|
"items.adjustments",
|
||||||
"items.variant",
|
"items.variant.product.profiles",
|
||||||
"items.variant.product",
|
|
||||||
"payments",
|
"payments",
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import {
|
|||||||
ICacheService,
|
ICacheService,
|
||||||
IEventBusService,
|
IEventBusService,
|
||||||
IInventoryService,
|
IInventoryService,
|
||||||
IStockLocationService,
|
|
||||||
InventoryItemDTO,
|
InventoryItemDTO,
|
||||||
InventoryLevelDTO,
|
InventoryLevelDTO,
|
||||||
|
IStockLocationService,
|
||||||
ReservationItemDTO,
|
ReservationItemDTO,
|
||||||
ReserveQuantityContext,
|
ReserveQuantityContext,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { LineItem, Product, ProductVariant } from "../models"
|
import { LineItem, Product, ProductVariant } from "../models"
|
||||||
import { MedusaError, isDefined } from "@medusajs/utils"
|
import { isDefined, MedusaError } from "@medusajs/utils"
|
||||||
import { PricedProduct, PricedVariant } from "../types/pricing"
|
import { PricedProduct, PricedVariant } from "../types/pricing"
|
||||||
|
|
||||||
import { ProductVariantInventoryItem } from "../models/product-variant-inventory-item"
|
import { ProductVariantInventoryItem } from "../models/product-variant-inventory-item"
|
||||||
@@ -303,9 +303,14 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
|||||||
// Verify that variant exists
|
// Verify that variant exists
|
||||||
const variants = await this.productVariantService_
|
const variants = await this.productVariantService_
|
||||||
.withTransaction(this.activeManager_)
|
.withTransaction(this.activeManager_)
|
||||||
.list({
|
.list(
|
||||||
|
{
|
||||||
id: data.map((d) => d.variantId),
|
id: data.map((d) => d.variantId),
|
||||||
})
|
},
|
||||||
|
{
|
||||||
|
select: ["id"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const foundVariantIds = new Set(variants.map((v) => v.id))
|
const foundVariantIds = new Set(variants.map((v) => v.id))
|
||||||
const requestedVariantIds = new Set(data.map((v) => v.variantId))
|
const requestedVariantIds = new Set(data.map((v) => v.variantId))
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager, In } from "typeorm"
|
||||||
import { ProductVariantService, SearchService } from "."
|
import { ProductVariantService, SearchService } from "."
|
||||||
import { TransactionBaseService } from "../interfaces"
|
import { TransactionBaseService } from "../interfaces"
|
||||||
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
|
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
ProductType,
|
ProductType,
|
||||||
ProductVariant,
|
ProductVariant,
|
||||||
SalesChannel,
|
SalesChannel,
|
||||||
|
ShippingProfile,
|
||||||
} from "../models"
|
} from "../models"
|
||||||
import { ImageRepository } from "../repositories/image"
|
import { ImageRepository } from "../repositories/image"
|
||||||
import {
|
import {
|
||||||
@@ -446,6 +447,10 @@ class ProductService extends TransactionBaseService {
|
|||||||
|
|
||||||
let product = productRepo.create(rest)
|
let product = productRepo.create(rest)
|
||||||
|
|
||||||
|
if (rest.profile_id) {
|
||||||
|
product.profiles = [{ id: rest.profile_id! }] as ShippingProfile[]
|
||||||
|
}
|
||||||
|
|
||||||
if (images?.length) {
|
if (images?.length) {
|
||||||
product.images = await imageRepo.upsertImages(images)
|
product.images = await imageRepo.upsertImages(images)
|
||||||
}
|
}
|
||||||
@@ -563,6 +568,10 @@ class ProductService extends TransactionBaseService {
|
|||||||
...rest
|
...rest
|
||||||
} = update
|
} = update
|
||||||
|
|
||||||
|
if (rest.profile_id) {
|
||||||
|
product.profiles = [{ id: rest.profile_id! }] as ShippingProfile[]
|
||||||
|
}
|
||||||
|
|
||||||
if (!product.thumbnail && !update.thumbnail && images?.length) {
|
if (!product.thumbnail && !update.thumbnail && images?.length) {
|
||||||
product.thumbnail = images[0]
|
product.thumbnail = images[0]
|
||||||
}
|
}
|
||||||
@@ -918,21 +927,33 @@ class ProductService extends TransactionBaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Assign a product to a profile, if a profile id null is provided then detach the product from the profile
|
||||||
* @param productIds ID or IDs of the products to update
|
* @param productIds ID or IDs of the products to update
|
||||||
* @param profileId Shipping profile ID to update the shipping options with
|
* @param profileId Shipping profile ID to update the shipping options with
|
||||||
* @returns updated shipping options
|
* @returns updated products
|
||||||
*/
|
*/
|
||||||
async updateShippingProfile(
|
async updateShippingProfile(
|
||||||
productIds: string | string[],
|
productIds: string | string[],
|
||||||
profileId: string
|
profileId: string | null
|
||||||
): Promise<Product[]> {
|
): Promise<Product[]> {
|
||||||
return await this.atomicPhase_(async (manager) => {
|
return await this.atomicPhase_(async (manager) => {
|
||||||
const productRepo = manager.withRepository(this.productRepository_)
|
const productRepo = manager.withRepository(this.productRepository_)
|
||||||
|
|
||||||
const ids = isString(productIds) ? [productIds] : productIds
|
const ids = isString(productIds) ? [productIds] : productIds
|
||||||
|
|
||||||
const products = await productRepo.upsertShippingProfile(ids, profileId)
|
let products = (
|
||||||
|
await this.list(
|
||||||
|
{ id: In(ids) },
|
||||||
|
{ relations: ["profiles"], select: ["id"] }
|
||||||
|
)
|
||||||
|
).map((product) => {
|
||||||
|
product.profiles = !profileId
|
||||||
|
? []
|
||||||
|
: ([{ id: profileId }] as ShippingProfile[])
|
||||||
|
return product
|
||||||
|
})
|
||||||
|
|
||||||
|
products = await productRepo.save(products)
|
||||||
|
|
||||||
await this.eventBus_
|
await this.eventBus_
|
||||||
.withTransaction(manager)
|
.withTransaction(manager)
|
||||||
|
|||||||
@@ -530,7 +530,9 @@ class ReturnService extends TransactionBaseService {
|
|||||||
{
|
{
|
||||||
id: returnOrder.items.map(({ item_id }) => item_id),
|
id: returnOrder.items.map(({ item_id }) => item_id),
|
||||||
},
|
},
|
||||||
{ relations: ["tax_lines", "variant", "variant.product"] }
|
{
|
||||||
|
relations: ["tax_lines", "variant.product.profiles"],
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
returnData.items = returnOrder.items.map((item) => {
|
returnData.items = returnOrder.items.map((item) => {
|
||||||
|
|||||||
@@ -290,23 +290,16 @@ class ShippingProfileService extends TransactionBaseService {
|
|||||||
this.shippingProfileRepository_
|
this.shippingProfileRepository_
|
||||||
)
|
)
|
||||||
|
|
||||||
let profile = await this.retrieve(profileId, {
|
const profile = await this.retrieve(profileId)
|
||||||
relations: [
|
|
||||||
"products",
|
|
||||||
"products.profile",
|
|
||||||
"shipping_options",
|
|
||||||
"shipping_options.profile",
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
const { metadata, products, shipping_options, ...rest } = update
|
const { metadata, products, shipping_options, ...rest } = update
|
||||||
|
|
||||||
if (products) {
|
if (products) {
|
||||||
profile = await this.addProduct(profile.id, products)
|
await this.addProduct(profile.id, products)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shipping_options) {
|
if (shipping_options) {
|
||||||
profile = await this.addShippingOption(profile.id, shipping_options)
|
await this.addShippingOption(profile.id, shipping_options)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
@@ -347,12 +340,22 @@ class ShippingProfileService extends TransactionBaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a product of an array of products to the profile.
|
* @deprecated use {@link addProducts} instead
|
||||||
|
*/
|
||||||
|
async addProduct(
|
||||||
|
profileId: string,
|
||||||
|
productId: string | string[]
|
||||||
|
): Promise<ShippingProfile> {
|
||||||
|
return await this.addProducts(profileId, productId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a product or an array of products to the profile.
|
||||||
* @param profileId - the profile to add the products to.
|
* @param profileId - the profile to add the products to.
|
||||||
* @param productId - the ID of the product or multiple products to add.
|
* @param productId - the ID of the product or multiple products to add.
|
||||||
* @return the result of update
|
* @return the result of update
|
||||||
*/
|
*/
|
||||||
async addProduct(
|
async addProducts(
|
||||||
profileId: string,
|
profileId: string,
|
||||||
productId: string | string[]
|
productId: string | string[]
|
||||||
): Promise<ShippingProfile> {
|
): Promise<ShippingProfile> {
|
||||||
@@ -364,14 +367,27 @@ class ShippingProfileService extends TransactionBaseService {
|
|||||||
profileId
|
profileId
|
||||||
)
|
)
|
||||||
|
|
||||||
return await this.retrieve(profileId, {
|
return await this.retrieve(profileId)
|
||||||
relations: [
|
|
||||||
"products",
|
|
||||||
"products.profile",
|
|
||||||
"shipping_options",
|
|
||||||
"shipping_options.profile",
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a product or an array of products from the profile.
|
||||||
|
* @param profileId - the profile to add the products to.
|
||||||
|
* @param productId - the ID of the product or multiple products to add.
|
||||||
|
* @return the result of update
|
||||||
|
*/
|
||||||
|
async removeProducts(
|
||||||
|
profileId: string | null,
|
||||||
|
productId: string | string[]
|
||||||
|
): Promise<ShippingProfile | void> {
|
||||||
|
return await this.atomicPhase_(async (manager) => {
|
||||||
|
const productServiceTx = this.productService_.withTransaction(manager)
|
||||||
|
|
||||||
|
await productServiceTx.updateShippingProfile(
|
||||||
|
isString(productId) ? [productId] : productId,
|
||||||
|
null
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,12 +412,7 @@ class ShippingProfileService extends TransactionBaseService {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return await this.retrieve(profileId, {
|
return await this.retrieve(profileId, {
|
||||||
relations: [
|
relations: ["products.profiles", "shipping_options.profile"],
|
||||||
"products",
|
|
||||||
"products.profile",
|
|
||||||
"shipping_options",
|
|
||||||
"shipping_options.profile",
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,13 +88,13 @@ class StoreService extends TransactionBaseService {
|
|||||||
},
|
},
|
||||||
config
|
config
|
||||||
)
|
)
|
||||||
const store = await storeRepo.findOne(query)
|
const stores = await storeRepo.find(query)
|
||||||
|
|
||||||
if (!store) {
|
if (!stores.length) {
|
||||||
throw new MedusaError(MedusaError.Types.NOT_FOUND, "Store does not exist")
|
throw new MedusaError(MedusaError.Types.NOT_FOUND, "Store does not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
return store
|
return stores[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getDefaultCurrency_(code: string): Partial<Currency> {
|
protected getDefaultCurrency_(code: string): Partial<Currency> {
|
||||||
|
|||||||
@@ -584,10 +584,7 @@ class SwapService extends TransactionBaseService {
|
|||||||
|
|
||||||
const swap = await this.retrieve(swapId, {
|
const swap = await this.retrieve(swapId, {
|
||||||
relations: [
|
relations: [
|
||||||
"order",
|
"order.items.variant.product.profiles",
|
||||||
"order.items",
|
|
||||||
"order.items.variant",
|
|
||||||
"order.items.variant.product",
|
|
||||||
"order.swaps",
|
"order.swaps",
|
||||||
"order.swaps.additional_items",
|
"order.swaps.additional_items",
|
||||||
"order.discounts",
|
"order.discounts",
|
||||||
@@ -934,10 +931,8 @@ class SwapService extends TransactionBaseService {
|
|||||||
relations: [
|
relations: [
|
||||||
"payment",
|
"payment",
|
||||||
"shipping_address",
|
"shipping_address",
|
||||||
"additional_items",
|
|
||||||
"additional_items.tax_lines",
|
"additional_items.tax_lines",
|
||||||
"additional_items.variant",
|
"additional_items.variant.product.profiles",
|
||||||
"additional_items.variant.product",
|
|
||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
"shipping_methods.shipping_option",
|
"shipping_methods.shipping_option",
|
||||||
"shipping_methods.tax_lines",
|
"shipping_methods.tax_lines",
|
||||||
|
|||||||
@@ -85,16 +85,16 @@ class UserService extends TransactionBaseService {
|
|||||||
const userRepo = this.activeManager_.withRepository(this.userRepository_)
|
const userRepo = this.activeManager_.withRepository(this.userRepository_)
|
||||||
const query = buildQuery({ id: userId }, config)
|
const query = buildQuery({ id: userId }, config)
|
||||||
|
|
||||||
const user = await userRepo.findOne(query)
|
const users = await userRepo.find(query)
|
||||||
|
|
||||||
if (!user) {
|
if (!users.length) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.NOT_FOUND,
|
MedusaError.Types.NOT_FOUND,
|
||||||
`User with id: ${userId} was not found`
|
`User with id: ${userId} was not found`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return user
|
return users[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -189,9 +189,7 @@ class CartCompletionStrategy extends AbstractCartCompletionStrategy {
|
|||||||
"discounts",
|
"discounts",
|
||||||
"discounts.rule",
|
"discounts.rule",
|
||||||
"gift_cards",
|
"gift_cards",
|
||||||
"items",
|
"items.variant.product.profiles",
|
||||||
"items.variant",
|
|
||||||
"items.variant.product",
|
|
||||||
"items.adjustments",
|
"items.adjustments",
|
||||||
"region",
|
"region",
|
||||||
"region.tax_rates",
|
"region.tax_rates",
|
||||||
@@ -295,7 +293,7 @@ class CartCompletionStrategy extends AbstractCartCompletionStrategy {
|
|||||||
"region",
|
"region",
|
||||||
"payment",
|
"payment",
|
||||||
"payment_sessions",
|
"payment_sessions",
|
||||||
"items.variant.product",
|
"items.variant.product.profiles",
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,159 +0,0 @@
|
|||||||
import { EntityManager } from "typeorm"
|
|
||||||
import {
|
|
||||||
IInventoryService,
|
|
||||||
MedusaContainer,
|
|
||||||
ProductTypes,
|
|
||||||
} from "@medusajs/types"
|
|
||||||
import { ulid } from "ulid"
|
|
||||||
import { MedusaError } from "@medusajs/utils"
|
|
||||||
import {
|
|
||||||
DistributedTransaction,
|
|
||||||
TransactionHandlerType,
|
|
||||||
TransactionOrchestrator,
|
|
||||||
TransactionPayload,
|
|
||||||
TransactionState,
|
|
||||||
TransactionStepsDefinition,
|
|
||||||
} from "../../../utils/transaction"
|
|
||||||
import { CreateProductVariantInput } from "../../../types/product-variant"
|
|
||||||
import {
|
|
||||||
attachInventoryItems,
|
|
||||||
createInventoryItems,
|
|
||||||
createProducts,
|
|
||||||
removeInventoryItems,
|
|
||||||
removeProducts,
|
|
||||||
} from "../../functions"
|
|
||||||
|
|
||||||
enum Actions {
|
|
||||||
createProduct = "createProduct",
|
|
||||||
createPrices = "createPrices",
|
|
||||||
attachToSalesChannel = "attachToSalesChannel",
|
|
||||||
createInventoryItems = "createInventoryItems",
|
|
||||||
attachInventoryItems = "attachInventoryItems",
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflowSteps: TransactionStepsDefinition = {
|
|
||||||
next: {
|
|
||||||
action: Actions.createProduct,
|
|
||||||
saveResponse: true,
|
|
||||||
next: {
|
|
||||||
action: Actions.attachToSalesChannel,
|
|
||||||
saveResponse: true,
|
|
||||||
next: {
|
|
||||||
action: Actions.createPrices,
|
|
||||||
saveResponse: true,
|
|
||||||
next: {
|
|
||||||
action: Actions.createInventoryItems,
|
|
||||||
saveResponse: true,
|
|
||||||
next: {
|
|
||||||
action: Actions.attachInventoryItems,
|
|
||||||
noCompensation: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const createProductOrchestrator = new TransactionOrchestrator(
|
|
||||||
"create-product",
|
|
||||||
workflowSteps
|
|
||||||
)
|
|
||||||
|
|
||||||
type InjectedDependencies = {
|
|
||||||
manager: EntityManager
|
|
||||||
container: MedusaContainer
|
|
||||||
inventoryService?: IInventoryService
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createProductWorkflow(
|
|
||||||
dependencies: InjectedDependencies,
|
|
||||||
productId: string,
|
|
||||||
input: CreateProductVariantInput[]
|
|
||||||
): Promise<DistributedTransaction> {
|
|
||||||
const { manager, container } = dependencies
|
|
||||||
async function transactionHandler(
|
|
||||||
actionId: string,
|
|
||||||
type: TransactionHandlerType,
|
|
||||||
payload: TransactionPayload
|
|
||||||
) {
|
|
||||||
const command = {
|
|
||||||
[Actions.createProduct]: {
|
|
||||||
[TransactionHandlerType.INVOKE]: async (
|
|
||||||
data: ProductTypes.CreateProductDTO[]
|
|
||||||
) => {
|
|
||||||
return await createProducts({
|
|
||||||
container,
|
|
||||||
data,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
[TransactionHandlerType.COMPENSATE]: async (
|
|
||||||
data: any[],
|
|
||||||
{ invoke }
|
|
||||||
) => {
|
|
||||||
const createdProducts = invoke[Actions.createProduct]
|
|
||||||
return await removeProducts({ container, data: createdProducts })
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[Actions.createInventoryItems]: {
|
|
||||||
[TransactionHandlerType.INVOKE]: async (
|
|
||||||
data: CreateProductVariantInput[],
|
|
||||||
{ invoke }
|
|
||||||
) => {
|
|
||||||
const { [Actions.createProduct]: products } = invoke
|
|
||||||
|
|
||||||
return await createInventoryItems({
|
|
||||||
container,
|
|
||||||
manager,
|
|
||||||
data: products,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
[TransactionHandlerType.COMPENSATE]: async (_, { invoke }) => {
|
|
||||||
const variantInventoryItemsData = invoke[Actions.createInventoryItems]
|
|
||||||
await removeInventoryItems({
|
|
||||||
container,
|
|
||||||
manager,
|
|
||||||
data: variantInventoryItemsData,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[Actions.attachInventoryItems]: {
|
|
||||||
[TransactionHandlerType.INVOKE]: async (
|
|
||||||
data: CreateProductVariantInput[],
|
|
||||||
{ invoke }
|
|
||||||
) => {
|
|
||||||
const { [Actions.createInventoryItems]: inventoryItemsResult } =
|
|
||||||
invoke
|
|
||||||
|
|
||||||
return await attachInventoryItems({
|
|
||||||
container,
|
|
||||||
manager,
|
|
||||||
data: inventoryItemsResult,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return command[actionId][type](payload.data, payload.context)
|
|
||||||
}
|
|
||||||
|
|
||||||
const orchestrator = createProductOrchestrator
|
|
||||||
|
|
||||||
const transaction = await orchestrator.beginTransaction(
|
|
||||||
ulid(),
|
|
||||||
transactionHandler,
|
|
||||||
input
|
|
||||||
)
|
|
||||||
|
|
||||||
await orchestrator.resume(transaction)
|
|
||||||
|
|
||||||
if (transaction.getState() !== TransactionState.DONE) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_DATA,
|
|
||||||
transaction
|
|
||||||
.getErrors()
|
|
||||||
.map((err) => err.error?.message)
|
|
||||||
.join("\n")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return transaction
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,421 @@
|
|||||||
|
import {
|
||||||
|
TransactionHandlerType,
|
||||||
|
TransactionPayload,
|
||||||
|
TransactionStepHandler,
|
||||||
|
TransactionStepsDefinition,
|
||||||
|
} from "../../../utils/transaction"
|
||||||
|
import {
|
||||||
|
IInventoryService,
|
||||||
|
MedusaContainer,
|
||||||
|
ProductTypes,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
defaultAdminProductFields,
|
||||||
|
defaultAdminProductRelations,
|
||||||
|
} from "../../../api"
|
||||||
|
import {
|
||||||
|
attachInventoryItems,
|
||||||
|
attachSalesChannelToProducts,
|
||||||
|
attachShippingProfileToProducts,
|
||||||
|
createInventoryItems,
|
||||||
|
createProducts,
|
||||||
|
CreateProductsData,
|
||||||
|
CreateProductsPreparedData,
|
||||||
|
detachInventoryItems,
|
||||||
|
detachSalesChannelFromProducts,
|
||||||
|
detachShippingProfileFromProducts,
|
||||||
|
prepareCreateProductsData,
|
||||||
|
removeInventoryItems,
|
||||||
|
removeProducts,
|
||||||
|
updateProductsVariantsPrices,
|
||||||
|
} from "../../functions"
|
||||||
|
import { PricingService, ProductService } from "../../../services"
|
||||||
|
import { CreateProductsWorkflowInputData, InjectedDependencies } from "./types"
|
||||||
|
|
||||||
|
export enum CreateProductsWorkflowActions {
|
||||||
|
prepare = "prepare",
|
||||||
|
createProducts = "createProducts",
|
||||||
|
attachToSalesChannel = "attachToSalesChannel",
|
||||||
|
attachShippingProfile = "attachShippingProfile",
|
||||||
|
createPrices = "createPrices",
|
||||||
|
createInventoryItems = "createInventoryItems",
|
||||||
|
attachInventoryItems = "attachInventoryItems",
|
||||||
|
result = "result",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const workflowSteps: TransactionStepsDefinition = {
|
||||||
|
next: {
|
||||||
|
action: CreateProductsWorkflowActions.prepare,
|
||||||
|
noCompensation: true,
|
||||||
|
next: {
|
||||||
|
action: CreateProductsWorkflowActions.createProducts,
|
||||||
|
next: [
|
||||||
|
{
|
||||||
|
action: CreateProductsWorkflowActions.attachShippingProfile,
|
||||||
|
saveResponse: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: CreateProductsWorkflowActions.attachToSalesChannel,
|
||||||
|
saveResponse: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: CreateProductsWorkflowActions.createPrices,
|
||||||
|
next: {
|
||||||
|
action: CreateProductsWorkflowActions.createInventoryItems,
|
||||||
|
next: {
|
||||||
|
action: CreateProductsWorkflowActions.attachInventoryItems,
|
||||||
|
next: {
|
||||||
|
action: CreateProductsWorkflowActions.result,
|
||||||
|
noCompensation: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldSkipInventoryStep = (
|
||||||
|
container: MedusaContainer,
|
||||||
|
stepName: string
|
||||||
|
) => {
|
||||||
|
const inventoryService = container.resolve(
|
||||||
|
"inventoryService"
|
||||||
|
) as IInventoryService
|
||||||
|
if (!inventoryService) {
|
||||||
|
const logger = container.resolve("logger")
|
||||||
|
logger.warn(
|
||||||
|
`Inventory service not found. You should install the @medusajs/inventory package to use inventory. The '${stepName}' will be skipped.`
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transactionHandler(
|
||||||
|
dependencies: InjectedDependencies
|
||||||
|
): TransactionStepHandler {
|
||||||
|
const { manager, container } = dependencies
|
||||||
|
|
||||||
|
const command = {
|
||||||
|
[CreateProductsWorkflowActions.prepare]: {
|
||||||
|
[TransactionHandlerType.INVOKE]: async (
|
||||||
|
data: CreateProductsWorkflowInputData
|
||||||
|
) => {
|
||||||
|
return await prepareCreateProductsData({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[CreateProductsWorkflowActions.createProducts]: {
|
||||||
|
[TransactionHandlerType.INVOKE]: async (
|
||||||
|
data: CreateProductsData
|
||||||
|
): Promise<ProductTypes.ProductDTO[]> => {
|
||||||
|
return await createProducts({
|
||||||
|
container,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[TransactionHandlerType.COMPENSATE]: async (
|
||||||
|
data: CreateProductsWorkflowInputData,
|
||||||
|
{ invoke }
|
||||||
|
) => {
|
||||||
|
const products = invoke[
|
||||||
|
CreateProductsWorkflowActions.createProducts
|
||||||
|
] as ProductTypes.ProductDTO[]
|
||||||
|
|
||||||
|
if (!products?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return await removeProducts({
|
||||||
|
container,
|
||||||
|
data: products,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[CreateProductsWorkflowActions.attachShippingProfile]: {
|
||||||
|
[TransactionHandlerType.INVOKE]: async (
|
||||||
|
data: CreateProductsWorkflowInputData,
|
||||||
|
{ invoke }
|
||||||
|
) => {
|
||||||
|
const products = invoke[
|
||||||
|
CreateProductsWorkflowActions.createProducts
|
||||||
|
] as ProductTypes.ProductDTO[]
|
||||||
|
const { productsHandleShippingProfileIdMap } = invoke[
|
||||||
|
CreateProductsWorkflowActions.prepare
|
||||||
|
] as CreateProductsPreparedData
|
||||||
|
|
||||||
|
return await attachShippingProfileToProducts({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data: {
|
||||||
|
productsHandleShippingProfileIdMap,
|
||||||
|
products,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[TransactionHandlerType.COMPENSATE]: async (
|
||||||
|
data: CreateProductsWorkflowInputData,
|
||||||
|
{ invoke }
|
||||||
|
) => {
|
||||||
|
const products = invoke[
|
||||||
|
CreateProductsWorkflowActions.createProducts
|
||||||
|
] as ProductTypes.ProductDTO[]
|
||||||
|
const { productsHandleShippingProfileIdMap } = invoke[
|
||||||
|
CreateProductsWorkflowActions.prepare
|
||||||
|
] as CreateProductsPreparedData
|
||||||
|
|
||||||
|
return await detachShippingProfileFromProducts({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data: {
|
||||||
|
productsHandleShippingProfileIdMap,
|
||||||
|
products,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[CreateProductsWorkflowActions.attachToSalesChannel]: {
|
||||||
|
[TransactionHandlerType.INVOKE]: async (
|
||||||
|
data: CreateProductsWorkflowInputData,
|
||||||
|
{ invoke }
|
||||||
|
) => {
|
||||||
|
const products = invoke[
|
||||||
|
CreateProductsWorkflowActions.createProducts
|
||||||
|
] as ProductTypes.ProductDTO[]
|
||||||
|
const { productsHandleSalesChannelsMap } = invoke[
|
||||||
|
CreateProductsWorkflowActions.prepare
|
||||||
|
] as CreateProductsPreparedData
|
||||||
|
|
||||||
|
return await attachSalesChannelToProducts({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data: {
|
||||||
|
productsHandleSalesChannelsMap,
|
||||||
|
products,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[TransactionHandlerType.COMPENSATE]: async (
|
||||||
|
data: CreateProductsWorkflowInputData,
|
||||||
|
{ invoke }
|
||||||
|
) => {
|
||||||
|
const products = invoke[
|
||||||
|
CreateProductsWorkflowActions.createProducts
|
||||||
|
] as ProductTypes.ProductDTO[]
|
||||||
|
const { productsHandleSalesChannelsMap } = invoke[
|
||||||
|
CreateProductsWorkflowActions.prepare
|
||||||
|
] as CreateProductsPreparedData
|
||||||
|
|
||||||
|
return await detachSalesChannelFromProducts({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data: {
|
||||||
|
productsHandleSalesChannelsMap,
|
||||||
|
products,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[CreateProductsWorkflowActions.createInventoryItems]: {
|
||||||
|
[TransactionHandlerType.INVOKE]: async (
|
||||||
|
data: CreateProductsWorkflowInputData,
|
||||||
|
{ invoke }
|
||||||
|
) => {
|
||||||
|
const shouldSkipStep_ = shouldSkipInventoryStep(
|
||||||
|
container,
|
||||||
|
CreateProductsWorkflowActions.createInventoryItems
|
||||||
|
)
|
||||||
|
if (shouldSkipStep_) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { [CreateProductsWorkflowActions.createProducts]: products } =
|
||||||
|
invoke
|
||||||
|
|
||||||
|
return await createInventoryItems({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data: products,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[TransactionHandlerType.COMPENSATE]: async (_, { invoke }) => {
|
||||||
|
const shouldSkipStep_ = shouldSkipInventoryStep(
|
||||||
|
container,
|
||||||
|
CreateProductsWorkflowActions.createInventoryItems
|
||||||
|
)
|
||||||
|
|
||||||
|
const variantInventoryItemsData =
|
||||||
|
invoke[CreateProductsWorkflowActions.createInventoryItems]
|
||||||
|
|
||||||
|
if (shouldSkipStep_ || !variantInventoryItemsData?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await removeInventoryItems({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data: variantInventoryItemsData,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[CreateProductsWorkflowActions.attachInventoryItems]: {
|
||||||
|
[TransactionHandlerType.INVOKE]: async (
|
||||||
|
data: CreateProductsWorkflowInputData,
|
||||||
|
{ invoke }
|
||||||
|
) => {
|
||||||
|
const shouldSkipStep_ = shouldSkipInventoryStep(
|
||||||
|
container,
|
||||||
|
CreateProductsWorkflowActions.attachInventoryItems
|
||||||
|
)
|
||||||
|
if (shouldSkipStep_) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
[CreateProductsWorkflowActions.createInventoryItems]:
|
||||||
|
inventoryItemsResult,
|
||||||
|
} = invoke
|
||||||
|
|
||||||
|
return await attachInventoryItems({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data: inventoryItemsResult,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[TransactionHandlerType.COMPENSATE]: async (
|
||||||
|
data: CreateProductsWorkflowInputData,
|
||||||
|
{ invoke }
|
||||||
|
) => {
|
||||||
|
const shouldSkipStep_ = shouldSkipInventoryStep(
|
||||||
|
container,
|
||||||
|
CreateProductsWorkflowActions.attachInventoryItems
|
||||||
|
)
|
||||||
|
|
||||||
|
const {
|
||||||
|
[CreateProductsWorkflowActions.createInventoryItems]:
|
||||||
|
inventoryItemsResult,
|
||||||
|
} = invoke
|
||||||
|
|
||||||
|
if (shouldSkipStep_ || !inventoryItemsResult?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return await detachInventoryItems({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data: inventoryItemsResult,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[CreateProductsWorkflowActions.createPrices]: {
|
||||||
|
[TransactionHandlerType.INVOKE]: async (
|
||||||
|
data: CreateProductsWorkflowInputData,
|
||||||
|
{ invoke }
|
||||||
|
) => {
|
||||||
|
const { productsHandleVariantsIndexPricesMap } = invoke[
|
||||||
|
CreateProductsWorkflowActions.prepare
|
||||||
|
] as CreateProductsPreparedData
|
||||||
|
const products = invoke[
|
||||||
|
CreateProductsWorkflowActions.createProducts
|
||||||
|
] as ProductTypes.ProductDTO[]
|
||||||
|
|
||||||
|
return await updateProductsVariantsPrices({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data: {
|
||||||
|
products,
|
||||||
|
productsHandleVariantsIndexPricesMap,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[TransactionHandlerType.COMPENSATE]: async (
|
||||||
|
data: CreateProductsWorkflowInputData,
|
||||||
|
{ invoke }
|
||||||
|
) => {
|
||||||
|
const { productsHandleVariantsIndexPricesMap } = invoke[
|
||||||
|
CreateProductsWorkflowActions.prepare
|
||||||
|
] as CreateProductsPreparedData
|
||||||
|
const products = invoke[
|
||||||
|
CreateProductsWorkflowActions.createProducts
|
||||||
|
] as ProductTypes.ProductDTO[]
|
||||||
|
|
||||||
|
if (!productsHandleVariantsIndexPricesMap?.size) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedProductsHandleVariantsIndexPricesMap = new Map()
|
||||||
|
productsHandleVariantsIndexPricesMap.forEach((items, productHandle) => {
|
||||||
|
const existingItems =
|
||||||
|
updatedProductsHandleVariantsIndexPricesMap.get(productHandle) ?? []
|
||||||
|
|
||||||
|
items.forEach(({ index }) => {
|
||||||
|
existingItems.push({
|
||||||
|
index,
|
||||||
|
prices: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
updatedProductsHandleVariantsIndexPricesMap.set(productHandle, items)
|
||||||
|
})
|
||||||
|
|
||||||
|
return await updateProductsVariantsPrices({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data: {
|
||||||
|
products,
|
||||||
|
productsHandleVariantsIndexPricesMap:
|
||||||
|
updatedProductsHandleVariantsIndexPricesMap,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[CreateProductsWorkflowActions.result]: {
|
||||||
|
[TransactionHandlerType.INVOKE]: async (
|
||||||
|
data: CreateProductsWorkflowInputData,
|
||||||
|
{ invoke }
|
||||||
|
) => {
|
||||||
|
const { [CreateProductsWorkflowActions.createProducts]: products } =
|
||||||
|
invoke
|
||||||
|
|
||||||
|
const productService = container.resolve(
|
||||||
|
"productService"
|
||||||
|
) as ProductService
|
||||||
|
const pricingService = container.resolve(
|
||||||
|
"pricingService"
|
||||||
|
) as PricingService
|
||||||
|
|
||||||
|
const rawProduct = await productService
|
||||||
|
.withTransaction(manager)
|
||||||
|
.retrieve(products[0].id, {
|
||||||
|
select: defaultAdminProductFields,
|
||||||
|
relations: defaultAdminProductRelations,
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await pricingService
|
||||||
|
.withTransaction(manager)
|
||||||
|
.setProductPrices([rawProduct])
|
||||||
|
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
actionId: string,
|
||||||
|
type: TransactionHandlerType,
|
||||||
|
payload: TransactionPayload
|
||||||
|
) => command[actionId][type](payload.data, payload.context)
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { ulid } from "ulid"
|
||||||
|
import { MedusaError } from "@medusajs/utils"
|
||||||
|
import {
|
||||||
|
TransactionOrchestrator,
|
||||||
|
TransactionState,
|
||||||
|
} from "../../../utils/transaction"
|
||||||
|
import { AdminPostProductsReq } from "../../../api"
|
||||||
|
import { Product } from "../../../models"
|
||||||
|
import { PricedProduct } from "../../../types/pricing"
|
||||||
|
import {
|
||||||
|
CreateProductsWorkflowActions,
|
||||||
|
transactionHandler,
|
||||||
|
workflowSteps,
|
||||||
|
} from "./definition"
|
||||||
|
import { InjectedDependencies } from "./types"
|
||||||
|
|
||||||
|
const createProductsOrchestrator = new TransactionOrchestrator(
|
||||||
|
"create-products",
|
||||||
|
workflowSteps
|
||||||
|
)
|
||||||
|
|
||||||
|
export async function createProductsWorkflow(
|
||||||
|
dependencies: InjectedDependencies,
|
||||||
|
input: AdminPostProductsReq[]
|
||||||
|
): Promise<(Product | PricedProduct)[]> {
|
||||||
|
const transaction = await createProductsOrchestrator.beginTransaction(
|
||||||
|
ulid(),
|
||||||
|
transactionHandler(dependencies),
|
||||||
|
input
|
||||||
|
)
|
||||||
|
|
||||||
|
await createProductsOrchestrator.resume(transaction)
|
||||||
|
|
||||||
|
if (transaction.getState() !== TransactionState.DONE) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
transaction
|
||||||
|
.getErrors()
|
||||||
|
.map((err) => err.error?.message)
|
||||||
|
.join("\n")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transaction.getContext().invoke[
|
||||||
|
CreateProductsWorkflowActions.result
|
||||||
|
] as (Product | PricedProduct)[]
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { MedusaContainer } from "@medusajs/types"
|
||||||
|
import { AdminPostProductsReq } from "../../../api"
|
||||||
|
|
||||||
|
export type InjectedDependencies = {
|
||||||
|
manager: EntityManager
|
||||||
|
container: MedusaContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CreateProductsWorkflowInputData = AdminPostProductsReq[]
|
||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
ProductTypes,
|
ProductTypes,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
|
import { ProductVariantInventoryService } from "../../services"
|
||||||
|
|
||||||
export async function attachInventoryItems({
|
export async function attachInventoryItems({
|
||||||
container,
|
container,
|
||||||
@@ -17,18 +18,13 @@ export async function attachInventoryItems({
|
|||||||
inventoryItem: InventoryItemDTO
|
inventoryItem: InventoryItemDTO
|
||||||
}[]
|
}[]
|
||||||
}) {
|
}) {
|
||||||
const productVariantInventoryService = container
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
.resolve("productVariantInventoryService")
|
container.resolve("productVariantInventoryService").withTransaction(manager)
|
||||||
.withTransaction(manager)
|
|
||||||
|
|
||||||
return await Promise.all(
|
const inventoryData = data.map(({ variant, inventoryItem }) => ({
|
||||||
data
|
variantId: variant.id,
|
||||||
.filter((d) => d)
|
inventoryItemId: inventoryItem.id,
|
||||||
.map(async ({ variant, inventoryItem }) => {
|
}))
|
||||||
return await productVariantInventoryService.attachInventoryItem(
|
|
||||||
variant.id,
|
return await productVariantInventoryService.attachInventoryItem(inventoryData)
|
||||||
inventoryItem.id
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { SalesChannelService } from "../../services"
|
||||||
|
|
||||||
|
type ProductHandle = string
|
||||||
|
type SalesChannelId = string
|
||||||
|
|
||||||
|
export async function attachSalesChannelToProducts({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
container: MedusaContainer
|
||||||
|
manager: EntityManager
|
||||||
|
data: {
|
||||||
|
productsHandleSalesChannelsMap: Map<ProductHandle, SalesChannelId[]>
|
||||||
|
products: ProductTypes.ProductDTO[]
|
||||||
|
}
|
||||||
|
}): Promise<void> {
|
||||||
|
const salesChannelService: SalesChannelService = container.resolve(
|
||||||
|
"salesChannelService"
|
||||||
|
)
|
||||||
|
const salesChannelServiceTx = salesChannelService.withTransaction(manager)
|
||||||
|
|
||||||
|
const salesChannelIdProductIdsMap = new Map<ProductHandle, SalesChannelId[]>()
|
||||||
|
data.products.forEach((product) => {
|
||||||
|
const salesChannelIds = data.productsHandleSalesChannelsMap.get(
|
||||||
|
product.handle!
|
||||||
|
)
|
||||||
|
if (salesChannelIds) {
|
||||||
|
salesChannelIds.forEach((salesChannelId) => {
|
||||||
|
const productIds = salesChannelIdProductIdsMap.get(salesChannelId) || []
|
||||||
|
productIds.push(product.id)
|
||||||
|
salesChannelIdProductIdsMap.set(salesChannelId, productIds)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(salesChannelIdProductIdsMap.entries()).map(
|
||||||
|
async ([salesChannelId, productIds]) => {
|
||||||
|
return await salesChannelServiceTx.addProducts(
|
||||||
|
salesChannelId,
|
||||||
|
productIds
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { ShippingProfileService } from "../../services"
|
||||||
|
|
||||||
|
type ProductHandle = string
|
||||||
|
type ShippingProfileId = string
|
||||||
|
|
||||||
|
export async function attachShippingProfileToProducts({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
container: MedusaContainer
|
||||||
|
manager: EntityManager
|
||||||
|
data: {
|
||||||
|
productsHandleShippingProfileIdMap: Map<ProductHandle, ShippingProfileId>
|
||||||
|
products: ProductTypes.ProductDTO[]
|
||||||
|
}
|
||||||
|
}): Promise<void> {
|
||||||
|
const shippingProfileService: ShippingProfileService = container.resolve(
|
||||||
|
"shippingProfileService"
|
||||||
|
)
|
||||||
|
const shippingProfileServiceTx =
|
||||||
|
shippingProfileService.withTransaction(manager)
|
||||||
|
|
||||||
|
const profileIdProductIdsMap = new Map<ShippingProfileId, ProductHandle[]>()
|
||||||
|
data.products.forEach((product) => {
|
||||||
|
const profileId = data.productsHandleShippingProfileIdMap.get(
|
||||||
|
product.handle!
|
||||||
|
)
|
||||||
|
if (profileId) {
|
||||||
|
const productIds = profileIdProductIdsMap.get(profileId) || []
|
||||||
|
productIds.push(product.id)
|
||||||
|
profileIdProductIdsMap.set(profileId, productIds)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(profileIdProductIdsMap.entries()).map(
|
||||||
|
async ([profileId, productIds]) => {
|
||||||
|
return await shippingProfileServiceTx.addProducts(profileId, productIds)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||||
|
|
||||||
export async function removeProducts({
|
export type CreateProductsData = ProductTypes.CreateProductDTO[]
|
||||||
|
|
||||||
|
export async function createProducts({
|
||||||
container,
|
container,
|
||||||
data,
|
data,
|
||||||
}: {
|
}: {
|
||||||
container: MedusaContainer
|
container: MedusaContainer
|
||||||
data: ProductTypes.ProductDTO[]
|
data: CreateProductsData
|
||||||
}): Promise<ProductTypes.ProductDTO[]> {
|
}): Promise<ProductTypes.ProductDTO[]> {
|
||||||
const productModuleService = container.resolve("productModuleService")
|
const productModuleService: ProductTypes.IProductModuleService =
|
||||||
return await productModuleService.softDelete(data.map((p) => p.id))
|
container.resolve("productModuleService")
|
||||||
|
|
||||||
|
return await productModuleService.create(data)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import {
|
||||||
|
InventoryItemDTO,
|
||||||
|
MedusaContainer,
|
||||||
|
ProductTypes,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { ProductVariantInventoryService } from "../../services"
|
||||||
|
|
||||||
|
export async function detachInventoryItems({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
container: MedusaContainer
|
||||||
|
manager: EntityManager
|
||||||
|
data: {
|
||||||
|
variant: ProductTypes.ProductVariantDTO
|
||||||
|
inventoryItem: InventoryItemDTO
|
||||||
|
}[]
|
||||||
|
}) {
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
container.resolve("productVariantInventoryService").withTransaction(manager)
|
||||||
|
|
||||||
|
return await Promise.all(
|
||||||
|
data.map(async ({ variant, inventoryItem }) => {
|
||||||
|
return await productVariantInventoryService.detachInventoryItem(
|
||||||
|
inventoryItem.id,
|
||||||
|
variant.id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { SalesChannelService } from "../../services"
|
||||||
|
|
||||||
|
type ProductHandle = string
|
||||||
|
type SalesChannelId = string
|
||||||
|
|
||||||
|
export async function detachSalesChannelFromProducts({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
container: MedusaContainer
|
||||||
|
manager: EntityManager
|
||||||
|
data: {
|
||||||
|
productsHandleSalesChannelsMap: Map<ProductHandle, SalesChannelId[]>
|
||||||
|
products: ProductTypes.ProductDTO[]
|
||||||
|
}
|
||||||
|
}): Promise<void> {
|
||||||
|
const salesChannelService: SalesChannelService = container.resolve(
|
||||||
|
"salesChannelService"
|
||||||
|
)
|
||||||
|
const salesChannelServiceTx = salesChannelService.withTransaction(manager)
|
||||||
|
|
||||||
|
const salesChannelIdProductIdsMap = new Map<ProductHandle, SalesChannelId[]>()
|
||||||
|
data.products.forEach((product) => {
|
||||||
|
const salesChannelIds = data.productsHandleSalesChannelsMap.get(
|
||||||
|
product.handle!
|
||||||
|
)
|
||||||
|
if (salesChannelIds) {
|
||||||
|
salesChannelIds.forEach((salesChannelId) => {
|
||||||
|
const productIds = salesChannelIdProductIdsMap.get(salesChannelId) || []
|
||||||
|
productIds.push(product.id)
|
||||||
|
salesChannelIdProductIdsMap.set(salesChannelId, productIds)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(salesChannelIdProductIdsMap.entries()).map(
|
||||||
|
async ([salesChannelId, productIds]) => {
|
||||||
|
return await salesChannelServiceTx.removeProducts(
|
||||||
|
salesChannelId,
|
||||||
|
productIds
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { ShippingProfileService } from "../../services"
|
||||||
|
|
||||||
|
type ProductHandle = string
|
||||||
|
type ShippingProfileId = string
|
||||||
|
|
||||||
|
export async function detachShippingProfileFromProducts({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
container: MedusaContainer
|
||||||
|
manager: EntityManager
|
||||||
|
data: {
|
||||||
|
productsHandleShippingProfileIdMap: Map<ProductHandle, ShippingProfileId>
|
||||||
|
products: ProductTypes.ProductDTO[]
|
||||||
|
}
|
||||||
|
}): Promise<void> {
|
||||||
|
const shippingProfileService: ShippingProfileService = container.resolve(
|
||||||
|
"shippingProfileService"
|
||||||
|
)
|
||||||
|
const shippingProfileServiceTx =
|
||||||
|
shippingProfileService.withTransaction(manager)
|
||||||
|
|
||||||
|
const profileIdProductIdsMap = new Map<ShippingProfileId, ProductHandle[]>()
|
||||||
|
data.products.forEach((product) => {
|
||||||
|
const profileId = data.productsHandleShippingProfileIdMap.get(
|
||||||
|
product.handle!
|
||||||
|
)
|
||||||
|
if (profileId) {
|
||||||
|
const productIds = profileIdProductIdsMap.get(profileId) || []
|
||||||
|
productIds.push(product.id)
|
||||||
|
profileIdProductIdsMap.set(profileId, productIds)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(profileIdProductIdsMap.entries()).map(
|
||||||
|
async ([profileId, productIds]) => {
|
||||||
|
return await shippingProfileServiceTx.removeProducts(
|
||||||
|
profileId,
|
||||||
|
productIds
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -3,3 +3,10 @@ export * from "./remove-products"
|
|||||||
export * from "./create-inventory-items"
|
export * from "./create-inventory-items"
|
||||||
export * from "./remove-inventory-items"
|
export * from "./remove-inventory-items"
|
||||||
export * from "./attach-inventory-items"
|
export * from "./attach-inventory-items"
|
||||||
|
export * from "./prepare-create-products-data"
|
||||||
|
export * from "./attach-shipping-profile-to-products"
|
||||||
|
export * from "./detach-shipping-profile-from-products"
|
||||||
|
export * from "./attach-sales-channel-to-products"
|
||||||
|
export * from "./detach-sales-channel-from-products"
|
||||||
|
export * from "./update-products-variants-prices"
|
||||||
|
export * from "./detach-inventory-items"
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { SalesChannelService, ShippingProfileService } from "../../services"
|
||||||
|
import { kebabCase } from "@medusajs/utils"
|
||||||
|
import { FlagRouter } from "../../utils/flag-router"
|
||||||
|
import SalesChannelFeatureFlag from "../../loaders/feature-flags/sales-channels"
|
||||||
|
import { SalesChannel, ShippingProfileType } from "../../models"
|
||||||
|
import { ProductVariantPricesCreateReq } from "../../types/product-variant"
|
||||||
|
import { AdminPostProductsReq } from "../../api"
|
||||||
|
|
||||||
|
type CreateProductsInputData = AdminPostProductsReq[]
|
||||||
|
|
||||||
|
type ShippingProfileId = string
|
||||||
|
type SalesChannelId = string
|
||||||
|
type ProductHandle = string
|
||||||
|
type VariantIndexAndPrices = {
|
||||||
|
index: number
|
||||||
|
prices: ProductVariantPricesCreateReq[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CreateProductsPreparedData = {
|
||||||
|
products: ProductTypes.CreateProductDTO[]
|
||||||
|
productsHandleShippingProfileIdMap: Map<ProductHandle, ShippingProfileId>
|
||||||
|
productsHandleSalesChannelsMap: Map<ProductHandle, SalesChannelId[]>
|
||||||
|
productsHandleVariantsIndexPricesMap: Map<
|
||||||
|
ProductHandle,
|
||||||
|
VariantIndexAndPrices[]
|
||||||
|
>
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function prepareCreateProductsData({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
container: MedusaContainer
|
||||||
|
manager: EntityManager
|
||||||
|
data: CreateProductsInputData
|
||||||
|
}): Promise<CreateProductsPreparedData> {
|
||||||
|
const shippingProfileService: ShippingProfileService = container
|
||||||
|
.resolve("shippingProfileService")
|
||||||
|
.withTransaction(manager)
|
||||||
|
const featureFlagRouter: FlagRouter = container.resolve("featureFlagRouter")
|
||||||
|
const salesChannelService: SalesChannelService = container
|
||||||
|
.resolve("salesChannelService")
|
||||||
|
.withTransaction(manager)
|
||||||
|
|
||||||
|
const shippingProfileServiceTx =
|
||||||
|
shippingProfileService.withTransaction(manager)
|
||||||
|
|
||||||
|
const shippingProfiles = await shippingProfileServiceTx.list({
|
||||||
|
type: [ShippingProfileType.DEFAULT, ShippingProfileType.GIFT_CARD],
|
||||||
|
})
|
||||||
|
const defaultShippingProfile = shippingProfiles.find(
|
||||||
|
(sp) => sp.type === ShippingProfileType.DEFAULT
|
||||||
|
)
|
||||||
|
const gitCardShippingProfile = shippingProfiles.find(
|
||||||
|
(sp) => sp.type === ShippingProfileType.GIFT_CARD
|
||||||
|
)
|
||||||
|
|
||||||
|
let defaultSalesChannel: SalesChannel | undefined
|
||||||
|
if (featureFlagRouter.isFeatureEnabled(SalesChannelFeatureFlag.key)) {
|
||||||
|
defaultSalesChannel = await salesChannelService
|
||||||
|
.withTransaction(manager)
|
||||||
|
.retrieveDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
const productsHandleShippingProfileIdMap = new Map<
|
||||||
|
ProductHandle,
|
||||||
|
ShippingProfileId
|
||||||
|
>()
|
||||||
|
const productsHandleSalesChannelsMap = new Map<
|
||||||
|
ProductHandle,
|
||||||
|
SalesChannelId[]
|
||||||
|
>()
|
||||||
|
const productsHandleVariantsIndexPricesMap = new Map<
|
||||||
|
ProductHandle,
|
||||||
|
VariantIndexAndPrices[]
|
||||||
|
>()
|
||||||
|
|
||||||
|
for (const product of data) {
|
||||||
|
product.handle ??= kebabCase(product.title)
|
||||||
|
|
||||||
|
if (product.is_giftcard) {
|
||||||
|
productsHandleShippingProfileIdMap.set(
|
||||||
|
product.handle!,
|
||||||
|
gitCardShippingProfile!.id
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
productsHandleShippingProfileIdMap.set(
|
||||||
|
product.handle!,
|
||||||
|
defaultShippingProfile!.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
featureFlagRouter.isFeatureEnabled(SalesChannelFeatureFlag.key) &&
|
||||||
|
!product.sales_channels?.length
|
||||||
|
) {
|
||||||
|
productsHandleSalesChannelsMap.set(product.handle!, [
|
||||||
|
defaultSalesChannel!.id,
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
productsHandleSalesChannelsMap.set(
|
||||||
|
product.handle!,
|
||||||
|
product.sales_channels!.map((s) => s.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (product.variants) {
|
||||||
|
const hasPrices = product.variants.some((variant) => {
|
||||||
|
return variant.prices.length > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
if (hasPrices) {
|
||||||
|
const items =
|
||||||
|
productsHandleVariantsIndexPricesMap.get(product.handle!) ?? []
|
||||||
|
|
||||||
|
product.variants.forEach((variant, index) => {
|
||||||
|
items.push({
|
||||||
|
index,
|
||||||
|
prices: variant.prices,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
productsHandleVariantsIndexPricesMap.set(product.handle!, items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data = data.map((productData) => {
|
||||||
|
delete productData.sales_channels
|
||||||
|
return productData
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
products: data as ProductTypes.CreateProductDTO[],
|
||||||
|
productsHandleShippingProfileIdMap,
|
||||||
|
productsHandleSalesChannelsMap,
|
||||||
|
productsHandleVariantsIndexPricesMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,12 +15,8 @@ export async function removeInventoryItems({
|
|||||||
const inventoryService = container.resolve("inventoryService")
|
const inventoryService = container.resolve("inventoryService")
|
||||||
const context = { transactionManager: manager }
|
const context = { transactionManager: manager }
|
||||||
|
|
||||||
return await Promise.all(
|
|
||||||
data.map(async ({ inventoryItem }) => {
|
|
||||||
return await inventoryService!.deleteInventoryItem(
|
return await inventoryService!.deleteInventoryItem(
|
||||||
inventoryItem.id,
|
data.map(({ inventoryItem }) => inventoryItem.id),
|
||||||
context
|
context
|
||||||
)
|
)
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||||
|
|
||||||
export async function createProducts({
|
export async function removeProducts({
|
||||||
container,
|
container,
|
||||||
data,
|
data,
|
||||||
}: {
|
}: {
|
||||||
container: MedusaContainer
|
container: MedusaContainer
|
||||||
data: ProductTypes.CreateProductDTO[]
|
data: ProductTypes.ProductDTO[]
|
||||||
}) {
|
}) {
|
||||||
const productModuleService = container.resolve("productModuleService")
|
const productModuleService: ProductTypes.IProductModuleService =
|
||||||
return await productModuleService.create(data)
|
container.resolve("productModuleService")
|
||||||
|
return await productModuleService.softDelete(data.map((p) => p.id))
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { ProductVariantService } from "../../services"
|
||||||
|
import {
|
||||||
|
ProductVariantPricesCreateReq,
|
||||||
|
UpdateVariantPricesData,
|
||||||
|
} from "../../types/product-variant"
|
||||||
|
import { MedusaError } from "@medusajs/utils"
|
||||||
|
|
||||||
|
type ProductHandle = string
|
||||||
|
type VariantIndexAndPrices = {
|
||||||
|
index: number
|
||||||
|
prices: ProductVariantPricesCreateReq[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateProductsVariantsPrices({
|
||||||
|
container,
|
||||||
|
manager,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
container: MedusaContainer
|
||||||
|
manager: EntityManager
|
||||||
|
data: {
|
||||||
|
products: ProductTypes.ProductDTO[]
|
||||||
|
productsHandleVariantsIndexPricesMap: Map<
|
||||||
|
ProductHandle,
|
||||||
|
VariantIndexAndPrices[]
|
||||||
|
>
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
const productVariantService: ProductVariantService = container.resolve(
|
||||||
|
"productVariantService"
|
||||||
|
)
|
||||||
|
const productVariantServiceTx = productVariantService.withTransaction(manager)
|
||||||
|
|
||||||
|
const variantIdsPricesData: UpdateVariantPricesData[] = []
|
||||||
|
const productsMap = new Map<string, ProductTypes.ProductDTO>(
|
||||||
|
data.products.map((p) => [p.handle!, p])
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const mapData of data.productsHandleVariantsIndexPricesMap.entries()) {
|
||||||
|
const [handle, variantData] = mapData
|
||||||
|
|
||||||
|
const product = productsMap.get(handle)
|
||||||
|
if (!product) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Product with handle ${handle} not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
variantData.forEach((item, index) => {
|
||||||
|
const variant = product.variants[index]
|
||||||
|
variantIdsPricesData.push({
|
||||||
|
variantId: variant.id,
|
||||||
|
prices: item.prices,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await productVariantServiceTx.updateVariantPrices(variantIdsPricesData)
|
||||||
|
}
|
||||||
@@ -845,8 +845,8 @@
|
|||||||
"nullable": false,
|
"nullable": false,
|
||||||
"mappedType": "text"
|
"mappedType": "text"
|
||||||
},
|
},
|
||||||
"product_image_id": {
|
"image_id": {
|
||||||
"name": "product_image_id",
|
"name": "image_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"unsigned": false,
|
"unsigned": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
@@ -860,7 +860,10 @@
|
|||||||
"indexes": [
|
"indexes": [
|
||||||
{
|
{
|
||||||
"keyName": "product_images_pkey",
|
"keyName": "product_images_pkey",
|
||||||
"columnNames": ["product_id", "product_image_id"],
|
"columnNames": [
|
||||||
|
"product_id",
|
||||||
|
"image_id"
|
||||||
|
],
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"primary": true,
|
"primary": true,
|
||||||
"unique": true
|
"unique": true
|
||||||
@@ -877,9 +880,11 @@
|
|||||||
"deleteRule": "cascade",
|
"deleteRule": "cascade",
|
||||||
"updateRule": "cascade"
|
"updateRule": "cascade"
|
||||||
},
|
},
|
||||||
"product_images_product_image_id_foreign": {
|
"product_images_image_id_foreign": {
|
||||||
"constraintName": "product_images_product_image_id_foreign",
|
"constraintName": "product_images_image_id_foreign",
|
||||||
"columnNames": ["product_image_id"],
|
"columnNames": [
|
||||||
|
"image_id"
|
||||||
|
],
|
||||||
"localTableName": "public.product_images",
|
"localTableName": "public.product_images",
|
||||||
"referencedColumnNames": ["id"],
|
"referencedColumnNames": ["id"],
|
||||||
"referencedTableName": "public.image",
|
"referencedTableName": "public.image",
|
||||||
@@ -1119,6 +1124,15 @@
|
|||||||
"default": "0",
|
"default": "0",
|
||||||
"mappedType": "decimal"
|
"mappedType": "decimal"
|
||||||
},
|
},
|
||||||
|
"product_id": {
|
||||||
|
"name": "product_id",
|
||||||
|
"type": "text",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": false,
|
||||||
|
"mappedType": "text"
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"name": "created_at",
|
"name": "created_at",
|
||||||
"type": "timestamptz",
|
"type": "timestamptz",
|
||||||
@@ -1148,15 +1162,6 @@
|
|||||||
"nullable": true,
|
"nullable": true,
|
||||||
"length": 6,
|
"length": 6,
|
||||||
"mappedType": "datetime"
|
"mappedType": "datetime"
|
||||||
},
|
|
||||||
"product_id": {
|
|
||||||
"name": "product_id",
|
|
||||||
"type": "text",
|
|
||||||
"unsigned": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"primary": false,
|
|
||||||
"nullable": false,
|
|
||||||
"mappedType": "text"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "product_variant",
|
"name": "product_variant",
|
||||||
|
|||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
import { Migration } from "@mikro-orm/migrations"
|
import { Migration } from "@mikro-orm/migrations"
|
||||||
|
|
||||||
export class Migration20230710091208 extends Migration {
|
export class Migration20230719100648 extends Migration {
|
||||||
async up(): Promise<void> {
|
async up(): Promise<void> {
|
||||||
this.addSql(
|
this.addSql(
|
||||||
'create table "product_category" ("id" text not null, "name" text not null, "description" text not null default \'\', "handle" text not null, "mpath" text not null, "is_active" boolean not null default false, "is_internal" boolean not null default false, "rank" numeric not null default 0, "parent_category_id" text null, "created_at" timestamptz not null, "updated_at" timestamptz not null, constraint "product_category_pkey" primary key ("id"));'
|
'create table "product_category" ("id" text not null, "name" text not null, "description" text not null default \'\', "handle" text not null, "mpath" text not null, "is_active" boolean not null default false, "is_internal" boolean not null default false, "rank" numeric not null default 0, "parent_category_id" text null, "created_at" timestamptz not null, "updated_at" timestamptz not null, constraint "product_category_pkey" primary key ("id"));'
|
||||||
@@ -70,7 +70,7 @@ export class Migration20230710091208 extends Migration {
|
|||||||
)
|
)
|
||||||
|
|
||||||
this.addSql(
|
this.addSql(
|
||||||
'create table "product_images" ("product_id" text not null, "product_image_id" text not null, constraint "product_images_pkey" primary key ("product_id", "product_image_id"));'
|
'create table "product_images" ("product_id" text not null, "image_id" text not null, constraint "product_images_pkey" primary key ("product_id", "image_id"));'
|
||||||
)
|
)
|
||||||
|
|
||||||
this.addSql(
|
this.addSql(
|
||||||
@@ -78,7 +78,7 @@ export class Migration20230710091208 extends Migration {
|
|||||||
)
|
)
|
||||||
|
|
||||||
this.addSql(
|
this.addSql(
|
||||||
'create table "product_variant" ("id" text not null, "title" text not null, "sku" text null, "barcode" text null, "ean" text null, "upc" text null, "inventory_quantity" numeric not null default 100, "allow_backorder" boolean not null default false, "manage_inventory" boolean not null default true, "hs_code" text null, "origin_country" text null, "mid_code" text null, "material" text null, "weight" numeric null, "length" numeric null, "height" numeric null, "width" numeric null, "metadata" jsonb null, "variant_rank" numeric null default 0, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, "product_id" text not null, constraint "product_variant_pkey" primary key ("id"));'
|
'create table "product_variant" ("id" text not null, "title" text not null, "sku" text null, "barcode" text null, "ean" text null, "upc" text null, "inventory_quantity" numeric not null default 100, "allow_backorder" boolean not null default false, "manage_inventory" boolean not null default true, "hs_code" text null, "origin_country" text null, "mid_code" text null, "material" text null, "weight" numeric null, "length" numeric null, "height" numeric null, "width" numeric null, "metadata" jsonb null, "variant_rank" numeric null default 0, "product_id" text not null, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, constraint "product_variant_pkey" primary key ("id"));'
|
||||||
)
|
)
|
||||||
this.addSql(
|
this.addSql(
|
||||||
'create index "IDX_product_variant_deleted_at" on "product_variant" ("deleted_at");'
|
'create index "IDX_product_variant_deleted_at" on "product_variant" ("deleted_at");'
|
||||||
@@ -138,7 +138,7 @@ export class Migration20230710091208 extends Migration {
|
|||||||
'alter table "product_images" add constraint "product_images_product_id_foreign" foreign key ("product_id") references "product" ("id") on update cascade on delete cascade;'
|
'alter table "product_images" add constraint "product_images_product_id_foreign" foreign key ("product_id") references "product" ("id") on update cascade on delete cascade;'
|
||||||
)
|
)
|
||||||
this.addSql(
|
this.addSql(
|
||||||
'alter table "product_images" add constraint "product_images_product_image_id_foreign" foreign key ("product_image_id") references "image" ("id") on update cascade on delete cascade;'
|
'alter table "product_images" add constraint "product_images_image_id_foreign" foreign key ("image_id") references "image" ("id") on update cascade on delete cascade;'
|
||||||
)
|
)
|
||||||
|
|
||||||
this.addSql(
|
this.addSql(
|
||||||
@@ -32,7 +32,7 @@ class ProductOptionValue {
|
|||||||
@Property({ columnType: "text" })
|
@Property({ columnType: "text" })
|
||||||
value: string
|
value: string
|
||||||
|
|
||||||
@Property({ persist: false })
|
@Property({ columnType: "text", nullable: true })
|
||||||
option_id!: string
|
option_id!: string
|
||||||
|
|
||||||
@ManyToOne(() => ProductOption, {
|
@ManyToOne(() => ProductOption, {
|
||||||
@@ -41,7 +41,7 @@ class ProductOptionValue {
|
|||||||
})
|
})
|
||||||
option: ProductOption
|
option: ProductOption
|
||||||
|
|
||||||
@Property({ persist: false })
|
@Property({ columnType: "text", nullable: true })
|
||||||
variant_id!: string
|
variant_id!: string
|
||||||
|
|
||||||
@ManyToOne(() => ProductVariant, {
|
@ManyToOne(() => ProductVariant, {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class ProductOption {
|
|||||||
@Property({ columnType: "text" })
|
@Property({ columnType: "text" })
|
||||||
title: string
|
title: string
|
||||||
|
|
||||||
@Property({ persist: false })
|
@Property({ columnType: "text", nullable: true })
|
||||||
product_id!: string
|
product_id!: string
|
||||||
|
|
||||||
@ManyToOne(() => Product, {
|
@ManyToOne(() => Product, {
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class ProductVariant {
|
|||||||
@Property({ columnType: "numeric", nullable: true, default: 0 })
|
@Property({ columnType: "numeric", nullable: true, default: 0 })
|
||||||
variant_rank?: number | null
|
variant_rank?: number | null
|
||||||
|
|
||||||
@Property({ persist: false })
|
@Property({ columnType: "text", nullable: true })
|
||||||
product_id!: string
|
product_id!: string
|
||||||
|
|
||||||
@Property({ onCreate: () => new Date(), columnType: "timestamptz" })
|
@Property({ onCreate: () => new Date(), columnType: "timestamptz" })
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ class Product {
|
|||||||
@Property({ columnType: "text", nullable: true })
|
@Property({ columnType: "text", nullable: true })
|
||||||
material?: string | null
|
material?: string | null
|
||||||
|
|
||||||
@Property({ persist: false })
|
@Property({ columnType: "text", nullable: true })
|
||||||
collection_id!: string
|
collection_id!: string
|
||||||
|
|
||||||
@ManyToOne(() => ProductCollection, {
|
@ManyToOne(() => ProductCollection, {
|
||||||
@@ -109,7 +109,7 @@ class Product {
|
|||||||
})
|
})
|
||||||
collection!: ProductCollection
|
collection!: ProductCollection
|
||||||
|
|
||||||
@Property({ persist: false })
|
@Property({ columnType: "text", nullable: true })
|
||||||
type_id!: string
|
type_id!: string
|
||||||
|
|
||||||
@ManyToOne(() => ProductType, {
|
@ManyToOne(() => ProductType, {
|
||||||
@@ -132,6 +132,8 @@ class Product {
|
|||||||
pivotTable: "product_images",
|
pivotTable: "product_images",
|
||||||
index: "IDX_product_image_id",
|
index: "IDX_product_image_id",
|
||||||
cascade: ["soft-remove"] as any,
|
cascade: ["soft-remove"] as any,
|
||||||
|
joinColumn: "product_id",
|
||||||
|
inverseJoinColumn: "image_id",
|
||||||
})
|
})
|
||||||
images = new Collection<ProductImage>(this)
|
images = new Collection<ProductImage>(this)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import * as ProductModels from "@models"
|
|
||||||
|
|
||||||
import { ModuleExports } from "@medusajs/types"
|
import { ModuleExports } from "@medusajs/types"
|
||||||
import { ProductModuleService } from "@services"
|
import { ProductModuleService } from "@services"
|
||||||
import loadConnection from "./loaders/connection"
|
import loadConnection from "./loaders/connection"
|
||||||
@@ -7,10 +5,8 @@ import loadContainer from "./loaders/container"
|
|||||||
|
|
||||||
const service = ProductModuleService
|
const service = ProductModuleService
|
||||||
const loaders = [loadContainer, loadConnection] as any
|
const loaders = [loadContainer, loadConnection] as any
|
||||||
const models = Object.values(ProductModels)
|
|
||||||
|
|
||||||
export const moduleDefinition: ModuleExports = {
|
export const moduleDefinition: ModuleExports = {
|
||||||
service,
|
service,
|
||||||
loaders,
|
loaders,
|
||||||
models,
|
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
MedusaContext,
|
MedusaContext,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { serialize } from "@mikro-orm/core"
|
import { serialize } from "@mikro-orm/core"
|
||||||
import { doNotForceTransaction } from "../utils"
|
|
||||||
|
|
||||||
// TODO: Should we create a mikro orm specific package for this and the soft deletable decorator util?
|
// TODO: Should we create a mikro orm specific package for this and the soft deletable decorator util?
|
||||||
|
|
||||||
@@ -28,28 +27,17 @@ async function transactionWrapper(
|
|||||||
return await task(transaction)
|
return await task(transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
const forkedManager = this.manager_.fork()
|
|
||||||
|
|
||||||
const options = {}
|
const options = {}
|
||||||
|
|
||||||
|
if (transaction) {
|
||||||
|
Object.assign(options, { ctx: transaction })
|
||||||
|
}
|
||||||
|
|
||||||
if (isolationLevel) {
|
if (isolationLevel) {
|
||||||
Object.assign(options, { isolationLevel })
|
Object.assign(options, { isolationLevel })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transaction) {
|
return await (this.manager_ as SqlEntityManager).transactional(task, options)
|
||||||
Object.assign(options, { ctx: transaction })
|
|
||||||
await forkedManager.begin(options)
|
|
||||||
} else {
|
|
||||||
await forkedManager.begin(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await task(forkedManager)
|
|
||||||
await forkedManager.commit()
|
|
||||||
return result
|
|
||||||
} catch (e) {
|
|
||||||
await forkedManager.rollback()
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateDeletedAtRecursively = async <T extends object = any>(
|
const updateDeletedAtRecursively = async <T extends object = any>(
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ export class ProductRepository extends AbstractBaseRepository<Product> {
|
|||||||
@MedusaContext()
|
@MedusaContext()
|
||||||
{ transactionManager: manager }: Context = {}
|
{ transactionManager: manager }: Context = {}
|
||||||
): Promise<Product[]> {
|
): Promise<Product[]> {
|
||||||
console.log((this as any).prototype)
|
|
||||||
const products = data.map((product) => {
|
const products = data.map((product) => {
|
||||||
return (manager as SqlEntityManager).create(Product, product)
|
return (manager as SqlEntityManager).create(Product, product)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -349,12 +349,12 @@ export default class ProductModuleService<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isDefined(productData.type)) {
|
if (isDefined(productData.type)) {
|
||||||
productData.type_id = (
|
productData.type = (
|
||||||
await this.productTypeService_.upsert(
|
await this.productTypeService_.upsert(
|
||||||
[productData.type as ProductTypes.CreateProductTypeDTO],
|
[productData.type as ProductTypes.CreateProductTypeDTO],
|
||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
)?.[0]!.id
|
)?.[0]!
|
||||||
}
|
}
|
||||||
|
|
||||||
return productData as CreateProductOnlyDTO
|
return productData as CreateProductOnlyDTO
|
||||||
|
|||||||
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user