Your IP : 216.73.216.86


Current Path : /var/www/homesaver/www/bitrix/modules/catalog/lib/integration/report/handler/
Upload File :
Current File : /var/www/homesaver/www/bitrix/modules/catalog/lib/integration/report/handler/basehandler.php

<?php

namespace Bitrix\Catalog\Integration\Report\Handler;

use Bitrix\Catalog\Access\AccessController;
use Bitrix\Catalog\Access\ActionDictionary;
use Bitrix\Catalog\Integration\Report\Filter\StoreStockFilter;
use Bitrix\Catalog\Integration\Report\StoreStock\StoreStockQuantity;
use Bitrix\Catalog\Integration\Report\StoreStock\StoreStockSale;
use Bitrix\Catalog\StoreProductTable;
use Bitrix\Catalog\StoreTable;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ORM\Fields\ExpressionField;
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Query\Join;
use Bitrix\Main\Type\DateTime;
use Bitrix\Main\UI\Filter\Options;
use Bitrix\Main\Web\Uri;
use Bitrix\Report\VisualConstructor\AnalyticBoard;
use Bitrix\Report\VisualConstructor\Handler\BaseReport;
use Bitrix\Report\VisualConstructor\Helper\Filter;
use Bitrix\Report\VisualConstructor\IReportMultipleData;
use Bitrix\Report\VisualConstructor\RuntimeProvider\AnalyticBoardProvider;

abstract class BaseHandler extends BaseReport  implements IReportMultipleData
{
	protected const MAX_CHART_COLUMNS_COUNT = 5;
	protected const MAX_STORES_LIST_CHARS = 25;

	abstract public function prepare();

	public function getMultipleData()
	{
		return $this->getCalculatedData();
	}

	public function getMultipleDemoData()
	{
		return [];
	}

	protected function getStoreTotals(): array
	{
		$storeTotals = $this->getStoreProductData();
		if (!empty($storeTotals))
		{
			$receivedQuantities = $this->getReceivedQuantity();
			$soldAmounts = $this->getSoldAmounts();
			$outgoingQuantities = $this->getOutgoingQuantity();
			$receivedQuantitiesDifference = $this->getReceivedQuantityForDifference();
			$outgoingQuantitiesDifference = $this->getOutgoingQuantityForDifference();
			$soldAmountsDifference = $this->getSoldAmountsForDifference();

			$groupedStoreTotals = [];
			foreach ($storeTotals as $key => $storeTotal)
			{
				$storeId = (int)$storeTotal['TMP_STORE_ID'];
				$measureId = (int)$storeTotal['MEASURE_ID'] ?: \CCatalogMeasure::getDefaultMeasure(true)['ID'];
				if (isset($groupedStoreTotals[$storeId][$measureId]))
				{
					$groupedStoreTotals[$storeId][$measureId]['AMOUNT_SUM'] += (float)$storeTotal['AMOUNT_SUM'];
					$groupedStoreTotals[$storeId][$measureId]['QUANTITY_RESERVED_SUM'] += (float)$storeTotal['QUANTITY_RESERVED_SUM'];

					continue;
				}

				$groupedStoreTotals[$storeId][$measureId] = [
					'TITLE' => $storeTotal['TITLE'],
					'TMP_STORE_ID' => $storeId,
					'SORT' => $storeTotal['SORT'],
					'AMOUNT_SUM' => (float)$storeTotal['AMOUNT_SUM'],
					'QUANTITY_RESERVED_SUM' => (float)$storeTotal['QUANTITY_RESERVED_SUM'],
					'MEASURE_ID' => $measureId,
				];

				if (array_key_exists($storeId, $receivedQuantities))
				{
					$groupedStoreTotals[$storeId][$measureId]['RECEIVED_QUANTITIES'] = $receivedQuantities[$storeId];
				}
				if (array_key_exists($storeId, $receivedQuantitiesDifference))
				{
					$groupedStoreTotals[$storeId][$measureId]['RECEIVED_QUANTITIES_DIFFERENCE'] = $receivedQuantitiesDifference[$storeId];
				}

				if (array_key_exists($storeId, $outgoingQuantities))
				{
					$groupedStoreTotals[$storeId][$measureId]['OUTGOING_QUANTITIES'] = $outgoingQuantities[$storeId];
				}
				if (array_key_exists($storeId, $outgoingQuantitiesDifference))
				{
					$groupedStoreTotals[$storeId][$measureId]['OUTGOING_QUANTITIES_DIFFERENCE'] = $outgoingQuantitiesDifference[$storeId];
				}

				if (array_key_exists($storeId, $soldAmounts))
				{
					$groupedStoreTotals[$storeId][$measureId]['SOLD_AMOUNTS'] = $soldAmounts[$storeId];
				}
				if (array_key_exists($storeId, $soldAmountsDifference))
				{
					$groupedStoreTotals[$storeId][$measureId]['SOLD_AMOUNTS_DIFFERENCE'] = $soldAmountsDifference[$storeId];
				}

				ksort($groupedStoreTotals[$storeId]);
			}
			ksort($groupedStoreTotals);

			$storeTotals = $this->prepareStoreTotals($groupedStoreTotals);
		}

		return $storeTotals;
	}

