Your IP : 18.217.97.131


Current Path : /home/bitrix/ext_www/dev.easy-comfort.com.ua/bitrix/modules/sale/lib/cashbox/
Upload File :
Current File : /home/bitrix/ext_www/dev.easy-comfort.com.ua/bitrix/modules/sale/lib/cashbox/checkmanager.php

<?php

namespace Bitrix\Sale\Cashbox;

use Bitrix\Main\Error;
use Bitrix\Sale\Cashbox\Internals\CashboxCheckTable;
use Bitrix\Sale\Cashbox\Internals\CashboxTable;
use Bitrix\Sale\EntityMarker;
use Bitrix\Sale\Internals\CollectableEntity;
use Bitrix\Sale\Internals\Entity;
use Bitrix\Main;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Type;
use Bitrix\Sale;
use Bitrix\Sale\Payment;
use Bitrix\Sale\Result;

Loc::loadLanguageFile(__FILE__);

final class CheckManager
{
	const EVENT_ON_GET_CUSTOM_CHECK = 'OnGetCustomCheckList';
	const EVENT_ON_CHECK_PRINT_SEND = 'OnPrintableCheckSend';
	const EVENT_ON_BEFORE_CHECK_ADD_VERIFY = 'OnBeforeCheckAddVerify';
	const EVENT_ON_CHECK_PRINT_ERROR = 'OnCheckPrintError';
	const MIN_TIME_FOR_SWITCH_CASHBOX = 240;

	/** This is time re-sending a check print in minutes */
	const CHECK_RESENDING_TIME = 1;
	const CHECK_LIMIT_RECORDS = 5;

	/**
	 * @param CollectableEntity[] $entities
	 * @param $type
	 * @return Result
	 */
	public static function addByType(array $entities, $type)
	{
		$result = new Result();

		if ($type === '')
		{
			$result->addError(new Error(Loc::getMessage('SALE_CASHBOX_ERROR_EMPTY_CHECK_TYPE')));
			return $result;
		}

		$check = static::createByType($type);
		if ($check === null)
		{
			$result->addError(new Error(Loc::getMessage('SALE_CASHBOX_ERROR_CHECK')));
			return $result;
		}

		$cashboxList = array();
		foreach ($entities as $entity)
		{
			$items = Manager::getListWithRestrictions($entity);
			if (!$cashboxList)
				$cashboxList = $items;
			else
				$cashboxList = array_intersect($items, $cashboxList);
		}

		$entity = reset($entities);
		$order = static::getOrder($entity);

		if (!$cashboxList)
		{
			$dbRes = CashboxTable::getList(array('filter' => array('ACTIVE' => 'Y')));
			if ($dbRes->fetch())
				$result->addError(new Error(Loc::getMessage('SALE_CASHBOX_NOT_FOUND')));

			return $result;
		}

		$check->setEntities($entities);
		$check->setAvailableCashbox($cashboxList);

		$validateResult = $check->validate();
		if (!$validateResult->isSuccess())
		{
			$result->addErrors($validateResult->getErrors());
			return $result;
		}

		$saveResult = $check->save();
		if ($saveResult->isSuccess())
		{
			$checkId = $saveResult->getId();
			$order->addPrintedCheck($check);

			foreach ($cashboxList as $item)
			{
				$cashbox = Cashbox::create($item);
				if ($cashbox instanceof IPrintImmediately)
				{
					$printResult = $cashbox->printImmediately($check);
					if ($printResult->isSuccess())
					{
						$data = $printResult->getData();
						CashboxCheckTable::update(
							$checkId,
							array(
								'STATUS' => 'P',
								'DATE_PRINT_START' => new Type\DateTime(),
								'CASHBOX_ID' => $item['ID'],
								'EXTERNAL_UUID' => $data['UUID']
							)
						);

						return $result;
					}
					else
					{
						$result->addErrors($printResult->getErrors());
					}
				}
			}

			global $CACHE_MANAGER;
			foreach ($cashboxList as $cashbox)
			{
				$CACHE_MANAGER->Read(CACHED_b_sale_order, 'sale_checks_'.$cashbox['ID']);
				$CACHE_MANAGER->SetImmediate('sale_checks_'.$cashbox['ID'], true);
			}
		}
		else
		{
			$result->addErrors($saveResult->getErrors());
		}

		return $result;
	}

