<?php
namespace Entity;

class Validator_InvoiceCashing extends \App\Entity\Utils\Extension_ValidateDefaultAbstract
{

	protected function isAccessible_insert()
	{

		// current collected messages
		$currMessages=$this->entity->getMessages();

		// basic add validation
		if (!\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('Modules/Invoices/Cashings/Listing-insert')) {
			$currMessages->addWarningCode('noAccessToOption');
		}

	}

	protected function check_insert()
	{

		// get current fields details
		$fields=$this->entity->getFields();

		// current collected messages
		$currMessages=$this->entity->getMessages();

		$OK=true;

		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();

		$docCfg = \App\Config\CashingDocuments::getInstance();

		// simplified vars
		$docType=$fields['docType'];
		$docDate=$fields['docDate'];
		$docValue=$fields['value'];

		$issuableDocTypes = \App\Config\IssueDocuments::getInstance()->getCashingDocsList($docType);

		// CHECK ACCESS BY DOCUMENT TYPE //////////////////////////////////////////////////////////
		if ($docType) {
			$userDocBlock=\App\UserAccess\CurrentUserAccessFast::getInstance()->getAccessOptionValue('InvoiceCashings-mngDocsBlockedByDocType');
			if (is_array($userDocBlock) and in_array($docType, $userDocBlock)) {
				$OK=false;
				$currMessages->addWarningCode('insert_blockedDocType');
			}
		}

		// CHECK IF THE INVOICE IS CORRECT ////////////////////////////////////////////////////////
		$invoice=\Entity\Invoice::selectForUpdate($fields['idInvoice']);
		if (!$invoice->recordExists()) {
			$currMessages->addErrorCode('insert_invalidInvoiceId');
		}
		// invoice must not be voided
		if ($invoice->voidTime) {
			$OK=false;
			$currMessages->addWarningCode('insert_invoiceVoided');
		}
		// invoice must be issued
		if (!\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('Modules/Invoices/Cashings/Listing-cashInvoiceNotIssued') and !$invoice->issueTime) {
			$OK=false;
			$currMessages->addWarningCode('insert_invoiceNotIssued');
		}
		// invoice must not be temporary
		if ($invoice->temporary) {
			$OK=false;
			$currMessages->addWarningCode('insert_invoiceIsTemp');
		}

		// CHECK DOC TYPE /////////////////////////////////////////////////////////////////////////
		// check if this is a valid doc type
		if ($docType and !\App\Config\CashingDocuments::getInstance()->isActive($docType)) {
			$currMessages->addErrorCode('docTypeInvalid', 'docType');
		}

		// CHECK ID OFFICE ////////////////////////////////////////////////////////////////////////
		$idOffice = null;
		if (array_key_exists('idOffice', $fields)) {
			$idOffice=$fields['idOffice'];
			$off=\Entity\Office::selectForUpdate($fields['idOffice']);
			if (!$off->recordExists()) {
				$currMessages->addErrorCode('invalidOfficeId');
			} elseif ($off->deleted) {
				$OK=false;
				$currMessages->addWarningCode('officeWasDeleted');
			}
		}

		// CHECK ID USER //////////////////////////////////////////////////////////////////////////
		if (array_key_exists('idUser', $fields)) {
			$usr=\Entity\User::selectForUpdate($fields['idUser']);
			if (!$usr->recordExists() or $usr->deleted) {
				$currMessages->addErrorCode('invalidUserId');
			} elseif ($usr->idOffice!=$idOffice) {
				$currMessages->addErrorCode('userFromOtherOffice');
			}
		}

		// CHECK DOCUMENT DATE ////////////////////////////////////////////////////////////////////
		// CHECK IF THE DATE IS VALID -------------------------------------------------------------
		if ($docDate and !validDate($docDate)) {
			$OK=false;
			$currMessages->addWarningCode('docDateInvalid', 'docDate');
		}
		// CHECK THAT THE DOC DATE HAS CURRENCY RATES SET -----------------------------------------
		if ($docDate and !\App\CurrencyExchange\Date::getInstance($docDate)->getLastDefinedDate()) {
			$OK=false;
			$currMessages->addWarningCode('docDateNoCurrRates', 'docDate');
		}
		// CHECK IF DOCUMENT DATE IS OLDER THAN CASHBOOK LAST CLOSED BALANCE ----------------------
		if ($OK and $fields['idOffice']) {
			// if document type is included in the cashbook
			$cashingDocsCfgGroup = \App\Config\CashingDocuments::getInstance()->getGroup($docType);
			if ($cashingDocsCfgGroup == 'cash') {
				$lastClosedDate=\Cashbook::getLastClosedBalanceDate($fields['idOffice']);
				// if last closed date exists (cashbook is used) and if the date is smaller than last closed balance
				if ($lastClosedDate and $docDate<=$lastClosedDate) {
					$OK=false;
					$currMessages->addWarningCode('docBeforeCashbookBalance', 'docDate', $lastClosedDate);
				}
			}
		}

		// CHECK DOCUMENT SERIES/NUMBER ///////////////////////////////////////////////////////////
		// CHECK IF MANUAL DOCUMENT NUMBER NOT SET ------------------------------------------------
		if ($docCfg->getNumber($docType)=='mandatory' and !$fields['docNumber']) {
			$OK=false;
			$currMessages->addWarningCode('insert_docNumberNotSet', 'docNumber');
		}
		// CHECK IF DOCUMENT NUMBER MUST NOT BE SET -----------------------------------------------
		if ( ($docCfg->getNumber($docType)=='series' or $docCfg->getNumber($docType)=='hidden') and $fields['docNumber'] ) {
			$OK=false;
			$currMessages->addWarningCode('insert_docNumberMustNotBeSet');
		}
		// IF CASHING SERIES MUST NOT BE SET ------------------------------------------------------
		if ($docCfg->getNumber($docType)!="series" and $fields['idCashingSeries']) {
			$OK=false;
			$currMessages->addWarningCode('insert_docSeriesMustNotBeSet');
		}
		// CHECK IF DOCUMENT HAS SERIES -----------------------------------------------------------
		if ($docCfg->getNumber($docType)=="series") {
			if (!$fields['idCashingSeries']) { // id cashing series must be set
				$OK=false;
				$currMessages->addWarningCode('docSeriesNotSet', 'idCashingSeries');
			}
			if ($fields['docNumber']) { // manual docNumber must NOT be set
				$OK=false;
				$currMessages->addWarningCode('insert_docNumberMustNotBeSet');
			}
			if (!$issuableDocTypes) {
				$currMessages->addWarningCode('docNotIssuable');
			}
		}

		// CHECK SERIES DETAILS ///////////////////////////////////////////////////////////////////
		if ($OK and $fields['idCashingSeries']) {
			// get series details
			$seriesDetails=\Entity\DocumentSeries::select($fields['idCashingSeries']);

			// check if record exists
			if (!$seriesDetails->recordExists()) {
				$OK=false; $currMessages->addWarningCode('badIdSeries', 'idCashingSeries');
			}
			// check if series was deleted
			elseif ($seriesDetails->deleted) {
				$OK=false; $currMessages->addWarningCode('seriesDeleted', 'idCashingSeries');
			}
			// check series status
			elseif ($seriesDetails->status!='WRK') {
				$OK=false; $currMessages->addWarningCode('seriesStatusBad', 'idCashingSeries');
			}
			// check document type
			if ($OK and !array_intersect(array_keys($issuableDocTypes), $seriesDetails->documentTypes)) {
				$OK=false; $currMessages->addWarningCode('badSeriesDocType', 'idCashingSeries');
			}
			// check series currency
			if ($OK and !$seriesDetails->allCurrencies and !$seriesDetails->validCurrenciesList[ $fields['currency'] ]) {
				$OK=false; $currMessages->addWarningCode('invoiceSeriesCurrRestrict');
			}
			// check validity date
			if ($OK and ($docDate<$seriesDetails->validStartDate or $docDate>$seriesDetails->validEndDate) ) {
				$OK=false; $currMessages->addWarningCode('invoiceSeriesInvalidPeriod', 'idCashingSeries');
			}
		}

		// CHECK DOCUMENT VALUE SIGN //////////////////////////////////////////////////////////////
		if ($docCfg->getSign($docType)==-1 and $docValue>0) {
			$OK=false;
			$currMessages->addWarningCode('docMustBeNegative', 'value');
		}
		if ($docCfg->getSign($docType)==1 and $docValue<0) {
			$OK=false;
			$currMessages->addWarningCode('docMustBePositive', 'value');
		}

		// CHECK DOC CURRENCY /////////////////////////////////////////////////////////////////////
		if ($fields['currency'] and !\App\Config\Currencies::getInstance()->check($fields['currency'])) {
			$currMessages->addErrorCode('docCurrencyInvalid', '', $fields['currency']);
		}
		if ($fields['currency'] and $docValue) {
			if (round((float)$docValue, 6)!=\App\Config\CurrencyDecimals::roundPrice($docValue, $fields['currency'])) { // make sure floating point values are identically stored when compared
				$currMessages->addWarningCode('docValueDecimalsInvalid', '', 'docValue');
			}
		}

		// CHECK BANK ACCOUNT /////////////////////////////////////////////////////////////////////
		if ($docCfg->getBankAccount($docType)=="mandatory" and !$fields['idBankAccount']) {
			$OK=false;
			$currMessages->addWarningCode('bankAccNotSet', 'bankAccount');
		} elseif ($fields['idBankAccount']) {
			$ba = \Entity\AgencyBankAccount::select($fields['idBankAccount']);
			if (!$ba->recordExists() or $ba->deleted) {
				$OK=false;
				$currMessages->addWarningCode('bankAccInvalid', 'bankAccount');
			}
		}

		// CHECK EXPLANATIONS /////////////////////////////////////////////////////////////////////
		if ( $docCfg->getExplanations($docType)=="mandatory" and !$fields['explanations'] ) {
			$OK=false;
			$currMessages->addWarningCode('explNotSet', 'explanations');
		}

		// CHECK COMMENTS /////////////////////////////////////////////////////////////////////////
		if ( $docCfg->getComments($docType)=="mandatory" and !$fields['comments'] ) {
			$OK=false;
			$currMessages->addWarningCode('commentsNotSet', 'comments');
		}

		// CHECK IF THE DOCUMENT DOES NOT UNDERCASH OR OVERCASH THIS INVOICE //////////////////////
		if ($OK) {
			// the invoice total
			$invoiceTotal=$invoice->printTotal;
			// invoice cashed value (how much of the invoice will be cashed on this document
			$docValueInvCurr=\App\CurrencyExchange\Rates::getInstance($docDate)->convert($docValue, $fields['currency'], $invoice->currency, null);
			// new cashed status
			$newCashedValue=$invoice->cashedTotal+$docValueInvCurr;

			// INVOICE TOTAL IS POSITIVE ----------------------------------------------------------
			if ($invoiceTotal>=0) {
				// if total cashedValue is < 0
				if ( $newCashedValue<=-0.001 ) {
					$OK=false;
					$currMessages->addWarningCode('insert_negativeTotal');
				}
				// total cashedValue is > invoiceTotal + allow tolerance
				if ( $newCashedValue>$invoiceTotal*(1+\App\Config\InvoiceOverCashTolerance::getInstance()->get('positiveLimit')) ) {
					$OK=false;
					$currMessages->addWarningCode('insert_totalLessThanValue');;
				}
			}
			// INVOICE TOTAL IS NEGATIVE ----------------------------------------------------------
			else {
				// total cashedValue is > 0
				if ( $newCashedValue>=0.001 ) {
					$OK=false; $currMessages->addWarningCode('insert_positiveTotal');
				}
				// total cashedValue is < invoiceTotal - tolerance
				if ( $newCashedValue<$invoiceTotal*(1+\App\Config\InvoiceOverCashTolerance::getInstance()->get('negativeLimit')) ) {
					$OK=false;
					$currMessages->addWarningCode('insert_totalGreaterThanValue');
				}
			}
		}

		// CHECK CORPORATE CREDIT LIMIT: IF DOCUMENT IS NEGATIVE (CHECK CORPORATE DEBIT) //////////
		if ($CFG['corporateCreditLimit']['blockInvoicingActive']) { // if the blocking is active
    		if ($OK and $invoice->idCorporate and $docValue<0) {
    		    if ($invoice->corporate->creditLimitStatus == 'block') {
    				$OK=false;
    				$currMessages->addWarningCode('corpDebitExceedsBlockLimit');
    			}
    		}
		}


		// CHECK IMPORTED DOCUMENTS DETAILS ///////////////////////////////////////////////////////
		if (array_key_exists('idImportedDoc', $fields)) {

			if (!\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption("Modules/Documents/ImportedDocuments-process")) {
				$currMessages->addWarningCode('permissionDeniedToProcessDocument');
			}

			// get imported document details
			$importedDoc=\Entity\ImportedDocument::selectForUpdate($fields['idImportedDoc']);

			// check if document id is okay
			if (!$importedDoc->recordExists()) {
				$currMessages->addErrorCode('insert_impDocIdBad');
			}

			// check if the imported document category is cashing
			if ($importedDoc->docCategory!='cashing') {
				$currMessages->addErrorCode('insert_impDocNotCashing');
			}

			// check document status
			if ($importedDoc->status=='processed' or $importedDoc->status=='blocked' or $importedDoc->status=='stopped') {
				$OK=false;
				$currMessages->addWarningCode('insert_impDocStatusBad');
			}

			// check value sign depending on imported document sign
			if ($OK) {
				if ( ($fields['value']>=0 and $importedDoc->docValue<0) or ($fields['value']<0 and $importedDoc->docValue>=0) ) {
					$OK=false;
					$currMessages->addWarningCode('insert_cantChangeImpDocSign');
				}
			}

			// check that the currency doesn't change
			if ($fields['currency']!=$importedDoc->docCurrency) {
				$OK=false;
				$currMessages->addWarningCode('insert_cantChangeImpDocCurr', '', $importedDoc->docCurrency);
			}

			// check document total
			if ($OK) {
				// document total >= 0
				if ($fields['value']>=0) {
					if ($importedDoc->usedValue+$fields['value']>$importedDoc->docValue+0.001) {
						$OK=false;
						$currMessages->addWarningCode('insert_cantOverUseImpDoc', '', ($importedDoc->docValue-$importedDoc->usedValue).' '.$importedDoc->docCurrency);
					}
				}
				// document total < 0
				else {
					if ($importedDoc->usedValue+$fields['value']<$importedDoc->docValue-0.001) {
						$OK=false;
						$currMessages->addWarningCode('insert_cantOverUseImpDoc', '', ($importedDoc->docValue-$importedDoc->usedValue).' '.$importedDoc->docCurrency);
					}
				}
			}
		}


		// FIX TABLES AUTO-INCREMENTS /////////////////////////////////////////////////////////////
		if ($OK) {
			\App\Utils\Db\TablesUtils::getInstance()->checkMonthTableAutoincrement($this->entity->getTableGroup(), $this->entity->getTableMonth());
		}

		return $OK;

	}