	protected static function formChartSliderUrl(string $componentName, array $filter): string
	{
		$sliderUrl = \CComponentEngine::makeComponentPath($componentName);
		$sliderUrl = getLocalPath('components'.$sliderUrl.'/slider.php');

		$uri = new Uri($sliderUrl);

		if (isset($filter['STORES']))
		{
			$uri->addParams(['storeIds' => $filter['STORES']]);
		}
		if (isset($filter['PRODUCTS']))
		{
			$uri->addParams(['productIds' => $filter['PRODUCTS']]);
		}
		if (isset($filter['REPORT_INTERVAL']))
		{
			$uri->addParams([
				'reportFrom' => $filter['REPORT_INTERVAL']['FROM'],
				'reportTo' => $filter['REPORT_INTERVAL']['TO'],
			]);
		}
		else
		{
			$defaultInterval = StoreStockSale::getDefaultReportInterval();
			$uri->addParams([
				'reportFrom' => $defaultInterval['FROM'],
				'reportTo' => $defaultInterval['TO'],
			]);
		}

		return $uri->getUri();
	}

	private function getStoreProductData(): array
	{
		$accessController = AccessController::getCurrent();
		if (!$accessController->check(ActionDictionary::ACTION_STORE_VIEW))
		{
			return [];
		}

		// adding a new runtime reference field with right join
		// in order to select all the stores and not just the
		// ones that have corresponding entries in b_catalog_store_product
		$queryParams = [
			'select' => [
				'TITLE' => 'STORE_TMP.TITLE',
				'TMP_STORE_ID' => 'STORE_TMP.ID',
				'SORT' => 'STORE_TMP.SORT',
				'AMOUNT_SUM',
				'QUANTITY_RESERVED_SUM',
				'MEASURE_ID' => 'PRODUCT.MEASURE',
			],
			'filter' => [
				'=STORE_TMP.ACTIVE' => 'Y',
			],
			'group' => ['TMP_STORE_ID', 'MEASURE_ID'],
			'order' => ['SORT'],
			'runtime' => [
				new ExpressionField('AMOUNT_SUM', 'SUM(AMOUNT)'),
				new ExpressionField('QUANTITY_RESERVED_SUM', 'SUM(%s)', ['QUANTITY_RESERVED']),
				(new Reference(
					'STORE_TMP',
					StoreTable::class,
					Join::on('this.STORE_ID', 'ref.ID')
				))->configureJoinType(Join::TYPE_RIGHT),
			],
		];

		$userFilterParameters = $this->getFilterParameters();

		if (isset($userFilterParameters['STORES']) && is_array($userFilterParameters['STORES']))
		{
			$storesList = $userFilterParameters['STORES'];
		}
		else
		{
			$storesList = null;
		}

		$filteredStoresList = self::getFilteredByRightsStoreList($storesList);

		if (is_array($filteredStoresList))
		{
			$queryParams['filter']['=TMP_STORE_ID'] = $filteredStoresList;
		}

		if (!empty($userFilterParameters['PRODUCTS']) && is_array($userFilterParameters['PRODUCTS']))
		{
			$queryParams['filter'][] = $this->getProductFilter($userFilterParameters['PRODUCTS']);
		}

		return StoreProductTable::getList($queryParams)->fetchAll();
	}