	/**
	 * @param $checkId
	 * @param array $data
	 * @return Result
	 */
	public static function savePrintResult($checkId, array $data)
	{
		$result = new Result();

		if ($checkId <= 0)
		{
			$result->addError(new Error(Loc::getMessage('SALE_CASHBOX_ERROR_CHECK_ID')));
			return $result;
		}

		$order = null;
		$payment = null;

		$dbRes = CashboxCheckTable::getList(array('select' => array('*'), 'filter' => array('ID' => $checkId)));
		$check = $dbRes->fetch();
		if (!$check)
		{
			$result->addError(new Error(Loc::getMessage('SALE_CASHBOX_ERROR_CHECK_NOT_FOUND', array('#CHECK_ID#' => $checkId))));
			return $result;
		}

		if ($check['STATUS'] === 'Y')
			return $result;

		if ($check['ORDER_ID'] > 0)
		{
			$order = Sale\Order::load($check['ORDER_ID']);
			if ($order === null)
			{
				$result->addError(new Error(Loc::getMessage('SALE_CASHBOX_ERROR_CHECK_ORDER_LOAD')));
				return $result;
			}

			$paymentCollection = $order->getPaymentCollection();
			if ($check['PAYMENT_ID'] > 0)
			{
				$payment = $paymentCollection->getItemById($check['PAYMENT_ID']);
				if ($payment === null)
				{
					$result->addError(new Error(Loc::getMessage('SALE_CASHBOX_ERROR_CHECK_PAYMENT_LOAD')));
					return $result;
				}
			}
		}

		if (isset($data['ERROR']))
		{
			$errorMessage = Loc::getMessage('SALE_CASHBOX_ERROR_CHECK_PRINT', array('#CHECK_ID#' => $checkId));
			if ($data['ERROR']['MESSAGE'])
				$errorMessage .= ': '.$data['ERROR']['MESSAGE'];

			if ($data['ERROR']['TYPE'] === Errors\Warning::TYPE)
			{
				if ($check['CNT_FAIL_PRINT'] >= 3)
				{
					$data['ERROR']['TYPE'] = Errors\Error::TYPE;
				}
				else
				{
					CashboxCheckTable::update($checkId, array('CNT_FAIL_PRINT' => $check['CNT_FAIL_PRINT'] + 1));
					$result->addError(new Errors\Warning($errorMessage));
					return $result;
				}
			}

			if ($data['ERROR']['TYPE'] === Errors\Error::TYPE)
			{
				$updatedFields = array('STATUS' => 'E', 'DATE_PRINT_END' => new Main\Type\DateTime());
				if ((int)$check['CNT_FAIL_PRINT'] === 0)
					$updatedFields['CNT_FAIL_PRINT'] = 1;

				CashboxCheckTable::update($checkId, $updatedFields);

				if ($order !== null && $payment !== null)
				{
					$r = new Result();
					$errorCode = isset($data['ERROR']['CODE']) ? $data['ERROR']['CODE'] : 0;
					$r->addWarning(new Main\Error($errorMessage, $errorCode));
					EntityMarker::addMarker($order, $payment, $r);

					$payment->setField('MARKED', 'Y');
					$order->save();
				}

				$error = new Errors\Error($errorMessage);
			}
			else
			{
				$error = new Errors\Warning($errorMessage);
			}

			Manager::writeToLog($check['CASHBOX_ID'], $error);

			$event = new Main\Event('sale', static::EVENT_ON_CHECK_PRINT_ERROR, array($data));
			$event->send();

			$result->addError($error);
		}
		else
		{
			$updateResult = CashboxCheckTable::update(
				$checkId,
				array(
					'STATUS' => 'Y',
					'LINK_PARAMS' => $data['LINK_PARAMS'],
					'DATE_PRINT_END' => new Main\Type\DateTime()
				)
			);

			if ($updateResult->isSuccess())
			{
				if ($payment !== null)
				{
					$isSend = false;
					$event = new Main\Event('sale', static::EVENT_ON_CHECK_PRINT_SEND, array('PAYMENT' => $payment, 'CHECK' => $check));
					$event->send();

					$eventResults = $event->getResults();
					/** @var Main\EventResult $eventResult */
					foreach($eventResults as $eventResult)
					{
						if($eventResult->getType() == Main\EventResult::SUCCESS)
							$isSend = true;
					}

					if (!$isSend)
						Sale\Notify::callNotify($payment, Sale\EventActions::EVENT_ON_CHECK_PRINT);
				}
			}
			else
			{
				$result->addErrors($updateResult->getErrors());
			}
		}

		return $result;
	}

	/**
	 * @param CollectableEntity[] $entities
	 * @return Result
	 */
	public static function addChecks(array $entities)
	{
		$result = new Result();

		$map = static::collateDocuments($entities);
		foreach ($map as $check)
		{
			$isCorrect = true;

			$event = new Main\Event('sale', static::EVENT_ON_BEFORE_CHECK_ADD_VERIFY, array($check));
			$event->send();

			if ($event->getResults())
			{
				/** @var Main\EventResult $eventResult */
				foreach ($event->getResults() as $eventResult)
				{
					if ($eventResult->getType() !== Main\EventResult::ERROR)
					{
						$isCorrect = (bool)$eventResult->getParameters();
					}
				}
			}

			if ($isCorrect)
			{
				$addResult = static::addByType($check["ENTITIES"], $check["TYPE"]);
				if (!$addResult->isSuccess())
					$result->addErrors($addResult->getErrors());
			}
		}

		return $result;
	}

	/**
	 * @param Sale\Internals\CollectableEntity $entity
	 * @throws Main\ArgumentTypeException
	 * @return Sale\Order
	 */
	public static function getOrder($entity)
	{
		$order = null;

		if ($entity instanceof Sale\Payment)
		{
			/** @var Sale\PaymentCollection $col */
			$col = $entity->getCollection();
			$order = $col->getOrder();
		}
		elseif ($entity instanceof Sale\Shipment)
		{
			/** @var Sale\ShipmentCollection $col */
			$col = $entity->getCollection();
			$order = $col->getOrder();
		}
		else
		{
			throw new Main\ArgumentTypeException("entities");
		}

		return $order;
	}

	/**
	 * @return array
	 */
	private static function getBuildInCheckList()
	{
		return array(
			'\Bitrix\Sale\Cashbox\SellCheck',
			'\Bitrix\Sale\Cashbox\SellReturnCashCheck',
			'\Bitrix\Sale\Cashbox\SellReturnCheck',
//			'\Bitrix\Sale\Cashbox\PrePaymentReturnCheck',
//			'\Bitrix\Sale\Cashbox\PrePaymentCheck',
		);
	}

	/**
	 * @return array
	 */
	private static function getUserCheckList()
	{
		$checkList = array();

		$event = new Main\Event('sale', static::EVENT_ON_GET_CUSTOM_CHECK);
		$event->send();
		$resultList = $event->getResults();

		if (is_array($resultList) && !empty($resultList))
		{
			foreach ($resultList as $eventResult)
			{
				/** @var  Main\EventResult $eventResult */
				if ($eventResult->getType() === Main\EventResult::SUCCESS)
				{
					$params = $eventResult->getParameters();
					if (!empty($params) && is_array($params))
						$checkList = array_merge($checkList, $params);
				}
			}
		}

		return $checkList;
	}

	/**
	 * @return void
	 */
	public static function init()
	{
		static $isInit = false;

		if ($isInit === false)
		{
			$handlers = static::getUserCheckList();
			Main\Loader::registerAutoLoadClasses(null, $handlers);
			$isInit = true;
		}
	}

	/**
	 * @return array
	 */
	public static function getCheckList()
	{
		static $checkList = array();
		if (empty($checkList))
			$checkList = array_merge(static::getBuildInCheckList(), array_keys(static::getUserCheckList()));

		return $checkList;
	}

	/**
	 * @return array
	 */
	public static function getCheckTypeMap()
	{
		static::init();

		$result = array();
		$checkMap = static::getCheckList();

		/** @var Check $className */
		foreach ($checkMap as $className)
		{
			if (class_exists($className))
				$result[$className::getType()] = $className;
		}

		return $result;
	}

	/**
	 * @param string $type
	 * @return null|Check
	 */
	public static function createByType($type)
	{
		static::init();

		$typeMap = static::getCheckTypeMap();
		$handler = $typeMap[$type];

		return Check::create($handler);
	}

	/**
	 * @param array $entities
	 * @return Entity[]
	 * @throws Main\NotSupportedException
	 */
	public static function collateDocuments(array $entities)
	{
		$map = array();

		$event = new Main\Event('sale', 'OnCheckCollateDocuments', array(
			'ENTITIES' => $entities
		));
		$event->send();
		$eventResults = $event->getResults();
		if ($eventResults != null)
		{
			foreach ($eventResults as $eventResult)
			{
				if ($eventResult->getType() === Main\EventResult::SUCCESS)
				{
					$d = $eventResult->getParameters();
					if (!is_array($d))
						throw new Main\NotSupportedException("OnCheckCollateDocuments event result");

					$map = array_merge($map, $d);
				}
			}

			if (count($map) > 0)
				return $map;
		}

		$existingChecks = null;
		$order = null;
		foreach ($entities as $entity)
		{
			// load existing checks
			if ($existingChecks === null)
			{
				$existingChecks = array();
				$order = static::getOrder($entity);
				$db = CashboxCheckTable::getList(
					array(
						"filter" => array("ORDER_ID" => $order->getId()),
						"select" => array("ID", "PAYMENT_ID", "SHIPMENT_ID", "TYPE", "STATUS")
					)
				);
				while ($ar = $db->fetch())
				{
					if (intval($ar["PAYMENT_ID"]) > 0)
						$existingChecks["P"][ $ar["PAYMENT_ID"] ][] = $ar;
					if (intval($ar["SHIPMENT_ID"]) > 0)
						$existingChecks["S"][ $ar["SHIPMENT_ID"] ][] = $ar;
				}
			}

			// analysing
			// we should allow users to implement their own algorithms
			if (count($existingChecks) <= 0)
			{
				if ($entity instanceof Sale\Payment)
				{
					/** @var Sale\PaySystem\Service $ps */
					$ps = $entity->getPaySystem();
					if ($entity->isPaid()
						&& ($ps->getField("CAN_PRINT_CHECK") == "Y")
						&& ($entity->getSum() == $order->getPrice()))
					{
						$checkEntities[] = $entity;

						$shipmentCollection = $order->getShipmentCollection();
						/** @var Sale\Shipment $shipment */
						foreach ($shipmentCollection as $shipment)
						{
							if (!$shipment->isSystem())
								$checkEntities[] = $shipment;
						}

						$map[] = array("TYPE" => "sell", "ENTITIES" => $checkEntities);
					}
				}
			}
		}

		return $map;
	}

	/**
	 * @param array $cashboxIds
	 * @param array $orderIds
	 * @return array
	 */
	public static function getPrintableChecks(array $cashboxIds, array $orderIds = array())
	{
		$result = array();

		$filter = array(
			'LINK_PARAMS' => '',
			'CHECK2CASHBOX.CASHBOX_ID' => $cashboxIds,
			array(
				'LOGIC' => 'OR',
				array(
					'=STATUS' => 'N',
					'DATE_PRINT_START' => ''
				),
				array(
					'=STATUS' => 'P',
					'<MAX_DT_REPEAT_CHECK' => new Type\DateTime()
				)
			)
		);
		if ($orderIds)
			$filter['ORDER_ID'] = $orderIds;

		$limit = count($cashboxIds)*static::CHECK_LIMIT_RECORDS;
		$dbRes = CashboxCheckTable::getList(
			array(
				'select' => array('*', 'AVAILABLE_CASHBOX_ID' => 'CHECK2CASHBOX.CASHBOX_ID'),
				'filter' => $filter,
				'limit' => $limit,
				'runtime' => array(
					new Main\Entity\ExpressionField(
						'MAX_DT_REPEAT_CHECK',
						'DATE_ADD(DATE_PRINT_START, INTERVAL '.static::CHECK_RESENDING_TIME.' MINUTE)',
						null,
						array(
							'data_type' => 'datetime'
						)
					)
				)
			)
		);

		if ($data = $dbRes->fetch())
		{
			$con = Main\Application::getConnection();
			$dbLocRes = $con->query("SELECT GET_LOCK('get_check_list', 0) as L");
			$locResult = $dbLocRes->fetch();
			if ($locResult["L"] == "0")
				return $result;

			$i = 0;
			do
			{
				if (!isset($result[$data['ID']]))
				{
					$i++;
					if ($i > static::CHECK_LIMIT_RECORDS)
						break;

					$result[$data['ID']] = $data;
					$result[$data['ID']]['CASHBOX_LIST'] = array();
				}

				$result[$data['ID']]['CASHBOX_LIST'][] = $data['AVAILABLE_CASHBOX_ID'];
			}
			while ($data = $dbRes->fetch());

			foreach ($result as $checkId => $item)
			{
				if ($item['STATUS'] === 'P')
				{
					$now = new Type\DateTime();
					$nowTs = $now->getTimestamp();

					/** @var Type\DateTime $dateStartPrint */
					$dateStartPrint = $item['DATE_PRINT_START'];
					$dateStartPrintTs = $dateStartPrint->getTimestamp();

					if ($nowTs - $dateStartPrintTs > static::MIN_TIME_FOR_SWITCH_CASHBOX)
					{
						$availableCashboxIds = array_diff($cashboxIds, array($item['CASHBOX_ID']));
						if ($availableCashboxIds)
						{
							$result[$checkId]['CASHBOX_ID'] = Manager::chooseCashbox($availableCashboxIds);
							CashboxCheckTable::update($checkId, array('CASHBOX_ID' => $result[$checkId]['CASHBOX_ID']));
						}
					}
					else
					{
						if ($item['CASHBOX_ID'] > 0 && !in_array($item['CASHBOX_ID'], $cashboxIds))
							unset($result[$checkId]);
					}

					continue;
				}

				$result[$checkId]['CASHBOX_ID'] = Manager::chooseCashbox($item['CASHBOX_LIST']);
				CashboxCheckTable::update($checkId, array('STATUS' => 'P', 'DATE_PRINT_START' => new Type\DateTime(), 'CASHBOX_ID' => $result[$checkId]['CASHBOX_ID']));
			}

			$con->query("SELECT RELEASE_LOCK('get_check_list')");
		}

		return $result;
	}

	/**
	 * @param array $settings
	 * @return Check|null
	 */
	public static function create(array $settings)
	{
		$check = CheckManager::createByType($settings['TYPE']);
		if ($check)
			$check->init($settings);

		return $check;
	}

	/**
	 * @param Sale\Internals\CollectableEntity $entity
	 * @return array
	 */
	public static function getCheckInfo(Sale\Internals\CollectableEntity $entity)
	{
		$filter = array();
		if ($entity instanceof Sale\Payment)
			$filter['PAYMENT_ID'] = $entity->getId();

		return static::collectInfo($filter);
	}

	/**
	 * @param Sale\Internals\CollectableEntity $entity
	 * @return array
	 */
	public static function getLastPrintableCheckInfo(Sale\Internals\CollectableEntity $entity)
	{
		if (!($entity instanceof Sale\Payment))
			return array();

		$dbRes = CashboxCheckTable::getList(
			array(
				'select' => array('*'),
				'filter' => array('PAYMENT_ID' => $entity->getId(), 'STATUS' => 'Y'),
				'order' => array('DATE_PRINT_END' => 'DESC'),
				'limit' => 1
			)
		);

		if ($data = $dbRes->fetch())
		{
			$data['LINK'] = '';
			if (!empty($data['LINK_PARAMS']))
			{
				$cashbox = Manager::getObjectById($data['CASHBOX_ID']);
				if ($cashbox)
					$data['LINK'] = $cashbox->getCheckLink($data['LINK_PARAMS']);
			}

			return $data;
		}

		return array();
	}

	/**
	 * @param array $filter
	 *
	 * @return array
	 * @throws Main\ArgumentException
	 */
	public static function collectInfo(array $filter = array())
	{
		$result = array();
		
		$typeMap = CheckManager::getCheckTypeMap();

		$dbRes = CashboxCheckTable::getList(
			array(
				'select' => array('*'),
				'filter' => $filter
			)
		);

		while ($data = $dbRes->fetch())
		{
			$data['LINK'] = '';
			if (!empty($data['LINK_PARAMS']))
			{
				$cashbox = Manager::getObjectById($data['CASHBOX_ID']);
				if ($cashbox)
					$data['LINK'] = $cashbox->getCheckLink($data['LINK_PARAMS']);
			}

			/** @var Check $type */
			$type = $typeMap[$data['TYPE']];
			if (class_exists($type))
				$data['TYPE_NAME'] = $type::getName();

			$result[$data['ID']] = $data;
		}

		return $result;
	}

	/**
	 * @param $uuid
	 * @return array|false
	 */
	public static function getCheckInfoByExternalUuid($uuid)
	{
		$dbRes = CashboxCheckTable::getList(array('filter' => array('EXTERNAL_UUID' => $uuid)));
		return $dbRes->fetch();
	}

	/**
	 * @param $id
	 * @return Check|null
	 */
	public static function getObjectById($id)
	{
		if ($id <= 0)
			return null;

		$dbRes = CashboxCheckTable::getById($id);
		if ($checkInfo = $dbRes->fetch())
		{
			$check = static::createByType($checkInfo['TYPE']);
			if ($check)
			{
				$check->init($checkInfo);
				return $check;
			}
		}

		return null;
	}
}