	protected function isAccessible_update()
	{

		// current collected messages
		$currMessages=$this->entity->getMessages();

		if (!\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('Modules/Invoices/Cashings/Listing-update')) {
			$currMessages->addWarningCode('noAccessToOption');
		}

	}

	protected function isAvailable_update()
	{

		// current collected messages
		$currMessages=$this->entity->getMessages();

		// current entity id
		$currentIdEntity=$this->entity->getId();

		$doc=\Entity\InvoiceCashing::selectForUpdate($currentIdEntity);

		// CHECK THE DOCUMENT INFO ////////////////////////////////////////////////////////////////
		if (!$doc->recordExists()) {
			$currMessages->addErrorCode('update_invalidId');
		} else {
			if ($doc->voidTime) {
				$currMessages->addWarningCode('update_docVoided');
			}
			if ($doc->blockTime) {
				$currMessages->addWarningCode('update_docBlocked');
			}
			if ($doc->issueTime) {
				$currMessages->addWarningCode('update_docIssued');
			}
		}

	}


	/**
	 * Validates the fields for updating
	 * @return Boolean
	 */
	protected function check_update()
	{

		// get current fields details
		$fields=$this->entity->getFields();

		// current collected messages
		$currMessages=$this->entity->getMessages();

		// current entity id
		$currentIdEntity=$this->entity->getId();

		$OK=true;

		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();

		$docCfg = \App\Config\CashingDocuments::getInstance();

		// CHECK THAT BOTH VALUE AND CURRENCY ARE SET - OR NONE -----------------------------------
		if ( (array_key_exists('value', $fields) and !array_key_exists('currency', $fields)) or (!array_key_exists('value', $fields) and array_key_exists('currency', $fields)) ) {
			throw new \Exception('Both value and currency or none must be set when updating a cashing document.');
		}

		// document object
		$doc=\Entity\InvoiceCashing::selectForUpdate($currentIdEntity);

		// the cashing document invoice
		$invoice=$doc->invoice;

		// CHECK DOC TYPE (also sets $docType) ////////////////////////////////////////////////////
		if (array_key_exists('docType', $fields)) {
			$docType=$fields['docType'];
			// check if this is a valid doc type
			if ($docType and !\App\Config\CashingDocuments::getInstance()->isActive($docType)) {
				$currMessages->addErrorCode('docTypeInvalid', 'docType');
			}
		} else {
			$docType=$doc->docTypeCode;
		}

		// CHECK ACCESS BY DOCUMENT TYPE //////////////////////////////////////////////////////////
		$userDocBlock=\App\UserAccess\CurrentUserAccessFast::getInstance()->getAccessOptionValue('InvoiceCashings-mngDocsBlockedByDocType');
		if (is_array($userDocBlock) and in_array($doc->docTypeCode, $userDocBlock)) {
			$OK=false;
			$currMessages->addWarningCode('update_blockedDocType', 'docType');
		}
		elseif (is_array($userDocBlock) and $fields['docType'] and in_array($fields['docType'], $userDocBlock)) {
			$OK=false;
			$currMessages->addWarningCode('update_blockedDocType', 'docType');
		}

		// CHECK DOCUMENT DATE ////////////////////////////////////////////////////////////////////
		if (array_key_exists('docDate', $fields)) {
			$docDate = $fields['docDate'];
			// CHECK IF THE DATE IS VALID ---------------------------------------------------------
			if ($docDate and !validDate($docDate)) {
				$OK=false;
				$currMessages->addWarningCode('docDateInvalid', 'docDate');
			}
			// CHECK THAT THE DOC DATE HAS CURRENCY RATES SET -------------------------------------
			if ($docDate and !\App\CurrencyExchange\Date::getInstance($docDate)->getLastDefinedDate()) {
				$OK=false;
				$currMessages->addWarningCode('docDateNoCurrRates', 'docDate');
			}
		} else {
			$docDate=$doc->docDate;
		}

		// check that both/none of office/user are set
		if ( (array_key_exists('idOffice', $fields) and !array_key_exists('idUser', $fields)) or (!array_key_exists('idOffice', $fields) and array_key_exists('idUser', $fields)) ) {
			throw new \Exception('The office and user id can either be both set or none of them.');
		}

		// CHECK ID OFFICE ////////////////////////////////////////////////////////////////////////
		if (array_key_exists('idOffice', $fields)) {
			$idOffice=$fields['idOffice'];
			$off=\Entity\Office::selectForUpdate($fields['idOffice']);
			if (!$off->recordExists()) {
				$currMessages->addErrorCode('invalidOfficeId');
			} elseif ($off->deleted) {
				$OK=false;
				$currMessages->addWarningCode('officeWasDeleted');
			}
		} else {
			$idOffice=$doc->idOffice;
		}

		// CHECK ID USER //////////////////////////////////////////////////////////////////////////
		if (array_key_exists('idUser', $fields)) {
			$usr=\Entity\User::selectForUpdate($fields['idUser']);
			if (!$usr->recordExists() or $usr->deleted) {
				$currMessages->addErrorCode('invalidUserId');
			} elseif ($usr->idOffice!=$idOffice) {
				$currMessages->addErrorCode('userFromOtherOffice');
			}
		}

		// CHECK IF THE DOCUMENT IS ALREADY INCLUDED IN THE CASHBOOK ------------------------------
		// if document type is included in the cashbook
		$cashingDocsCfgType = \App\Config\CashingDocuments::getInstance()->getGroup($doc->docTypeCode);
		if ($cashingDocsCfgType == 'cash') {
			$lastClosedDate=\Cashbook::getLastClosedBalanceDate($doc->idOffice);
			// if last closed date exists (cashbook is used) and the date is smaller than last closed balance
			if ($lastClosedDate and $doc->docDate<=$lastClosedDate) {
				$OK=false;
				$currMessages->addWarningCode('update_docIncludedInCashbookBalance', 'docDate', \Engine\DateTime::createFromIsoDate($lastClosedDate)->formatDateStd());
			}
		}

		// CHECK IF THE DOCUMENT WOULD BE INCLUDED IN CASHBOOK AFTER THE UPDATE (before a closed balance)
		// if document type is included in the cashbook
		$cashingDocsCfgType = \App\Config\CashingDocuments::getInstance()->getGroup($docType);
		if ($cashingDocsCfgType == 'cash') {
			$lastClosedDate=\Cashbook::getLastClosedBalanceDate($idOffice);
			// if last closed date exists (cashbook is used) and if the date is smaller than last closed balance
			if ($lastClosedDate and $docDate<=$lastClosedDate) {
				$OK=false;
				$currMessages->addWarningCode('update_docWillBeIncludedInCashbookBalance', '', [\Engine\DateTime::createFromIsoDate($lastClosedDate)->formatDateStd(), \Entity\Office::selectForUpdate($idOffice)->name]);
			}
		}

		// AN IMPORTED DOCUMENT CANT HAVE ITS VALUE/CURRENCY CHANGED //////////////////////////////
		if ($doc->idImportedDoc) {
			if (array_key_exists('value', $fields) or array_key_exists('currency', $fields)) {
				throw new \Exception('An imported document cant have its value or currency changed.');
			}
		}

		// get issuable document type
		$issuableDocTypes = \App\Config\IssueDocuments::getInstance()->getCashingDocsList($docType);

		// determine the new docNumber
		if (array_key_exists('docNumber', $fields)) $docNumber=$fields['docNumber'];
		else $docNumber=$doc->docNumber;

		// determine the new idCashingSeries
		if (array_key_exists('idCashingSeries', $fields)) $idCashingSeries=$fields['idCashingSeries'];
		else $idCashingSeries=$doc->idCashingSeries;

		// determine document value
		if (array_key_exists('value', $fields)) $docValue=$fields['value'];
		else $docValue=$doc->value;

		// determine document currency
		if (array_key_exists('currency', $fields)) $docCurrency=$fields['currency'];
		else $docCurrency=$doc->currency;

		// determine document bank account
		if (array_key_exists('idBankAccount', $fields)) $docIdBankAccount=$fields['idBankAccount'];
		else $docIdBankAccount=$doc->idBankAccount;

		// determine document explanations
		if (array_key_exists('explanations', $fields)) $docExplanations=$fields['explanations'];
		else $docExplanations=$doc->explanations;

		// determine document comments
		if (array_key_exists('comments', $fields)) $docComments=$fields['comments'];
		else $docComments=$doc->comments;

		// CHECK DOCUMENT SERIES/NUMBER ///////////////////////////////////////////////////////////
		// CHECK IF MANUAL DOCUMENT NUMBER IS MANDATORY ---------------------------------------
		if ($docCfg->getNumber($docType)=='mandatory' and !$docNumber) {
			$OK=false;
			$currMessages->addWarningCode('update_docNumberNotSet', 'docNumber');
		}
		// CHECK IF DOCUMENT NUMBER MUST NOT BE SET -------------------------------------------
		if ( array_key_exists('docNumber', $fields) and ($docCfg->getNumber($docType)=='series' or $docCfg->getNumber($docType)=='hidden') ) {
			$OK=false;
			$currMessages->addWarningCode('update_docNumberMustNotBeSet');
		}
		// IF CASHING SERIES MUST NOT BE SET --------------------------------------------------
		if ($docCfg->getNumber($docType)!="series" and $idCashingSeries) {
			$OK=false;
			$currMessages->addWarningCode('update_docSeriesMustNotBeSet');
		}

		// CHECK DOCUMENT SERIES/NUMBER ///////////////////////////////////////////////////////////
		// CHECK IF DOCUMENT HAS SERIES (manual docNumber must NOT be set - checked above) --------
		if ($docCfg->getNumber($docType)=="series") {
			if (!$idCashingSeries) { // id cashing series must be set
				$OK=false;
				$currMessages->addWarningCode('docSeriesNotSet', 'idCashingSeries');
			}
			if (!$issuableDocTypes) { // this should never happen
				$currMessages->addErrorCode('docNotIssuable');
			}
		}

		// CHECK SERIES DETAILS ///////////////////////////////////////////////////////////////////
		if ($OK and $idCashingSeries) {
			// get series details
			$seriesDetails=\Entity\DocumentSeries::select($idCashingSeries);
			// check if record exists
			if (!$seriesDetails->recordExists()) {
				$OK=false; $currMessages->addWarningCode('badIdSeries', 'idCashingSeries');
			}
			// check if series was deleted
			elseif ($seriesDetails->deleted) {
				$OK=false; $currMessages->addWarningCode('seriesDeleted', 'idCashingSeries');
			}
			// check series status
			elseif ($seriesDetails->status!='WRK') {
				$OK=false; $currMessages->addWarningCode('seriesStatusBad', 'idCashingSeries');
			}
			// check document type
			if ($OK and !array_intersect(array_keys($issuableDocTypes), $seriesDetails->documentTypes)) {
				$OK=false; $currMessages->addWarningCode('badSeriesDocType', 'idCashingSeries');
			}
			// check series currency
			if ($OK and !$seriesDetails->allCurrencies and !$seriesDetails->validCurrenciesList[ $docCurrency ]) {
				$OK=false; $currMessages->addWarningCode('invoiceSeriesCurrRestrict');
			}
			// check validity date
			if ($OK and ($docDate<$seriesDetails->validStartDate or $docDate>$seriesDetails->validEndDate) ) {
				$OK=false; $currMessages->addWarningCode('invoiceSeriesInvalidPeriod', 'idCashingSeries');
			}
		}

		// CHECK DOCUMENT VALUE SIGN //////////////////////////////////////////////////////////////
		if ($docCfg->getSign($docType)==-1 and $docValue>0) {
			$OK=false;
			$currMessages->addWarningCode('docMustBeNegative', 'value');
		}
		if ($docCfg->getSign($docType)==1 and $docValue<0) {
			$OK=false;
			$currMessages->addWarningCode('docMustBePositive', 'value');
		}

		// CHECK DOC CURRENCY /////////////////////////////////////////////////////////////////////
		if (array_key_exists('currency', $fields) and !\App\Config\Currencies::getInstance()->check($docCurrency)) {
			$currMessages->addErrorCode('docCurrencyInvalid', '', $docCurrency);
		}
		if ($docCurrency and $docValue) {
			if (round($docValue, 6)!=\App\Config\CurrencyDecimals::roundPrice($docValue, $docCurrency)) { // make sure floating point values are identically stored when compared
				$currMessages->addWarningCode('docValueDecimalsInvalid', '', 'docValue');
			}
		}


		// CHECK BANK ACCOUNT /////////////////////////////////////////////////////////////////////
		if ($docCfg->getBankAccount($docType)=="mandatory" and !$docIdBankAccount) {
			$OK=false;
			$currMessages->addWarningCode('bankAccNotSet', 'bankAccount');
		} elseif (array_key_exists('idBankAccount', $fields) and $docIdBankAccount) { // if the bank account was set this time
			$ba = \Entity\AgencyBankAccount::select($docIdBankAccount);
			if (!$ba->recordExists() or $ba->deleted) {
				$OK=false;
				$currMessages->addWarningCode('bankAccInvalid', 'bankAccount');
			}
		}

		// CHECK EXPLANATIONS /////////////////////////////////////////////////////////////////////
		if ( $docCfg->getExplanations($docType)=="mandatory" and !$docExplanations ) {
			$OK=false;
			$currMessages->addWarningCode('explNotSet', 'explanations');
		}

		// CHECK COMMENTS /////////////////////////////////////////////////////////////////////////
		if ( $docCfg->getComments($docType)=="mandatory" and !$docComments ) {
			$OK=false;
			$currMessages->addWarningCode('commentsNotSet', 'comments');
		}

		// CHECK IF THE DOCUMENT DOES NOT UNDERCASH OR OVERCASH THIS INVOICE //////////////////////
		if ($OK) {
			// the invoice total
			$invoiceTotal=$invoice->printTotal;
			// invoice cashed value (without the current document)
			$invoiceCashedValue=$invoice->cashedTotal - $doc->invoiceCashedValue;
			// invoice cashed value (how much of the invoice will be cashed on this document
			$docValueInvCurr=\App\CurrencyExchange\Rates::getInstance($docDate)->convert($docValue, $docCurrency, $invoice->currency, null);
			// new cashed status
			$newCashedValue=$invoiceCashedValue+$docValueInvCurr;

			// INVOICE TOTAL IS POSITIVE ----------------------------------------------------------
			if ($invoiceTotal>=0) {
				// if total cashedValue is < 0
				if ( $newCashedValue<=-0.001 ) {
					$OK=false;
					$currMessages->addWarningCode('update_negativeTotal');
				}
				// total cashedValue is > invoiceTotal + tolerance
				if ( $newCashedValue>$invoiceTotal*(1+\App\Config\InvoiceOverCashTolerance::getInstance()->get('positiveLimit')) ) {
					$OK=false; $currMessages->addWarningCode('update_totalLessThanValue');;
				}
			}
			// INVOICE TOTAL IS NEGATIVE ----------------------------------------------------------
			else {
				// total cashedValue is > 0
				if ( $newCashedValue>=0.001 ) {
					$OK=false; $currMessages->addWarningCode('update_positiveTotal');
				}
				// total cashedValue is < invoiceTotal - tolerance
				if ( $newCashedValue<$invoiceTotal*(1+\App\Config\InvoiceOverCashTolerance::getInstance()->get('negativeLimit')) ) {
					$OK=false; $currMessages->addWarningCode('update_totalGreaterThanValue');
				}
			}
		}

		// CHECK CORPORATE CREDIT LIMIT: IF DOCUMENT IS NEGATIVE (CHECK CORPORATE DEBIT) //////////
		if ($CFG['corporateCreditLimit']['blockInvoicingActive']) { // if the blocking is active
    		if ($OK and $invoice->idCorporate and $docValue<0) {
    		    if ($invoice->corporate->creditLimitStatus == 'block') {
    				$OK=false;
    				$currMessages->addWarningCode('corpDebitExceedsBlockLimit');
    			}
    		}
		}

		return $OK;

	}



