Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions intaro.retailcrm/lib/component/handlers/eventshandlers.php
Original file line number Diff line number Diff line change
Expand Up @@ -365,28 +365,29 @@ public static function OnSaleOrderSavedHandler(Event $event): void
$isLoyaltyOn = ConfigProvider::getLoyaltyProgramStatus() === 'Y';
$isBonusesIssetAndAvailable = $isBonusInput && $availableBonuses >= $bonusFloat;

/** @var array $calculateItemsInput */
$calculateItemsInput = $isDataForLoyaltyDiscount
? json_decode(htmlspecialchars_decode($_POST['calculate-items-input']), true)
: [];

if ($isNewOrder && $isLoyaltyOn && ($isDataForLoyaltyDiscount || $isBonusInput)) {
self::$disableSaleHandler = true;

try {
$hlInfoBuilder = new LoyaltyDataBuilder();
$hlInfoBuilder->setOrder($order);

$discountInput = isset($_POST['loyalty-discount-input'])
? (float) $_POST['loyalty-discount-input']
: 0;

$discountInput = 0.0;
$loyaltyBonusMsg = 0;
$calculateItemsInput = $loyaltyService->getShopDiscountItems($order);

$preliminaryPrices = ConfigProvider::getLoyaltyBonusesForBasketRules() === 'Y'
? self::getBasketPrices($order)
: [];

if ($isDataForLoyaltyDiscount && $isBonusesIssetAndAvailable) {
$loyaltyDiscount = $loyaltyService->calculateLoyaltyDiscount($order, $bonusFloat);

if ($loyaltyDiscount !== null) {
$discountInput = $loyaltyDiscount;
}
}

//Если есть бонусы
if ($isBonusesIssetAndAvailable) {
$applyBonusResponse = $loyaltyService->applyBonusesInOrder($order, $bonusFloat);
Expand All @@ -402,7 +403,11 @@ public static function OnSaleOrderSavedHandler(Event $event): void

//Если бонусов нет, но скидка по ПЛ есть
if ($isDataForLoyaltyDiscount && !$isBonusInput) {
$loyaltyService->saveDiscounts($order, $calculateItemsInput);
$loyaltyDiscount = $loyaltyService->applyCalculatedDiscounts($order);

if ($loyaltyDiscount !== null) {
$discountInput = $loyaltyDiscount;
}
}

$orderLoyaltyDataService->saveBonusAndDiscToOrderProps(
Expand Down
37 changes: 0 additions & 37 deletions intaro.retailcrm/lib/service/loyaltybasketpriceapplier.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,43 +118,6 @@ public function apply(Order $order, $response, bool $runFinalAction = true): Res
return $result;
}

public function applySavedDiscounts(Order $order, array $calculateItemsInput): Result
{
$result = new Result();

try {
foreach ($order->getBasket() as $basketItem) {
$basketItemId = (string) $basketItem->getId();
$calculateItem = $calculateItemsInput[$basketItemId] ?? null;

if (!is_array($calculateItem)
|| !isset($calculateItem['SUM_NUM'], $calculateItem['QUANTITY'])
|| !is_numeric($calculateItem['SUM_NUM'])
|| !is_numeric($calculateItem['QUANTITY'])
|| (float) $calculateItem['QUANTITY'] <= 0
) {
$result->addError(new Error(sprintf('Saved loyalty discount for basket item %s is invalid.', $basketItemId)));
continue;
}

$price = (float) $calculateItem['SUM_NUM'] / (float) $calculateItem['QUANTITY'];
$setResult = $basketItem->setFields([
'CUSTOM_PRICE' => 'Y',
'DISCOUNT_PRICE' => $basketItem->getBasePrice() - $price,
'PRICE' => $price,
]);

if (!$setResult->isSuccess()) {
$result->addErrors($setResult->getErrors());
}
}
} catch (\Throwable $exception) {
$result->addError(new Error($exception->getMessage()));
}

return $result;
}

/**
* @param mixed $calculatedItem
*
Expand Down
182 changes: 179 additions & 3 deletions intaro.retailcrm/lib/service/loyaltyservice.php
Original file line number Diff line number Diff line change
Expand Up @@ -491,16 +491,192 @@ public function calculateOrderBasket(array $orderArResult, LoyaltyCalculateRespo

/**
* @param \Bitrix\Sale\Order $order
* @param array $calculateItemsInput
*
* @return float|null
*/
public function saveDiscounts(Order $order, array $calculateItemsInput): void
public function applyCalculatedDiscounts(Order $order): ?float
{
try {
$result = (new LoyaltyBasketPriceApplier())->applySavedDiscounts($order, $calculateItemsInput);
$loaded = $this->loadLoyaltyCalculation($order, 0);

if ($loaded === null) {
return null;
}

$result = (new LoyaltyBasketPriceApplier())->apply($order, $loaded['calculation'], false);

if (!$result->isSuccess()) {
$this->logger->write($result->getErrorMessages(), Constants::LOYALTY_ERROR);

return null;
}

return $this->resolveLoyaltyDiscount($loaded['calculation'], $loaded['shopDiscountTotal']);
} catch (Exception $exception) {
$this->logger->write($exception->getMessage(), Constants::LOYALTY_ERROR);

return null;
}
}

/**
* @param \Bitrix\Sale\Order $order
* @param float $bonuses
*
* @return float|null
*/
public function calculateLoyaltyDiscount(Order $order, float $bonuses = 0): ?float
{
try {
$loaded = $this->loadLoyaltyCalculation($order, $bonuses);

if ($loaded === null) {
return null;
}

return $this->resolveLoyaltyDiscount($loaded['calculation'], $loaded['shopDiscountTotal']);
} catch (Exception $exception) {
$this->logger->write($exception->getMessage(), Constants::LOYALTY_ERROR);

return null;
}
}

/**
* @param \Bitrix\Sale\Order $order
* @param float $bonuses
*
* @return array{calculation: \Intaro\RetailCrm\Model\Api\Response\Loyalty\LoyaltyCalculateResponse, shopDiscountTotal: float}|null
*/
private function loadLoyaltyCalculation(Order $order, float $bonuses): ?array
{
$basket = $order->getBasket();

if ($basket === null) {
$this->logger->write(
'Loyalty discount was not applied because the basket is empty.',
Constants::LOYALTY_ERROR
);

return null;
}

$calculateItems = [];
$shopDiscountTotal = 0.0;

foreach ($basket as $basketItem) {
$basketItemId = (string) $basketItem->getId();

if ($basketItemId === '' || $basketItemId === '0') {
continue;
}

$quantity = (float) $basketItem->getQuantity();
$shopDiscountTotal += (float) $basketItem->getDiscountPrice() * $quantity;
$calculateItems[] = [
'ID' => $basketItemId,
'XML_ID' => (string) $basketItem->getField('PRODUCT_XML_ID'),
'BASE_PRICE' => (float) $basketItem->getBasePrice(),
'PRICE' => (float) $basketItem->getPrice(),
'QUANTITY' => $quantity,
'PRICE_TYPE_ID' => $basketItem->getField('PRICE_TYPE_ID'),
];
}

if ($calculateItems === []) {
$this->logger->write(
'Loyalty discount was not applied because the basket has no saved items.',
Constants::LOYALTY_ERROR
);

return null;
}

$calculation = $this->getLoyaltyCalculate($calculateItems, $bonuses);

if (!$calculation instanceof LoyaltyCalculateResponse
|| !$calculation->success
|| !isset($calculation->order->items)
) {
$this->logger->write(
'Loyalty discount was not applied because CRM calculation failed.',
Constants::LOYALTY_ERROR
);

return null;
}

return [
'calculation' => $calculation,
'shopDiscountTotal' => $shopDiscountTotal,
];
}

/**
* @param \Bitrix\Sale\Order $order
*
* @return array<string, array<string, float>>
*/
public function getShopDiscountItems(Order $order): array
{
$items = [];
$basket = $order->getBasket();

if ($basket === null) {
return $items;
}

foreach ($basket as $basketItem) {
$basketItemId = (string) $basketItem->getId();

if ($basketItemId === '' || $basketItemId === '0') {
continue;
}

$items[$basketItemId] = [
'SHOP_ITEM_DISCOUNT' => round(
(float) $basketItem->getBasePrice() - (float) $basketItem->getPrice(),
2
),
];
}

return $items;
}

/**
* @param \Intaro\RetailCrm\Model\Api\Response\Loyalty\LoyaltyCalculateResponse $calculation
* @param float $shopDiscountTotal
*
* @return float
*/
private function resolveLoyaltyDiscount(LoyaltyCalculateResponse $calculation, float $shopDiscountTotal): float
{
if (!is_array($calculation->calculations)) {
return 0.0;
}

foreach ($calculation->calculations as $privilege) {
if ($privilege->maximum && $privilege->discount > 0) {
return max(0, round((float) $privilege->discount - $shopDiscountTotal, 2));
}
}

return 0.0;
}

/**
* Переданный расчёт из браузера не используется.
*
* @param \Bitrix\Sale\Order $order
* @param array $calculateItemsInput
*/
public function saveDiscounts(Order $order, array $calculateItemsInput): void
{
unset($calculateItemsInput);

try {
if ($this->applyCalculatedDiscounts($order) === null) {
return;
}

Expand Down
7 changes: 6 additions & 1 deletion intaro.retailcrm/options.php
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,12 @@
}

//update connection settings
if (isset($_POST['Update']) && ($_POST['Update'] === 'Y')) {
if (isset($_POST['Update']) && ($_POST['Update'] === 'Y') && check_bitrix_sessid()) {
if (!$USER->CanDoOperation('edit_other_settings') || $APPLICATION->GetGroupRight($mid) < 'W') {
$APPLICATION->AuthForm(GetMessage('ACCESS_DENIED'));
return;
}

$error = null;
$api_host = trim((string) ($_POST['api_host'] ?? ''));
$api_key = trim((string) ($_POST['api_key'] ?? ''));
Expand Down
15 changes: 15 additions & 0 deletions tests/lib/service/LoyaltyCalculationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use Intaro\RetailCrm\Component\Json\Deserializer;
use Intaro\RetailCrm\Component\Json\Serializer;
use Intaro\RetailCrm\Model\Api\LoyaltyCalculation;
use Intaro\RetailCrm\Model\Api\OrderProduct;
use Intaro\RetailCrm\Model\Api\Response\Loyalty\LoyaltyCalculateResponse;
use Intaro\RetailCrm\Model\Api\SerializedLoyaltyOrder;
Expand Down Expand Up @@ -107,4 +108,18 @@ public function testReadsOfferId(): void

self::assertSame('16', $parsed->offer->externalId);
}

public function testResolveLoyaltyDiscountSubtractsShopDiscount(): void
{
$calculation = new LoyaltyCalculateResponse();
$privilege = new LoyaltyCalculation();
$privilege->maximum = true;
$privilege->discount = 300.0;
$calculation->calculations = [$privilege];
$service = new LoyaltyService();
$method = new ReflectionMethod(LoyaltyService::class, 'resolveLoyaltyDiscount');
$method->setAccessible(true);

self::assertSame(100.0, $method->invoke($service, $calculation, 200.0));
}
}
Loading