| Current Path : /var/www/homesaver/www/bitrix/modules/sale/lib/reservation/ |
| Current File : /var/www/homesaver/www/bitrix/modules/sale/lib/reservation/basketreservationhistoryservice.php |
<?php
namespace Bitrix\Sale\Reservation;
use Bitrix\Main\Application;
use Bitrix\Main\DB\SqlExpression;
use Bitrix\Main\Loader;
use Bitrix\Main\ORM\Data\DeleteResult;
use Bitrix\Main\Result;
use Bitrix\Sale\Internals\BasketTable;
use Bitrix\Main\Type\DateTime;
use Bitrix\Sale\Internals\StoreProductTable;
use Bitrix\Sale\Reservation\Internals\BasketReservationHistoryTable;
use Bitrix\Sale\Reservation\Internals\BasketReservationTable;
use Exception;
/**
* Service for working with the history of basket reserves
*/
class BasketReservationHistoryService
{
public function __construct()
{
Loader::includeModule('catalog');
}
/**
* Rounding to the required accuracy within the service
*
* @param float $quantity
* @return float
*/
private function roundQuantity(float $quantity): float
{
$precision = 6;
return round($quantity, $precision, PHP_ROUND_HALF_DOWN);
}
/**
* Total reserved quantity by reservation history
*
* @param int $reservationId
* @return float
*/
public function getQuantityByReservation(int $reservationId): float
{
$total = 0.0;
$rows = BasketReservationHistoryTable::getList([
'select' => [
'QUANTITY',
],
'filter' => [
'=RESERVATION_ID' => $reservationId,
],
]);
foreach ($rows as $row)
{
$total += (float)$row['QUANTITY'];
}
return $total;
}
/**
* The available amount to be debited based on the reservation history.
*
* @see example in `getAvailableCountForBasketItem` method
*
* @param array $basketItemFilter filter for `BasketTable` tablet.
*
* @return array in format `$ret[$productId][$storeId]; // avaiableQuantity`
*/
public function getAvailableCountForBasketItems(array $basketItemFilter): array
{
$basketItems =
BasketTable::getList([
'select' => [
'ID',
'PRODUCT_ID',
],
'filter' => $basketItemFilter,
])
->fetchAll()
;
$basket2productIds = array_column($basketItems, 'PRODUCT_ID', 'ID');
if (empty($basket2productIds))
{
return [];
}
$calculator = new AvailableQuantityCalculator();
$rows = StoreProductTable::getList([
'select' => [
'PRODUCT_ID',
'STORE_ID',
'AMOUNT',
],
'filter' => [
'=PRODUCT_ID' => $basket2productIds,
],
]);
foreach ($rows as $row)
{
$calculator->setStoreQuantity($row['STORE_ID'], $row['PRODUCT_ID'], $row['AMOUNT']);
}
$reservationsRows = BasketReservationHistoryTable::getList([
'select' => [
'RESERVATION_ID',
'QUANTITY',
'STORE_ID' => 'RESERVATION.STORE_ID',
'BASKET_ID' => 'RESERVATION.BASKET_ID',
'PRODUCT_ID' => 'RESERVATION.BASKET.PRODUCT_ID',
],
'filter' => [
'!RESERVATION.STORE_ID' => null,
'=RESERVATION.BASKET.PRODUCT_ID' => $basket2productIds,
],
'order' => [
'DATE_RESERVE' => 'ASC',
],
]);
foreach ($reservationsRows as $row)
{
$calculator->addReservationHistory(
$row['STORE_ID'],
$row['PRODUCT_ID'],
$row['BASKET_ID'],
$row['QUANTITY']
);
}
return $calculator->getQuantityForBatch($basket2productIds);
}
/**
* The available amount to be debited based on the reservation history.
*
* @see example in `getAvailableCountForBasketItem` method
*
* @param int $orderId
* @return array in format `$ret[$productId][$storeId]; // avaiableQuantity`
*/
public function getAvailableCountForOrder(int $orderId): array
{
return $this->getAvailableCountForBasketItems([
'=ORDER_ID' => $orderId,
]);
}
/**
* The available amount to be debited based on the reservation history.
*
* Example 1, there are 100pcs of product A in stock, then:
* 1. Deal #1 - 80pcs reserved;
* 2. Deal #2 - 40pcs reserved;
* 3. Deal #1 - the reserve has been changed from 80pcs to 90pcs - in this situation,
* another record with a reserve of 10pcs is added to the history.
*
* Thus, deal #1 can write off only 80pcs (because 10pcs were reserved after deal #2),
* and deal #2 only 20pcs (because they were reserved after deal #1).
*
* Example 2, there are 100pcs of product A in stock, then:
* 1. Deal #1 - 40pcs reserved;
* 2. Deal #2 - 50pcs reserved;
*
* Thus, deal #1 can write off 50pcs (40 reserved + 10 non-reserved store balance),
* and deal #2 60pcs (50 reserved + 10 non-reserved store balance).
*
* @param int $basketId
* @param int $storeId
* @return float avaiable quantity
*/
public function getAvailableCountForBasketItem(int $basketId, int $storeId): float
{
$basketItem = BasketTable::getRow([
'select' => [
'PRODUCT_ID',
],
'filter' => [
'=ID' => $basketId,
],
]);
if (!$basketItem || !$basketItem['PRODUCT_ID'])
{
return 0.0;
}
$productId = (int)$basketItem['PRODUCT_ID'];
$storeQuantityRow = StoreProductTable::getRow([
'select' => [
'AMOUNT',
],
'filter' => [
'=STORE_ID' => $storeId,
'=PRODUCT_ID' => $productId,
],
]);
if (!$storeQuantityRow)
{
return 0.0;
}
$calculator = new AvailableQuantityCalculator();
$calculator->setStoreQuantity($storeId, $productId, $storeQuantityRow['AMOUNT']);
$reservationsRows = BasketReservationHistoryTable::getList([
'select' => [
'RESERVATION_ID',
'QUANTITY',
'BASKET_ID' => 'RESERVATION.BASKET_ID',
],
'filter' => [
'=RESERVATION.STORE_ID' => $storeId,
'=RESERVATION.BASKET.PRODUCT_ID' => $productId,
],
'order' => [
'DATE_RESERVE' => 'ASC',
],
]);
foreach ($reservationsRows as $row)
{
$calculator->addReservationHistory(
$storeId,
$productId,
$row['BASKET_ID'],
$row['QUANTITY']
);
}
return $calculator->getQuantityForItem($productId, $basketId, $storeId);
}
/**
* Add history row
*
* @param array $fields
* @return Result
*/
public function add(array $fields): Result
{
return BasketReservationHistoryTable::add($fields);
}
/**
* Add quantity to reservations history.
*
* @param int $reservationId
* @param float $quantity
* @return Result
*/
private function addQuantity(int $reservationId, float $quantity): Result
{
return $this->add([
'RESERVATION_ID' => $reservationId,
'DATE_RESERVE' => new DateTime(),
'QUANTITY' => $quantity,
]);
}
/**
* Add history row by reservation
*
* @param int $reservationId
* @return Result
*/
public function addByReservation(int $reservationId): Result
{
$reservation = BasketReservationTable::getRowById($reservationId);
if (!$reservation)
{
throw new Exception('Reservation not found');
}
return $this->addQuantity($reservationId, (float)$reservation['QUANTITY']);
}
/**
* Update history row
*
* @param int $id
* @param array $fields
* @return Result
*/
public function update(int $id, array $fields): Result
{
return BasketReservationHistoryTable::update($id, $fields);
}
/**
* Update history row by reservation
*
* @param int $reservationId
* @return Result
*/
public function updateByReservation(int $reservationId): Result
{
$reservation = BasketReservationTable::getRowById($reservationId);
if (!$reservation)
{
throw new Exception('Reservation not found');
}
$reservationQuantity = $this->roundQuantity($reservation['QUANTITY']);
$historyQuantity = $this->roundQuantity($this->getQuantityByReservation($reservationId));
if ($reservationQuantity !== $historyQuantity)
{
return $this->addQuantity($reservationId, $reservationQuantity - $historyQuantity);
}
return new Result();
}
/**
* Delete history row
*
* @param int $id
* @return Result
*/
public function delete(int $id): Result
{
return BasketReservationHistoryTable::delete($id);
}
/**
* Delete history rows by reservation.
*
* All related rows will be deleted!
*
* @param int $reservationId
* @return Result
*/
public function deleteByReservation(int $reservationId): Result
{
$result = new DeleteResult();
$rows = BasketReservationHistoryTable::getList([
'select' => [
'ID',
],
'filter' => [
'=RESERVATION_ID' => $reservationId,
],
]);
foreach ($rows as $row)
{
$deleteResult = BasketReservationHistoryTable::delete($row['ID']);
foreach ($deleteResult->getErrors() as $err)
{
$result->addError($err);
}
}
return $result;
}
}