	protected function isAccessible_block()
	{

		// current collected messages
		$currMessages=$this->entity->getMessages();

		// CHECK USER ACCESS ----------------------------------------------------------------------
		if (!\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('Modules/Invoices/Cashings/Listing-block')) {
			$currMessages->addWarningCode('noAccessToOption');
		}
	}

	protected function isAvailable_block()
	{

		// current collected messages
		$currMessages=$this->entity->getMessages();

		// current entity id
		$currentIdEntity=$this->entity->getId();

		// get cashing details
		$doc=\Entity\InvoiceCashing::selectForUpdate($currentIdEntity);

		// CHECK THE DOCUMENT DETAILS ------------------------------------------------------------
		if (!$doc->recordExists()) {
			$currMessages->addErrorCode('block_docIdInvalid');
		} elseif ($doc->voidTime) {
			$currMessages->addWarningCode('block_docVoided');
		} elseif ($doc->blockTime) {
			$currMessages->addWarningCode('block_docBlocked');
		}

	}



	protected function isAccessible_unblock()
	{

		// current collected messages
		$currMessages=$this->entity->getMessages();

		// CHECK USER ACCESS ----------------------------------------------------------------------
		if (!\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('Modules/Invoices/Cashings/Listing-unblock')) {
			$currMessages->addWarningCode('noAccessToOption');
		}
	}

