589 lines
23 KiB
C#
589 lines
23 KiB
C#
using Nop.Core.Caching;
|
|
using Nop.Core.Domain.Catalog;
|
|
using Nop.Core.Domain.Customers;
|
|
using Nop.Core.Domain.Directory;
|
|
using Nop.Core.Domain.Discounts;
|
|
using Nop.Core.Domain.Stores;
|
|
using Nop.Services.Customers;
|
|
using Nop.Services.Directory;
|
|
using Nop.Services.Discounts;
|
|
|
|
namespace Nop.Services.Catalog;
|
|
|
|
/// <summary>
|
|
/// Price calculation service
|
|
/// </summary>
|
|
public partial class PriceCalculationService : IPriceCalculationService
|
|
{
|
|
#region Fields
|
|
|
|
protected readonly CatalogSettings _catalogSettings;
|
|
protected readonly CurrencySettings _currencySettings;
|
|
protected readonly ICategoryService _categoryService;
|
|
protected readonly ICurrencyService _currencyService;
|
|
protected readonly ICustomerService _customerService;
|
|
protected readonly IDiscountService _discountService;
|
|
protected readonly IManufacturerService _manufacturerService;
|
|
protected readonly IProductAttributeParser _productAttributeParser;
|
|
protected readonly IProductService _productService;
|
|
protected readonly IStaticCacheManager _staticCacheManager;
|
|
|
|
#endregion
|
|
|
|
#region Ctor
|
|
|
|
public PriceCalculationService(CatalogSettings catalogSettings,
|
|
CurrencySettings currencySettings,
|
|
ICategoryService categoryService,
|
|
ICurrencyService currencyService,
|
|
ICustomerService customerService,
|
|
IDiscountService discountService,
|
|
IManufacturerService manufacturerService,
|
|
IProductAttributeParser productAttributeParser,
|
|
IProductService productService,
|
|
IStaticCacheManager staticCacheManager)
|
|
{
|
|
_catalogSettings = catalogSettings;
|
|
_currencySettings = currencySettings;
|
|
_categoryService = categoryService;
|
|
_currencyService = currencyService;
|
|
_customerService = customerService;
|
|
_discountService = discountService;
|
|
_manufacturerService = manufacturerService;
|
|
_productAttributeParser = productAttributeParser;
|
|
_productService = productService;
|
|
_staticCacheManager = staticCacheManager;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Utilities
|
|
|
|
/// <summary>
|
|
/// Gets allowed discounts applied to product
|
|
/// </summary>
|
|
/// <param name="product">Product</param>
|
|
/// <param name="customer">Customer</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the discounts
|
|
/// </returns>
|
|
protected virtual async Task<IList<Discount>> GetAllowedDiscountsAppliedToProductAsync(Product product, Customer customer)
|
|
{
|
|
var allowedDiscounts = new List<Discount>();
|
|
if (_catalogSettings.IgnoreDiscounts)
|
|
return allowedDiscounts;
|
|
|
|
var couponCodesToValidate = await _customerService.ParseAppliedDiscountCouponCodesAsync(customer);
|
|
|
|
foreach (var discount in await _discountService.GetAppliedDiscountsAsync(product))
|
|
if (discount.DiscountType == DiscountType.AssignedToSkus &&
|
|
(await _discountService.ValidateDiscountAsync(discount, customer, couponCodesToValidate)).IsValid)
|
|
allowedDiscounts.Add(discount);
|
|
|
|
return allowedDiscounts;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets allowed discounts applied to categories
|
|
/// </summary>
|
|
/// <param name="product">Product</param>
|
|
/// <param name="customer">Customer</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the discounts
|
|
/// </returns>
|
|
protected virtual async Task<IList<Discount>> GetAllowedDiscountsAppliedToCategoriesAsync(Product product, Customer customer)
|
|
{
|
|
var allowedDiscounts = new List<Discount>();
|
|
if (_catalogSettings.IgnoreDiscounts)
|
|
return allowedDiscounts;
|
|
|
|
//load cached discount models (performance optimization)
|
|
foreach (var discount in await _discountService.GetAllDiscountsAsync(DiscountType.AssignedToCategories))
|
|
{
|
|
//load identifier of categories with this discount applied to
|
|
var discountCategoryIds = await _categoryService.GetAppliedCategoryIdsAsync(discount, customer);
|
|
|
|
//compare with categories of this product
|
|
var productCategoryIds = new List<int>();
|
|
if (discountCategoryIds.Any())
|
|
{
|
|
productCategoryIds = (await _categoryService
|
|
.GetProductCategoriesByProductIdAsync(product.Id))
|
|
.Select(x => x.CategoryId)
|
|
.ToList();
|
|
}
|
|
|
|
var couponCodesToValidate = await _customerService.ParseAppliedDiscountCouponCodesAsync(customer);
|
|
|
|
foreach (var categoryId in productCategoryIds)
|
|
{
|
|
if (!discountCategoryIds.Contains(categoryId))
|
|
continue;
|
|
|
|
if (!_discountService.ContainsDiscount(allowedDiscounts, discount) &&
|
|
(await _discountService.ValidateDiscountAsync(discount, customer, couponCodesToValidate)).IsValid)
|
|
allowedDiscounts.Add(discount);
|
|
}
|
|
}
|
|
|
|
return allowedDiscounts;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets allowed discounts applied to manufacturers
|
|
/// </summary>
|
|
/// <param name="product">Product</param>
|
|
/// <param name="customer">Customer</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the discounts
|
|
/// </returns>
|
|
protected virtual async Task<IList<Discount>> GetAllowedDiscountsAppliedToManufacturersAsync(Product product, Customer customer)
|
|
{
|
|
var allowedDiscounts = new List<Discount>();
|
|
if (_catalogSettings.IgnoreDiscounts)
|
|
return allowedDiscounts;
|
|
|
|
foreach (var discount in await _discountService.GetAllDiscountsAsync(DiscountType.AssignedToManufacturers))
|
|
{
|
|
//load identifier of manufacturers with this discount applied to
|
|
var discountManufacturerIds = await _manufacturerService.GetAppliedManufacturerIdsAsync(discount, customer);
|
|
|
|
//compare with manufacturers of this product
|
|
var productManufacturerIds = new List<int>();
|
|
if (discountManufacturerIds.Any())
|
|
{
|
|
productManufacturerIds =
|
|
(await _manufacturerService
|
|
.GetProductManufacturersByProductIdAsync(product.Id))
|
|
.Select(x => x.ManufacturerId)
|
|
.ToList();
|
|
}
|
|
|
|
var couponCodesToValidate = await _customerService.ParseAppliedDiscountCouponCodesAsync(customer);
|
|
|
|
foreach (var manufacturerId in productManufacturerIds)
|
|
{
|
|
if (!discountManufacturerIds.Contains(manufacturerId))
|
|
continue;
|
|
|
|
if (!_discountService.ContainsDiscount(allowedDiscounts, discount) &&
|
|
(await _discountService.ValidateDiscountAsync(discount, customer, couponCodesToValidate)).IsValid)
|
|
allowedDiscounts.Add(discount);
|
|
}
|
|
}
|
|
|
|
return allowedDiscounts;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets allowed discounts
|
|
/// </summary>
|
|
/// <param name="product">Product</param>
|
|
/// <param name="customer">Customer</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the discounts
|
|
/// </returns>
|
|
protected virtual async Task<IList<Discount>> GetAllowedDiscountsAsync(Product product, Customer customer)
|
|
{
|
|
var allowedDiscounts = new List<Discount>();
|
|
if (_catalogSettings.IgnoreDiscounts)
|
|
return allowedDiscounts;
|
|
|
|
//discounts applied to products
|
|
foreach (var discount in await GetAllowedDiscountsAppliedToProductAsync(product, customer))
|
|
if (!_discountService.ContainsDiscount(allowedDiscounts, discount))
|
|
allowedDiscounts.Add(discount);
|
|
|
|
//discounts applied to categories
|
|
foreach (var discount in await GetAllowedDiscountsAppliedToCategoriesAsync(product, customer))
|
|
if (!_discountService.ContainsDiscount(allowedDiscounts, discount))
|
|
allowedDiscounts.Add(discount);
|
|
|
|
//discounts applied to manufacturers
|
|
foreach (var discount in await GetAllowedDiscountsAppliedToManufacturersAsync(product, customer))
|
|
if (!_discountService.ContainsDiscount(allowedDiscounts, discount))
|
|
allowedDiscounts.Add(discount);
|
|
|
|
return allowedDiscounts;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets discount amount
|
|
/// </summary>
|
|
/// <param name="product">Product</param>
|
|
/// <param name="customer">The customer</param>
|
|
/// <param name="productPriceWithoutDiscount">Already calculated product price without discount</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the discount amount, Applied discounts
|
|
/// </returns>
|
|
protected virtual async Task<(decimal, List<Discount>)> GetDiscountAmountAsync(Product product,
|
|
Customer customer,
|
|
decimal productPriceWithoutDiscount)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(product);
|
|
|
|
var appliedDiscounts = new List<Discount>();
|
|
var appliedDiscountAmount = decimal.Zero;
|
|
|
|
//we don't apply discounts to products with price entered by a customer
|
|
if (product.CustomerEntersPrice)
|
|
return (appliedDiscountAmount, appliedDiscounts);
|
|
|
|
//discounts are disabled
|
|
if (_catalogSettings.IgnoreDiscounts)
|
|
return (appliedDiscountAmount, appliedDiscounts);
|
|
|
|
var allowedDiscounts = await GetAllowedDiscountsAsync(product, customer);
|
|
|
|
//no discounts
|
|
if (!allowedDiscounts.Any())
|
|
return (appliedDiscountAmount, appliedDiscounts);
|
|
|
|
appliedDiscounts = _discountService.GetPreferredDiscount(allowedDiscounts, productPriceWithoutDiscount, out appliedDiscountAmount);
|
|
|
|
return (appliedDiscountAmount, appliedDiscounts);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// Gets the final price
|
|
/// </summary>
|
|
/// <param name="product">Product</param>
|
|
/// <param name="customer">The customer</param>
|
|
/// <param name="store">Store</param>
|
|
/// <param name="additionalCharge">Additional charge</param>
|
|
/// <param name="includeDiscounts">A value indicating whether include discounts or not for final price computation</param>
|
|
/// <param name="quantity">Shopping cart item quantity</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the final price without discounts, Final price, Applied discount amount, Applied discounts
|
|
/// </returns>
|
|
public virtual async Task<(decimal priceWithoutDiscounts, decimal finalPrice, decimal appliedDiscountAmount, List<Discount> appliedDiscounts)> GetFinalPriceAsync(Product product,
|
|
Customer customer,
|
|
Store store,
|
|
decimal additionalCharge = 0,
|
|
bool includeDiscounts = true,
|
|
int quantity = 1)
|
|
{
|
|
return await GetFinalPriceAsync(product, customer, store,
|
|
additionalCharge, includeDiscounts, quantity,
|
|
null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the final price
|
|
/// </summary>
|
|
/// <param name="product">Product</param>
|
|
/// <param name="customer">The customer</param>
|
|
/// <param name="store">Store</param>
|
|
/// <param name="additionalCharge">Additional charge</param>
|
|
/// <param name="includeDiscounts">A value indicating whether include discounts or not for final price computation</param>
|
|
/// <param name="quantity">Shopping cart item quantity</param>
|
|
/// <param name="rentalStartDate">Rental period start date (for rental products)</param>
|
|
/// <param name="rentalEndDate">Rental period end date (for rental products)</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the final price without discounts, Final price, Applied discount amount, Applied discounts
|
|
/// </returns>
|
|
public virtual async Task<(decimal priceWithoutDiscounts, decimal finalPrice, decimal appliedDiscountAmount, List<Discount> appliedDiscounts)> GetFinalPriceAsync(Product product,
|
|
Customer customer,
|
|
Store store,
|
|
decimal additionalCharge,
|
|
bool includeDiscounts,
|
|
int quantity,
|
|
DateTime? rentalStartDate,
|
|
DateTime? rentalEndDate)
|
|
{
|
|
return await GetFinalPriceAsync(product, customer, store, null, additionalCharge, includeDiscounts, quantity,
|
|
rentalStartDate, rentalEndDate);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the final price
|
|
/// </summary>
|
|
/// <param name="product">Product</param>
|
|
/// <param name="customer">The customer</param>
|
|
/// <param name="store">Store</param>
|
|
/// <param name="overriddenProductPrice">Overridden product price. If specified, then it'll be used instead of a product price. For example, used with product attribute combinations</param>
|
|
/// <param name="additionalCharge">Additional charge</param>
|
|
/// <param name="includeDiscounts">A value indicating whether include discounts or not for final price computation</param>
|
|
/// <param name="quantity">Shopping cart item quantity</param>
|
|
/// <param name="rentalStartDate">Rental period start date (for rental products)</param>
|
|
/// <param name="rentalEndDate">Rental period end date (for rental products)</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the final price without discounts, Final price, Applied discount amount, Applied discounts
|
|
/// </returns>
|
|
public virtual async Task<(decimal priceWithoutDiscounts, decimal finalPrice, decimal appliedDiscountAmount, List<Discount> appliedDiscounts)> GetFinalPriceAsync(Product product,
|
|
Customer customer,
|
|
Store store,
|
|
decimal? overriddenProductPrice,
|
|
decimal additionalCharge,
|
|
bool includeDiscounts,
|
|
int quantity,
|
|
DateTime? rentalStartDate,
|
|
DateTime? rentalEndDate)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(product);
|
|
|
|
var cacheKey = _staticCacheManager.PrepareKeyForDefaultCache(NopCatalogDefaults.ProductPriceCacheKey,
|
|
product,
|
|
overriddenProductPrice,
|
|
additionalCharge,
|
|
includeDiscounts,
|
|
quantity,
|
|
await _customerService.GetCustomerRoleIdsAsync(customer),
|
|
store);
|
|
|
|
//we do not cache price if this not allowed by settings or if the product is rental product
|
|
//otherwise, it can cause memory leaks (to store all possible date period combinations)
|
|
if (!_catalogSettings.CacheProductPrices || product.IsRental)
|
|
cacheKey.CacheTime = 0;
|
|
|
|
decimal rezPrice;
|
|
decimal rezPriceWithoutDiscount;
|
|
decimal discountAmount;
|
|
List<Discount> appliedDiscounts;
|
|
|
|
(rezPriceWithoutDiscount, rezPrice, discountAmount, appliedDiscounts) = await _staticCacheManager.GetAsync(cacheKey, async () =>
|
|
{
|
|
var discounts = new List<Discount>();
|
|
var appliedDiscountAmount = decimal.Zero;
|
|
|
|
//initial price
|
|
var price = overriddenProductPrice ?? product.Price;
|
|
|
|
//tier prices
|
|
var tierPrice = await _productService.GetPreferredTierPriceAsync(product, customer, store, quantity);
|
|
|
|
if (tierPrice != null)
|
|
price = tierPrice.Price;
|
|
|
|
//additional charge
|
|
price += additionalCharge;
|
|
|
|
//rental products
|
|
if (product.IsRental)
|
|
if (rentalStartDate.HasValue && rentalEndDate.HasValue)
|
|
price *= _productService.GetRentalPeriods(product, rentalStartDate.Value, rentalEndDate.Value);
|
|
|
|
var priceWithoutDiscount = price;
|
|
|
|
if (includeDiscounts)
|
|
{
|
|
//discount
|
|
var (tmpDiscountAmount, tmpAppliedDiscounts) = await GetDiscountAmountAsync(product, customer, price);
|
|
price -= tmpDiscountAmount;
|
|
|
|
if (tmpAppliedDiscounts?.Any() ?? false)
|
|
{
|
|
discounts.AddRange(tmpAppliedDiscounts);
|
|
appliedDiscountAmount = tmpDiscountAmount;
|
|
}
|
|
}
|
|
|
|
if (price < decimal.Zero)
|
|
price = decimal.Zero;
|
|
|
|
if (priceWithoutDiscount < decimal.Zero)
|
|
priceWithoutDiscount = decimal.Zero;
|
|
|
|
return (priceWithoutDiscount, price, appliedDiscountAmount, discounts);
|
|
});
|
|
|
|
return (rezPriceWithoutDiscount, rezPrice, discountAmount, appliedDiscounts);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the product cost (one item)
|
|
/// </summary>
|
|
/// <param name="product">Product</param>
|
|
/// <param name="attributesXml">Shopping cart item attributes in XML</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the product cost (one item)
|
|
/// </returns>
|
|
public virtual async Task<decimal> GetProductCostAsync(Product product, string attributesXml)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(product);
|
|
|
|
var cost = product.ProductCost;
|
|
var attributeValues = await _productAttributeParser.ParseProductAttributeValuesAsync(attributesXml);
|
|
foreach (var attributeValue in attributeValues)
|
|
{
|
|
switch (attributeValue.AttributeValueType)
|
|
{
|
|
case AttributeValueType.Simple:
|
|
//simple attribute
|
|
cost += attributeValue.Cost;
|
|
break;
|
|
case AttributeValueType.AssociatedToProduct:
|
|
//bundled product
|
|
var associatedProduct = await _productService.GetProductByIdAsync(attributeValue.AssociatedProductId);
|
|
if (associatedProduct != null)
|
|
cost += associatedProduct.ProductCost * attributeValue.Quantity;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return cost;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a price adjustment of a product attribute value
|
|
/// </summary>
|
|
/// <param name="product">Product</param>
|
|
/// <param name="value">Product attribute value</param>
|
|
/// <param name="customer">Customer</param>
|
|
/// <param name="store">Store</param>
|
|
/// <param name="productPrice">Product price (null for using the base product price)</param>
|
|
/// <param name="quantity">Shopping cart item quantity</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the price adjustment
|
|
/// </returns>
|
|
public virtual async Task<decimal> GetProductAttributeValuePriceAdjustmentAsync(Product product,
|
|
ProductAttributeValue value,
|
|
Customer customer,
|
|
Store store,
|
|
decimal? productPrice = null,
|
|
int quantity = 1)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(value);
|
|
|
|
var adjustment = decimal.Zero;
|
|
switch (value.AttributeValueType)
|
|
{
|
|
case AttributeValueType.Simple:
|
|
//simple attribute
|
|
if (value.PriceAdjustmentUsePercentage)
|
|
{
|
|
if (!productPrice.HasValue)
|
|
productPrice = (await GetFinalPriceAsync(product, customer, store, quantity: quantity)).finalPrice;
|
|
|
|
adjustment = (decimal)((float)productPrice * (float)value.PriceAdjustment / 100f);
|
|
}
|
|
else
|
|
{
|
|
adjustment = value.PriceAdjustment;
|
|
}
|
|
|
|
break;
|
|
case AttributeValueType.AssociatedToProduct:
|
|
//bundled product
|
|
var associatedProduct = await _productService.GetProductByIdAsync(value.AssociatedProductId);
|
|
if (associatedProduct != null)
|
|
adjustment = (await GetFinalPriceAsync(associatedProduct, customer, store)).finalPrice * value.Quantity;
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return adjustment;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Round a product or order total for the currency
|
|
/// </summary>
|
|
/// <param name="value">Value to round</param>
|
|
/// <param name="currency">Currency; pass null to use the primary store currency</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the rounded value
|
|
/// </returns>
|
|
public virtual async Task<decimal> RoundPriceAsync(decimal value, Currency currency = null)
|
|
{
|
|
//we use this method because some currencies (e.g. Hungarian Forint or Swiss Franc) use non-standard rules for rounding
|
|
//you can implement any rounding logic here
|
|
|
|
currency ??= await _currencyService.GetCurrencyByIdAsync(_currencySettings.PrimaryStoreCurrencyId);
|
|
|
|
return Round(value, currency.RoundingType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Round
|
|
/// </summary>
|
|
/// <param name="value">Value to round</param>
|
|
/// <param name="roundingType">The rounding type</param>
|
|
/// <returns>Rounded value</returns>
|
|
public virtual decimal Round(decimal value, RoundingType roundingType)
|
|
{
|
|
//default round (Rounding001)
|
|
var rez = Math.Round(value, 2);
|
|
var fractionPart = (rez - Math.Truncate(rez)) * 10;
|
|
|
|
//cash rounding not needed
|
|
if (fractionPart == 0)
|
|
return rez;
|
|
|
|
//Cash rounding (details: https://en.wikipedia.org/wiki/Cash_rounding)
|
|
switch (roundingType)
|
|
{
|
|
//rounding with 0.05 or 5 intervals
|
|
case RoundingType.Rounding005Up:
|
|
case RoundingType.Rounding005Down:
|
|
fractionPart = (fractionPart - Math.Truncate(fractionPart)) * 10;
|
|
|
|
fractionPart %= 5;
|
|
if (fractionPart == 0)
|
|
break;
|
|
|
|
if (roundingType == RoundingType.Rounding005Up)
|
|
fractionPart = 5 - fractionPart;
|
|
else
|
|
fractionPart *= -1;
|
|
|
|
rez += fractionPart / 100;
|
|
break;
|
|
//rounding with 0.10 intervals
|
|
case RoundingType.Rounding01Up:
|
|
case RoundingType.Rounding01Down:
|
|
fractionPart = (fractionPart - Math.Truncate(fractionPart)) * 10;
|
|
|
|
if (roundingType == RoundingType.Rounding01Down && fractionPart == 5)
|
|
fractionPart = -5;
|
|
else
|
|
fractionPart = fractionPart < 5 ? fractionPart * -1 : 10 - fractionPart;
|
|
|
|
rez += fractionPart / 100;
|
|
break;
|
|
//rounding with 0.50 intervals
|
|
case RoundingType.Rounding05:
|
|
fractionPart *= 10;
|
|
fractionPart = fractionPart < 25 ? fractionPart * -1 : fractionPart < 50 || fractionPart < 75 ? 50 - fractionPart : 100 - fractionPart;
|
|
|
|
rez += fractionPart / 100;
|
|
break;
|
|
//rounding with 1.00 intervals
|
|
case RoundingType.Rounding1:
|
|
case RoundingType.Rounding1Up:
|
|
fractionPart *= 10;
|
|
|
|
if (roundingType == RoundingType.Rounding1Up && fractionPart > 0)
|
|
rez = Math.Truncate(rez) + 1;
|
|
else
|
|
rez = fractionPart < 50 ? Math.Truncate(rez) : Math.Truncate(rez) + 1;
|
|
|
|
break;
|
|
case RoundingType.Rounding001:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return rez;
|
|
}
|
|
|
|
#endregion
|
|
} |