	protected function getProductFilter(array $productFilter): array
	{
		return [
			'=PRODUCT_ID' => StoreStockFilter::prepareProductFilter($productFilter),
			[
				'LOGIC' => 'OR',
				'!=AMOUNT' => 0,
				'!=QUANTITY_RESERVED' => 0,
			],
		];
	}

	private function prepareStoreTotals(array $storeTotals): array
	{
		$preparedTotals = [];

		foreach ($storeTotals as $storeId => $storeTotal)
		{
			foreach ($storeTotal as $measureId => $entry)
			{
				if (!isset($preparedTotals[$storeId]))
				{
					$preparedTotals[$storeId] = [
						'TITLE' => $entry['TITLE'],
						'STORE_ID' => $storeId,
						'TOTALS' => [],
					];
				}

				$soldAmountDifferenceData = $entry['SOLD_AMOUNTS_DIFFERENCE'] ?? [];
				$receivedQuantitiesDifferenceData = $entry['RECEIVED_QUANTITIES_DIFFERENCE'] ?? [];
				$outgoingQuantitiesDifferenceData = $entry['OUTGOING_QUANTITIES_DIFFERENCE'] ?? [];
				$amountSum =
					(float)$entry['AMOUNT_SUM']
					- ($receivedQuantitiesDifferenceData[$measureId] ?? 0.0)
					+ ($outgoingQuantitiesDifferenceData[$measureId] ?? 0.0)
					+ ($soldAmountDifferenceData[$measureId] ?? 0.0)
				;
				$quantityReservedSum = (float)$entry['QUANTITY_RESERVED_SUM'];

				$quantity = $amountSum - $quantityReservedSum;
				$productsSoldAmount = $entry['SOLD_AMOUNTS'] ?? [];
				$receivedQuantityData = $entry['RECEIVED_QUANTITIES'] ?? [];
				$outgoingQuantityData = $entry['OUTGOING_QUANTITIES'] ?? [];

				$startingQuantity =
					$amountSum
					- ($receivedQuantityData[$measureId] ?? 0.0)
					+ ($outgoingQuantityData[$measureId] ?? 0.0)
					+ ($productsSoldAmount[$measureId] ?? 0.0)
				;
				$receivedQuantity = ($receivedQuantityData[$measureId] ?? 0.0);
				$amountSold = $productsSoldAmount[$measureId] ?? 0.0;

				$isStoreEmpty = true;
				$values = [
					$startingQuantity, $receivedQuantity, $amountSum, $quantityReservedSum, $quantity, $amountSold,
				];
				foreach ($values as $value)
				{
					if ($value !== 0.0)
					{
						$isStoreEmpty = false;
						break;
					}
				}
				if ($isStoreEmpty)
				{
					continue;
				}

				if (!isset($preparedTotals[$storeId]['TOTALS'][$measureId]))
				{
					$preparedTotals[$storeId]['TOTALS'][$measureId] = [
						'STARTING_QUANTITY' => 0,
						'RECEIVED_QUANTITY' => 0,
						'AMOUNT_SUM' => 0,
						'QUANTITY_RESERVED_SUM' => 0,
						'QUANTITY' => 0,
						'AMOUNT_SOLD' => 0,
					];
				}

				$preparedTotals[$storeId]['TOTALS'][$measureId]['STARTING_QUANTITY'] += $startingQuantity;
				$preparedTotals[$storeId]['TOTALS'][$measureId]['RECEIVED_QUANTITY'] += $receivedQuantity;
				$preparedTotals[$storeId]['TOTALS'][$measureId]['AMOUNT_SUM'] += $amountSum;
				$preparedTotals[$storeId]['TOTALS'][$measureId]['QUANTITY_RESERVED_SUM'] += $quantityReservedSum;
				$preparedTotals[$storeId]['TOTALS'][$measureId]['QUANTITY'] += $quantity;
				$preparedTotals[$storeId]['TOTALS'][$measureId]['AMOUNT_SOLD'] += $amountSold;
			}
		}

		return $preparedTotals;
	}

