Your IP : 216.73.216.86


Current Path : /var/www/homesaver/www/bitrix/modules/sale/lib/reservation/
Upload File :
Current File : /var/www/homesaver/www/bitrix/modules/sale/lib/reservation/availablequantitycalculator.php

<?php

namespace Bitrix\Sale\Reservation;

/**
 * Calculation of balances based on the reservation history.
 *
 * For example:
 * ```php
 * $calculator = new \Bitrix\Sale\Reservation\AvailableQuantityCalculator();
 *
 * // load store balance
 * $rows = StoreProductTable::getList([...]);
 * foreach ($rows as $row)
 * {
 *     $calculator->setStoreQuantity($row['STORE_ID'], $row['PRODUCT_ID'], $row['AMOUNT']);
 * }
 *
 * // load history
 * $rows = BasketReservationHistoryTable::getList([...]);
 * foreach ($rows as $row)
 * {
 *     $calculator->addReservationHistory(
 *         $row['STORE_ID'],
 *         $row['PRODUCT_ID'],
 *         $row['BASKET_ID'],
 *         $row['QUANTITY']
 *     );
 * }
 *
 * // get count
 * $calculator->getQuantityForBatch([
 *     $basketId => $productId,
 *     $basketId => $productId,
 *     // ...
 * ]);
 * $calculator->getQuantityForItem($productId, $basketId, $storeId);
 * ```
 */
class AvailableQuantityCalculator
{
	/**
	 * Quantity product in stores.
	 *
	 * @var array
	 */
	private $storeProductQuantity = [];
	/**
	 * Reservation history (sort is important!).
	 *
	 * @var array
	 */
	private $reservationHistory = [];

	/**
	 * Set product store quantity.
	 *
	 * @param int $storeId
	 * @param int $productId
	 * @param float $quantity
	 * @return void
	 */
	public function setStoreQuantity(int $storeId, int $productId, float $quantity): void
	{
		$this->storeProductQuantity[$storeId][$productId] = $quantity;
	}

	/**
	 * Add an item reservation history.
	 * The order of addition is IMPORTANT!
	 *
	 * @param int $storeId
	 * @param int $productId
	 * @param int $basketId
	 * @param float $quantity
	 * @return void
	 */
	public function addReservationHistory(int $storeId, int $productId, int $basketId, float $quantity): void
	{
		$this->reservationHistory[] = compact(
			'storeId',
			'productId',
			'basketId',
			'quantity',
		);
	}

	/**
	 * Prepared reservation history.
	 *
	 * Actions:
	 * 1. collapse negative reservations;
	 *
	 * @return array
	 */
	private function getPreparedReservationHistory(): array
	{
		$reverseHistory = array_reverse($this->reservationHistory);
		$negativeReservations = [];

		$tmp = [];
		foreach ($reverseHistory as $item)
		{
			$storeId = $item['storeId'];
			$productId = $item['productId'];
			$basketId = $item['basketId'];
			$quantity = $item['quantity'];

			$key = join("_", [
				$storeId,
				$productId,
				$basketId,
			]);

			if ($quantity < 0.0)
			{
				$negativeReservations[$key] = $negativeReservations[$key] ?? 0.0;
				$negativeReservations[$key] += abs($quantity);
				continue;
			}
			elseif (isset($negativeReservations[$key]))
			{
				$negativeQuantity = $negativeReservations[$key];
				if ($negativeQuantity >= $quantity)
				{
					$negativeQuantity -= $quantity;
					if ($negativeQuantity > 0)
					{
						$negativeReservations[$key] = $negativeQuantity;
					}
					else
					{
						unset($negativeReservations[$key]);
					}
					continue;
				}
				else
				{
					$item['quantity'] -= $negativeQuantity;
					unset($negativeReservations[$key]);
				}
			}

			$tmp[] = $item;
		}

		return array_reverse($tmp);
	}

	/**
	 * Get available for debit product quantity for store.
	 *
	 * @param int $productId
	 * @param int $basketId
	 * @param int $storeId
	 * @return float
	 */
	public function getQuantityForItem(int $productId, int $basketId, int $storeId): float
	{
		$basketItemsStoreQuantity = $this->getQuantityForBatch([
			$basketId => $productId,
		]);
		return $basketItemsStoreQuantity[$basketId][$storeId] ?? 0.0;
	}

	/**
	 * Get available for debit product quantity for batch with basket items.
	 *
	 * @param array $basket2productId in format ['basketId' => 'productId', 'basketId' => 'productId', ...]
	 * @return array in format ['basketId' => ['storeId' => 'avaiableQuantity']]
	 */
	public function getQuantityForBatch(array $basket2productId): array
	{
		$basketItemsStoreQuantity = [];
		$currentStoreProductQuantity = $this->storeProductQuantity;
		$preparedReservationHistory = $this->getPreparedReservationHistory();

		foreach ($preparedReservationHistory as $item)
		{
			$storeId = $item['storeId'];
			$productId = $item['productId'];
			$basketId = $item['basketId'];

			$reservationQuantity = $item['quantity'];
			$storeQuantity = $currentStoreProductQuantity[$storeId][$productId] ?? 0.0;

			$isNeedBasketReservation = isset($basket2productId[$basketId]);
			if ($isNeedBasketReservation)
			{
				if ($storeQuantity > 0)
				{
					$basketItemsStoreQuantity[$basketId][$storeId] ??= 0.0;
					$basketItemsStoreQuantity[$basketId][$storeId] += min($storeQuantity, $reservationQuantity);
				}
			}

			if (isset($currentStoreProductQuantity[$storeId][$productId]))
			{
				$currentStoreProductQuantity[$storeId][$productId] -= $reservationQuantity;
			}
		}

		foreach ($basket2productId as $basketId => $productId)
		{
			foreach ($currentStoreProductQuantity as $storeId => $quantities)
			{
				if (
					isset($basketItemsStoreQuantity[$basketId])
					&& !isset($basketItemsStoreQuantity[$basketId][$storeId])
				)
				{
					continue;
				}
				$storeQuantity = $quantities[$productId] ?? 0.0;
				if ($storeQuantity > 0.0)
				{
					$basketItemsStoreQuantity[$basketId][$storeId] ??= 0.0;
					$basketItemsStoreQuantity[$basketId][$storeId] += $storeQuantity;
				}
			}
		}

		return $basketItemsStoreQuantity;
	}
}