	protected function isAvailable_unblock()
	{

		// current collected messages
		$currMessages=$this->entity->getMessages();

		// current entity id
		$currentIdEntity=$this->entity->getId();

		// get cashing details
		$doc = \Entity\InvoiceCashing::selectForUpdate($currentIdEntity);

		// CHECK THE DOCUMENT DETAILS ------------------------------------------------------------
		if (!$doc->recordExists()) {
			$currMessages->addErrorCode('block_docIdInvalid');
		} elseif ($doc->voidTime) {
			$currMessages->addWarningCode('unblock_docVoided');
		} elseif (!$doc->blockTime) {
			$currMessages->addWarningCode('unblock_docUnblocked');
		}

		// CHECK IF UNBLOCKING IS STILL ALLOWED ---------------------------------------------------

		$unblockDoc = \App\Config\DocumentsUnblocking::getInstance()->get('cashing');
		if (!$unblockDoc['unblockEnabled']) {
			$currMessages->addWarningCode('noAccessToOption');
		} else {
			$allowUntilDate=date(MYSQL_FORMAT, strtotime($doc->docDate.' + '.$unblockDoc['unblockAllowedFor'].' days'));
			if (TODAY_ISO>$allowUntilDate) {
				$currMessages->addWarningCode('cantUnblockBadPeriod', '', \Engine\DateTime::isoToStd($allowUntilDate));
			}
		}

	}