	private function getFormattedFilterForDifference(): ?array
	{
		$formattedFilter = $this->getFormattedFilter();
		$differenceFilter = $formattedFilter;
		$currentTime = new DateTime();
		$filterTimeTo = new DateTime($differenceFilter['REPORT_INTERVAL']['TO'] ?? null);
		if ($currentTime > $filterTimeTo)
		{
			$differenceFilter['REPORT_INTERVAL']['FROM'] = $differenceFilter['REPORT_INTERVAL']['TO'];
			\CTimeZone::Disable();
			$differenceFilter['REPORT_INTERVAL']['TO'] = $currentTime->toString();
			\CTimeZone::Enable();
		}
		else
		{
			return null;
		}

		return $differenceFilter;
	}

	private function getReceivedQuantityForDifference(): array
	{
		$formattedFilterForDifference = $this->getFormattedFilterForDifference();
		if (!$formattedFilterForDifference)
		{
			return [];
		}

		return StoreStockQuantity::getReceivedQuantityForStores($formattedFilterForDifference);
	}

	private function getOutgoingQuantityForDifference(): array
	{
		$formattedFilterForDifference = $this->getFormattedFilterForDifference();
		if (!$formattedFilterForDifference)
		{
			return [];
		}

		return StoreStockQuantity::getOutgoingQuantityForStores($formattedFilterForDifference);
	}

	private function getSoldAmountsForDifference(): array
	{
		$formattedFilterForDifference = $this->getFormattedFilterForDifference();
		if (!$formattedFilterForDifference)
		{
			return [];
		}

		return StoreStockSale::getProductsSoldAmountForStores($formattedFilterForDifference);
	}

	private function getReceivedQuantity(): array
	{
		$receivedQuantityFilter = $this->getFormattedFilter();

		return StoreStockQuantity::getReceivedQuantityForStores($receivedQuantityFilter);
	}

	private function getOutgoingQuantity(): array
	{
		$outgoingQuantityFilter = $this->getFormattedFilter();

		return StoreStockQuantity::getOutgoingQuantityForStores($outgoingQuantityFilter);
	}

	private function getSoldAmounts(): array
	{
		$filter = $this->getFormattedFilter();

		return StoreStockSale::getProductsSoldAmountForStores($filter);
	}

	/**
	 * @return array
	 */
	protected function getFormattedFilter(): array
	{
		$filter = $this->getFilterParameters();

		$formattedFilter = [];

		$storesList = (isset($filter['STORES']) && is_array($filter['STORES'])) ? $filter['STORES'] : null;
		$filteredStoresList = self::getFilteredByRightsStoreList($storesList);

		if (is_array($filteredStoresList))
		{
			$formattedFilter['STORES'] = $filteredStoresList;
		}

		if (!empty($filter['PRODUCTS']))
		{
			$formattedFilter['PRODUCTS'] = StoreStockFilter::prepareProductFilter($filter['PRODUCTS']);
		}

		if
		(
			!empty($filter['REPORT_INTERVAL_from'])
			&& !empty($filter['REPORT_INTERVAL_to'])
		)
		{
			$formattedFilter['REPORT_INTERVAL'] = [
				'FROM' => $filter['REPORT_INTERVAL_from'],
				'TO' => $filter['REPORT_INTERVAL_to'],
			];
		}
		return $formattedFilter;
	}

	private function getDefaultMeasure(): int
	{
		static $defaultMeasure = 0;

		if (empty($defaultMeasure))
		{
			$fetchedMeasure = \CCatalogMeasure::getList([], ['=IS_DEFAULT' => 'Y'])->Fetch();
			if ($fetchedMeasure)
			{
				$defaultMeasure = (int)$fetchedMeasure['ID'];
			}
			else
			{
				$defaultMeasure = 0;
			}
		}

		return $defaultMeasure;
	}

