fix(medusa): Default sales channel on product create (#3249)

What:
Assign the default sales channel if none is provided while creating a new product.


FIXES: CORE-1114

Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Carlos R. L. Rodrigues
2023-02-14 05:46:14 -03:00
committed by GitHub
parent 10ff72c30a
commit 80452332d8
8 changed files with 409 additions and 702 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
fix(medusa): default sales channel on product create

View File

@@ -1,448 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`/admin/products GET /admin/products returns a list of products with only giftcard in list 1`] = `
Array [
Object {
"categories": Array [],
"collection": null,
"collection_id": null,
"created_at": Any<String>,
"deleted_at": null,
"description": "test-giftcard-description",
"discountable": false,
"external_id": null,
"handle": "test-giftcard",
"height": null,
"hs_code": null,
"id": StringMatching /\\^prod_\\*/,
"images": Array [],
"is_giftcard": true,
"length": null,
"material": null,
"metadata": null,
"mid_code": null,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^opt_\\*/,
"metadata": null,
"product_id": StringMatching /\\^prod_\\*/,
"title": "Denominations",
"updated_at": Any<String>,
},
],
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"sales_channels": Array [],
"status": "draft",
"subtitle": null,
"tags": Array [],
"thumbnail": null,
"title": "Test Giftcard",
"type": null,
"type_id": null,
"updated_at": Any<String>,
"variants": Array [
Object {
"allow_backorder": false,
"barcode": null,
"calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>,
"deleted_at": null,
"ean": null,
"height": null,
"hs_code": null,
"id": StringMatching /\\^variant_\\*/,
"inventory_quantity": 0,
"length": null,
"manage_inventory": true,
"material": null,
"metadata": null,
"mid_code": null,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^opt_\\*/,
"metadata": null,
"option_id": StringMatching /\\^opt_\\*/,
"updated_at": Any<String>,
"value": "100",
"variant_id": StringMatching /\\^variant_\\*/,
},
],
"origin_country": null,
"original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [
Object {
"amount": 100,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": Any<String>,
"max_quantity": null,
"min_quantity": null,
"price_list": null,
"price_list_id": null,
"region_id": null,
"updated_at": Any<String>,
"variant_id": StringMatching /\\^variant_\\*/,
},
],
"product_id": StringMatching /\\^prod_\\*/,
"sku": null,
"tax_rates": null,
"title": "Test variant",
"upc": null,
"updated_at": Any<String>,
"weight": null,
"width": null,
},
],
"weight": null,
"width": null,
},
]
`;
exports[`/admin/products POST /admin/products creates a product 1`] = `
Object {
"categories": Array [],
"collection": Object {
"created_at": Any<String>,
"deleted_at": null,
"handle": "test-collection",
"id": "test-collection",
"metadata": null,
"title": "Test collection",
"updated_at": Any<String>,
},
"collection_id": "test-collection",
"created_at": Any<String>,
"deleted_at": null,
"description": "test-product-description",
"discountable": true,
"external_id": null,
"handle": "test",
"height": null,
"hs_code": null,
"id": StringMatching /\\^prod_\\*/,
"images": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": Any<String>,
"metadata": null,
"updated_at": Any<String>,
"url": "test-image.png",
},
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": Any<String>,
"metadata": null,
"updated_at": Any<String>,
"url": "test-image-2.png",
},
],
"is_giftcard": false,
"length": null,
"material": null,
"metadata": null,
"mid_code": null,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^opt_\\*/,
"metadata": null,
"product_id": StringMatching /\\^prod_\\*/,
"title": "size",
"updated_at": Any<String>,
},
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^opt_\\*/,
"metadata": null,
"product_id": StringMatching /\\^prod_\\*/,
"title": "color",
"updated_at": Any<String>,
},
],
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"sales_channels": Array [],
"status": "draft",
"subtitle": null,
"tags": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": Any<String>,
"metadata": null,
"updated_at": Any<String>,
"value": "123",
},
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": Any<String>,
"metadata": null,
"updated_at": Any<String>,
"value": "456",
},
],
"thumbnail": "test-image.png",
"title": "Test",
"type": Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "test-type",
"metadata": null,
"updated_at": Any<String>,
"value": "test-type",
},
"type_id": "test-type",
"updated_at": Any<String>,
"variants": Array [
Object {
"allow_backorder": false,
"barcode": null,
"calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>,
"deleted_at": null,
"ean": null,
"height": null,
"hs_code": null,
"id": StringMatching /\\^variant_\\*/,
"inventory_quantity": 10,
"length": null,
"manage_inventory": true,
"material": null,
"metadata": null,
"mid_code": null,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^optval_\\*/,
"metadata": null,
"option_id": StringMatching /\\^opt_\\*/,
"updated_at": Any<String>,
"value": "large",
"variant_id": StringMatching /\\^variant_\\*/,
},
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^optval_\\*/,
"metadata": null,
"option_id": StringMatching /\\^opt_\\*/,
"updated_at": Any<String>,
"value": "green",
"variant_id": StringMatching /\\^variant_\\*/,
},
],
"origin_country": null,
"original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [
Object {
"amount": 100,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": StringMatching /\\^ma_\\*/,
"max_quantity": null,
"min_quantity": null,
"price_list": null,
"price_list_id": null,
"region_id": null,
"updated_at": Any<String>,
"variant_id": StringMatching /\\^variant_\\*/,
},
Object {
"amount": 45,
"created_at": Any<String>,
"currency_code": "eur",
"deleted_at": null,
"id": StringMatching /\\^ma_\\*/,
"max_quantity": null,
"min_quantity": null,
"price_list": null,
"price_list_id": null,
"region_id": null,
"updated_at": Any<String>,
"variant_id": StringMatching /\\^variant_\\*/,
},
Object {
"amount": 30,
"created_at": Any<String>,
"currency_code": "dkk",
"deleted_at": null,
"id": StringMatching /\\^ma_\\*/,
"max_quantity": null,
"min_quantity": null,
"price_list": null,
"price_list_id": null,
"region_id": null,
"updated_at": Any<String>,
"variant_id": StringMatching /\\^variant_\\*/,
},
],
"product_id": StringMatching /\\^prod_\\*/,
"sku": null,
"tax_rates": null,
"title": "Test variant",
"upc": null,
"updated_at": Any<String>,
"weight": null,
"width": null,
},
],
"weight": null,
"width": null,
}
`;
exports[`/admin/products POST /admin/products updates a product (update prices, tags, update status, delete collection, delete type, replaces images) 1`] = `
Object {
"categories": Array [],
"collection": null,
"collection_id": null,
"created_at": Any<String>,
"deleted_at": null,
"description": "test-product-description",
"discountable": true,
"external_id": null,
"handle": "test-product",
"height": null,
"hs_code": null,
"id": "test-product",
"images": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^img_\\*/,
"metadata": null,
"updated_at": Any<String>,
"url": "test-image-2.png",
},
],
"is_giftcard": false,
"length": null,
"material": null,
"metadata": null,
"mid_code": null,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "test-option",
"metadata": null,
"product_id": "test-product",
"title": "test-option",
"updated_at": Any<String>,
},
],
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"sales_channels": Array [],
"status": "published",
"subtitle": null,
"tags": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "tag1",
"metadata": null,
"updated_at": Any<String>,
"value": "123",
},
],
"thumbnail": "test-image-2.png",
"title": "Test product",
"type": Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^ptyp_\\*/,
"metadata": null,
"updated_at": Any<String>,
"value": "test-type-2",
},
"type_id": StringMatching /\\^ptyp_\\*/,
"updated_at": Any<String>,
"variants": Array [
Object {
"allow_backorder": false,
"barcode": "test-barcode",
"calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>,
"deleted_at": null,
"ean": "test-ean",
"height": null,
"hs_code": null,
"id": "test-variant",
"inventory_quantity": 10,
"length": null,
"manage_inventory": true,
"material": null,
"metadata": null,
"mid_code": null,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "test-variant-option",
"metadata": null,
"option_id": "test-option",
"updated_at": Any<String>,
"value": "Default variant",
"variant_id": "test-variant",
},
],
"origin_country": null,
"original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [
Object {
"amount": 75,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": "test-price",
"max_quantity": null,
"min_quantity": null,
"price_list": null,
"price_list_id": null,
"region_id": null,
"updated_at": Any<String>,
"variant_id": "test-variant",
},
],
"product_id": "test-product",
"sku": "test-sku",
"tax_rates": null,
"title": "Test variant",
"upc": "test-upc",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
],
"weight": null,
"width": null,
}
`;

