diff --git a/cypress/elements/catalog/products/product-details.js b/cypress/elements/catalog/products/product-details.js
index bc234f018..254e7972b 100644
--- a/cypress/elements/catalog/products/product-details.js
+++ b/cypress/elements/catalog/products/product-details.js
@@ -23,5 +23,10 @@ export const PRODUCT_DETAILS = {
uploadImageButton: '[data-test="button-upload-image"]',
uploadSavedImagesButton: '[data-test="uploadImages"]',
uploadMediaUrlButton: '[data-test="uploadMediaUrl"]',
- saveUploadUrlButton: '[data-test-id="upload-url-button"]'
+ saveUploadUrlButton: '[data-test-id="upload-url-button"]',
+ addWarehouseButton: '[data-test-id="add-warehouse"]',
+ warehouseOption: "[role='menuitem']",
+ stockInput: '[data-test-id="stock-input"]',
+ costPriceInput: '[name*="costPrice"]',
+ sellingPriceInput: '[name*="channel-price"]'
};
diff --git a/cypress/elements/catalog/products/variants-selectors.js b/cypress/elements/catalog/products/variants-selectors.js
index ba21c4434..744127469 100644
--- a/cypress/elements/catalog/products/variants-selectors.js
+++ b/cypress/elements/catalog/products/variants-selectors.js
@@ -9,5 +9,6 @@ export const VARIANTS_SELECTORS = {
addWarehouseButton: "button[class*='MuiIconButton-colorPrimary']",
warehouseOption: "[role='menuitem']",
saveButton: "[data-test='button-bar-confirm']",
- skuInputInAddVariant: "[name='sku']"
+ skuInputInAddVariant: "[name='sku']",
+ stockInput: "[data-test-id='stock-input']"
};
diff --git a/cypress/integration/products/produkctsWithoutSku/createProductWithoutSku.js b/cypress/integration/products/produkctsWithoutSku/createProductWithoutSku.js
new file mode 100644
index 000000000..3ce148aa8
--- /dev/null
+++ b/cypress/integration/products/produkctsWithoutSku/createProductWithoutSku.js
@@ -0,0 +1,243 @@
+///
+///
+
+import faker from "faker";
+
+import { PRODUCT_DETAILS } from "../../../elements/catalog/products/product-details";
+import { PRODUCTS_LIST } from "../../../elements/catalog/products/products-list";
+import { AVAILABLE_CHANNELS_FORM } from "../../../elements/channels/available-channels-form";
+import { BUTTON_SELECTORS } from "../../../elements/shared/button-selectors";
+import { urlList } from "../../../fixtures/urlList";
+import { ONE_PERMISSION_USERS } from "../../../fixtures/users";
+import {
+ createProduct,
+ updateChannelInProduct
+} from "../../../support/api/requests/Product";
+import { createTypeProduct } from "../../../support/api/requests/ProductType";
+import {
+ deleteChannelsStartsWith,
+ getDefaultChannel
+} from "../../../support/api/utils/channelsUtils";
+import { createWaitingForCaptureOrder } from "../../../support/api/utils/ordersUtils";
+import * as productUtils from "../../../support/api/utils/products/productsUtils";
+import * as shippingUtils from "../../../support/api/utils/shippingUtils";
+import { getProductVariants } from "../../../support/api/utils/storeFront/storeFrontProductUtils";
+import filterTests from "../../../support/filterTests";
+import {
+ createFirstVariant,
+ createVariant
+} from "../../../support/pages/catalog/products/VariantsPage";
+import { selectChannelInDetailsPages } from "../../../support/pages/channelsPage";
+
+filterTests({ definedTags: ["all", "critical"], version: "3.1.0" }, () => {
+ describe("Creating variants", () => {
+ const startsWith = "CyCreateVariants-";
+ const attributeValues = ["value1", "value2"];
+
+ let defaultChannel;
+ let warehouse;
+ let attribute;
+ let productType;
+ let simpleProductType;
+ let category;
+ let shippingMethod;
+ let address;
+
+ before(() => {
+ cy.clearSessionData().loginUserViaRequest();
+ shippingUtils.deleteShippingStartsWith(startsWith);
+ productUtils.deleteProductsStartsWith(startsWith);
+ deleteChannelsStartsWith(startsWith);
+
+ const name = `${startsWith}${faker.datatype.number()}`;
+ const simpleProductTypeName = `${startsWith}${faker.datatype.number()}`;
+ getDefaultChannel()
+ .then(channel => {
+ defaultChannel = channel;
+ cy.fixture("addresses");
+ })
+ .then(fixtureAddresses => {
+ address = fixtureAddresses.plAddress;
+ shippingUtils.createShipping({
+ channelId: defaultChannel.id,
+ name,
+ address
+ });
+ })
+ .then(
+ ({
+ warehouse: warehouseResp,
+ shippingMethod: shippingMethodResp
+ }) => {
+ warehouse = warehouseResp;
+ shippingMethod = shippingMethodResp;
+ }
+ );
+ productUtils
+ .createTypeAttributeAndCategoryForProduct({ name, attributeValues })
+ .then(
+ ({
+ attribute: attributeResp,
+ productType: productTypeResp,
+ category: categoryResp
+ }) => {
+ attribute = attributeResp;
+ productType = productTypeResp;
+ category = categoryResp;
+ createTypeProduct({
+ name: simpleProductTypeName,
+ attributeId: attribute.id,
+ hasVariants: false
+ });
+ }
+ )
+ .then(type => {
+ simpleProductType = type;
+ });
+ });
+
+ beforeEach(() => {
+ cy.clearSessionData().loginUserViaRequest(
+ "auth",
+ ONE_PERMISSION_USERS.product
+ );
+ });
+
+ it("should create variant without sku by variant creator", () => {
+ const name = `${startsWith}${faker.datatype.number()}`;
+ const price = 10;
+ let createdProduct;
+
+ createProduct({
+ attributeId: attribute.id,
+ name,
+ productTypeId: productType.id,
+ categoryId: category.id
+ })
+ .then(resp => {
+ createdProduct = resp;
+ updateChannelInProduct({
+ productId: createdProduct.id,
+ channelId: defaultChannel.id
+ });
+ cy.visit(`${urlList.products}${createdProduct.id}`);
+ createFirstVariant({
+ warehouseId: warehouse.id,
+ price,
+ attribute: attributeValues[0]
+ });
+ getProductVariants(createdProduct.id, defaultChannel.slug);
+ })
+ .then(([variant]) => {
+ expect(variant).to.have.property("name", attributeValues[0]);
+ expect(variant).to.have.property("price", price);
+ createWaitingForCaptureOrder({
+ channelSlug: defaultChannel.slug,
+ email: "example@example.com",
+ variantsList: [variant],
+ shippingMethodId: shippingMethod.id,
+ address
+ });
+ })
+ .then(({ order }) => {
+ expect(order.id).to.be.ok;
+ });
+ });
+
+ it("should create variant without sku", () => {
+ const name = `${startsWith}${faker.datatype.number()}`;
+ const variants = [{ price: 7 }, { name: attributeValues[1], price: 16 }];
+ let createdProduct;
+
+ productUtils
+ .createProductInChannel({
+ name,
+ attributeId: attribute.id,
+ channelId: defaultChannel.id,
+ warehouseId: warehouse.id,
+ productTypeId: productType.id,
+ categoryId: category.id,
+ price: variants[0].price
+ })
+ .then(({ product: productResp }) => {
+ createdProduct = productResp;
+ cy.visit(`${urlList.products}${createdProduct.id}`);
+ createVariant({
+ warehouseName: warehouse.name,
+ attributeName: variants[1].name,
+ price: variants[1].price,
+ channelName: defaultChannel.name
+ });
+ })
+ .then(() => {
+ getProductVariants(createdProduct.id, defaultChannel.slug);
+ })
+ .then(([firstVariant, secondVariant]) => {
+ expect(firstVariant).to.have.property("price", variants[0].price);
+ expect(secondVariant).to.have.property("name", variants[1].name);
+ expect(secondVariant).to.have.property("price", variants[1].price);
+ createWaitingForCaptureOrder({
+ channelSlug: defaultChannel.slug,
+ email: "example@example.com",
+ variantsList: [secondVariant],
+ shippingMethodId: shippingMethod.id,
+ address
+ });
+ })
+ .then(({ order }) => {
+ expect(order.id).to.be.ok;
+ });
+ });
+
+ it("should create simple product without sku", () => {
+ const name = `${startsWith}${faker.datatype.number()}`;
+ cy.visit(urlList.products)
+ .get(PRODUCTS_LIST.createProductBtn)
+ .click()
+ .get(PRODUCT_DETAILS.productNameInput)
+ .type(name)
+ .fillAutocompleteSelect(
+ PRODUCT_DETAILS.productTypeInput,
+ simpleProductType.name
+ )
+ .fillAutocompleteSelect(PRODUCT_DETAILS.categoryInput);
+ selectChannelInDetailsPages(defaultChannel.name);
+ cy.get(PRODUCT_DETAILS.addWarehouseButton).click();
+ cy.contains(PRODUCT_DETAILS.warehouseOption, warehouse.name)
+ .click()
+ .get(PRODUCT_DETAILS.stockInput)
+ .clearAndType(10)
+ .get(PRODUCT_DETAILS.costPriceInput)
+ .type(10)
+ .get(PRODUCT_DETAILS.sellingPriceInput)
+ .type(10)
+ .get(AVAILABLE_CHANNELS_FORM.assignedChannels)
+ .click()
+ .get(
+ `${AVAILABLE_CHANNELS_FORM.availableForPurchaseRadioButtons}${AVAILABLE_CHANNELS_FORM.radioButtonsValueTrue}`
+ )
+ .click()
+ .get(
+ `${AVAILABLE_CHANNELS_FORM.publishedRadioButtons}${AVAILABLE_CHANNELS_FORM.radioButtonsValueTrue}`
+ )
+ .click()
+ .addAliasToGraphRequest("VariantCreate")
+ .get(BUTTON_SELECTORS.confirm)
+ .click()
+ .confirmationMessageShouldDisappear()
+ .wait("@VariantCreate")
+ .then(({ response }) => {
+ const variants = [
+ response.body.data.productVariantCreate.productVariant
+ ];
+ createWaitingForCaptureOrder({
+ channelSlug: defaultChannel.slug,
+ email: "example@example.com",
+ variantsList: variants,
+ shippingMethodId: shippingMethod.id,
+ address
+ });
+ });
+ });
+ });
+});
diff --git a/cypress/support/api/requests/ProductType.js b/cypress/support/api/requests/ProductType.js
index 5432a0139..6aa56216c 100644
--- a/cypress/support/api/requests/ProductType.js
+++ b/cypress/support/api/requests/ProductType.js
@@ -90,6 +90,23 @@ export function deleteProductType(productTypeId) {
return cy.sendRequestWithQuery(mutation);
}
+export function productAttributeAssignmentUpdate({
+ productTypeId,
+ attributeId,
+ variantSelection = true
+}) {
+ const mutation = `mutation {
+ productAttributeAssignmentUpdate(
+ operations: {id: "${attributeId}", variantSelection: ${variantSelection}} productTypeId:"${productTypeId}") {
+ errors {
+ field
+ message
+ }
+ }
+ }`;
+ return cy.sendRequestWithQuery(mutation);
+}
+
export function getProductType(productTypeId) {
const query = `query{
productType(id:"${productTypeId}"){
diff --git a/cypress/support/api/utils/storeFront/storeFrontProductUtils.js b/cypress/support/api/utils/storeFront/storeFrontProductUtils.js
index 10581f92d..8d0213631 100644
--- a/cypress/support/api/utils/storeFront/storeFrontProductUtils.js
+++ b/cypress/support/api/utils/storeFront/storeFrontProductUtils.js
@@ -22,6 +22,7 @@ export const getProductVariants = (productId, channelSlug) => {
getProductDetails(productId, channelSlug).then(resp => {
const variantsList = resp.body.data.product.variants;
return variantsList.map(element => ({
+ id: element.id,
name: element.name,
price: element.pricing.price.gross.amount
}));
diff --git a/cypress/support/pages/catalog/products/VariantsPage.js b/cypress/support/pages/catalog/products/VariantsPage.js
index 4363f956e..ae4077e47 100644
--- a/cypress/support/pages/catalog/products/VariantsPage.js
+++ b/cypress/support/pages/catalog/products/VariantsPage.js
@@ -6,12 +6,18 @@ import { BUTTON_SELECTORS } from "../../../../elements/shared/button-selectors";
import { selectChannelVariantInDetailsPage } from "../../channelsPage";
import { fillUpPriceList } from "./priceListComponent";
-export function variantsShouldBeVisible({ name, price }) {
- cy.contains(PRODUCT_DETAILS.variantRow, name).should("be.visible");
+export function variantsShouldBeVisible({ price }) {
+ cy.get(PRODUCT_DETAILS.variantRow).should("be.visible");
cy.contains(PRODUCT_DETAILS.variantPrice, price);
}
-export function createFirstVariant({ sku, warehouseId, price, attribute }) {
+export function createFirstVariant({
+ sku,
+ warehouseId,
+ price,
+ attribute,
+ quantity = 1
+}) {
cy.get(PRODUCT_DETAILS.addVariantsButton).click();
cy.get(PRODUCT_DETAILS.addVariantsOptionDialog.optionMultiple).click();
cy.get(BUTTON_SELECTORS.submit).click();
@@ -24,11 +30,14 @@ export function createFirstVariant({ sku, warehouseId, price, attribute }) {
fillUpPriceList(price);
cy.get(`[name*='${warehouseId}']`)
.click()
+ .get(VARIANTS_SELECTORS.stockInput)
+ .type(quantity)
.get(VARIANTS_SELECTORS.nextButton)
- .click()
- .get(VARIANTS_SELECTORS.skuInput)
- .type(sku)
- .addAliasToGraphRequest("ProductVariantBulkCreate")
+ .click();
+ if (sku) {
+ cy.get(VARIANTS_SELECTORS.skuInput).type(sku);
+ }
+ cy.addAliasToGraphRequest("ProductVariantBulkCreate")
.get(VARIANTS_SELECTORS.nextButton)
.click()
.waitForRequestAndCheckIfNoErrors("@ProductVariantBulkCreate")
@@ -43,7 +52,8 @@ export function createVariant({
attributeName,
price,
costPrice = price,
- channelName
+ channelName,
+ quantity = 10
}) {
cy.get(PRODUCT_DETAILS.addVariantsButton)
.click()
@@ -51,15 +61,18 @@ export function createVariant({
.click()
.get(VARIANTS_SELECTORS.attributeOption)
.contains(attributeName)
- .click()
- .get(VARIANTS_SELECTORS.skuInputInAddVariant)
- .type(sku)
- .get(VARIANTS_SELECTORS.addWarehouseButton)
.click();
- cy.contains(VARIANTS_SELECTORS.warehouseOption, warehouseName).click({
- force: true
- });
- cy.get(VARIANTS_SELECTORS.saveButton)
+ if (sku) {
+ cy.get(VARIANTS_SELECTORS.skuInputInAddVariant).type(sku);
+ }
+ cy.get(VARIANTS_SELECTORS.addWarehouseButton).click();
+ cy.contains(VARIANTS_SELECTORS.warehouseOption, warehouseName)
+ .click({
+ force: true
+ })
+ .get(VARIANTS_SELECTORS.stockInput)
+ .type(quantity)
+ .get(VARIANTS_SELECTORS.saveButton)
.click()
.get(BUTTON_SELECTORS.back)
.click()
diff --git a/src/products/components/ProductStocks/ProductStocks.tsx b/src/products/components/ProductStocks/ProductStocks.tsx
index 2b2f1b035..92669916e 100644
--- a/src/products/components/ProductStocks/ProductStocks.tsx
+++ b/src/products/components/ProductStocks/ProductStocks.tsx
@@ -375,6 +375,7 @@ const ProductStocks: React.FC = ({
= pr
}