	protected function isAvailable_cancel()
	{

		// current collected messages
		$currMessages=$this->entity->getMessages();

		// current entity id
		$currentIdEntity=$this->entity->getId();

		$OK=true;

		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();

		// CHECK IF DOCUMENT ID IS OKAY ///////////////////////////////////////////////////////////
		$doc=\Entity\InvoiceCashing::selectForUpdate($currentIdEntity);
		if (!$doc->recordExists()) {
			$currMessages->addErrorCode('cancel_invalidId');
		} elseif ($doc->voidTime) {
			$OK=false;
			$currMessages->addWarningCode('cancel_docVoided');
		} elseif ($doc->blockTime) {
			$OK=false;
			$currMessages->addWarningCode('cancel_docBlocked');
		}

		// CHECK ACCESS BY DOCUMENT TYPE //////////////////////////////////////////////////////////
		$userDocBlock=\App\UserAccess\CurrentUserAccessFast::getInstance()->getAccessOptionValue('InvoiceCashings-mngDocsBlockedByDocType');
		if (is_array($userDocBlock) and in_array($doc->docTypeCode, $userDocBlock)) {
			$OK=false;
			$currMessages->addWarningCode('cancel_blockedDocType');
		}

		// check voiding of issued docs
		if ($doc->issueTime and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('Modules/Invoices/Cashings/Listing-voidIssued')) {
			$currMessages->addWarningCode('noAccessToOption');
		}

		// check voiding of unissued docs
		if (!$doc->issueTime and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('Modules/Invoices/Cashings/Listing-voidNotIssued')) {
			$currMessages->addWarningCode('noAccessToOption');
		}

		// the cashing document invoice (also blocks the invoice)
		$invoice=$doc->invoice;

		// the imported document (also blocks the imported doc)
		$importedDocument=$doc->importedDocument;

		if ($importedDocument and $importedDocument->status=='stopped') {
			$currMessages->addErrorCode('cancel_impDocStopped');
		}

		// CHECK IF THE DOC IS ISSUED AND IT CAN BE VOIDED ////////////////////////////////////////
		if ($OK and $doc->issueTime) {
			$voidingDetails=\App\Config\CashingVoiding::getInstance()->getAll();
			// CHECK THE PERIOD -------------------------------------------------------------------
			if (!$voidingDetails['allow']) {
				$OK=false;
				$currMessages->addWarningCode('cancel_voidingBlocked');
			} else {
				$period=countDaysBetween($doc->docDate, TODAY_ISO);
				// period exceeded
				if ($period>$voidingDetails['fixedDays']) {
					$OK=false;
					if (!$voidingDetails['fixedDays']) // the same day
						$currMessages->addWarningCode('cancel_voidDocOnIssueDay');
					else // 1 or more more than 1 days
						$currMessages->addWarningCode('cancel_voidDocOnPeriod', '', $voidingDetails['fixedDays']);
				}
			}
			// CHECK IF THE SAME MONTH ---------------------------------------------------------------
			if ($OK and $voidingDetails['sameMonth']) {
				if ( \Engine\DateTime::createFromIsoDate($doc->docDate)->format('Ym') != TODAY_YEAR.TODAY_MONTH ) {
					$OK=false;
					$currMessages->addWarningCode('cancel_voidDocOnIssueMonth');
				}
			}
		}


		// CHECK IF THE VALUE EXCEEDS THE LIMITS //////////////////////////////////////////////////
		if ($OK) {

			// the invoice total
			$invoiceTotal=$invoice->printTotal;
			// invoice cashed value (how much of the invoice will be cashed on this document
			$docValueInvCurr=\App\CurrencyExchange\Rates::getInstance($doc->docDate)->convert($doc->value, $doc->currency, $invoice->currency, null);
			// new cashed status
			$newCashedValue=$invoice->cashedTotal-$docValueInvCurr;

			// INVOICE TOTAL IS POSITIVE ----------------------------------------------------------
			if ($invoiceTotal>=0) {
				// if total cashedValue is < 0
				if ( $newCashedValue<=-0.001 ) {
					$OK=false;
					$currMessages->addWarningCode('cancel_negativeTotal');
				}
				// total cashedValue is > invoiceTotal + tolerance
				if ( $newCashedValue>$invoiceTotal*(1+\App\Config\InvoiceOverCashTolerance::getInstance()->get('positiveLimit')) ) {
					$OK=false;
					$currMessages->addWarningCode('cancel_totalLessThanValue');;
				}
			}
			// INVOICE TOTAL IS NEGATIVE ----------------------------------------------------------
			else {
				// total cashedValue is > 0
				if ( $newCashedValue>=0.001 ) {
					$OK=false;
					$currMessages->addWarningCode('cancel_positiveTotal');
				}
				// total cashedValue is < invoiceTotal - tolerance
				if ( $newCashedValue<$invoiceTotal*(1+\App\Config\InvoiceOverCashTolerance::getInstance()->get('negativeLimit')) ) {
					$OK=false;
					$currMessages->addWarningCode('cancel_totalGreaterThanValue');
				}
			}

		}

		// COMPARE THE DOCUMENT DATE WITH THE CASHBOOK LAST CLOSED DATE ///////////////////////////
		if ($OK) {
			// if document type is included in the cashbook
			$cashingDocsCfgType = \App\Config\CashingDocuments::getInstance()->getGroup($doc->docTypeCode);
			if ($cashingDocsCfgType == 'cash') {
				$lastClosedDate=\Cashbook::getLastClosedBalanceDate($doc->idOffice);
				// if last closed date exists (cashbook is used)
				if ($lastClosedDate) {
					// if the date is smaller than last closed balance
					if ($doc->docDate<=$lastClosedDate) {
						$OK=false;
						$currMessages->addWarningCode('cancel_docInCashbook', '', $lastClosedDate);
					}
				}
			}
		}

		// CHECK CORPORATE CREDIT LIMIT (FOR POSITIVE DOCUMENTS) //////////////////////////////////
		if ($OK and $CFG['corporateCreditLimit']['blockInvoicingActive']) { // if the blocking is active
		    if ($invoice->idCorporate and $doc->value>0) {
    		    if ($invoice->corporate->creditLimitStatus == 'block') {
    				$OK=false;
    				$currMessages->addWarningCode('cancel_creditLimitMet');
    			}
		    }
		}

	}

	/**
	 * Checks the field for voiding docs (esp. void reason).
	 */
	protected function check_cancel()
	{

		// current collected messages
		$currMessages=$this->entity->getMessages();

		// get current fields details
		$fields=$this->entity->getFields();

		$entityCfg = \Entity\InvoiceCashing::loadCFG();

		if ($entityCfg['_special']['voidReasonMandatory'] and !$fields['voidReason']) {
			$currMessages->addWarningCode('cancel_voidReasonMandatory');
		}
		if ($fields['voidReason'] and !in_array($fields['voidReason'], $entityCfg['_special']['voidReasons'])) {
			$currMessages->addWarningCode('cancel_voidReasonInvalid');
		}

	}

}

