All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.liferay.commerce.discount.internal.CommerceDiscountCalculationV2Impl Maven / Gradle / Ivy

/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */

package com.liferay.commerce.discount.internal;

import com.liferay.commerce.account.model.CommerceAccount;
import com.liferay.commerce.context.CommerceContext;
import com.liferay.commerce.currency.model.CommerceCurrency;
import com.liferay.commerce.currency.model.CommerceMoney;
import com.liferay.commerce.currency.model.CommerceMoneyFactory;
import com.liferay.commerce.discount.CommerceDiscountCalculation;
import com.liferay.commerce.discount.CommerceDiscountValue;
import com.liferay.commerce.discount.application.strategy.CommerceDiscountApplicationStrategy;
import com.liferay.commerce.discount.constants.CommerceDiscountConstants;
import com.liferay.commerce.discount.model.CommerceDiscount;
import com.liferay.commerce.discount.model.CommerceDiscountRule;
import com.liferay.commerce.discount.rule.type.CommerceDiscountRuleType;
import com.liferay.commerce.discount.rule.type.CommerceDiscountRuleTypeRegistry;
import com.liferay.commerce.discount.service.CommerceDiscountRuleLocalService;
import com.liferay.commerce.discount.service.CommerceDiscountUsageEntryLocalService;
import com.liferay.commerce.model.CommerceOrder;
import com.liferay.commerce.price.list.model.CommercePriceListDiscountRel;
import com.liferay.commerce.price.list.service.CommercePriceListDiscountRelLocalService;
import com.liferay.commerce.pricing.configuration.CommercePricingConfiguration;
import com.liferay.commerce.product.model.CPInstance;
import com.liferay.commerce.product.service.CPInstanceLocalService;
import com.liferay.petra.string.StringPool;
import com.liferay.portal.kernel.dao.orm.QueryUtil;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.module.configuration.ConfigurationException;
import com.liferay.portal.kernel.module.configuration.ConfigurationProvider;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.Validator;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;

/**
 * @author Riccardo Alberti
 */
