diff --git a/cypress/elements/catalog/products/variants-selectors.js b/cypress/elements/catalog/products/variants-selectors.js
index 68911eefc..f24bb2fbd 100644
--- a/cypress/elements/catalog/products/variants-selectors.js
+++ b/cypress/elements/catalog/products/variants-selectors.js
@@ -11,6 +11,7 @@ export const VARIANTS_SELECTORS = {
saveButton: "[data-test='button-bar-confirm']",
skuInputInAddVariant: "[name='sku']",
preorderCheckbox: "[name='isPreorder']",
+ channelThresholdInput: "[name='channel-threshold']",
setUpEndDateButton: "[name='hasPreorderEndDate']",
preorderEndDateInput: "[name='preorderEndDateTime:date']",
preorderEndTimeInput: "[name='preorderEndDateTime:time']",
diff --git a/cypress/fixtures/urlList.js b/cypress/fixtures/urlList.js
index a3a4be248..2bbe33da6 100644
--- a/cypress/fixtures/urlList.js
+++ b/cypress/fixtures/urlList.js
@@ -79,10 +79,10 @@ export const warehouseDetailsUrl = warehouseId =>
export const saleDetailsUrl = saleId => `${urlList.sales}${saleId}`;
-export const voucherDetailsUrl = voucherId => `${urlList.vouchers}${voucherId}`;
-
export const variantDetailsUrl = (productId, variantId) =>
- `${urlList.products}${productId}/${urlList.variant}${variantId}`;
+ `${urlList.products}${productId}/${urlList.variants}${variantId}`;
+
+export const voucherDetailsUrl = voucherId => `${urlList.vouchers}${voucherId}`;
export const stripeConfirmationUrl = id =>
`https://api.stripe.com/v1/payment_intents/${id}/confirm`;
diff --git a/cypress/integration/preorders/stocksAndThreshold.js b/cypress/integration/preorders/stocksAndThreshold.js
new file mode 100644
index 000000000..46fcc586f
--- /dev/null
+++ b/cypress/integration/preorders/stocksAndThreshold.js
@@ -0,0 +1,117 @@
+///
+///
+
+import { VARIANTS_SELECTORS } from "../../elements/catalog/products/variants-selectors";
+import { BUTTON_SELECTORS } from "../../elements/shared/button-selectors";
+import { variantDetailsUrl } from "../../fixtures/urlList";
+import { createCheckout } from "../../support/api/requests/Checkout";
+import { fulfillOrder } from "../../support/api/requests/Order";
+import { getVariant } from "../../support/api/requests/Product";
+import { createWaitingForCaptureOrder } from "../../support/api/utils/ordersUtils";
+import { createProductWithShipping } from "../../support/api/utils/products/productsUtils";
+import filterTests from "../../support/filterTests";
+import { saveVariant } from "../../support/pages/catalog/products/VariantsPage";
+
+filterTests({ definedTags: ["all"], version: "3.1.0" }, () => {
+ describe("Creating variants", () => {
+ const startsWith = "CreatePreOrder";
+ const attributeValues = ["value1", "value2"];
+ const preorder = {
+ globalThreshold: 15
+ };
+
+ let defaultChannel;
+ let product;
+ let variantsList;
+ let warehouse;
+ let checkoutData;
+
+ before(() => {
+ cy.clearSessionData().loginUserViaRequest();
+ createProductWithShipping({
+ name: startsWith,
+ attributeValues,
+ preorder
+ }).then(resp => {
+ checkoutData = {
+ address: resp.address,
+ channelSlug: resp.defaultChannel.slug,
+ email: "example@example.com",
+ shippingMethodId: resp.shippingMethod.id,
+ variantsList: resp.variantsList
+ };
+ warehouse = resp.warehouse;
+ defaultChannel = resp.defaultChannel;
+ product = resp.product;
+ variantsList = resp.variantsList;
+ });
+ });
+
+ beforeEach(() => {
+ cy.clearSessionData().loginUserViaRequest();
+ });
+
+ it("should not be able to order more products then channel threshold", () => {
+ cy.visit(variantDetailsUrl(product.id, variantsList[0].id))
+ .get(VARIANTS_SELECTORS.channelThresholdInput)
+ .type(5)
+ .addAliasToGraphRequest("ProductVariantChannelListingUpdate");
+ saveVariant("VariantUpdate");
+ cy.wait("@ProductVariantChannelListingUpdate");
+ checkoutData.productQuantity = 7;
+ createCheckout(checkoutData).then(({ errors }) => {
+ expect(errors[0].field).to.eq("quantity");
+ });
+ });
+
+ it("should not be able to order more products then threshold even if channel is not exceeded", () => {
+ cy.visit(variantDetailsUrl(product.id, variantsList[0].id))
+ .get(VARIANTS_SELECTORS.channelThresholdInput)
+ .type(40);
+ saveVariant("VariantUpdate");
+ checkoutData.productQuantity = 20;
+ createCheckout(checkoutData).then(({ errors }) => {
+ expect(errors[0].field).to.eq("quantity");
+ });
+ });
+
+ it("should allocate variants bought in preorder to correct warehouses", () => {
+ let order;
+ createWaitingForCaptureOrder(checkoutData)
+ .then(({ order: orderResp }) => {
+ order = orderResp;
+ cy.visit(variantDetailsUrl(product.id, variantsList[0].id))
+ .get(VARIANTS_SELECTORS.preorderCheckbox)
+ .click()
+ .addAliasToGraphRequest("ProductVariantPreorderDeactivate")
+ .get(BUTTON_SELECTORS.submit)
+ .click()
+ .waitForRequestAndCheckIfNoErrors(
+ "@ProductVariantPreorderDeactivate"
+ );
+ getVariant(variantsList[0].id, defaultChannel.slug);
+ })
+ .then(variantResp => {
+ expect(
+ variantResp.stocks[0].quantityAllocated,
+ "product is allocated with correct quantity"
+ ).to.eq(1);
+ expect(
+ variantResp.stocks[0].warehouse.id,
+ "product is allocated to correct warehouse"
+ ).to.eq(warehouse.id);
+ })
+ .then(() => {
+ fulfillOrder({
+ orderId: order.id,
+ warehouse: warehouse.id,
+ quantity: 1,
+ linesId: order.lines
+ });
+ })
+ .then(({ errors }) => {
+ expect(errors, "no errors when fulfilling order").to.be.empty;
+ });
+ });
+ });
+});
diff --git a/cypress/support/api/requests/Checkout.js b/cypress/support/api/requests/Checkout.js
index 9e12f4b57..a39299712 100644
--- a/cypress/support/api/requests/Checkout.js
+++ b/cypress/support/api/requests/Checkout.js
@@ -141,6 +141,9 @@ export function completeCheckout(checkoutId, paymentData) {
checkoutComplete(checkoutId:"${checkoutId}" ${paymentDataLine}){
order{
id
+ lines{
+ id
+ }
paymentStatus
total{
gross{
diff --git a/cypress/support/api/requests/Product.js b/cypress/support/api/requests/Product.js
index c021a95b6..76d7c09fe 100644
--- a/cypress/support/api/requests/Product.js
+++ b/cypress/support/api/requests/Product.js
@@ -150,9 +150,14 @@ export function createVariant({
costPrice = 1,
trackInventory = true,
weight = 1,
+ attributeValues = ["value"],
attributeName = "value",
- attributeValues = ["value"]
+ preorder
}) {
+ const preorderLines = getValueWithDefault(
+ preorder,
+ `preorder:${stringify(preorder)}`
+ );
const skuLines = getValueWithDefault(sku, `sku: "${sku}"`);
const attributeLines = getValueWithDefault(
@@ -183,6 +188,7 @@ export function createVariant({
const mutation = `mutation{
productVariantBulkCreate(product: "${productId}", variants: {
+ ${preorderLines}
${attributeLines}
weight: ${weight}
${skuLines}
@@ -237,7 +243,7 @@ export function getVariants(variantsList) {
return cy.sendRequestWithQuery(query).its("body.data.productVariants");
}
-export function getVariant(id, channel, auth = "auth") {
+export function getVariant(id, channelSlug, auth = "auth") {
const preorder = returnValueDependsOnShopVersion(
"3.1",
`preorder{
@@ -248,9 +254,15 @@ export function getVariant(id, channel, auth = "auth") {
);
const query = `query{
- productVariant(id:"${id}" channel:"${channel}"){
+ productVariant(id:"${id}" channel:"${channelSlug}"){
id
name
+ stocks{
+ warehouse{
+ id
+ }
+ quantityAllocated
+ }
${preorder}
sku
pricing{
diff --git a/cypress/support/api/utils/products/productsUtils.js b/cypress/support/api/utils/products/productsUtils.js
index f9059c270..110b27ca9 100644
--- a/cypress/support/api/utils/products/productsUtils.js
+++ b/cypress/support/api/utils/products/productsUtils.js
@@ -30,6 +30,7 @@ export function createProductInChannel({
description = null,
trackInventory = true,
weight = 1,
+ preorder,
sku = name
}) {
let product;
@@ -57,7 +58,8 @@ export function createProductInChannel({
channelId,
price,
trackInventory,
- weight
+ weight,
+ preorder
});
})
.then(variantsResp => {
@@ -116,6 +118,7 @@ export function deleteProductsAndCreateNewOneWithNewDataAndDefaultChannel({
name,
description = name,
warehouseId,
+ preorder,
attributeValues = ["value"],
sku = name,
productPrice = 10
@@ -146,8 +149,9 @@ export function deleteProductsAndCreateNewOneWithNewDataAndDefaultChannel({
collectionId: collection.id,
description,
warehouseId,
- sku,
- price: productPrice
+ price: productPrice,
+ preorder,
+ sku
});
})
.then(({ product, variantsList }) => ({ product, variantsList }));
@@ -158,7 +162,8 @@ export function createProductWithShipping({
attributeValues = ["value"],
sku = name,
productPrice = 10,
- shippingPrice = 10
+ shippingPrice = 10,
+ preorder
}) {
let address;
let warehouse;
@@ -194,9 +199,10 @@ export function createProductWithShipping({
deleteProductsAndCreateNewOneWithNewDataAndDefaultChannel({
name,
warehouseId: warehouse.id,
+ productPrice,
+ preorder,
attributeValues,
- sku,
- productPrice
+ sku
});
}
)
diff --git a/cypress/support/customCommands/basicOperations/index.js b/cypress/support/customCommands/basicOperations/index.js
index 3784e1294..2da1e2098 100644
--- a/cypress/support/customCommands/basicOperations/index.js
+++ b/cypress/support/customCommands/basicOperations/index.js
@@ -12,7 +12,7 @@ Cypress.Commands.add("waitForRequestAndCheckIfNoErrors", alias => {
cy.wait(alias).then(resp => {
expect(
resp.response.body.errors,
- `There are errors in ${alias} operation in graphql response`
+ `No errors in ${alias} operation in graphql response`
).to.be.undefined;
return resp;
});
diff --git a/src/products/components/ProductStocks/ProductStocks.tsx b/src/products/components/ProductStocks/ProductStocks.tsx
index c70ea5835..013b6e03d 100644
--- a/src/products/components/ProductStocks/ProductStocks.tsx
+++ b/src/products/components/ProductStocks/ProductStocks.tsx
@@ -604,6 +604,7 @@ const ProductStocks: React.FC = ({