	protected function prepareOverallTotals(array $storeTotals): array
	{
		$overallTotals = [];

		foreach ($storeTotals as $storeTotalEntry)
		{
			foreach ($storeTotalEntry['TOTALS'] as $measureId => $total)
			{
				$startingQuantitySum = (float)$total['STARTING_QUANTITY'];
				$amountSum = (float)$total['AMOUNT_SUM'];
				$receivedQuantity = (float)$total['RECEIVED_QUANTITY'];
				$quantityReservedSum = (float)$total['QUANTITY_RESERVED_SUM'];
				$amountSoldSum = (float)$total['AMOUNT_SOLD'];

				if (!isset($overallTotals[$measureId]))
				{
					$overallTotals[$measureId] = [
						'STARTING_QUANTITY' => 0,
						'RECEIVED_QUANTITY' => 0,
						'AMOUNT_SUM' => 0,
						'QUANTITY_RESERVED_SUM' => 0,
						'AMOUNT_SOLD' => 0,
						'QUANTITY' => 0,
					];
				}
				$overallTotals[$measureId]['STARTING_QUANTITY'] += $startingQuantitySum;
				$overallTotals[$measureId]['RECEIVED_QUANTITY'] += $receivedQuantity;
				$overallTotals[$measureId]['AMOUNT_SUM'] += $amountSum;
				$overallTotals[$measureId]['QUANTITY_RESERVED_SUM'] += $quantityReservedSum;
				$overallTotals[$measureId]['QUANTITY'] += $amountSum - $quantityReservedSum;
				$overallTotals[$measureId]['AMOUNT_SOLD'] += $amountSoldSum;
			}
		}

		return $overallTotals;
	}

	protected static function getAnalyticBoardByKey($key): ?AnalyticBoard
	{
		$boardProvider = new AnalyticBoardProvider();
		$boardProvider->addFilter('boardKey', $key);

		return $boardProvider->execute()->getFirstResult();
	}

	protected function getFilter(): Filter
	{
		static $filter;
		if ($filter)
		{
			return $filter;
		}

		$boardKey = $this->getWidgetHandler()->getWidget()->getBoardId();
		$board = self::getAnalyticBoardByKey($boardKey);
		if ($board)
		{
			$filter = $board->getFilter();
		}
		else
		{
			$filter = new Filter($boardKey);
		}

		return $filter;
	}

	protected function getFilterParameters(): array
	{
		static $filterParameters = [];

		$filter = $this->getFilter();
		$filterId = $filter->getFilterParameters()['FILTER_ID'];

		if (!isset($filterParameters[$filterId]))
		{
			$options = new Options($filterId, $filter::getPresetsList());
			$fieldList = $filter::getFieldsList();
			$filterParameters[$filterId] = $options->getFilter($fieldList);
		}

		return $filterParameters[$filterId];
	}

	protected static function getNoAccessToStoresStub(): array
	{
		return [
			'title' => Loc::getMessage('BASE_HANDLER_EMPTY_PERMITTED_STORES_LIST_STUB_TITLE'),
			'description' => Loc::getMessage('BASE_HANDLER_EMPTY_PERMITTED_STORES_LIST_STUB_DESCRIPTION'),
		];
	}

	/**
	 * @param array|null $inputStoreList
	 * @return array|null
	 */
	private static function getFilteredByRightsStoreList(?array $inputStoreList = null): ?array
	{
		$accessController = AccessController::getCurrent();

		if (!$accessController->check(ActionDictionary::ACTION_STORE_VIEW))
		{
			return [];
		}

		if (!$accessController->checkCompleteRight(ActionDictionary::ACTION_STORE_VIEW))
		{
			$availableStores = $accessController->getPermissionValue(ActionDictionary::ACTION_STORE_VIEW) ?? [];

			if (is_array($inputStoreList))
			{
				return array_values(array_intersect($availableStores, $inputStoreList));
			}

			return $availableStores;
		}

		return $inputStoreList;
	}
}