@Component(
	property = "commerce.discount.calculation.key=v2.0",
	service = CommerceDiscountCalculation.class
)
public class CommerceDiscountCalculationV2Impl
	extends BaseCommerceDiscountCalculation {

	@Override
	public CommerceDiscountValue getOrderShippingCommerceDiscountValue(
			CommerceOrder commerceOrder, BigDecimal shippingAmount,
			CommerceContext commerceContext)
		throws PortalException {

		if (commerceOrder == null) {
			return null;
		}

		return _getCommerceDiscountValue(
			commerceOrder, shippingAmount, commerceContext,
			CommerceDiscountConstants.TARGET_SHIPPING);
	}

	@Override
	public CommerceDiscountValue getOrderSubtotalCommerceDiscountValue(
			CommerceOrder commerceOrder, BigDecimal subtotalAmount,
			CommerceContext commerceContext)
		throws PortalException {

		if (commerceOrder == null) {
			return null;
		}

		return _getCommerceDiscountValue(
			commerceOrder, subtotalAmount, commerceContext,
			CommerceDiscountConstants.TARGET_SUBTOTAL);
	}

	@Override
	public CommerceDiscountValue getOrderTotalCommerceDiscountValue(
			CommerceOrder commerceOrder, BigDecimal totalAmount,
			CommerceContext commerceContext)
		throws PortalException {

		if (commerceOrder == null) {
			return null;
		}

		return _getCommerceDiscountValue(
			commerceOrder, totalAmount, commerceContext,
			CommerceDiscountConstants.TARGET_TOTAL);
	}

	@Override
	public CommerceDiscountValue getProductCommerceDiscountValue(
			long cpInstanceId, int quantity, BigDecimal productUnitPrice,
			CommerceContext commerceContext)
		throws PortalException {

		return getProductCommerceDiscountValue(
			cpInstanceId, 0, quantity, productUnitPrice, commerceContext);
	}

	@Override
	public CommerceDiscountValue getProductCommerceDiscountValue(
			long cpInstanceId, long commercePriceListId, int quantity,
			BigDecimal productUnitPrice, CommerceContext commerceContext)
		throws PortalException {

		CPInstance cpInstance = _cpInstanceLocalService.getCPInstance(
			cpInstanceId);

		List commercePriceListDiscountRels =
			_commercePriceListDiscountRelLocalService.
				getCommercePriceListDiscountRels(commercePriceListId);

		if ((commercePriceListDiscountRels != null) &&
			!commercePriceListDiscountRels.isEmpty()) {

			Stream stream =
				commercePriceListDiscountRels.stream();

			long[] commerceDiscountIds = stream.mapToLong(
				CommercePriceListDiscountRel::getCommerceDiscountId
			).toArray();

			List commerceDiscounts =
				commerceDiscountLocalService.getPriceListCommerceDiscounts(
					commerceDiscountIds, cpInstance.getCPDefinitionId());

			if (commerceDiscounts.isEmpty()) {
				return null;
			}

			return _getCommerceDiscountValues(
				productUnitPrice, quantity, commerceContext, commerceDiscounts);
		}

		List commerceDiscounts =
			getProductCommerceDiscountByHierarchy(
				cpInstance.getCompanyId(), commerceContext.getCommerceAccount(),
				commerceContext.getCommerceChannelId(),
				cpInstance.getCPDefinitionId());

		if (commerceDiscounts.isEmpty()) {
			return null;
		}

		return _getCommerceDiscountValues(
			productUnitPrice, quantity, commerceContext, commerceDiscounts);
	}

	public void unsetCommerceDiscountApplicationStrategy(
		CommerceDiscountApplicationStrategy commerceDiscountApplicationStrategy,
		Map properties) {

		String commerceDiscountApplicationStrategyKey = GetterUtil.getString(
			properties.get("commerce.discount.application.strategy.key"));

		_commerceDiscountApplicationStrategyMap.remove(
			commerceDiscountApplicationStrategyKey);
	}

	@Reference(
		cardinality = ReferenceCardinality.MULTIPLE,
		policy = ReferencePolicy.DYNAMIC,
		policyOption = ReferencePolicyOption.GREEDY
	)
	protected void setCommerceDiscountApplicationStrategy(
		CommerceDiscountApplicationStrategy commerceDiscountApplicationStrategy,
		Map properties) {

		String commerceDiscountApplicationStrategyKey = GetterUtil.getString(
			properties.get("commerce.discount.application.strategy.key"));

		_commerceDiscountApplicationStrategyMap.put(
			commerceDiscountApplicationStrategyKey,
			commerceDiscountApplicationStrategy);
	}

	private CommerceDiscountApplicationStrategy
			_getCommerceDiscountApplicationStrategy()
		throws ConfigurationException {

		CommercePricingConfiguration commercePricingConfiguration =
			_configurationProvider.getSystemConfiguration(
				CommercePricingConfiguration.class);

		String commerceDiscountApplicationStrategy =
			commercePricingConfiguration.commerceDiscountApplicationStrategy();

		if (!_commerceDiscountApplicationStrategyMap.containsKey(
				commerceDiscountApplicationStrategy)) {

			if (_log.isWarnEnabled()) {
				_log.warn(
					"No commerce discount application strategy specified for " +
						commerceDiscountApplicationStrategy);
			}

			return null;
		}

		return _commerceDiscountApplicationStrategyMap.get(
			commerceDiscountApplicationStrategy);
	}

	private BigDecimal _getCommerceDiscountLevel(
			BigDecimal currentDiscountLevel, BigDecimal commercePrice,
			CommerceCurrency commerceCurrency, long commerceDiscountId,
			BigDecimal commerceDiscountValue, boolean isUsePercentage)
		throws PortalException {

		if ((commerceDiscountValue == null) ||
			(commercePrice.compareTo(BigDecimal.ZERO) == 0)) {

			return null;
		}

		CommerceDiscount commerceDiscount =
			commerceDiscountLocalService.getCommerceDiscount(
				commerceDiscountId);

		BigDecimal discountAmount = BigDecimal.ZERO;

		if (isUsePercentage) {
			discountAmount = commercePrice.multiply(commerceDiscountValue);
			discountAmount = discountAmount.divide(_ONE_HUNDRED);

			BigDecimal maximumDiscountAmount =
				commerceDiscount.getMaximumDiscountAmount();

			if ((maximumDiscountAmount.compareTo(BigDecimal.ZERO) > 0) &&
				(discountAmount.compareTo(maximumDiscountAmount) > 0)) {

				discountAmount = commerceDiscount.getMaximumDiscountAmount();
			}
		}
		else {
			discountAmount = commerceDiscountValue;

			if (commerceDiscountValue.compareTo(commercePrice) > 0) {
				discountAmount = commercePrice;
			}
		}

		RoundingMode roundingMode = RoundingMode.valueOf(
			commerceCurrency.getRoundingMode());

		BigDecimal discountedAmount = commercePrice.subtract(discountAmount);

		BigDecimal discountPercentage = _getDiscountPercentage(
			discountedAmount, commercePrice, roundingMode);

		if ((currentDiscountLevel == null) ||
			(discountPercentage.compareTo(currentDiscountLevel) > 0)) {

			if (isUsePercentage) {
				return commerceDiscountValue;
			}

			return discountPercentage;
		}

		return currentDiscountLevel;
	}

	private BigDecimal[] _getCommerceDiscountLevels(
			String couponCode, BigDecimal commercePrice,
			CommerceContext commerceContext,
			List commerceDiscounts)
		throws PortalException {

		if (couponCode.isEmpty()) {
			CommerceOrder commerceOrder = commerceContext.getCommerceOrder();

			if (commerceOrder != null) {
				couponCode = commerceOrder.getCouponCode();
			}
		}

		CommerceCurrency commerceCurrency =
			commerceContext.getCommerceCurrency();

		BigDecimal[] levels = {
			BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO
		};

		for (CommerceDiscount commerceDiscount : commerceDiscounts) {
			String discountCouponCode = commerceDiscount.getCouponCode();

			if (!_isValidCouponCode(
					commerceDiscount.getCommerceDiscountId(), couponCode,
					discountCouponCode, commerceContext)) {

				continue;
			}

			if (_isValidDiscount(commerceContext, commerceDiscount)) {
				String discountLevel = commerceDiscount.getLevel();

				if (discountLevel.isEmpty() ||
					discountLevel.equals(CommerceDiscountConstants.LEVEL_L1)) {

					levels[0] = _getCommerceDiscountLevel(
						levels[0], commercePrice, commerceCurrency,
						commerceDiscount.getCommerceDiscountId(),
						commerceDiscount.getLevel1(),
						commerceDiscount.isUsePercentage());
				}

				if (commerceDiscount.isUsePercentage()) {
					if (discountLevel.isEmpty() ||
						discountLevel.equals(
							CommerceDiscountConstants.LEVEL_L2)) {

						levels[1] = _getCommerceDiscountLevel(
							levels[1], commercePrice, commerceCurrency,
							commerceDiscount.getCommerceDiscountId(),
							commerceDiscount.getLevel2(),
							commerceDiscount.isUsePercentage());
					}

					if (discountLevel.isEmpty() ||
						discountLevel.equals(
							CommerceDiscountConstants.LEVEL_L3)) {

						levels[2] = _getCommerceDiscountLevel(
							levels[2], commercePrice, commerceCurrency,
							commerceDiscount.getCommerceDiscountId(),
							commerceDiscount.getLevel3(),
							commerceDiscount.isUsePercentage());
					}

					if (discountLevel.isEmpty() ||
						discountLevel.equals(
							CommerceDiscountConstants.LEVEL_L4)) {

						levels[3] = _getCommerceDiscountLevel(
							levels[3], commercePrice, commerceCurrency,
							commerceDiscount.getCommerceDiscountId(),
							commerceDiscount.getLevel4(),
							commerceDiscount.isUsePercentage());
					}
				}
			}
		}

		return levels;
	}

	private CommerceDiscountValue _getCommerceDiscountValue(
			CommerceOrder commerceOrder, BigDecimal amount,
			CommerceContext commerceContext, String discountType)
		throws PortalException {

		if ((amount == null) || (amount.compareTo(BigDecimal.ZERO) <= 0)) {
			return null;
		}

		List commerceDiscounts =
			getOrderCommerceDiscountByHierarchy(
				commerceOrder.getCompanyId(),
				commerceContext.getCommerceAccount(),
				commerceContext.getCommerceChannelId(), discountType);

		if (commerceDiscounts.isEmpty()) {
			return null;
		}

		BigDecimal[] commerceDiscountLevels = _getCommerceDiscountLevels(
			commerceOrder.getCouponCode(), amount, commerceContext,
			commerceDiscounts);

		CommerceDiscountApplicationStrategy
			commerceDiscountApplicationStrategy =
				_getCommerceDiscountApplicationStrategy();

		BigDecimal discountedAmount =
			commerceDiscountApplicationStrategy.applyCommerceDiscounts(
				amount, commerceDiscountLevels);

		BigDecimal currentDiscountAmount = amount.subtract(discountedAmount);

		CommerceCurrency commerceCurrency =
			commerceContext.getCommerceCurrency();

		RoundingMode roundingMode = RoundingMode.valueOf(
			commerceCurrency.getRoundingMode());

		currentDiscountAmount = currentDiscountAmount.setScale(
			_SCALE, roundingMode);

		CommerceMoney discountAmount = _commerceMoneyFactory.create(
			commerceCurrency, currentDiscountAmount);

		return new CommerceDiscountValue(
			0, discountAmount,
			_getDiscountPercentage(discountedAmount, amount, roundingMode),
			commerceDiscountLevels);
	}

	private CommerceDiscountValue _getCommerceDiscountValues(
			BigDecimal commercePrice, int quantity,
			CommerceContext commerceContext,
			List commerceDiscounts)
		throws PortalException {

		BigDecimal[] commerceDiscountLevels = _getCommerceDiscountLevels(
			StringPool.BLANK, commercePrice, commerceContext,
			commerceDiscounts);

		CommerceDiscountApplicationStrategy
			commerceDiscountApplicationStrategy =
				_getCommerceDiscountApplicationStrategy();

		BigDecimal discountedAmount =
			commerceDiscountApplicationStrategy.applyCommerceDiscounts(
				commercePrice, commerceDiscountLevels);

		BigDecimal currentDiscountAmount = commercePrice.subtract(
			discountedAmount);

		CommerceCurrency commerceCurrency =
			commerceContext.getCommerceCurrency();

		RoundingMode roundingMode = RoundingMode.valueOf(
			commerceCurrency.getRoundingMode());

		currentDiscountAmount = currentDiscountAmount.setScale(
			_SCALE, roundingMode);

		if (currentDiscountAmount.compareTo(BigDecimal.ZERO) == 0) {
			return null;
		}

		CommerceMoney discountAmount = _commerceMoneyFactory.create(
			commerceCurrency,
			currentDiscountAmount.multiply(new BigDecimal(quantity)));

		return new CommerceDiscountValue(
			0, discountAmount,
			_getDiscountPercentage(
				discountedAmount, commercePrice, roundingMode),
			commerceDiscountLevels);
	}

	private BigDecimal _getDiscountPercentage(
		BigDecimal discountedAmount, BigDecimal amount,
		RoundingMode roundingMode) {

		double actualPrice = discountedAmount.doubleValue();
		double originalPrice = amount.doubleValue();

		double percentage = actualPrice / originalPrice;

		BigDecimal discountPercentage = new BigDecimal(percentage);

		discountPercentage = discountPercentage.multiply(_ONE_HUNDRED);

		MathContext mathContext = new MathContext(
			discountPercentage.precision(), roundingMode);

		return _ONE_HUNDRED.subtract(discountPercentage, mathContext);
	}

	private boolean _isValidCouponCode(
			long commerceDiscountId, String couponCode,
			String discountCouponCode, CommerceContext commerceContext)
		throws PortalException {

		long commerceAccountId = 0;

		CommerceAccount commerceAccount = commerceContext.getCommerceAccount();

		if (commerceAccount != null) {
			commerceAccountId = commerceAccount.getCommerceAccountId();
		}

		if ((Validator.isBlank(discountCouponCode) ||
			 Objects.equals(couponCode, discountCouponCode)) &&
			_commerceDiscountUsageEntryLocalService.
				validateDiscountLimitationUsage(
					commerceAccountId, commerceDiscountId)) {

			return true;
		}

		return false;
	}

	private boolean _isValidDiscount(
			CommerceContext commerceContext, CommerceDiscount commerceDiscount)
		throws PortalException {

		List commerceDiscountRules =
			_commerceDiscountRuleLocalService.getCommerceDiscountRules(
				commerceDiscount.getCommerceDiscountId(), QueryUtil.ALL_POS,
				QueryUtil.ALL_POS, null);

		if (commerceDiscountRules.isEmpty()) {
			return true;
		}

		for (CommerceDiscountRule commerceDiscountRule :
				commerceDiscountRules) {

			CommerceDiscountRuleType commerceDiscountRuleType =
				_commerceDiscountRuleTypeRegistry.getCommerceDiscountRuleType(
					commerceDiscountRule.getType());

			boolean commerceDiscountRuleTypeEvaluation =
				commerceDiscountRuleType.evaluate(
					commerceDiscountRule, commerceContext);

			if (!commerceDiscountRuleTypeEvaluation &&
				commerceDiscount.isRulesConjunction()) {

				return false;
			}
			else if (commerceDiscountRuleTypeEvaluation &&
					 !commerceDiscount.isRulesConjunction()) {

				return true;
			}
		}

		return commerceDiscount.isRulesConjunction();
	}

	private static final BigDecimal _ONE_HUNDRED = BigDecimal.valueOf(100);

	private static final int _SCALE = 10;

	private static final Log _log = LogFactoryUtil.getLog(
		CommerceDiscountCalculationV2Impl.class);

	private final Map
		_commerceDiscountApplicationStrategyMap = new ConcurrentHashMap<>();

	@Reference
	private CommerceDiscountRuleLocalService _commerceDiscountRuleLocalService;

	@Reference
	private CommerceDiscountRuleTypeRegistry _commerceDiscountRuleTypeRegistry;

	@Reference
	private CommerceDiscountUsageEntryLocalService
		_commerceDiscountUsageEntryLocalService;

	@Reference
	private CommerceMoneyFactory _commerceMoneyFactory;

	@Reference
	private CommercePriceListDiscountRelLocalService
		_commercePriceListDiscountRelLocalService;

	@Reference
	private ConfigurationProvider _configurationProvider;

	@Reference
	private CPInstanceLocalService _cpInstanceLocalService;

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy