<?php

namespace Modules\Documents\ImportedDocuments\CompensateDocs;

/**
 * Creates a new order/service/invoice/creditNoteInvoice (without issuing them) and adds positive
 * imported documents on the invoice, and negative imported documents on the credit note.
 * @author Gabriel Grosu
 */
class Model extends \App\Mvc\ModelAbstract
{

	/**
	 * Imported documents ids.
	 * @var array
	 */
	private $importedDocumentsIds = [];

	/**
	 * The details of the service that is added automatically.
	 * @var array
	 */
	private $serviceDetails = [];

	/**
	 * Total documents (positive ones) - as absolute value.
	 * @var float
	 */
	private $docsTotalAbs = 0;

	/**
	 * The documents currency. Offer and invoice currencty will be the same.
	 * @var string
	 */
	private $currency = null;

	/**
	 * The client (customer) id.
	 * @var integer
	 */
	private $idCustomer = 0;

	/**
	 * The client (corporate) id.
	 * @var integer
	 */
	private $idCorporate = 0;

	/**
	 * The added service id.
	 * @var integer
	 */
	private $idService = 0;


	public function __construct(array $importedDocumentsIds)
	{
		parent::__construct();
		sort($importedDocumentsIds);
		$this->importedDocumentsIds = $importedDocumentsIds;
	}

	/**
	 * Sets the service details.
	 * @param array $serviceDetails
	 */
	public function setServiceDetails(array $serviceDetails)
	{
		$this->serviceDetails = $serviceDetails; // TODO: [notImportant] might be nice to validate the input (only the keys)
	}

	/**
	 * Adds order/service/invoice/creditNote for this operation.
	 */
	public function process()
	{

		// CHECKS THE IMPORTED DOCS IDS
		$this->check();

		// adds the order/service/invoice/creditNote
		$this->addOrder();

	}

	/**
	 * Checks that the current operation is ok to be executed.
	 */
	private function check()
	{
		// check if inside transaction
		if (!\DB::isInsideTransaction()) {
			throw new \Exception('You must be inside a transaction when inserting a refund deposit document.');
		}

		$this->isAvailable(true);

	}

	/**
	 * Checks if the operation is available - checks the imported docs details.
	 * @throws \App\MessagesException - if any problem found
	 */
	public function isAvailable($selectForUpdate)
	{

		$msgs = new \App\MessagesCollector();

		\App\Loader_Lang::getInstance()->loadSection('Modules/Documents/ImportedDocuments/CompensateDocs');
		$_LANG = \App\Loader_Lang::getInstance()->getGlobal();

		// check user access
		if (!\App\UserAccess\CurrentUserAccess::getInstance()->hasAccessToOption('Modules/Documents/ImportedDocuments-compensateDocs')) {
			$msgs->addWarning($_LANG['noAccessToOption']);
		}

		// check that all docs have the same client and currency; also saved the client, and totals
		$currencies = $clients = [];
		$idCustomer = $idCorporate = 0;
		$totalNotUsedDocsValue = $negativeTtl = 0;
		foreach ($this->importedDocumentsIds as $idImportedDocument) {
			$docEnt = \Entity\ImportedDocument::select($idImportedDocument, $selectForUpdate);
			$currencies[$docEnt->docCurrency] = true;
			$clients[$docEnt->idCorporate.'|'.$docEnt->idCustomer] = true;
			if (count($currencies)!=1) {
				$msgs->addWarning($_LANG['Documents']['ImportedDocuments']['CompensateDocs']['index']['msgs']['err_sameCurr']);
				break;
			}
			if (count($clients)!=1) {
				$msgs->addWarning($_LANG['Documents']['ImportedDocuments']['CompensateDocs']['index']['msgs']['err_sameClient']);
				break;
			}
			if ($docEnt->status!='new' and $docEnt->status!='partial') {
				$msgs->addWarning($_LANG['Documents']['ImportedDocuments']['CompensateDocs']['index']['msgs']['err_wrongSts']);
				break;
			}
			if ($docEnt->systemStatus!='added') {
				$msgs->addWarning($_LANG['Documents']['ImportedDocuments']['CompensateDocs']['index']['msgs']['err_wrongSysSts']);
				break;
			}
			if ($docEnt->docCategory!='cashing') {
				$msgs->addWarning($_LANG['Documents']['ImportedDocuments']['CompensateDocs']['index']['msgs']['err_wrongDocCat']);
				break;
			}
			$totalNotUsedDocsValue += $docEnt->docValue - $docEnt->usedValue;
			if ($docEnt->docValue<0) {
				$negativeTtl += $docEnt->docValue - $docEnt->usedValue;
			}
			if ($docEnt->idCorporate) {
				$idCorporate = $docEnt->idCorporate;
			}
			if ($docEnt->idCustomer) {
				$idCustomer = $docEnt->idCustomer;
			}
		}

		// check that there is at leat one negative doc
		if (!round($negativeTtl, 2)) {
			$msgs->addWarning($_LANG['Documents']['ImportedDocuments']['CompensateDocs']['index']['msgs']['err_noNegativeDocs']);
		}

		// validate that the total is zero or positive
		if (round($totalNotUsedDocsValue)<0) {
			$msgs->addWarning($_LANG['Documents']['ImportedDocuments']['CompensateDocs']['index']['msgs']['err_totalPositive']);
		}

		// throw errors if found
		if ($msgs->hasErrorOrWarning()) {
			throw new \App\MessagesException($msgs);
		}

		// save important data
		$this->docsTotalAbs = abs($negativeTtl);
		$this->currency = key($currencies);
		$this->idCorporate = $idCorporate;
		$this->idCustomer = $idCustomer;

	}