View File

@@ -20,6 +20,7 @@ const {
simpleProductFactory,
simpleDiscountFactory,
simpleProductCategoryFactory,
simpleSalesChannelFactory,
simpleRegionFactory,
} = require("../../factories")
const { DiscountRuleType, AllocationType } = require("@medusajs/medusa/dist")
@@ -59,6 +60,12 @@ describe("/admin/products", () => {
beforeEach(async () => {
await productSeeder(dbConnection)
await adminSeeder(dbConnection)
await simpleSalesChannelFactory(dbConnection, {
name: "Default channel",
id: "default-channel",
is_default: true,
})
})
afterEach(async () => {
@@ -686,55 +693,57 @@ describe("/admin/products", () => {
)
expect(response.status).toEqual(200)
expect(response.data.products).toMatchSnapshot([
{
title: "Test Giftcard",
id: expect.stringMatching(/^prod_*/),
is_giftcard: true,
description: "test-giftcard-description",
profile_id: expect.stringMatching(/^sp_*/),
options: [
{
title: "Denominations",
id: expect.stringMatching(/^opt_*/),
product_id: expect.stringMatching(/^prod_*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
expect(response.data.products).toEqual(
expect.arrayContaining([
expect.objectContaining({
title: "Test Giftcard",
id: expect.stringMatching(/^prod_*/),
is_giftcard: true,
description: "test-giftcard-description",
profile_id: expect.stringMatching(/^sp_*/),
options: expect.arrayContaining([
expect.objectContaining({
title: "Denominations",
id: expect.stringMatching(/^opt_*/),
product_id: expect.stringMatching(/^prod_*/),
created_at: expect.any(String),
updated_at: expect.any(String),
}),
]),
variants: [
{
title: "Test variant",
id: expect.stringMatching(/^variant_*/),
product_id: expect.stringMatching(/^prod_*/),
created_at: expect.any(String),
updated_at: expect.any(String),
prices: [
{
id: expect.any(String),
currency_code: "usd",
amount: 100,
variant_id: expect.stringMatching(/^variant_*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
options: [
{
id: expect.stringMatching(/^opt_*/),
option_id: expect.stringMatching(/^opt_*/),
created_at: expect.any(String),
variant_id: expect.stringMatching(/^variant_*/),
updated_at: expect.any(String),
},
],
},
],
created_at: expect.any(String),
updated_at: expect.any(String),
},
])
variants: expect.arrayContaining([
expect.objectContaining({
title: "Test variant",
id: expect.stringMatching(/^variant_*/),
product_id: expect.stringMatching(/^prod_*/),
created_at: expect.any(String),
updated_at: expect.any(String),
prices: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
currency_code: "usd",
amount: 100,
variant_id: expect.stringMatching(/^variant_*/),
created_at: expect.any(String),
updated_at: expect.any(String),
}),
]),
options: expect.arrayContaining([
expect.objectContaining({
id: expect.stringMatching(/^opt_*/),
option_id: expect.stringMatching(/^opt_*/),
created_at: expect.any(String),
variant_id: expect.stringMatching(/^variant_*/),
updated_at: expect.any(String),
}),
]),
}),
]),
created_at: expect.any(String),
updated_at: expect.any(String),
}),
])
)
})
it("returns a list of products not containing a giftcard in list", async () => {
@@ -1029,6 +1038,12 @@ describe("/admin/products", () => {
beforeEach(async () => {
await productSeeder(dbConnection)
await adminSeeder(dbConnection)
await simpleSalesChannelFactory(dbConnection, {
name: "Default channel",
id: "default-channel",
is_default: true,
})
})
afterEach(async () => {
@@ -1077,126 +1092,128 @@ describe("/admin/products", () => {
})
expect(response.status).toEqual(200)
expect(response.data.product).toMatchSnapshot({
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: [
{
id: expect.any(String),
url: "test-image.png",
created_at: expect.any(String),
updated_at: expect.any(String),
},
{
id: expect.any(String),
url: "test-image-2.png",
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
thumbnail: "test-image.png",
tags: [
{
id: expect.any(String),
value: "123",
created_at: expect.any(String),
updated_at: expect.any(String),
},
{
id: expect.any(String),
value: "456",
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
type: {
value: "test-type",
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),
},
collection: {
id: "test-collection",
title: "Test collection",
created_at: expect.any(String),
updated_at: expect.any(String),
},
options: [
{
id: expect.stringMatching(/^opt_*/),
product_id: expect.stringMatching(/^prod_*/),
title: "size",
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),
},
{
id: expect.stringMatching(/^opt_*/),
product_id: expect.stringMatching(/^prod_*/),
title: "color",
}),
collection: expect.objectContaining({
id: "test-collection",
title: "Test collection",
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
variants: [
{
id: expect.stringMatching(/^variant_*/),
product_id: expect.stringMatching(/^prod_*/),
updated_at: expect.any(String),
created_at: expect.any(String),
title: "Test variant",
prices: [
{
id: expect.stringMatching(/^ma_*/),
currency_code: "usd",
amount: 100,
created_at: expect.any(String),
updated_at: expect.any(String),
variant_id: expect.stringMatching(/^variant_*/),
},
{
id: expect.stringMatching(/^ma_*/),
currency_code: "eur",
amount: 45,
created_at: expect.any(String),
updated_at: expect.any(String),
variant_id: expect.stringMatching(/^variant_*/),
},
{
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: [
{
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_*/),
},
{
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_*/),
},
],
},
],
})
}),
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("creates a product that is not discountable", async () => {
@@ -1356,92 +1373,94 @@ describe("/admin/products", () => {
expect(response.status).toEqual(200)
expect(response.data.product).toMatchSnapshot({
id: "test-product",
created_at: expect.any(String),
description: "test-product-description",
discountable: true,
handle: "test-product",
images: [
{
created_at: expect.any(String),
deleted_at: null,
id: expect.stringMatching(/^img_*/),
metadata: null,
updated_at: expect.any(String),
url: "test-image-2.png",
},
],
is_giftcard: false,
options: [
{
created_at: expect.any(String),
id: "test-option",
product_id: "test-product",
title: "test-option",
updated_at: expect.any(String),
},
],
profile_id: expect.stringMatching(/^sp_*/),
status: "published",
tags: [
{
created_at: expect.any(String),
id: "tag1",
updated_at: expect.any(String),
value: "123",
},
],
thumbnail: "test-image-2.png",
title: "Test product",
type: {
expect(response.data.product).toEqual(
expect.objectContaining({
id: "test-product",
created_at: expect.any(String),
id: expect.stringMatching(/^ptyp_*/),
updated_at: expect.any(String),
value: "test-type-2",
},
type_id: expect.stringMatching(/^ptyp_*/),
updated_at: expect.any(String),
variants: [
{
allow_backorder: false,
barcode: "test-barcode",
description: "test-product-description",
discountable: true,
handle: "test-product",
images: expect.arrayContaining([
expect.objectContaining({
created_at: expect.any(String),
deleted_at: null,
id: expect.stringMatching(/^img_*/),
metadata: null,
updated_at: expect.any(String),
url: "test-image-2.png",
}),
]),
is_giftcard: false,
options: expect.arrayContaining([
expect.objectContaining({
created_at: expect.any(String),
id: "test-option",
product_id: "test-product",
title: "test-option",
updated_at: expect.any(String),
}),
]),
profile_id: expect.stringMatching(/^sp_*/),
status: "published",
tags: expect.arrayContaining([
expect.objectContaining({
created_at: expect.any(String),
id: "tag1",
updated_at: expect.any(String),
value: "123",
}),
]),
thumbnail: "test-image-2.png",
title: "Test product",
type: expect.objectContaining({
created_at: expect.any(String),
ean: "test-ean",
id: "test-variant",
inventory_quantity: 10,
manage_inventory: true,
options: [
{
created_at: expect.any(String),
deleted_at: null,
id: "test-variant-option",
metadata: null,
option_id: "test-option",
updated_at: expect.any(String),
value: "Default variant",
variant_id: "test-variant",
},
],
origin_country: null,
prices: [
{
amount: 75,
created_at: expect.any(String),
currency_code: "usd",
id: "test-price",
updated_at: expect.any(String),
variant_id: "test-variant",
},
],
product_id: "test-product",
sku: "test-sku",
title: "Test variant",
upc: "test-upc",
id: expect.stringMatching(/^ptyp_*/),
updated_at: expect.any(String),
},
],
})
value: "test-type-2",
}),
type_id: expect.stringMatching(/^ptyp_*/),
updated_at: expect.any(String),
variants: expect.arrayContaining([
expect.objectContaining({
allow_backorder: false,
barcode: "test-barcode",
created_at: expect.any(String),
ean: "test-ean",
id: "test-variant",
inventory_quantity: 10,
manage_inventory: true,
options: expect.arrayContaining([
expect.objectContaining({
created_at: expect.any(String),
deleted_at: null,
id: "test-variant-option",
metadata: null,
option_id: "test-option",
updated_at: expect.any(String),
value: "Default variant",
variant_id: "test-variant",
}),
]),
origin_country: null,
prices: expect.arrayContaining([
expect.objectContaining({
amount: 75,
created_at: expect.any(String),
currency_code: "usd",
id: "test-price",
updated_at: expect.any(String),
variant_id: "test-variant",
}),
]),
product_id: "test-product",
sku: "test-sku",
title: "Test variant",
upc: "test-upc",
updated_at: expect.any(String),
}),
]),
})
)
})
it("updates product (removes images when empty array included)", async () => {
@@ -1813,6 +1832,11 @@ describe("/admin/products", () => {
beforeEach(async () => {
await productSeeder(dbConnection)
await adminSeeder(dbConnection)
await simpleSalesChannelFactory(dbConnection, {
name: "Default channel",
id: "default-channel",
is_default: true,
})
})
afterEach(async () => {
@@ -1859,6 +1883,11 @@ describe("/admin/products", () => {
await productSeeder(dbConnection)
await adminSeeder(dbConnection)
await priceListSeeder(dbConnection)
await simpleSalesChannelFactory(dbConnection, {
name: "Default channel",
id: "default-channel",
is_default: true,
})
})
afterEach(async () => {
@@ -2326,6 +2355,11 @@ describe("/admin/products", () => {
try {
await productSeeder(dbConnection)
await adminSeeder(dbConnection)
await simpleSalesChannelFactory(dbConnection, {
name: "Default channel",
id: "default-channel",
is_default: true,
})
} catch (err) {
console.log(err)
throw err
@@ -2398,6 +2432,11 @@ describe("/admin/products", () => {
beforeEach(async () => {
await productSeeder(dbConnection)
await adminSeeder(dbConnection)
await simpleSalesChannelFactory(dbConnection, {
name: "Default channel",
id: "default-channel",
is_default: true,
})
})
afterEach(async () => {
@@ -2871,6 +2910,11 @@ describe("/admin/products", () => {
beforeEach(async () => {
await productSeeder(dbConnection)
await adminSeeder(dbConnection)
await simpleSalesChannelFactory(dbConnection, {
name: "Default channel",
id: "default-channel",
is_default: true,
})
})
afterEach(async () => {

View File

@@ -850,6 +850,7 @@ describe("sales channels", () => {
salesChannel = await simpleSalesChannelFactory(dbConnection, {
name: "test name",
description: "test description",
is_default: true,
})
})
@@ -899,6 +900,47 @@ describe("sales channels", () => {
})
)
})
it("should assign the default sales channel to a product if none is provided when creating it", async () => {
const api = useApi()
const payload = {
title: "Product-no-saleschannel",
description: "test-product-description",
type: { value: "test-type" },
options: [{ title: "size" }],
variants: [
{
title: "Test variant",
inventory_quantity: 10,
prices: [{ currency_code: "usd", amount: 100 }],
options: [{ value: "large" }],
},
],
}
const response = await api
.post("/admin/products", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.product).toEqual(
expect.objectContaining({
sales_channels: [
expect.objectContaining({
id: salesChannel.id,
name: salesChannel.name,
}),
],
})
)
})
})
describe("POST /admin/products/:id", () => {

View File

@@ -3,6 +3,7 @@ import { request } from "../../../../../helpers/test-request"
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
import { ShippingProfileServiceMock } from "../../../../../services/__mocks__/shipping-profile"
import { SalesChannelServiceMock } from "../../../../../services/__mocks__/sales-channel"
describe("POST /admin/products", () => {
describe("successful creation with variants", () => {
@@ -46,6 +47,7 @@ describe("POST /admin/products", () => {
})
it("returns 200", () => {
expect(SalesChannelServiceMock.retrieveDefault).toHaveBeenCalledTimes(1)
expect(subject.status).toEqual(200)
})
@@ -114,6 +116,13 @@ describe("POST /admin/products", () => {
is_giftcard: false,
options: [{ title: "Denominations" }],
profile_id: IdMap.getId("default_shipping_profile"),
sales_channels: [
{
description: "sales channel 1 description",
is_disabled: false,
name: "sales channel 1 name",
},
],
})
})
@@ -173,6 +182,13 @@ describe("POST /admin/products", () => {
is_giftcard: true,
status: "draft",
profile_id: IdMap.getId("giftCardProfile"),
sales_channels: [
{
description: "sales channel 1 description",
is_disabled: false,
name: "sales channel 1 name",
},
],
})
})

View File

@@ -14,6 +14,7 @@ import {
ProductService,
ProductVariantInventoryService,
ProductVariantService,
SalesChannelService,
ShippingProfileService,
} from "../../../../services"
import {
@@ -121,6 +122,10 @@ export default async (req, res) => {
const inventoryService: IInventoryService | undefined =
req.scope.resolve("inventoryService")
const salesChannelService: SalesChannelService = req.scope.resolve(
"salesChannelService"
)
const entityManager: EntityManager = req.scope.resolve("manager")
const newProduct = await entityManager.transaction(async (manager) => {
@@ -143,6 +148,14 @@ export default async (req, res) => {
.retrieveDefault()
}
// If no sales channel available, set the default one
if (!validated?.sales_channels?.length) {
const defaultSalesChannel = await salesChannelService
.withTransaction(manager)
.retrieveDefault()
validated.sales_channels = [defaultSalesChannel]
}
const newProduct = await productService
.withTransaction(manager)
.create({ ...validated, profile_id: shippingProfile.id })

View File

@@ -1,4 +1,4 @@
import { IdMap } from "medusa-test-utils";
import { IdMap } from "medusa-test-utils"
export const SalesChannelServiceMock = {
withTransaction: function () {
@@ -19,12 +19,14 @@ export const SalesChannelServiceMock = {
listAndCount: jest.fn().mockImplementation(() => {
return Promise.resolve([
[{
id: IdMap.getId("sales_channel_1"),
name: "sales channel 1 name",
description: "sales channel 1 description",
is_disabled: false,
}],
[
{
id: IdMap.getId("sales_channel_1"),
name: "sales channel 1 name",
description: "sales channel 1 description",
is_disabled: false,
},
],
1,
])
}),
@@ -48,6 +50,14 @@ export const SalesChannelServiceMock = {
})
}),
retrieveDefault: jest.fn().mockImplementation(() => {
return Promise.resolve({
name: "sales channel 1 name",
description: "sales channel 1 description",
is_disabled: false,
})
}),
removeProducts: jest.fn().mockImplementation((id, productIds) => {
return Promise.resolve()
}),

View File

@@ -49,6 +49,10 @@ class SalesChannelService extends TransactionBaseService {
this.storeService_ = storeService
}
private getManager(): EntityManager {
return this.transactionManager_ ?? this.manager_
}
/**
* A generic retrieve used to find a sales channel by different attributes.
*
@@ -60,7 +64,7 @@ class SalesChannelService extends TransactionBaseService {
selector: Selector<SalesChannel>,
config: FindConfig<SalesChannel> = {}
): Promise<SalesChannel> {
const manager = this.manager_
const manager = this.getManager()
const salesChannelRepo = manager.getCustomRepository(
this.salesChannelRepository_
@@ -145,7 +149,7 @@ class SalesChannelService extends TransactionBaseService {
take: 20,
}
): Promise<[SalesChannel[], number]> {
const manager = this.manager_
const manager = this.getManager()
const salesChannelRepo = manager.getCustomRepository(
this.salesChannelRepository_
)
@@ -289,6 +293,27 @@ class SalesChannelService extends TransactionBaseService {
})
}
/**
* Retrieves the default sales channel.
* @return the sales channel
*/
async retrieveDefault(): Promise<SalesChannel> {
const manager = this.getManager()
const store = await this.storeService_.withTransaction(manager).retrieve({
relations: ["default_sales_channel"],
})
if (!store.default_sales_channel) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Default Sales channel was not found`
)
}
return store.default_sales_channel
}
/**
* Remove a batch of product from a sales channel
* @param salesChannelId - The id of the sales channel on which to remove the products