<?php

namespace Entity;

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

	protected function check_insert()
	{

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

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

		// document configs
		$cfg=$this->entity->getCFG();

		$OK=true;

		global $CFG;

		// the object class and array of details
		$docCfgObj = \App\Config\IssueDocuments::getInstance();
		$docCfgArr = $docCfgObj->get($fields['docType']);

		// CHECK MANDATORY FIELDS /////////////////////////////////////////////////////////////////
		if (!$fields['pivot']) throw new \Exception('Document `pivot` is not set.');
		if (!$fields['idPivot']) throw new \Exception('Document `pivot id` is not set.');
		if (!$fields['docType']) throw new \Exception('Document type is not set.');
		if (!$fields['generation']) throw new \Exception('Document `generation` type is not set.');
		if ($fields['docType']) {
			if (!$docCfgObj->exists($fields['docType'])) {
				throw new \Exception('The document type is not valid.');
			}
			if (!$docCfgObj->isActive($fields['docType'])) {
				$currMessages->addErrorCode('insert_inactiveGlobalDocType');
			}
		}

		// check services list - allowed only for reservations
		if (array_key_exists('servicesList', $fields) and $fields['pivot']!='order') {
			throw new \Exception('The `servicesList` attribute can be set only for `reservation` pivot.');
		}

		// CHECK THAT PIVOT AND DOCUMENT TYPE ARE CORRECT /////////////////////////////////////////
		if ($docCfgObj->getDocPivot($fields['docType'])!=$fields['pivot']) throw new \Exception('Pivot and document type are not correctly linked.');

		// CHECK USER ACCESS //////////////////////////////////////////////////////////////////////
		if (
			($fields['pivot']=="invoice" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('invoiceDocumentsManagement')) or
			($fields['pivot']=="cashing" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('cashingDocumentsManagement')) or
			($fields['pivot']=="payment" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('paymentDocumentsManagement')) or
			($fields['pivot']=="order" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('reservationDocumentsManagement')) or
			($fields['pivot']=="service" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('serviceDocumentsManagement')) or
			($fields['pivot']=="cashBookDoc" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('cashBookDocumentsManagement')) or
			($fields['pivot']=="importedService" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('importedServiceDocumentsManagement')) or
			($fields['pivot']=="importedDocument" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('importedDocsDocumentsManagement')) or
			($fields['pivot']=="balance" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('balanceDocumentsManagement'))
		) {
			$OK=false;
			$currMessages->addWarningCode('noAccessToOption');
		}

		// CHECK SERVICE DOC TYPE - depends on the service code ///////////////////////////////////
		if ($fields['pivot']=="service" and $fields['docType']) {
			$serviceEnt = \Entity\Service::selectForUpdate($fields['idPivot']);
			if ($serviceEnt->chosenOfferId) {
				$servDocCfg = \App\Config\IssueDocuments::getInstance()->getServiceDocsList($serviceEnt->chosenOffer->serviceCode);
			}
			else {
				$servDocCfg = \App\Config\IssueDocuments::getInstance()->getServiceDocsList($serviceEnt->serviceCode);
			}
			if (!$servDocCfg or !array_key_exists($fields['docType'], $servDocCfg)) {
				$OK=false;
				$currMessages->addWarningCode('insert_docTypeNotActive');
			}
		}

		// GET PIVOT DETAILS //////////////////////////////////////////////////////////////////////
		$pivotObj=$this->entity->getInsertingDocumentPivot();
		// check if the pivot exists
		if (!$pivotObj->recordExists()) {
			$currMessages->addErrorCode('insert_invalidPivotId');
		}

		// the list of generated documents
		$docsList=$pivotObj->documentsList;

		// CHECK DOC CATEGORY /////////////////////////////////////////////////////////////////////
		$docCategory = $docCfgObj->getDocCategory($fields['docType']);
		if ($docCategory) {
			if ($fields['pivot']=="cashing" and $docCategory!=$pivotObj->docType) {
				$OK=false;
				$currMessages->addErrorCode('insert_invalidDocCategory');
			}
			if ($fields['pivot']=="payment" and $docCategory!=$pivotObj->docType) {
				$OK=false;
				$currMessages->addErrorCode('insert_invalidDocCategory');
			}
			if ($fields['pivot']=="cashBookDoc" and $docCategory!=$pivotObj->type) {
				$OK=false;
				$currMessages->addErrorCode('insert_invalidDocCategory');
			}
			if ($fields['pivot']=="importedDocument" and $docCategory!=$pivotObj->docType) {
				$OK=false;
				$currMessages->addErrorCode('insert_invalidDocCategory');
			}
		}

		// CHECK THAT THE TEMPLATE IS SET FOR 'manual' DOCS ///////////////////////////////////////
		if ($fields['generation']=='auto' and !$fields['idTemplate']) {
			throw new \Exception('The template id must be set for `auto` generated documents.');
		}
		if ($fields['generation']=='manual' and $fields['idTemplate']) {
			throw new \Exception('The template id must NOT be set for `manual` generated documents.');
		}

		// get template object
		$docTpl = null;
		if ($fields['idTemplate']) {
			$docTpl = \Entity\DocumentTemplate::select($fields['idTemplate']);
		}

		// CHECK IF THE TEMPLATE ID IS VALID //////////////////////////////////////////////////////
		if ($fields['idTemplate']) {
			if (!$docTpl->recordExists()) {
				$OK=false;
				$currMessages->addWarningCode('insert_invalidDocumentTemplate');
			} elseif ($docTpl->deleted) {
				$OK=false;
				$currMessages->addWarningCode('insert_templateDeleted');
			} elseif (!$docTpl->active) {
				$OK=false;
				$currMessages->addWarningCode('insert_templateInactive');
			} elseif ($docTpl->docType!=$fields['docType']) {
				$OK=false;
				$currMessages->addWarningCode('insert_invalidDocumentTemplate');
			} elseif (!$docTpl->allUsers and $fields['idIssueUser'] and !in_array($fields['idIssueUser'], $docTpl->validUsersList)) {
				$OK=false;
				$currMessages->addWarningCode('insert_userCantUseTemplate');
			}
			// check template content
			if (!$docTpl->templateContent) {
				$OK=false;
				$currMessages->addWarningCode('insert_templateContentUndefined');
			}
		}

		// CHECK AUTO PROCESS RULE
		if ($fields['idAutoProcessRule']) {
			$autoPrRule = \Entity\AutoProcessRule::select($fields['idAutoProcessRule']);
			if (!$autoPrRule->recordExists()) {
				$OK=false;
				$currMessages->addWarningCode('invalidAutoProcessRule');
			}
			elseif ($autoPrRule->deleted) {
				$OK=false;
				$currMessages->addWarningCode('deletedAutoProcessRule');
			}
			elseif (!$autoPrRule->active) {
				$OK=false;
				$currMessages->addWarningCode('inactiveAutoProcessRule');
			}
		}

		// CHECK IF THE FORMAT IS VALID ///////////////////////////////////////////////////////////
		if ($OK) {
			// get format depending on generation type
			if ($fields['generation']=='manual') {
				$docFormat=$fields['format'];
			} else {
				if ($fields['format']) throw new \Exception('The document `format` cannot be set for automatically generated documents.');
				$docFormat=$docTpl->format;
			}

			if (!$docFormat) {
				$OK=false;
				$currMessages->addWarningCode('insert_noDocumentFormat');
			}

			if ($fields['generation']=='manual') {	// manual uploaded document
				// check format
				if (!in_array($docFormat, $CFG['documents']['manualFormats'])) {
					$OK=false;
					$currMessages->addWarningCode('insert_invalidDocFormat');
				}
			} else {	// automatic generation
				// check format
				if (!$CFG['documents']['formats'][$docFormat]) {
					$OK=false;
					$currMessages->addWarningCode('insert_invalidDocFormat');
				} elseif (!$CFG['documents']['formats'][$docFormat]['active']) {
					$OK=false;
					$currMessages->addWarningCode('insert_inactiveDocFormat');
				}
			}
		}

		// CHECK FILE NAME/CONTENT ////////////////////////////////////////////////////////////////
		if ($fields['generation']=='manual') {
			if (!$fields['docFile']) {
				$OK=false;
				$currMessages->addWarningCode('insert_fileNameNotSet');
			}
			if (!$fields['docContent']) {
				$OK=false;
				$currMessages->addWarningCode('insert_fileContentNotSet');
			}
			if ($fields['docFile']) {
				$docNameOrig=$fields['docFile'];
				$docNameFiltered=preg_replace('/[^a-zA-Z0-9\(\)\.\-\_\ \[\]\{\}]/', '', $docNameOrig);
				if ($docNameFiltered!=$docNameOrig) {
					$OK=false;
					$currMessages->addWarningCode('insert_fileNameNotClean');
				}
			}
		}
		// check docContent and docFile fields for 'auto' generation
		if ($fields['generation']=='auto' and array_key_exists('docContent', $fields)) throw new \Exception('You cannot set the `docContent` field for a new `auto` generating document.');
		if ($fields['generation']=='auto' and array_key_exists('docFile', $fields)) throw new \Exception('You cannot set the `docFile` field for a new `auto` generating document.');


		// CHECK DOCUMENT SERIES/NUMBER/DATE //////////////////////////////////////////////////////
		$OK=($this->check_insert_docNumberAndDate() and $OK);


		// CHECK RESERVATION SPECIFIC VALIDATIONS /////////////////////////////////////////////////
		if ($fields['pivot']=='order') {

			// CHECK THE SERVICES LIST ------------------------------------------------------------
			if ($fields['servicesList']) {
				$servicesList=\Entity\Order::selectForUpdate($fields['idPivot'])->servicesList;
				foreach($fields['servicesList'] as $idServ) {
					if (!$servicesList[$idServ]) {
						$currMessages->addErrorCode('insert_invalidServId', '', $idServ);
					} elseif ($servicesList[$idServ]['statusOp']=='XX') {
						$currMessages->addErrorCode('insert_cancelledServId', '', $idServ);
					}
				}
			}

		}


		// CHECK INVOICE SPECIFIC VALIDATIONS /////////////////////////////////////////////////////
		$OK=($this->check_insert_invoice() and $OK);

		// CHECK TEMPLATE MANDATORY FIELDS ////////////////////////////////////////////////////////
		if ($fields['idTemplate']) {
			// CHECK TEMPLATE STANDARD FIELDS -----------------------------------------------------
			// this will store all mandatory and required fields
			$mandatoryFields= [];
			if ($docTpl->fieldsDetails) {
				// loop and attach each mandatory/required fields
				foreach($docTpl->fieldsDetails as $fld=>$type) {
					if ($type=='mandatory' or $type=='required') {
						$mandatoryFields[$fld]=$type;
					}
				}
			}
			// rewrite doc fields configs based on criteria, values and other info
			if ($cfg['_special']['rewriteFieldsDetailsOnDocGeneration'][$fields['docType']]) {
				foreach($cfg['_special']['rewriteFieldsDetailsOnDocGeneration'][$fields['docType']] as $fld) {
					if ( \App\GenDocument\Vars\PivotBuilderValues::fromPivot($fields['pivot'], $fields['idPivot'], $fields['docType'])->getValue($fld['criterionField']) == $fld['criterionValue'] ) {
						$mandatoryFields[$fld['updateField']] = $fld['updateValue'];
					}
				}
			}

			// check m/r fields
			if ($mandatoryFields) {
				foreach($mandatoryFields as $fld=>$type) {
					// if the field has no value
					if (!\App\GenDocument\Vars\PivotBuilderValues::fromPivot($fields['pivot'], $fields['idPivot'], $fields['docType'])->getValue($fld)) {
						$OK=false;
						$currMessages->addWarningCode('insert_mandatoryStdField', '', \App\GenDocument\Vars\PivotBuilderConfig::fromPivot($fields['pivot'])->getName($fld));
					}
				}
			}


			// CHECK TEMPLATE CUSTOM FIELDS -------------------------------------------------------
			if ($docTpl->customFieldsDetails) {
				$pivotVars = \App\GenDocument\Vars\PivotBuilderValues::fromPivot($fields['pivot'], $fields['idPivot'], $fields['docType']);
				// loop through each mandatiry/required fields
				foreach($docTpl->customFieldsDetails as $fld=>$fldDet) {
					if ($fldDet['type']=='mandatory') {
						// if the field has no value
						if ( ! ($pivotVars->getValue($fld) or ($pivotVars->getValue($fld)===null and $fldDet['value'])) ) {
							$OK=false;
							$currMessages->addWarningCode('insert_mandatoryCustField', '', $fldDet['name']);
						}
					}
				}
			}
		}


		// CHECK THAT THE TEMPLATE IS FORCED TO BE THE SAME AS THE ORIGINAL ///////////////////////
		if ($docCfgArr['forceSameTemplate']) {
			// determine which is the forced template
			$forceIdTemplate=0;
			foreach($docsList as $doc) {
				if ($doc['docType']==$fields['docType'] and !$doc['preview']) {
					$forceIdTemplate=$doc['idTemplate'];
					break;
				}
			}
			// check if it used the same template
			if ($forceIdTemplate and $fields['idTemplate']!=$forceIdTemplate) {
				$OK=false;
				$currMessages->addWarningCode('insert_docTemplateForced', '', \Entity\DocumentTemplate::select($forceIdTemplate)->name);
			}
		}


		// CHECK MAX GENERATION COUNT /////////////////////////////////////////////////////////////
		if ($docCfgArr['maxGenerationCount']) {
			// count this docTypes generations (non-preview)
			$genCount=0;
			foreach($docsList as $doc) {
				if ($doc['docType']==$fields['docType'] and !$doc['preview']) $genCount++;
			}
			// check the generations count
			if ($genCount>=$docCfgArr['maxGenerationCount']) {
				$OK=false;
				$currMessages->addWarningCode('insert_docGenerationsLimited', '', $docCfgArr['maxGenerationCount']);
			}
		}


		// CHECK CORRECTION INVOICE - an issued and voided invoice must be found, no non-voided invoices must be found
		if ($OK and $fields['docType']=='correctionInvoice' and $fields['docNumber']) { // TODO: to be moved into a plugin
			// search for issues, not voided invoices with the same number and in the same date
			$invoicesSearchObj=new \App\EntityList\Invoices();
			$invoicesSearchObj->setColumn('inv.voidTime');
			$invoicesSearchObj->setFilter('invoiceDate', '=', $pivotObj->invoiceDate);
			$invoicesSearchObj->setFilter('invoiceNumber', '=', $fields['docNumber']);
			$invoicesSearchObj->setFilter('issueTime', '>', 0);
			$invoicesSearchObj->applyUserRestriction(false);
			$invoicesSearch=$invoicesSearchObj->getExecution();

			// analyze the found invoices
			$countVoidedInvoices=0;
			foreach($invoicesSearch as $inv) {
				// an unvoided invoice found
				if (!$inv['voidTime']) {
					$OK=false;
					$currMessages->addWarningCode('insert_docCorrInvNotVoided', '', $fields['docNumber'].'/'.$pivotObj->invoiceDate);
					break;
				} else {
					$countVoidedInvoices++;
				}
			}
			// check: if no voided invoices found - do not allow the generation of correction invoice
			if (!$countVoidedInvoices) {
				$currMessages->addWarningCode('insert_docCorrInvNotVoided2', '', $fields['docNumber'].'/'.$pivotObj->invoiceDate);
				$OK=false;
			}
		}

		// CHECK THE USER (issue user) ////////////////////////////////////////////////////////////
		if ($fields['idIssueUser']) {
			$issueUser = User::select($fields['idIssueUser']);
			if (!$issueUser->recordExists() or $issueUser->deleted) {
				$currMessages->addWarningCode('insert_issueUserInvalidDel');
				$OK=false;
			}
		}


		// CHECK THAT THE PATH TO THE DOCUMENTS IS SAFE TO BE WRITTEN ON //////////////////////////
		$documentSubpath=$this->entity->getTableMonth().'/'.$fields['pivot'].'/'.$fields['idPivot'].'/';
		if ( !\Engine\Dir::create(MODULE_REL_PATH.$CFG['paths']['generatedDocuments'].$documentSubpath) ) {
			$currMessages->addErrorCode('insert_docFullPathNotOk');
		}

		// RUN THE QC RULES FOR THIS PIVOT ////////////////////////////////////////////////////////
		if ($fields['pivot'] == 'invoice') {
			// check these invoice issue rules
			$applyRulesObj = new \App\QcRules\Apply('DocumentInvoice');
			// fill in the invoice fields obj
			$invFields = new \App\QcRules\DocumentInvoice_Fields();
			$invoiceEntity = \Entity\Invoice::selectForUpdate($fields['idPivot']);
			$invFields->docType = 			$fields['docType'];
			if ($invoiceEntity->idCorporate) {
				$invFields->idCorporate = 	$invoiceEntity->idCorporate;
			}
			if ($invoiceEntity->idCustomer) {
				$invFields->idCustomer = 	$invoiceEntity->idCustomer;
			}
			$invFields->idSeries = 			$invoiceEntity->idInvoiceSeries;
			$invFields->processedRemarks = 	$invoiceEntity->processedRemarks;

			$invFields->idResponsibleUser = $invoiceEntity->idUser;
			$invFields->idResponsibleOffice=$invoiceEntity->idOffice;

			if (!$applyRulesObj->checkFields($invFields)) {
				$_LANG = \App\Loader_Lang::getInstance()->getAll();
				foreach ($applyRulesObj->getAllUnmetRules() as $idRule=>$rule) {
					$currMessages->addWarningText(sprintf($_LANG['QcRules']['messages']['ruleFailed'], $rule->createDescription(), '#'.$idRule));
				}
			}
		}

		// FIX INNODB INCREMENT ///////////////////////////////////////////////////////////////////
		\App\Utils\Db\TablesUtils::getInstance()->checkMonthTableAutoincrement($this->entity->getTableGroup(), $this->entity->getTableMonth());

	}


	/**
	 * Validates document series and number depending doc configuration, generation type.
	 */
	private function check_insert_docNumberAndDate()
	{

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

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

		$OK=true;

		// the doc series id (may be rewritten during the method execution)
		$idDocumentSeries=$fields['idDocumentSeries'];

		// check if doc type and pivot is set
		if (!$fields['docType']) throw new \Exception("No docType set - can't execute ".__METHOD__.".");
		if (!$fields['pivot']) throw new \Exception("No pivot type set - can't execute ".__METHOD__.".");
		if (!$fields['idPivot']) throw new \Exception("No pivot id set - can't execute ".__METHOD__.".");

		// the document date (poroesses the configuration also)
		$documentDate=$this->entity->getDocumentDate();

		// get document type configs
		$docCfgObj = \App\Config\IssueDocuments::getInstance();
		$docCfg_docDate = $docCfgObj->getDocDate($fields['docType']);
		$docCfg_docNumberType = $docCfgObj->getDocNumberType($fields['docType']);
		$docCfg_numberReuse = $docCfgObj->get($fields['docType'])['numberReuse'];

		// CHECK DOC NUMBER IF THE GENERATION TYPE IS 'manual' ////////////////////////////////////
		if ($fields['generation']=='manual') {
			// document series are not allowed
			if ($fields['idDocumentSeries']) throw new \Exception("Manual documents can't have document series attached.");
			// document number must be numeric (if set)
			if ($fields['docNumber'] and !is_numeric($fields['docNumber'])) {
				$OK=false;
				$currMessages->addWarningCode('insert_docNumberNotValid', 'documentNumber');
			}
		}

		// DOC DATE FOR PIVOT `importedService` CAN BE ONLY `today` OR `manual`
		if ($fields['pivot'] == 'importedService' and !in_array($docCfg_docDate, ['today', 'manual'])) {
			throw new \Exception(sprintf("This document type `%s` should have config `docDate` set only as `today` or `manual`'.", $fields['docType']));
		}

		// DOC NUMBER TYPE FOR PIVOT `importedService` CAN BE ONLY `chooseSeries` OR `manualNumber`
		if ($fields['pivot'] == 'importedService' and !in_array($docCfg_docNumberType, ['chooseSeries', 'manualNumber'])) {
			throw new \Exception(sprintf("This document type `%s` should have config `docNumberType` set only as `chooseSeries` or `manualNumber`'.", $fields['docType']));
		}

		// CHECK DOC DATE IF THE DATE IS MANUALLY INPUTTED ////////////////////////////////////////
		if ($fields['generation']=='manual' or $docCfg_docDate=='manual') {
			// check document date
			if (!$documentDate) {
				$OK=false;
				$currMessages->addWarningCode('insert_docDateNotSet');
			} elseif (!validDate($documentDate)) {
				$OK=false;
				$currMessages->addWarningCode('insert_docDateNotValid');
			} elseif ($documentDate>TODAY_ISO) {
				$OK=false;
				$currMessages->addWarningCode('insert_docDateInFuture');
			}
		}

		// CHECK IF THE GENERATION TYPE IS 'auto' /////////////////////////////////////////////////
		if ($fields['generation']=='auto') {

			// CHECK DOCUMENT SERIES TYPE ---------------------------------------------------------
			// docNumberType = chooseSeries (id series must be associated)
			if ($docCfg_docNumberType=='chooseSeries') {
				if (!$fields['idDocumentSeries']) {
					$OK=false;
					$currMessages->addWarningCode('insert_docSeriesNotSet', 'idDocumentSeries');
				}
			}
			// docNumberType = manualNumber or pivotNumber (id series must NOT be associated)
			elseif (($docCfg_docNumberType=='manualNumber' or $docCfg_docNumberType=='pivotNumber')) {
				if ($fields['idDocumentSeries']) {
					$currMessages->addErrorCode('insert_docSeriesSet', 'idDocumentSeries');
				}

			}
			// docNumberType = pivotSeries - the field name which indicates the series id field
			elseif ($docCfg_docNumberType=='pivotSeries') {

				if (!$idDocumentSeries) {
					$OK=false;
					$currMessages->addWarningCode('insert_docSeriesNotSet', 'idDocumentSeries');
				} else {

					// if pivot = invoice
					if ($fields['pivot']=='invoice') {
						if ($idDocumentSeries!=Invoice::selectForUpdate($fields['idPivot'])->idInvoiceSeries) {
							$OK=false;
							$currMessages->addWarningCode('insert_docSeriesNotValid', 'idDocumentSeries', \Entity\Invoice::selectForUpdate($fields['idPivot'])->invoiceSeries->code);
						}
					}
					// if pivot = cashing
					elseif ($fields['pivot']=='cashing') {
						if ($idDocumentSeries!=\Entity\InvoiceCashing::selectForUpdate($fields['idPivot'])->idCashingSeries) {
							$OK=false;
							$currMessages->addWarningCode('insert_docSeriesNotValid', 'idDocumentSeries', \Entity\InvoiceCashing::selectForUpdate($fields['idPivot'])->cashingSeries->code);
						}
					}
					// if pivot = payment
					elseif ($fields['pivot'] == 'payment') {
						if ($idDocumentSeries != \Entity\SupplierInvoicePayment::selectForUpdate($fields['idPivot'])->idPaymentSeries) {
							$OK=false;
							$currMessages->addWarningCode('insert_docSeriesNotValid', 'idDocumentSeries', \Entity\SupplierInvoicePayment::selectForUpdate($fields['idPivot'])->paymentSeries->code);
						}
					}
					// this docType cant be 'pivotSeries
					else {
						throw new \Exception("This document type `{$fields['docType']}` does not support `docNumberType` = 'pivotSeries'.");
					}
				}
			}
			// wrong docNumberType
			else {
				throw new \Exception("Wrong docNumberType configuration (`{$docCfg_docNumberType}`) for `{$fields['docType']}` document type.");
			}

			// if document number type  = manualNumber - fields['documentNumber'] must be set
			if ($docCfg_docNumberType=='manualNumber') {
				if (!is_numeric($fields['docNumber']) or $fields['docNumber']<=0) {
					$OK=false;
					$currMessages->addWarningCode('insert_manualDocNoInvalid');
				}
			}
			// other types of document number type - fields['documentNumber'] must NOT be set
			else {
				if (array_key_exists('docNumber', $fields)) {
					throw new \Exception('The field `docNumber` should be set for Document :: insert only if the the `docNumberType` config is `manualNumber`.');
				}
			}

		}

		$series = null;

		// IF DOCUMENT SERIES IS SET - IT MUST BE A VALID ONE /////////////////////////////////////
		if ($idDocumentSeries) {
			$series=\Entity\DocumentSeries::selectForUpdate($idDocumentSeries);
			$getNewNumberFlag=$this->entity->getNewNumberFlag();
			//check if record exists
			if (!$series->recordExists()) {
				$currMessages->addErrorCode('insert_seriesIdInvalid', 'idDocumentSeries');
			}
			// check if deleted
			elseif ($series->deleted and $getNewNumberFlag) {
				$OK=false;
				$currMessages->addWarningCode('insert_seriesDeleted', 'idDocumentSeries');
			}
			// check document type
			elseif (!in_array($fields['docType'], $series->documentTypes)) {
				$OK=false;
				$currMessages->addWarningCode('insert_seriesDocTypeInvalid', 'idDocumentSeries');
			}
			// check series office
			elseif ($series->idOffice and $fields['idIssueUser'] and $series->idOffice!=User::select($fields['idIssueUser'])->idOffice and $getNewNumberFlag) {
				$OK=false;
				$currMessages->addWarningCode('insert_seriesOfficeInvalid', 'idDocumentSeries');
			}
			// check all users
			elseif (!$series->allUsers and $fields['idIssueUser'] and !in_array($fields['idIssueUser'], $series->validUsersList) and $getNewNumberFlag) {
				$OK=false;
				$currMessages->addWarningCode('insert_seriesUserRestricted', 'idDocumentSeries');
			}
			// check start valability
			elseif ($documentDate<$series->validStartDate and $getNewNumberFlag) {
				$OK=false;
				$currMessages->addWarningCode('insert_seriesValidStartDateRestricted', 'idDocumentSeries');
			}
			// check end valability
			elseif ($documentDate>$series->validEndDate and $getNewNumberFlag) {
				$OK=false;
				$currMessages->addWarningCode('insert_seriesValidEndDateRestricted', 'idDocumentSeries');
			}
			// check series status (if new number will be generated)
			if ($series->status=='END' and $getNewNumberFlag) {
				$OK=false;
				$currMessages->addWarningCode('insert_seriesIsEnded', 'idDocumentSeries');
			}

			// check document currency
			if (!$series->allCurrencies) {
				// determine document currency (blank if it does not apply)
				if ($fields['pivot']=='invoice') {
					$curr=Invoice::selectForUpdate($fields['idPivot'])->currency;
				}
				elseif ($fields['pivot']=='cashing') {
					$curr=\Entity\InvoiceCashing::selectForUpdate($fields['idPivot'])->currency;
				}
				elseif ($fields['pivot'] == 'payment') {
					$curr=\Entity\SupplierInvoicePayment::selectForUpdate($fields['idPivot'])->currency;
				}
				elseif ($fields['pivot'] == 'cashBookDoc') {
					$curr = \Entity\CashBookDocument::selectForUpdate($fields['idPivot'])->currency;
				}
				else $curr='';
				if ($curr and !$series->validCurrenciesList[$curr]) {
					$OK=false;
					$currMessages->addWarningCode('insert_seriesCurrencyRestricted', 'idDocumentSeries', $curr);
				}
			}

			// check document template
			if (!$series->allTemplates and !in_array($fields['idTemplate'], $series->validTemplatesList)) {
				$OK=false;
				$currMessages->addWarningCode('insert_seriesTemplatesRestricted', '', \Entity\DocumentTemplate::select($fields['idTemplate'])->name);
			}
		}

		// CHECK THAT DOCUMENTS ARE IN CHRONOLOGICAL ORDER ////////////////////////////////////////
		// (this only applies for 'auto' documents that will generate a new number in this execution - using series)
		if ($fields['generation']=='auto') {

			$originalDocNumber = null;

			// determine if this document if the first one of this type generated on this pivot
			$newNumberForDoc=true;

			// if 'reuse' - check if this is the first generation
			if ($docCfg_numberReuse) {
				$pivotObj=$this->entity->getInsertingDocumentPivot();
				foreach($pivotObj->documentsList as $doc) {
					if ($doc['docType']==$fields['docType'] and !$doc['preview']) {
						$newNumberForDoc=false;
						$originalDocNumber=$doc['docNumber'];
						break;
					}
				}

				// for manual doc numbers - check the reuse number
				if ($fields['docNumber'] and $originalDocNumber and $originalDocNumber!=$fields['docNumber']) {
					$OK=false;
					$currMessages->addWarningCode('insert_reuseManualDocNotOk', '', $originalDocNumber);
				}

			}

			// if this is the first document generation
			if ($series && $newNumberForDoc) {
				$documentEntityCfg = \Entity\Document::loadCFG();
				if ($documentEntityCfg['_special']['forceChronologicalSeriesUse'] and $series->lastDate and $documentDate<$series->lastDate) {
					$OK=false;
					$currMessages->addWarningCode( 'insert_seriesMustHaveChronlgOrder', 'idDocumentSeries', [$series->lastDate, $documentDate]);
				}
			}

		}

		// if the current doc upload is supposed to "issue" the current doc pivot - then check doc number and date
		if (
			$fields['generation']=='manual' and \App\Config\IssueDocuments::getInstance()->isMainDocOnPivot($fields['docType']) and
			in_array(\App\Config\IssueDocuments::getInstance()->getDocPivot($fields['docType']), ['invoice', 'cashing', 'payment', 'cashBookDoc']) and
			!$this->entity->getInsertingDocumentPivot()->issueTime
		) {
			if (!$documentDate) {
				$OK=false;
				$currMessages->addWarningCode( 'mandatoryField', 'documentDate');
			}
			if (!$this->entity->getNewNumberFlag()) {
				$OK=false;
				$currMessages->addWarningCode( 'mandatoryField', 'documentNumber');
			}
		}


		return $OK;

	}


	/**
	 * Some extra validations only for invoice pivot.
	 */
	private function check_insert_invoice()
	{

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

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

		$OK=true;

		// set and check doc type and pivot
		$docType=$fields['docType'];
		$pivot=$fields['pivot'];
		if (!$docType) return false;
		if (!$pivot) return false;
		if ($pivot!='invoice') return true;
		if (!$fields['idPivot']) return false;

		// invoice object
		$invoice=$this->entity->getInsertingDocumentPivot();
		if (!$invoice->recordExists()) return false;

		if ($docType and \App\Config\IssueDocuments::getInstance()->isMainDocOnPivot($docType)) {

			// check if the invoice is temporary (only if this is maindoc on this pivot)
			if ($invoice->temporary) {
				$OK=0;
				$currMessages->addWarningCode('insert_invoiceIsTemporary');
			}

			// test if corporate is blocked (and invoice is not yet issued)
			if (!$invoice->issueTime and $invoice->idCorporate and $invoice->corporate->locked) {
				$OK=0;
				$currMessages->addWarningCode('insert_invoiceClientBlocked');
			}
			// check if customer is blocked (and invoice is not yet issued)
			if (!$invoice->issueTime and $invoice->idCustomer and $invoice->customer->locked) {
				$OK=0;
				$currMessages->addWarningCode('insert_invoiceClientBlocked');
			}

			// check corporate credit limit (block limit) - if the blocking is activated
			$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();
			if ($CFG['corporateCreditLimit']['blockInvoicingActive']) {
    			if ($invoice->idCorporate and $this->entity->getNewNumberFlag()) {
    				if ($invoice->corporate->creditLimitStatus=='block' and $invoice->printTotal>0) {
    					$OK=false;
    					$currMessages->addWarningCode('insert_invoiceCorpCreditLimitExc');
    				}
    			}
			}

		}

		return $OK;

	}


	/**
	 * Checks the fields for 'update' operation.
	 */
	protected function check_update()
	{

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

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

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

		// get document details (to lock the record in the db)
		Document::selectForUpdate($currentIdEntity);

		// CHECK THAT THE DOCCONTENT FILE CONTENT IS FILLED IN ------------------------------------
		if (array_key_exists('docFileContent', $fields) and !strlen($fields['docFileContent'])) {
			$currMessages->addWarningCode('update_docContentEmpty');
		}



	}


	protected function isAccessible_sendMail()
	{

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

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

		// get document details
		$doc=Document::selectForUpdate($currentIdEntity);

		// CHECK USER ACCESS //////////////////////////////////////////////////////////////////////
		$pivot = $doc->pivotCode;
		if (
			($pivot=="invoice" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('invoiceDocumentsManagement')) or
			($pivot=="cashing" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('cashingDocumentsManagement')) or
			($pivot=="payment" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('paymentDocumentsManagement')) or
			($pivot=="order" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('reservationDocumentsManagement')) or
			($pivot=="service" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('serviceDocumentsManagement')) or
			($pivot=="cashBookDoc" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('cashBookDocumentsManagement')) or
			($pivot=="importedService" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('importedServiceDocumentsManagement'))
		) {
			$currMessages->addWarningCode('sendMail_noAccessToPivot');
		}

	}

	protected function isAvailable_sendMail()
	{

		$currentIdEntity = $this->entity->getId();

		$currMessages = $this->entity->getMessages();

		// get document details
		$docEnt = Document::selectForUpdate($currentIdEntity);

		if (!$docEnt->recordExists()) {
			$currMessages->addErrorCode('sendMail_invalidDocId');
		}

	}

	protected function check_sendMail()
	{

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

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

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


		// the doc details
		$docEnt = Document::selectForUpdate($currentIdEntity);

		// CHECK IF THE DOCUMENT CAN BE SENT //////////////////////////////////////////////////////
		if ($docEnt->disableEmailSending) {
			$currMessages->addWarningCode('sendMail_emailSendDisabled');
		}

		// CHECK THAT THE SUBJECT/BODY IS SET /////////////////////////////////////////////////////
		if (!$fields['subject']) {
			$currMessages->addWarningCode('sendMail_noSubj');
		}
		if (!$fields['body']) {
			$currMessages->addWarningCode('sendMail_noBody');
		}

		// CHECK SENDER ADDRESS ///////////////////////////////////////////////////////////////////
		if (!$fields['idSenderUser'] and !$fields['forceSenderEmail']) {
			$currMessages->addWarningCode('sendMail_noSender');
		}
		// check sender
		if ($fields['idSenderUser']) {
			$sender = User::select($fields['idSenderUser']);
			if (!$sender->recordExists()) {
				$currMessages->addWarningCode('sendMail_invalidSender');
			} elseif (!$sender->email and !$fields['forceSenderEmail']) {
				$currMessages->addWarningCode('sendMail_noSender');
			}
		}
		// check from address
		if ($fields['forceSenderEmail']) {
			if (!\Engine\Validator::set($fields['forceSenderEmail'])->isEmail()) {
				$currMessages->addWarningCode('sendMail_senderEmailInvalid');
			}
		}

		// CHECK RECEIVERS ////////////////////////////////////////////////////////////////////////
		if (!$fields['receiversTo']) {
			$currMessages->addWarningCode('sendMail_noReceiverTo');
		}
		// CHECK RECEIVERS(TO) ADDRESSES ----------------------------------------------------------
		else {
			foreach($fields['receiversTo'] as $receiver) {
				if (!$receiver or !\Engine\Validator::set($receiver)->isEmail()) {
					$currMessages->addWarningCode('sendMail_receiverBadAddr', '', $receiver);
				}
			}
		}

		// CHECK RECEIVERS(CC) ADDRESSES ----------------------------------------------------------
		if ($fields['receiversCc']) {
			foreach($fields['receiversCc'] as $receiver) {
				if (!$receiver or !\Engine\Validator::set($receiver)->isEmail()) {
					$currMessages->addWarningCode('sendMail_receiverBadAddr', '', $receiver);
				}
			}
		}

		// CHECK THAT THE GENERATED FILES EXIST ///////////////////////////////////////////////////
		if ($fields['generatedFiles']) {
			foreach($fields['generatedFiles'] as $idDoc) {
				$thisDoc=Document::selectForUpdate($idDoc);
				if (!$thisDoc->recordExists()) {
					$currMessages->addErrorCode('sendMail_invalidAttachDoc');
				}
			}
		}


		// CHECK UPLOADED FILES ////////////////////////////////////////////////////////////////////
		if ($fields['uploadFiles']) {
			foreach ($fields['uploadFiles'] as $fileName => $fileWithPath) {
				if (!$fileWithPath) {
					$currMessages->addWarningCode('filePathIncorrect');
				}
				elseif (!$fileName) {
					$currMessages->addWarningCode('fileNameIncorrect');
				}
				elseif ($fileName != \Engine\File::cleanName($fileName)) {
					$currMessages->addWarningCode('fileNameInvalid');
				}
			}
		}

		// only some pivots accept calendars
		if ($fields['attachmentCalendar'] and !in_array($docEnt->pivotCode, ['service', 'order'])) {
			$currMessages->addWarningCode('sendMail_calendarInvalidPivot');
		}

	}

	protected function isAccessible_openDocument()
	{

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

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

		// get document details
		$doc=Document::selectForUpdate($currentIdEntity);

		// CHECK USER ACCESS //////////////////////////////////////////////////////////////////////
		$pivot=$doc->pivotCode;
		if (
			($pivot=="invoice" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('invoiceDocumentsManagement')) or
			($pivot=="cashing" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('cashingDocumentsManagement')) or
			($pivot=="payment" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('paymentDocumentsManagement')) or
			($pivot=="order" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('reservationDocumentsManagement')) or
			($pivot=="service" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('serviceDocumentsManagement')) or
			($pivot=="cashBookDoc" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('cashBookDocumentsManagement')) or
			($pivot=="importedService" and !\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption('importedServiceDocumentsManagement'))
		) {
			$currMessages->addWarningCode('sendMail_noAccessToPivot');
		}

	}

	protected function isAvailable_openDocument()
	{
		// current entity id
		$currentIdEntity = $this->entity->getId();

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

		// CHECK ID -------------------------------------------------------------------------------
		$document = \Entity\Document::select($currentIdEntity);
		if (!$document->recordExists()) {
			$currMessages->addErrorCode('openDocument_invalidId');
		}
	}

	protected function check_openDocument()
	{
		// current collected messages
		$currMessages = $this->entity->getMessages();

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

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

		// the doc details
		$documentEntity = \Entity\Document::selectForUpdate($currentIdEntity);

		// the doc config object
		$docCfgObj = \App\Config\IssueDocuments::getInstance();

		// validate action field
		if (!in_array($fields['action'], ['download', 'view', 'print'])) {
			$currMessages->addWarningCode('openDocument_invalidAction');
		}

		if ($docCfgObj->get($documentEntity->docTypeCode)['printOriginalOnce']) {
			// check if this is the first document on this pivot
			$originalDocument = true;

			$documentObject = new \App\EntityList\Documents();
			$documentObject->setFilter('doc.pivot', '=', $documentEntity->pivotCode);
			$documentObject->setFilter('doc.idPivot', '=', $documentEntity->idPivot);
			$documentObject->setFilter('doc.docType', '=', $documentEntity->docTypeCode);
			$documentObject->setFilter('doc.preview', '=', '0');
			$documentObject->setFilter('doc.extraInfo', 'NOT LIKE', '%\"voided\"=>\"1\"%');
			$documentObject->setOrderBy('id', 'ASC');
			$documentObject->applyUserRestriction(false);
			$documentObject->setLimit(1);
			$documentArray = $documentObject->exportRecords();

			if ($documentArray[0]['id'] != $currentIdEntity) {
				$originalDocument = false;
			}

			if ($originalDocument) {
				if ($fields['action'] != 'print') {
					$currMessages->addWarningCode('openDocument_origDocCanOnlyBePrintedOnce');
				}

				if ($fields['action'] == 'print' and $documentEntity->printed) {
					$currMessages->addWarningCode('openDocument_onlyOnePrintForInvDocs', null, $documentEntity->pivotCode);
				}
			}
		}
	}

	protected function isAccessible_deleteFile()
	{
		// current collected messages
		$currMessages=$this->entity->getMessages();

		// CHECK USER ACCESS //////////////////////////////////////////////////////////////////////
		if (!\App\UserAccess\CurrentUserAccessFast::getInstance()->hasAccessToOption("Modules/Documents/DocumentsList-deleteFile")) {
			$currMessages->addWarningCode('noAccessToOption');
		}
	}

	protected function isAvailable_deleteFile()
	{
		// current entity id
		$currentIdEntity = $this->entity->getId();

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

		// CHECK ID -------------------------------------------------------------------------------
		$document = \Entity\Document::selectForUpdate($currentIdEntity);
		if (!$document->recordExists()) {
			$currMessages->addErrorCode('deleteFile_invalidId');
		}

		if (!$document->docFile or !\Engine\File::check($document->docFileNameWithPath)) {
			$currMessages->addWarningCode('deleteFile_fileAlreadyDeleted');
		}
	}
}