	/**
	 * Adds the order/offer/invoices.
	 */
	private function addOrder()
	{

		// ADD ORDER AND SERVICE ------------------------------------------------------------------
		$orderInsertEnt = \Entity\Order::insert();
		$serviceInsertEnt = \Entity\Service::insert();

		if ($this->idCorporate) {
			$orderInsertEnt->idCorporates =		[$this->idCorporate];
			$orderInsertEnt->idCorporateOwner =	$this->idCorporate;
			$orderInsertEnt->undefinedPax =		[$this->serviceDetails['undefinedPaxType']=>1];
			$orderInsertEnt->idCustomers =		[];
			$orderInsertEnt->idCustomerOwner =	0;
		} elseif ($this->idCustomer) {
			$orderInsertEnt->idCorporates =		[];
			$orderInsertEnt->idCorporateOwner =	0;
			$orderInsertEnt->idCustomers =		[$this->idCustomer];
			$orderInsertEnt->idCustomerOwner =	$this->idCustomer;
			$orderInsertEnt->undefinedPax =		[];
		}
		$orderInsertEnt->servicesList =			[$serviceInsertEnt];
		$orderInsertEnt->idUser =				CURRENT_ID_USER;
		$orderInsertEnt->idOffice =				CURRENT_ID_OFFICE;
		$orderInsertEnt->idDepartment =			CURRENT_ID_DEPT;

		$serviceInsertEnt->serviceCode =		$this->serviceDetails['serviceCode'];
		if ($this->idCorporate) {
			$serviceInsertEnt->idCustomers =	[];
			$serviceInsertEnt->undefinedPax =	[$this->serviceDetails['undefinedPaxType']=>1];
		} elseif ($this->idCustomer) {
			$serviceInsertEnt->idCustomers =	[$this->idCustomer];
			$serviceInsertEnt->undefinedPax =	[];
		}
		$serviceInsertEnt->startDatetime =		TODAY_ISO;
		$serviceInsertEnt->idUser =				CURRENT_ID_USER;
		$serviceInsertEnt->idOffice =			CURRENT_ID_OFFICE;
		$serviceInsertEnt->extraInfo = 			[ 'compensatedDocumentsList' => $this->importedDocumentsIds ];

		$orderInsertEnt->save();

		$this->idService = $serviceInsertEnt->getId();
		$idOrder = $orderInsertEnt->getId();


		// ADD OFFER ON THIS SERVICE --------------------------------------------------------------
		$offerInsertEnt = \Entity\Offer::insert();
		$offerItemInsertEnt = \Entity\OfferItem::insert();
		$offerInsertEnt->idSupplier =			$this->serviceDetails['offerIdSupplier'];
		$offerInsertEnt->offerItems = 			[$offerItemInsertEnt];
		$offerInsertEnt->idService =			$this->idService;
		$offerInsertEnt->serviceCode =			$this->serviceDetails['serviceCode'];
		$offerInsertEnt->startDatetime =		\Engine\DateTime::create();
		$offerInsertEnt->location =				'int';
		$offerInsertEnt->calculationType =		\App\Config\Services_Calculations::getInstance()->getDefaultCalculationCode($this->serviceDetails['serviceCode'], 'int');
		$offerInsertEnt->offerCurrency =		$this->currency;
		$offerInsertEnt->serviceFeeCurrency =	$this->currency;
		$offerInsertEnt->totalCurrency =		$this->currency;
		$offerInsertEnt->offerType =			'serviceSimple';
		$offerInsertEnt->commissionable =		true;
		$offerInsertEnt->discountPercentSource =	'servicePrice';
		$offerInsertEnt->serviceFeeMode =		'value';
		$offerInsertEnt->serviceFeePercentSource = 'servicePrice';
		$offerInsertEnt->supplierVatIncluded = 	true;
		$offerInsertEnt->commissionVatIncluded = true;
		$offerInsertEnt->discountVatIncluded =	true;
		$offerInsertEnt->serviceFeeVatIncluded = true;
		$offerInsertEnt->taxVatIncluded =		true;
		$offerInsertEnt->currencyType = 		\App\Config\CurrencyTypes::getInstance()->getDefaultCurrencyType();
		$offerInsertEnt->currencyDate = 		\App\CurrencyExchange\Date::getInstance()->getLastDefinedDate();

		$offerItemInsertEnt->idCustomer =		0;
		$offerItemInsertEnt->productMultiplier = '';
		$offerItemInsertEnt->customerType =		'';
		$offerItemInsertEnt->supplierInitialValue = $this->docsTotalAbs;
		$offerItemInsertEnt->supplierInitialCurrency = $this->currency;


		$offerInsertEnt->save();
		$idOffer = $offerInsertEnt->getId();


		// CHOOSE AND CONFIRM THE OFFER -----------------------------------------------------------
		$serviceUpdateEnt = \Entity\Service::update($this->idService);
		$serviceUpdateEnt->chosenOfferId = $idOffer;
		$serviceUpdateEnt->confirmedOffer = true;
		$serviceUpdateEnt->save();


		// ADD INITIAL INVOICE --------------------------------------------------------------------
		$invoiceInsertEnt = \Entity\Invoice::insert();
		$invoiceInsertEnt->invoiceDate =		TODAY_ISO;
		$invoiceInsertEnt->idReservation =		$idOrder;
		$invoiceInsertEnt->dueDate =			TODAY_ISO;
		if ($this->idCorporate) {
			$invoiceInsertEnt->idCorporate =	$this->idCorporate;
			$invoiceInsertEnt->idCorporateOffice = \Entity\Corporate::selectForUpdate($this->idCorporate)->idMainOffice;
		} else {
			$invoiceInsertEnt->idCustomer =		$this->idCustomer;
		}

		$invoiceInsertEnt->currency =			$this->currency;
		$invoiceInsertEnt->idInvoiceSeries =	$this->serviceDetails['invoiceIdSeries'];

		$invoiceInsertEnt->formOfPayment =		$this->serviceDetails['invoiceFormOfPayment'];
		$invoiceInsertEnt->idBankAccount =		$this->serviceDetails['invoiceIdBankAccount'];
		$invoiceInsertEnt->temporary =			false;
		$invoiceInsertEnt->roundTotal =			false;

		$invoiceInsertEnt->idUser =				CURRENT_ID_USER;
		$invoiceInsertEnt->idOffice =			CURRENT_ID_OFFICE;
		$invoiceInsertEnt->servicesInvoiceOptions = [
			[
				'invoiceContent' =>			'total',
				'idReservationService' =>	$this->idService,
				'quantityType' =>			'one',
			]
		];

		$invoiceInsertEnt->save();

		$idInvoiceInitial = $invoiceInsertEnt->getId();
		// ISSUE INVOICE USING THE CONFIGURED SETTINGS --------------------------------------------
		if ($this->serviceDetails['invoiceIssue']) {
			$invoiceIssueEnt = \Entity\Document::insert();
			$invoiceIssueEnt->pivot =				'invoice';
			$invoiceIssueEnt->idPivot =				$idInvoiceInitial;
			$invoiceIssueEnt->generation =			'auto';
			$invoiceIssueEnt->docType =				$this->serviceDetails['invoiceDocumentType'];
			$invoiceIssueEnt->idTemplate =			$this->serviceDetails['invoiceDocumentIdTemplate'];
			$invoiceIssueEnt->idDocumentSeries =	($this->serviceDetails['invoiceIssueIdSeries'] ? $this->serviceDetails['invoiceIssueIdSeries'] : $this->serviceDetails['invoiceIdSeries']);
			$invoiceIssueEnt->idIssueUser =			CURRENT_ID_USER;
			$invoiceIssueEnt->save();
		}

		// ADD CREDIT NOTE INVOICE ----------------------------------------------------------------
		$invoiceReverseEnt = clone $invoiceInsertEnt;
		$invoiceReverseEnt->servicesInvoiceOptions = [
			[
				'invoiceContent' =>			'reverseTotal',
				'idReservationService' =>	$this->idService,
			]
		];
		$invoiceReverseEnt->save();

		$idInvoiceReverse = $invoiceReverseEnt->getId();

		// ISSUE CREDIT NOTE USING THE CONFIGURED SETTINGS --------------------------------------------
		if ($this->serviceDetails['creditNoteIssue']) {
			$invoiceIssueEnt = \Entity\Document::insert();
			$invoiceIssueEnt->pivot =				'invoice';
			$invoiceIssueEnt->idPivot =				$idInvoiceReverse;
			$invoiceIssueEnt->generation =			'auto';
			$invoiceIssueEnt->docType =				$this->serviceDetails['creditNoteDocumentType'];
			$invoiceIssueEnt->idTemplate =			$this->serviceDetails['creditNoteDocumentIdTemplate'];
			$invoiceIssueEnt->idDocumentSeries =	$this->serviceDetails['creditNoteIdSeries'];
			$invoiceIssueEnt->idIssueUser =			CURRENT_ID_USER;
			$invoiceIssueEnt->save();
		}

		$compensationTransactionId = uniqid();

		// ADD POSITIVE DOCS ON INITIAL INVOICE ---------------------------------------------------
		$invoiceTotal = $this->docsTotalAbs;
		$cashedSoFar = 0;
		foreach ($this->importedDocumentsIds as $idImportedDocument) {

			$importedDocEnt = \Entity\ImportedDocument::selectForUpdate($idImportedDocument);

			// if the document has a positive value
			if ($importedDocEnt->docValue>0 and $cashedSoFar<$invoiceTotal) {

				$invoiceCashingEnt = \Entity\InvoiceCashing::insert();

				$impDocModel = new \Modules\Documents\ImportedDocuments\ProcessDocument\Model\Model();
				$impDocModel->populateInvoiceCashing($invoiceCashingEnt, $importedDocEnt, $idInvoiceInitial);

				$invoiceCashingEnt->idUser = CURRENT_ID_USER;
				$invoiceCashingEnt->idOffice = CURRENT_ID_OFFICE;
				$invoiceCashingEnt->extraInfo = [ 'compensatedDocumentsList' => $this->importedDocumentsIds, 'compensationTransactionId'=>$compensationTransactionId ];
				$invoiceCashingEnt->save();

				$cashedSoFar += $importedDocEnt->docValue;

			}

		}
		// ADD NEGATIVE DOCS ON REVERSE INVOICE ---------------------------------------------------
		foreach ($this->importedDocumentsIds as $idImportedDocument) {

			$importedDocEnt = \Entity\ImportedDocument::selectForUpdate($idImportedDocument);

			// if the document has a positive value
			if ($importedDocEnt->docValue<0) {

				$invoiceCashingEnt = \Entity\InvoiceCashing::insert();

				$impDocModel = new \Modules\Documents\ImportedDocuments\ProcessDocument\Model\Model();
				$impDocModel->populateInvoiceCashing($invoiceCashingEnt, $importedDocEnt, $idInvoiceReverse);

				$invoiceCashingEnt->idUser = CURRENT_ID_USER;
				$invoiceCashingEnt->idOffice = CURRENT_ID_OFFICE;
				$invoiceCashingEnt->extraInfo = [ 'compensatedDocumentsList' => $this->importedDocumentsIds, 'compensationTransactionId'=>$compensationTransactionId ];
				$invoiceCashingEnt->save();

			}

		}

		// CANCEL ORDER ---------------------------------------------------------------------------
		if ($this->serviceDetails['cancelOrder']) {
			$servicesList = \Entity\Order::select($idOrder)->servicesList;
			foreach ($servicesList as $idServ=>$servDet) {
				$servObj=\Entity\Service::cancel($idServ);
				$servObj->cancelReason=$this->serviceDetails['cancelServiceReason'];
				$servObj->save();
			}
		}

	}

}