CPDefinitionMetaKeywords
CPDefinitionMetaTitle
CPDefinitionNameDefaultLanguage
+ CPDefinitionOptionRel
CPDefinitionOptionRelPriceType
CPDefinitionOptionSKUContributor
CPDefinitionOptionValueRelCPInstance
@@ -1563,6 +1564,7 @@
CPInstanceJson
CPInstanceMaxPriceValue
CPInstanceMinPriceValue
+ CPInstanceOptionValueRel
CPInstancePrice
CPInstancePromoPrice
CPInstanceReplacementCPInstanceUuid
diff --git a/modules/apps/commerce/commerce-service/src/main/java/com/liferay/commerce/service/impl/CommerceOrderItemLocalServiceImpl.java b/modules/apps/commerce/commerce-service/src/main/java/com/liferay/commerce/service/impl/CommerceOrderItemLocalServiceImpl.java
index c2983f56290bd4..1d3456ecbfb446 100644
--- a/modules/apps/commerce/commerce-service/src/main/java/com/liferay/commerce/service/impl/CommerceOrderItemLocalServiceImpl.java
+++ b/modules/apps/commerce/commerce-service/src/main/java/com/liferay/commerce/service/impl/CommerceOrderItemLocalServiceImpl.java
@@ -37,12 +37,18 @@
import com.liferay.commerce.price.CommerceProductPriceImpl;
import com.liferay.commerce.price.CommerceProductPriceRequest;
import com.liferay.commerce.product.constants.CPConstants;
+import com.liferay.commerce.product.exception.CPDefinitionOptionRelException;
+import com.liferay.commerce.product.exception.CPInstanceOptionValueRelException;
import com.liferay.commerce.product.exception.NoSuchCPInstanceException;
import com.liferay.commerce.product.exception.NoSuchCPInstanceUnitOfMeasureException;
import com.liferay.commerce.product.model.CPDefinition;
+import com.liferay.commerce.product.model.CPDefinitionOptionRel;
+import com.liferay.commerce.product.model.CPDefinitionOptionValueRel;
import com.liferay.commerce.product.model.CPInstance;
import com.liferay.commerce.product.model.CPInstanceUnitOfMeasure;
import com.liferay.commerce.product.model.CPMeasurementUnit;
+import com.liferay.commerce.product.model.CPOption;
+import com.liferay.commerce.product.model.CPOptionValue;
import com.liferay.commerce.product.option.CommerceOptionValue;
import com.liferay.commerce.product.option.CommerceOptionValueHelper;
import com.liferay.commerce.product.service.CPDefinitionLocalService;
@@ -50,6 +56,7 @@
import com.liferay.commerce.product.service.CPInstanceLocalService;
import com.liferay.commerce.product.service.CPInstanceUnitOfMeasureLocalService;
import com.liferay.commerce.product.service.CPMeasurementUnitLocalService;
+import com.liferay.commerce.product.util.CPInstanceHelper;
import com.liferay.commerce.product.util.CPJSONUtil;
import com.liferay.commerce.service.CommerceOrderLocalService;
import com.liferay.commerce.service.base.CommerceOrderItemLocalServiceBaseImpl;
@@ -71,6 +78,7 @@
import com.liferay.portal.kernel.json.JSONException;
import com.liferay.portal.kernel.json.JSONFactory;
import com.liferay.portal.kernel.json.JSONObject;
+import com.liferay.portal.kernel.json.JSONUtil;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.model.User;
@@ -1382,6 +1390,8 @@ private CommerceOrderItem _createCommerceOrderItem(
GetterUtil.getBoolean(
serviceContext.getAttribute("validateOrder"), true));
+ json = _validateJSON(cpDefinition, cpInstance, json);
+
long commerceOrderItemId = counterLocalService.increment();
CommerceOrderItem commerceOrderItem =
@@ -2431,6 +2441,10 @@ private CommerceOrderItem _updateCommerceOrderItem(
GetterUtil.getBoolean(
serviceContext.getAttribute("validateOrder"), true));
+ json = _validateJSON(
+ commerceOrderItem.getCPDefinition(),
+ commerceOrderItem.fetchCPInstance(), json);
+
_updateCommerceInventoryBookedQuantity(
userId, commerceOrderItem,
commerceOrderItem.getCommerceInventoryBookedQuantityId(), quantity,
@@ -2468,6 +2482,10 @@ private CommerceOrderItem _updateCommerceOrderItem(
GetterUtil.getBoolean(
serviceContext.getAttribute("validateOrder"), true));
+ json = _validateJSON(
+ commerceOrderItem.getCPDefinition(),
+ commerceOrderItem.fetchCPInstance(), json);
+
_updateCommerceInventoryBookedQuantity(
userId, commerceOrderItem,
commerceOrderItem.getCommerceInventoryBookedQuantityId(), quantity,
@@ -2623,6 +2641,45 @@ private void _validate(
}
}
+ private String _validateJSON(
+ CPDefinition cpDefinition, CPInstance cpInstance, String json)
+ throws PortalException {
+
+ String sanitizedJSON = json;
+ JSONArray optionJSONArray = CPJSONUtil.toJSONArray(json);
+
+ Map>
+ cpDefinitionOptionRelKeysOptionValueRelKeysMap =
+ _cpDefinitionOptionRelLocalService.
+ getCPDefinitionOptionRelKeysCPDefinitionOptionValueRelKeys(
+ cpInstance.getCPInstanceId());
+
+ JSONArray optionRelJSONArray = CPJSONUtil.toJSONArray(
+ cpDefinitionOptionRelKeysOptionValueRelKeysMap);
+
+ for (CPDefinitionOptionRel cpDefinitionOptionRel :
+ cpDefinition.getCPDefinitionOptionRels()) {
+
+ if (cpDefinitionOptionRel.isSkuContributor()) {
+ _validateSkuContributorOption(
+ cpDefinitionOptionRel, cpInstance, optionJSONArray,
+ optionRelJSONArray);
+
+ sanitizedJSON = optionJSONArray.toString();
+ }
+ else if (cpDefinitionOptionRel.isRequired()) {
+ if (CPJSONUtil.isEmpty(json)) {
+ throw new CPDefinitionOptionRelException(
+ "Required option is missing");
+ }
+
+ _validateRequiredOption(cpDefinitionOptionRel, optionJSONArray);
+ }
+ }
+
+ return sanitizedJSON;
+ }
+
private void _validateParentCommerceOrderId(
CommerceOrderItem commerceOrderItem)
throws PortalException {
@@ -2637,6 +2694,170 @@ private void _validateParentCommerceOrderId(
}
}
+ private void _validateRequiredOption(
+ CPDefinitionOptionRel cpDefinitionOptionRel,
+ JSONArray optionJSONArray)
+ throws PortalException {
+
+ CPOption cpOption = cpDefinitionOptionRel.getCPOption();
+
+ boolean containsRequiredOption = false;
+
+ for (int i = 0; i < optionJSONArray.length(); i++) {
+ JSONObject jsonObject = optionJSONArray.getJSONObject(i);
+
+ String key = jsonObject.getString("key");
+
+ if (Objects.equals(key, cpOption.getKey())) {
+ JSONArray valueJSONArray = jsonObject.getJSONArray("value");
+
+ if ((valueJSONArray == null) ||
+ (valueJSONArray.length() == 0)) {
+
+ throw new CPInstanceOptionValueRelException(
+ "Required option must have a value");
+ }
+
+ String optionTypeKey = cpOption.getCommerceOptionTypeKey();
+
+ if (optionTypeKey.matches("checkbox_multiple|radio|select")) {
+ boolean multipleSelect = false;
+ List stringList = new ArrayList<>();
+
+ if (Objects.equals(optionTypeKey, "checkbox_multiple")) {
+ multipleSelect = true;
+ stringList = JSONUtil.toStringList(valueJSONArray);
+ }
+
+ List cpOptionValues =
+ cpOption.getCPOptionValues();
+
+ for (CPOptionValue cpOptionValue : cpOptionValues) {
+ if (multipleSelect) {
+ stringList.remove(cpOptionValue.getKey());
+
+ if (stringList.isEmpty()) {
+ containsRequiredOption = true;
+
+ break;
+ }
+ }
+ else {
+ if (Objects.equals(
+ cpOptionValue.getKey(),
+ valueJSONArray.get(0))) {
+
+ containsRequiredOption = true;
+
+ break;
+ }
+ }
+ }
+ }
+ else if (Objects.equals(optionTypeKey, "checkbox")) {
+ String valueString = valueJSONArray.getString(0);
+
+ if (Objects.equals(valueString, cpOption.getKey()) ||
+ Objects.equals(valueString, "[]")) {
+
+ containsRequiredOption = true;
+ }
+ }
+ else {
+ containsRequiredOption = true;
+
+ break;
+ }
+ }
+ }
+
+ if (!containsRequiredOption) {
+ throw new CPDefinitionOptionRelException(
+ "Required option is missing");
+ }
+ }
+
+ private void _validateSkuContributorOption(
+ CPDefinitionOptionRel cpDefinitionOptionRel, CPInstance cpInstance,
+ JSONArray optionJSONArray, JSONArray optionRelJSONArray)
+ throws PortalException {
+
+ boolean jsonOptionExists = false;
+
+ for (int i = 0; i < optionRelJSONArray.length(); i++) {
+ JSONObject instanceJSONObject = optionRelJSONArray.getJSONObject(i);
+
+ if (Objects.equals(
+ instanceJSONObject.get("key"),
+ cpDefinitionOptionRel.getKey())) {
+
+ for (int j = 0; j < optionJSONArray.length(); j++) {
+ JSONObject optionJSONObject = optionJSONArray.getJSONObject(
+ j);
+
+ String key = optionJSONObject.getString("key");
+
+ if (Objects.equals(key, cpDefinitionOptionRel.getKey())) {
+ jsonOptionExists = true;
+
+ optionJSONObject.put(
+ "skuOptionName",
+ instanceJSONObject.get("skuOptionName")
+ ).put(
+ "skuOptionValueNames",
+ instanceJSONObject.get("skuOptionValueNames")
+ ).put(
+ "value", instanceJSONObject.get("value")
+ );
+
+ break;
+ }
+ }
+
+ if (!jsonOptionExists) {
+ JSONObject jsonObject = _jsonFactory.createJSONObject();
+
+ Map>
+ cpDefinitionOptionRelValueRelMap =
+ _cpInstanceHelper.
+ getCPInstanceCPDefinitionOptionRelsMap(
+ cpInstance.getCPInstanceId());
+
+ List
+ cpDefinitionOptionValueRelList =
+ cpDefinitionOptionRelValueRelMap.get(
+ cpDefinitionOptionRel);
+
+ CPDefinitionOptionValueRel cpDefinitionOptionValueRel =
+ cpDefinitionOptionValueRelList.get(0);
+
+ jsonObject.put(
+ "key", instanceJSONObject.get("key")
+ ).put(
+ "price", cpDefinitionOptionValueRel.getPrice()
+ ).put(
+ "priceType", cpDefinitionOptionRel.getPriceType()
+ ).put(
+ "quantity", cpDefinitionOptionValueRel.getQuantity()
+ ).put(
+ "skuOptionKey", instanceJSONObject.get("key")
+ ).put(
+ "skuOptionName", instanceJSONObject.get("skuOptionName")
+ ).put(
+ "skuOptionValueKey", instanceJSONObject.get("value")
+ ).put(
+ "skuOptionValueNames",
+ instanceJSONObject.get("skuOptionValueNames")
+ ).put(
+ "value", instanceJSONObject.get("value")
+ );
+
+ optionJSONArray.put(jsonObject);
+ }
+ }
+ }
+ }
+
private static final String[] _SELECTED_FIELD_NAMES = {
Field.ENTRY_CLASS_PK, Field.COMPANY_ID, Field.UID
};
@@ -2688,6 +2909,9 @@ private void _validateParentCommerceOrderId(
private CPDefinitionOptionRelLocalService
_cpDefinitionOptionRelLocalService;
+ @Reference
+ private CPInstanceHelper _cpInstanceHelper;
+
@Reference
private CPInstanceLocalService _cpInstanceLocalService;
diff --git a/modules/apps/commerce/headless/headless-commerce/headless-commerce-admin-order-impl/src/main/java/com/liferay/headless/commerce/admin/order/internal/jaxrs/exception/mapper/CPDefinitionOptionRelExceptionMapper.java b/modules/apps/commerce/headless/headless-commerce/headless-commerce-admin-order-impl/src/main/java/com/liferay/headless/commerce/admin/order/internal/jaxrs/exception/mapper/CPDefinitionOptionRelExceptionMapper.java
new file mode 100644
index 00000000000000..73b090497b1eab
--- /dev/null
+++ b/modules/apps/commerce/headless/headless-commerce/headless-commerce-admin-order-impl/src/main/java/com/liferay/headless/commerce/admin/order/internal/jaxrs/exception/mapper/CPDefinitionOptionRelExceptionMapper.java
@@ -0,0 +1,39 @@
+/**
+ * SPDX-FileCopyrightText: (c) 2025 Liferay, Inc. https://liferay.com
+ * SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
+ */
+
+package com.liferay.headless.commerce.admin.order.internal.jaxrs.exception.mapper;
+
+import com.liferay.commerce.product.exception.CPDefinitionOptionRelException;
+import com.liferay.portal.vulcan.jaxrs.exception.mapper.BaseExceptionMapper;
+import com.liferay.portal.vulcan.jaxrs.exception.mapper.Problem;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * @author Lianne Louie
+ */
+@Component(
+ property = {
+ "osgi.jaxrs.application.select=(osgi.jaxrs.name=Liferay.Headless.Commerce.Admin.Order)",
+ "osgi.jaxrs.extension=true",
+ "osgi.jaxrs.name=Liferay.Headless.Commerce.Admin.Order.CPDefinitionOptionRelExceptionMapper"
+ },
+ service = ExceptionMapper.class
+)
+public class CPDefinitionOptionRelExceptionMapper
+ extends BaseExceptionMapper {
+
+ @Override
+ protected Problem getProblem(
+ CPDefinitionOptionRelException cpDefinitionOptionRelException) {
+
+ return new Problem(
+ Response.Status.BAD_REQUEST, "The option is invalid");
+ }
+
+}
\ No newline at end of file
diff --git a/modules/apps/commerce/headless/headless-commerce/headless-commerce-admin-order-impl/src/main/java/com/liferay/headless/commerce/admin/order/internal/jaxrs/exception/mapper/CPInstanceOptionValueRelExceptionMapper.java b/modules/apps/commerce/headless/headless-commerce/headless-commerce-admin-order-impl/src/main/java/com/liferay/headless/commerce/admin/order/internal/jaxrs/exception/mapper/CPInstanceOptionValueRelExceptionMapper.java
new file mode 100644
index 00000000000000..8a0ffcc9dc6dcc
--- /dev/null
+++ b/modules/apps/commerce/headless/headless-commerce/headless-commerce-admin-order-impl/src/main/java/com/liferay/headless/commerce/admin/order/internal/jaxrs/exception/mapper/CPInstanceOptionValueRelExceptionMapper.java
@@ -0,0 +1,39 @@
+/**
+ * SPDX-FileCopyrightText: (c) 2025 Liferay, Inc. https://liferay.com
+ * SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
+ */
+
+package com.liferay.headless.commerce.admin.order.internal.jaxrs.exception.mapper;
+
+import com.liferay.commerce.product.exception.CPInstanceOptionValueRelException;
+import com.liferay.portal.vulcan.jaxrs.exception.mapper.BaseExceptionMapper;
+import com.liferay.portal.vulcan.jaxrs.exception.mapper.Problem;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * @author Lianne Louie
+ */
+@Component(
+ property = {
+ "osgi.jaxrs.application.select=(osgi.jaxrs.name=Liferay.Headless.Commerce.Admin.Order)",
+ "osgi.jaxrs.extension=true",
+ "osgi.jaxrs.name=Liferay.Headless.Commerce.Admin.Order.CPInstanceOptionValueRelExceptionMapper"
+ },
+ service = ExceptionMapper.class
+)
+public class CPInstanceOptionValueRelExceptionMapper
+ extends BaseExceptionMapper {
+
+ @Override
+ protected Problem getProblem(
+ CPInstanceOptionValueRelException cpInstanceOptionValueRelException) {
+
+ return new Problem(
+ Response.Status.BAD_REQUEST, "The option value is invalid");
+ }
+
+}
\ No newline at end of file
diff --git a/modules/apps/commerce/headless/headless-commerce/headless-commerce-admin-order-test/src/testIntegration/java/com/liferay/headless/commerce/admin/order/resource/v1_0/test/OrderItemResourceTest.java b/modules/apps/commerce/headless/headless-commerce/headless-commerce-admin-order-test/src/testIntegration/java/com/liferay/headless/commerce/admin/order/resource/v1_0/test/OrderItemResourceTest.java
index 179565d48517be..11d59a825d2a09 100644
--- a/modules/apps/commerce/headless/headless-commerce/headless-commerce-admin-order-test/src/testIntegration/java/com/liferay/headless/commerce/admin/order/resource/v1_0/test/OrderItemResourceTest.java
+++ b/modules/apps/commerce/headless/headless-commerce/headless-commerce-admin-order-test/src/testIntegration/java/com/liferay/headless/commerce/admin/order/resource/v1_0/test/OrderItemResourceTest.java
@@ -15,11 +15,14 @@
import com.liferay.commerce.media.constants.CommerceMediaConstants;
import com.liferay.commerce.model.CommerceOrder;
import com.liferay.commerce.model.CommerceOrderItem;
+import com.liferay.commerce.product.constants.CPConstants;
import com.liferay.commerce.product.constants.CommerceChannelConstants;
import com.liferay.commerce.product.model.CPDefinition;
import com.liferay.commerce.product.model.CPInstance;
+import com.liferay.commerce.product.model.CPOption;
import com.liferay.commerce.product.model.CommerceCatalog;
import com.liferay.commerce.product.model.CommerceChannel;
+import com.liferay.commerce.product.service.CPOptionLocalService;
import com.liferay.commerce.product.service.CommerceChannelLocalService;
import com.liferay.commerce.product.test.util.CPTestUtil;
import com.liferay.commerce.product.type.virtual.constants.VirtualCPTypeConstants;
@@ -36,6 +39,7 @@
import com.liferay.headless.commerce.admin.order.client.dto.v1_0.OrderItem;
import com.liferay.petra.string.StringBundler;
import com.liferay.petra.string.StringPool;
+import com.liferay.portal.kernel.json.JSONFactory;
import com.liferay.portal.kernel.model.User;
import com.liferay.portal.kernel.repository.model.FileEntry;
import com.liferay.portal.kernel.service.ServiceContext;
@@ -199,6 +203,15 @@ public void testPatchOrderItemByExternalReferenceCode() throws Exception {
super.testPatchOrderItemByExternalReferenceCode();
}
+ @Override
+ @Test
+ public void testPostOrderIdOrderItem() throws Exception {
+ super.testPostOrderIdOrderItem();
+
+ _testPostOrderItemWithMissingRequiredOption();
+ _testPostOrderItemWithSkuContributorOption();
+ }
+
@Override
protected String[] getAdditionalAssertFieldNames() {
return new String[] {"quantity"};
@@ -394,6 +407,67 @@ private OrderItem _addCommerceOrderItem(OrderItem orderItem)
};
}
+ private OrderItem _addProductWithMissingOptionJSON(
+ boolean required, boolean skuContributor)
+ throws Exception {
+
+ CPInstance cpInstance = CPTestUtil.addCPInstanceWithRandomSku(
+ _commerceCatalog.getGroupId());
+
+ CPOption cpOption = _cpOptionLocalService.addCPOption(
+ null, _user.getUserId(),
+ RandomTestUtil.randomLocaleStringMap(),
+ RandomTestUtil.randomLocaleStringMap(),
+ CPConstants.PRODUCT_OPTION_SELECT_KEY,
+ RandomTestUtil.randomBoolean(), required,
+ skuContributor, RandomTestUtil.randomString(), _serviceContext);
+
+ CPTestUtil.addCPDefinitionOptionRel(
+ _commerceCatalog.getGroupId(), cpInstance.getCPDefinitionId(),
+ cpOption.getCPOptionId());
+
+ CPTestUtil.addCPDefinitionOptionValueRel(
+ cpInstance.getCPDefinitionId(), cpOption.getCPOptionId(),
+ RandomTestUtil.randomString(), RandomTestUtil.randomString(),
+ CPConstants.PRODUCT_OPTION_PRICE_TYPE_STATIC, required,
+ skuContributor, _serviceContext);
+
+ return new OrderItem() {
+ {
+ bookedQuantityId = RandomTestUtil.randomLong();
+ deliveryGroup = StringUtil.toLowerCase(
+ RandomTestUtil.randomString());
+ discountManuallyAdjusted = RandomTestUtil.randomBoolean();
+ externalReferenceCode = RandomTestUtil.randomString();
+ id = RandomTestUtil.randomLong();
+ options = "";
+ orderExternalReferenceCode =
+ _commerceOrder.getExternalReferenceCode();
+ orderId = _commerceOrder.getCommerceOrderId();
+ priceManuallyAdjusted = RandomTestUtil.randomBoolean();
+ printedNote = StringUtil.toLowerCase(
+ RandomTestUtil.randomString());
+ quantity = BigDecimal.valueOf(RandomTestUtil.randomInt(1, 100));
+ replacedSkuExternalReferenceCode =
+ RandomTestUtil.randomString();
+ requestedDeliveryDate = RandomTestUtil.nextDate();
+ shippable = RandomTestUtil.randomBoolean();
+ shippedQuantity = BigDecimal.valueOf(
+ RandomTestUtil.randomInt());
+ shippingAddressExternalReferenceCode =
+ RandomTestUtil.randomString();
+ shippingAddressId = RandomTestUtil.randomLong();
+ sku = cpInstance.getSku();
+ skuExternalReferenceCode =
+ cpInstance.getExternalReferenceCode();
+ skuId = cpInstance.getCPInstanceId();
+ subscription = RandomTestUtil.randomBoolean();
+ unitOfMeasure = StringUtil.toLowerCase(
+ RandomTestUtil.randomString());
+ }
+ };
+ }
+
private OrderItem _getOrderItem(long fileEntryId, String url)
throws Exception {
@@ -448,6 +522,27 @@ private OrderItem _getOrderItem(long fileEntryId, String url)
};
}
+ private void _testPostOrderItemWithMissingRequiredOption()
+ throws Exception {
+
+ OrderItem orderItem = _addProductWithMissingOptionJSON(
+ true, false);
+
+ assertHttpResponseStatusCode(
+ 400,
+ orderItemResource.postOrderIdOrderItemHttpResponse(
+ _commerceOrder.getCommerceOrderId(), orderItem));
+ }
+
+ private void _testPostOrderItemWithSkuContributorOption() throws Exception {
+ OrderItem orderItem = _addProductWithMissingOptionJSON(false, true);
+
+ assertHttpResponseStatusCode(
+ 200,
+ orderItemResource.postOrderIdOrderItemHttpResponse(
+ _commerceOrder.getCommerceOrderId(), orderItem));
+ }
+
private AccountEntry _accountEntry;
@Inject
@@ -464,6 +559,9 @@ private OrderItem _getOrderItem(long fileEntryId, String url)
@Inject
private CommerceCurrencyLocalService _commerceCurrencyLocalService;
+ @Inject
+ private CPOptionLocalService _cpOptionLocalService;
+
private CommerceOrder _commerceOrder;
@Inject
@@ -483,6 +581,9 @@ private OrderItem _getOrderItem(long fileEntryId, String url)
@Inject
private DLAppLocalService _dlAppLocalService;
+ @Inject
+ private JSONFactory _jsonFactory;
+
@Inject
private Portal _portal;
diff --git a/modules/test/playwright/tests/commerce/commerce-product-content-web/productCard.spec.ts b/modules/test/playwright/tests/commerce/commerce-product-content-web/productCard.spec.ts
index 38d9ec45dca52f..ec2eb0863b8408 100644
--- a/modules/test/playwright/tests/commerce/commerce-product-content-web/productCard.spec.ts
+++ b/modules/test/playwright/tests/commerce/commerce-product-content-web/productCard.spec.ts
@@ -256,3 +256,102 @@ test('COMMERCE-6193. As a buyer, I want the first selectable quantity of a produ
);
}
});
+
+test('LPD-25497 Users should not be able to instantly add to cart for product card if options exist', async ({
+ apiHelpers,
+ commerceThemeMiniumCatalogPage,
+ page,
+}) => {
+ const {site} = await miniumSetUp(apiHelpers);
+
+ const account = await apiHelpers.headlessAdminUser.postAccount({
+ name: getRandomString(),
+ type: 'business',
+ });
+ apiHelpers.data.push({id: account.id, type: 'account'});
+
+ const user =
+ await apiHelpers.headlessAdminUser.getUserAccountByEmailAddress(
+ 'demo.unprivileged@liferay.com'
+ );
+ const rolesResponse = await apiHelpers.headlessAdminUser.getAccountRoles(
+ account.id
+ );
+
+ const accountRoleBuyer = rolesResponse?.items?.filter((role) => {
+ return role.name === 'Buyer';
+ });
+
+ await apiHelpers.headlessAdminUser.assignAccountRoles(
+ account.externalReferenceCode,
+ accountRoleBuyer[0].id,
+ user.emailAddress
+ );
+
+ const siteRole =
+ await apiHelpers.headlessAdminUser.getRoleByName('Site Member');
+
+ await apiHelpers.headlessAdminUser.assignUserToSite(
+ siteRole.id,
+ site.id,
+ user.id
+ );
+
+ await apiHelpers.headlessAdminUser.assignUserToAccountByEmailAddress(
+ account.id,
+ [user.emailAddress]
+ );
+
+ const option = await apiHelpers.headlessCommerceAdminCatalog.postOption(
+ 'select',
+ getRandomString(),
+ 'Color',
+ 1
+ );
+
+ const catalog = await apiHelpers.headlessCommerceAdminCatalog.postCatalog();
+
+ const product = await apiHelpers.headlessCommerceAdminCatalog.postProduct({
+ catalogId: catalog.id,
+ name: {en_US: getRandomString()},
+ productOptions: [
+ {
+ fieldType: 'select',
+ key: option.key,
+ name: option.name,
+ optionId: option.id,
+ priceType: 'dynamic',
+ priority: 1,
+ productOptionValues: [
+ {
+ key: 'black',
+ name: {
+ en_US: 'Black',
+ },
+ priority: 1,
+ quantity: 1,
+ },
+ {
+ key: 'white',
+ name: {
+ en_US: 'White',
+ },
+ priority: 2,
+ quantity: 1,
+ },
+ ],
+ },
+ ],
+ });
+
+ await performLogout(page);
+ await performLogin(page, 'demo.unprivileged');
+
+ await page.goto(`/web/${site.name}`);
+
+ const productName = product.name['en_US'];
+
+ await expect(
+ commerceThemeMiniumCatalogPage.productCard(productName)
+ ).toHaveText('View all variants');
+});