<?php

// TODO: [notImportant] scoatem de tot asta sau o facem plugin?: $CFG['invoices']['restrictions']['blockInvoiceIfTotalIsZero']=false;

// TODO: [notImportant] dupa ce se reface adaugarea facturii, sa se ruleze: " UPDATE `zzReservationServiceInvoiceRows_201202` SET finalizeAdvanceMode='' WHERE special!='' "

namespace Modules\Invoices\InvoiceInsert;

class Controller extends \App\Mvc\ControllerAbstract
{

	/**
	 * The current step (step 1 = services selection; step 2 = invoice preview).
	 * @var integer
	 */
	protected $step = 1;

	/**
	 * The current idOrder.
	 * @var Int
	 */
	protected $idOrder;

	/**
	 *
	 * {@inheritDoc}
	 * @see \App\Mvc\ControllerAbstract::initialize()
	 */
	protected function initialize()
	{
		parent::initialize();

		$this->actionsCfg['index'] = [ 'checkSection' => false, 'checkOption' => 'Modules/Invoices/InvoiceInsert' ];
		$this->actionsCfg['getDueDate'] = [ 'checkSection' => false, 'checkOption' => false,];
	}


	/**
	 * The main action - manages both steps.
	 * @return string
	 */
	protected function actionIndex($R)
	{

		// collects messages
		$msgs = new \App\MessagesCollector();

		$_LANG = \App\Loader_Lang::getInstance()->getGlobal();

		$this->idOrder = $R['idOrder'];

		// check order id
		if (!is_numeric($this->idOrder) or !\Entity\Order::select($this->idOrder)->recordExists()) {
			$msgs->addError($_LANG['Invoices']['InvoiceInsert']['index']['step1']['validateMsgs']['orderIdInvalid']);
		}

		// DETERMINE STEP /////////////////////////////////////////////////////////////////////////
		if ($R['previewInvoice']) {
			try {
				$this->validateInvoiceOptions($R);
				$this->step = 2;
			} catch (\App\MessagesException $e) {
				$msgs->appendMessages($e->getMessagesCollector());
			}
		}
		// if second submit button pressed (save) - skip to step2 automatically
		if ($R['save']) {
			$this->step = 2;
		}

		// helper for invoice preview
		$model = new ModelInvoicePreview($this->idOrder);

		// GET DEAULT FORM DATA (for both steps) //////////////////////////////////////////////////
		if (!$R['previewInvoice'] and !$R['save'] and !$R['servicesSelect']) { // no submit button was presed (step 1)
			$formData = $this->getDefaultFormDataStep1($R, $model);
		} elseif ($this->step==2 and !$R['save'] and !$R['refreshInvoice']) { // step 2 - the first time
			$formData = $this->getDefaultFormDataStep2($R);
		} else { // step2 - adding invoice
			$formData = $R;
		}


		// ADD CORPORATE ON ORDER AND INVOICE /////////////////////////////////////////////////////
		if ($formData['idCorporateInsert']['id']) {
			$newIdCorporate = $formData['idCorporateInsert']['id'];

			// check if the new corporate is not already present on the order
			$orderEnt = \Entity\Order::select($this->idOrder);
			if (!in_array($newIdCorporate, $orderEnt->idCorporatesList)) {

				try {

					\DB::startTransaction();

					$orderSelEnt = \Entity\Order::selectForUpdate($this->idOrder);
					$newIdCorporatesList = array_merge($orderSelEnt->idCorporatesList, [$newIdCorporate]);

					$orderEnt = \Entity\Order::update($this->idOrder);
					$orderEnt->idCorporates = $newIdCorporatesList;
					$orderEnt->save();

					// rewrite the invoice client id
					$formData['idClient'] = $newIdCorporate.'|'.\Entity\Corporate::selectForUpdate($newIdCorporate)->idMainOffice;
					$formData['refreshClientProfile'] = true;

					\DB::commitTransaction();

					$msgs->addSuccess($_LANG['Invoices']['InvoiceInsert']['index']['step2']['msgs']['corpAddedOnOrder']);

				} catch (\App\Entity\Exception $e) {

					\DB::rollbackTransaction();

					$msgs->appendMessages($e->getMessagesCollector());

				}

			}
			// corporate already present - select the client on the invoice
			else {
				$formData['idClient'] = $newIdCorporate.'|'.\Entity\Corporate::select($newIdCorporate)->idMainOffice;
			}
		}

		// ADD CUSTOMER ON ORDER AND INVOICE /////////////////////////////////////////////////////
		if ($formData['idCustomerInsert']['id']) {
			$newIdCustomer = $formData['idCustomerInsert']['id'];

			// check if the new customer is not already present on the order
			$orderEnt = \Entity\Order::select($this->idOrder);
			if (!in_array($newIdCustomer, $orderEnt->idCustomersList)) {

				try {

					\DB::startTransaction();

					$orderSelEnt = \Entity\Order::selectForUpdate($this->idOrder);
					$newIdCustomersList = array_merge($orderSelEnt->idCustomersList, [$newIdCustomer]);

					$orderEnt = \Entity\Order::update($this->idOrder);
					$orderEnt->idCustomers = $newIdCustomersList;
					$orderEnt->undefinedPax = $orderSelEnt->undefinedPax;
					$orderEnt->save();

					// rewrite the invoice client id
					$formData['idClient'] = '0|'.$newIdCustomer;

					\DB::commitTransaction();

					$msgs->addSuccess($_LANG['Invoices']['InvoiceInsert']['index']['step2']['msgs']['custAddedOnOrder']);

				} catch (\App\Entity\Exception $e) {

					\DB::rollbackTransaction();

					$msgs->appendMessages($e->getMessagesCollector());

				}

			}
			// customer already present - select the client on the invoice
			else {
				$formData['idClient'] = '0|'.$newIdCustomer;
			}
		}


		// POPULATE THE INVOICE ROWS (FOR PREVIEW) ////////////////////////////////////////////////
		if ($this->step==2) {
			$invoiceInsertEnt = \Entity\Invoice::insert();
			$this->populateInvoiceEntity($invoiceInsertEnt, $formData);
			$invoiceRows = $invoiceInsertEnt->createInvoiceRows();

			$redirect = false;

			// AND ALSO SAVE INVOICE --------------------------------------------------------------
			if ($R['save'] or $R['autoInsert']) {

				try {

					\DB::startTransaction();
					$invoiceInsertEnt->save();
//					\DB::rollbackTransaction();
					\DB::commitTransaction();

					// indicate where to redirect
					$redirect = ($R['redirect']=='invoiceInsert')?'invoiceInsert':'invoicesList';

				} catch (\App\Entity\Exception $e) {
					\DB::rollbackTransaction();
					// add messages (with field)
					foreach($e->getMessagesCollector()->getMessages() as $thisMsg) {
						$thisMsg->setText($thisMsg->getTextWithField());
						$msgs->addMessage($thisMsg);
					}
				}

			}

			// EVERYTHING WAS FINE - REDIRECT TO THE CORRECT SECTION ------------------------------
			if ($redirect=='invoiceInsert') {
				$response = new \Engine\Mvc\Response\HtmlRedirect(\Engine\Mvc\Dispatcher::getInstance()->getAppUrl("Modules/Invoices/InvoiceInsert", '', ['idOrder'=>$this->idOrder]));
				return $response;
			}
			if ($redirect=='invoicesList') {
				$response = new \Engine\Mvc\Response\HtmlRedirect(\Engine\Mvc\Dispatcher::getInstance()->getAppUrl("Modules/Invoices/InvoicesList"));
				return $response;
			}

		}


		// INSTANTIATE THE VIEW ///////////////////////////////////////////////////////////////////

		// instantiate the view
		if ($this->step==1) {
			$view = new View_Step1($this);
		} else {
			$view = new View_Step2($this);
		}

		// ATTACH INFORMATION TO VIEW -------------------------------------------------------------

		// GENERAL STUFF ..........................................................................
		$view->setOutputVar('orderEnt', \Entity\Order::select($this->idOrder));
		$view->setOutputVar('formData', $formData);
		$view->getMessagesCollector()->appendMessages($msgs);
		$view->setOutputVar('clientsList', $model->getInvoiceClientsList());
		$view->setOutputVar('clientsDefaultProfile', $model->getClientsDefaultProfile());

		// STEP 1 STUFF ...........................................................................
		if ($this->step==1) {
			$view->setOutputVar('servicesList', $model->getInvoicingServicesListDetails());
		}

		// STEP 2 STUFF ...........................................................................
		if ($this->step==2) {
			$view->setOutputVar('invoiceRows', $invoiceRows);
			$view->setOutputVar('invoiceCfgs', \Entity\Invoice::loadCFG());
		}

		// render the view
		return new \Engine\Mvc\Response\Html($view->render());
	}


	/**
	 * Validate the content of the request when invoice preview button is pressed.
	 * @param array $R
	 * @throws \App\MessagesException
	 */
	protected function validateInvoiceOptions(array $R)
	{

		$_LANG = \App\Loader_Lang::getInstance()->getGlobal();

		$msgs = new \App\MessagesCollector();

		//  CHECK INVOICE CURRENCY AND CLIENT /////////////////////////////////////////////////////
		if (!$R['invoiceCurrency']) {
			$msgs->addWarning($_LANG['Invoices']['InvoiceInsert']['index']['step1']['validateMsgs']['invCurrNotSel']);
		}
		if (!$R['idClient']) {
			$msgs->addWarning($_LANG['Invoices']['InvoiceInsert']['index']['step1']['validateMsgs']['clientNotSel']);
		} else {
			[$cl1, $cl2] = explode('|', $R['idClient']);
			// check corporate without office
			if ($cl1 and !$cl2) {
				$msgs->addError($_LANG['Invoices']['InvoiceInsert']['index']['step1']['validateMsgs']['invalidCorpClient']);
			}
			if (!$cl1 and !$cl2) {
				$msgs->addError($_LANG['Invoices']['InvoiceInsert']['index']['step1']['validateMsgs']['invalidCustClient']);
			}
		}

		//  CHECK INVOICE OPTIONS /////////////////////////////////////////////////////////////////
		if (!$R['servicesSelect']) {
			$msgs->addWarning($_LANG['Invoices']['InvoiceInsert']['index']['step1']['validateMsgs']['noServiceSelected']);
		} else {
			// loop through the selection of services and check the values
			foreach($R['servicesSelect'] as $idService=>$nada) {
				if (!$R['invoiceOption'][$idService]) {
					$msgs->addWarning(sprintf($_LANG['Invoices']['InvoiceInsert']['index']['step1']['validateMsgs']['noOptionSelectedForService'], $idService));
				}
				if ($R['invoiceOption'][$idService]=='advance' and !$R['advanceValue'][$idService]) {
					$msgs->addWarning(sprintf($_LANG['Invoices']['InvoiceInsert']['index']['step1']['validateMsgs']['noAdvanceValSet'], $idService));
				}
				if ($R['invoiceOption'][$idService]=='penalty' and !$R['penaltyValue'][$idService]) {
					$msgs->addWarning(sprintf($_LANG['Invoices']['InvoiceInsert']['index']['step1']['validateMsgs']['noPenaltyValSet'], $idService));
				}
				if ($R['invoiceOption'][$idService]=='totalWithPenalty' and !$R['totalWithPenaltyValue'][$idService]) {
					$msgs->addWarning(sprintf($_LANG['Invoices']['InvoiceInsert']['index']['step1']['validateMsgs']['noPenaltyValSet'], $idService));
				}
			}
		}

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

	}

	protected function getDefaultFormDataStep1($R, ModelInvoicePreview $model)
	{

		$return = [];

		// get default client
		$clientDetails = $model->getInvoiceClientsList();
		if ($clientDetails['defaultCorporate']) {
			$return['idClient'] = $clientDetails['defaultCorporate'];
		} else {
			$return['idClient'] = $clientDetails['defaultCustomer'];
		}

		// get default currency
		$defaultProfile = $model->getClientsDefaultProfile();
		$return['invoiceCurrency'] = $defaultProfile[$return['idClient']]['currency']['value'];

		// for all services (except TI) TINA auto-selects the first option in the list
		$servicesList = $model->getInvoicingServicesListDetails();
		foreach($servicesList as $idService=>$serviceDetails) {
			if ($serviceDetails['statusFin']!='TI') {
				$return['invoiceOption'][$idService] = key($serviceDetails['invoiceOptions']);
			}

			$serviceEnt = \Entity\Service::select($idService);

			// if multiple quantity types, select "one" by default
			if (count($serviceEnt->invoiceQuantityTypes)>1) {
				$return['totalQuantityType'][$idService] = 'one';
				$return['totalWithPenaltyQuantityType'][$idService] = 'one';
			}

			// COPY REFUND DETAILS FROM IMPORTED SERVICE(S) ---------------------------------------
			if (!$serviceEnt->refunded and $serviceEnt->importedServicesList) {

				$currency = null;

				// manages imported services references (there may be more than one reference, and we should make sure that all values, maybe different curencies, are correctly summed up)
				foreach($serviceEnt->importedServicesList as $impServDet) {

					// if imported service is refunded and some refund details found...
					if ($impServDet['systemStatus']=='refunded') {
						$impServEnt = \Entity\ImportedService::select($impServDet['id']);
						$impServRfndDet = $impServEnt->object->refundDetails;

						/**
						 * @var \App\ImportedOrder\RefundDetails $impServRfndDet
						 */

						if ($impServRfndDet) {

							// set the currency from the first service with refund details values
							if (!$currency and $impServRfndDet->currency) {
								$currency = $impServRfndDet->currency;
							}

							// set all values (by adding all components from each imported service - refund details)
							if ($currency and $impServRfndDet->fareUsedSupplierValue) {
								$return['refundFareUsedSupplierPriceTotal'][$idService] += \App\CurrencyExchange\Rates::getInstance()->convert($impServRfndDet->fareUsedSupplierValue, $impServRfndDet->currency, $currency);
							}
							if ($currency and $impServRfndDet->fareUsedSupplierVat) {
								$return['refundFareUsedSupplierPriceVat'][$idService] += \App\CurrencyExchange\Rates::getInstance()->convert($impServRfndDet->fareUsedSupplierVat, $impServRfndDet->currency, $currency);
							}
							if ($currency and $impServRfndDet->fareUsedCommissionValue) {
								$return['refundFareUsedCommissionTotal'][$idService] += \App\CurrencyExchange\Rates::getInstance()->convert($impServRfndDet->fareUsedCommissionValue, $impServRfndDet->currency, $currency);
							}
							if ($currency and $impServRfndDet->fareUsedTaxValue) {
								$return['refundFareUsedTaxTotal'][$idService] += \App\CurrencyExchange\Rates::getInstance()->convert($impServRfndDet->fareUsedTaxValue, $impServRfndDet->currency, $currency);
							}
							if ($currency and $impServRfndDet->fareUsedTaxVat) {
								$return['refundFareUsedTaxVat'][$idService] += \App\CurrencyExchange\Rates::getInstance()->convert($impServRfndDet->fareUsedTaxVat, $impServRfndDet->currency, $currency);
							}
							if ($currency and $impServRfndDet->penaltyValue) {
								$return['refundPenaltyTotal'][$idService] += \App\CurrencyExchange\Rates::getInstance()->convert($impServRfndDet->penaltyValue, $impServRfndDet->currency, $currency);
							}
							if ($currency and $impServRfndDet->serviceFeeValue) {
								$return['refundServiceFeeTotal'][$idService] += \App\CurrencyExchange\Rates::getInstance()->convert($impServRfndDet->serviceFeeValue, $impServRfndDet->currency, $currency);
							}
							if (!isset($return['refundReturnServiceFee'][$idService])) {
								$return['refundReturnServiceFee'][$idService] = $impServRfndDet->returnOriginalServiceFee;
							}

						}

					}
				}

				// set the currency for all refund details components (if any vales are set)
				if ($currency) {
					$return['refundFareUsedSupplierPriceCurrency'][$idService] = $currency;
					$return['refundFareUsedCommissionCurrency'][$idService] = $currency;
					$return['refundFareUsedTaxCurrency'][$idService] = $currency;
					$return['refundPenaltyCurrency'][$idService] = $currency;
					$return['refundServiceFeeCurrency'][$idService] = $currency;
				}

			}

			// SET UP THE SERVICE FEE FROM OFFER PROFILE RULES ....................................
			if ($return['refundServiceFeeTotal'][$idService]===null) {

				// populate the fields for the offer profile calculation
				$fields = new \App\OfferProfileRules\Fields();
				$fields->populateFromOfferEntitySelect($serviceEnt->chosenOffer);
				$fields->operation = 'refund';

				$profileCollector = new \App\OfferProfileRules\GetProfile($fields);
				$profileCollector->search();

				if ($profileCollector->getServiceFeeDetails()) {
					$serviceFeeForRefundDetails = $profileCollector->getServiceFeeForRefunds($serviceEnt);
					$return['refundServiceFeeTotal'][$idService] = $serviceFeeForRefundDetails['value'];
					$return['refundServiceFeeCurrency'][$idService] = $serviceFeeForRefundDetails['currency'];
				}

			}

		}

		return $return;
	}

	/**
	 * Loads the default values for the invoice on the first display of step2.
	 * @param array $R
	 * @return array
	 */
	protected function getDefaultFormDataStep2(array $R) :array
	{

		$formData = $R;

		// main inits

		[$idCorp, ] = explode('|', $formData['idClient']);

		$orderEnt = \Entity\Order::select($R['idOrder']);

		$miceProjectProfile = \App\EntityPreference\MiceProject::getInstance($orderEnt->idMiceProject);


		// GENERAL / SIMPLE CONFIGS ---------------------------------------------------------------
		$formData['user']['id'] = \Entity\Corporate::select($idCorp)->idInvoiceResponsible?:CURRENT_ID_USER;
		$formData['invoiceDate'] = TODAY_ISO;
		$formData['temporary'] = $this->cfg['defaultValues']['temporaryInvoice'];
		$formData['remarks'] = $this->cfg['defaultValues']['remarks'];
		$formData['copyServiceRemarks'] = true; // this is just a flag in order to know that we need to process service remarks

		// INVOICE SERIES (MICE PROJECT OR USER PROFILE) ------------------------------------------
		$idSeries = \App\EntityPreference\Corporate::getInstance($idCorp)->invoiceSeries;
		if (!$idSeries) {
			$idSeries = $miceProjectProfile->idInvoiceSeries;
		}
		if (!$idSeries) {
			// look for the first main doc on invoice pivot
			$firstInvoiceIssueDoc = \App\Config\IssueDocuments::getInstance()->getFirstMainDoc('invoice');
			$prefAttribute = 'defaultDocSeries_'.$firstInvoiceIssueDoc;
			$idSeries = \App\EntityPreference\User::getInstance()->$prefAttribute;
		}
		$formData['idSeries'] = (int)$idSeries;

		// DETERMINE DUE DATE BASED ON CLIENT PROFILE ---------------------------------------------

		// get first service code
		$idService = key($R['servicesSelect']);
		$servEnt = \Entity\Service::select($idService);
		$serviceCode = $servEnt->chosenOffer->serviceCode;
		if (!$serviceCode) $serviceCode = $servEnt->serviceCode;

		// determine the due date
		$formData['dueDate'] = $this->calculateDueDateFromProfile(TODAY_ISO, $R['idClient'], $serviceCode);

		// prepare currency date
		if ($this->cfg['defaultValues']['currencyDate']) {
			$formData['forceCurrencyDate'] = $this->cfg['defaultValues']['currencyDate'];
		}

		// prepare currency type
		if ($idCorp) {
			$currType = \App\EntityPreference\Corporate::getInstance($idCorp)->invoiceCurrencyType;
			if ($currType) {
				$formData['forceCurrencyType'] = $currType;
			}
		}
		if (!$formData['forceCurrencyType'] and $this->cfg['defaultValues']['currencyType']) {
			$formData['forceCurrencyType'] = $this->cfg['defaultValues']['currencyType'];
		}

		// load corporate profile scheme for rows description, if any
		$invRowsScheme = $miceProjectProfile->invoiceRowsDescriptionScheme;
		if (!$invRowsScheme and $idCorp) {
			$invRowsScheme = \App\EntityPreference\Corporate::getInstance($idCorp)->invoiceRowsScheme;
		}
		$formData['rowDescriptionScheme'] = $invRowsScheme;

		// load hiding of rows from corporate profile
		if ($idCorp) {
			$hiddenRowsCorporateProfileSer = \App\EntityPreference\Corporate::getInstance($idCorp)->hideInvoiceRowsComponents;
			if ($hiddenRowsCorporateProfileSer) {
				$hiddenRowsCorporateProfile = unserialize($hiddenRowsCorporateProfileSer);
				$formData['forceHideRowsComponentsFromProfile'] = $hiddenRowsCorporateProfile;
			}

		}

		// load the corporate profile for rounding invoice total
		$formData['roundTotal'] = null;
		if (\App\Config\InvoiceRounding::getInstance()->isEnabled()) {
			if ($idCorp) {
				$invRounding = \App\EntityPreference\Corporate::getInstance($idCorp)->roundInvoice;
				if ($invRounding!==null) {
					$formData['roundTotal'] = $invRounding;
				}
			}
			if ($formData['roundTotal']===null) {
				$formData['roundTotal'] = \App\Config\InvoiceRounding::getInstance()->defaultByCurrency($formData['invoiceCurrency']);
			}
		}

		// populate the final bank account id
		$formData['idBankAccount'] = $this->getDefaultBankAccountId($formData['invoiceCurrency'], (int)$miceProjectProfile->idAgencyBankAccount, (int)$idCorp);

		// prepare finalize advance mode
		if (!$formData['forceFinalizeAdvanceMode']) {
			$formData['forceFinalizeAdvanceMode'] = $this->cfg['defaultValues']['finalizeAdvanceMode'];
		}

		// form of payment
		$formData['formOfPayment'] = \App\Config\FormsOfPayment::getInstance()->getDefaultItem();

		// other fields
		$formData['comments'] = '';

		// fulfillment date
		$formData['determineFulfillmentDate'] = 1;

		return $formData;

	}

	/**
	 * Populated the invoice entity (insert operation) with all formData details.
	 * The formData can also be updated at this step (eg. the remarks).
	 * @param \Entity\Invoice $invoiceInsertEnt
	 * @param array $formData
	 */
	private function populateInvoiceEntity(\Entity\Invoice $invoiceInsertEnt, array &$formData)
	{

		[$idCorp, $idCust] = explode('|', $formData['idClient']);

		// attach normal fields
		$invoiceInsertEnt->idReservation	= $this->idOrder;
		$invoiceInsertEnt->currency			= $formData['invoiceCurrency'];
		$invoiceInsertEnt->idInvoiceSeries	= $formData['idSeries'];
		$invoiceInsertEnt->invoiceDate		= $formData['invoiceDate'];
		$invoiceInsertEnt->dueDate			= $formData['dueDate']?:null;
		$invoiceInsertEnt->temporary		= (Int)$formData['temporary'];
		$invoiceInsertEnt->formOfPayment	= $formData['formOfPayment'];
		$invoiceInsertEnt->idBankAccount	= $formData['idBankAccount'];
		$invoiceInsertEnt->idUser			= $formData['user']['id'];
		$invoiceInsertEnt->idOffice			= \Entity\User::select($formData['user']['id'])->idOffice;
		$invoiceInsertEnt->comments			= $formData['comments'];
		$invoiceInsertEnt->roundTotal		= (Int)$formData['roundTotal'];

		// client
		if ($idCorp) {
			$invoiceInsertEnt->idCorporate	= $idCorp;
			$invoiceInsertEnt->idCorporateOffice = $idCust;
		} else {
			$invoiceInsertEnt->idCustomer	= $idCust;
		}


		// add service invoice options
		$sios = [];
		foreach($formData['servicesSelect'] as $idService=>$active) {

			if ($active) {

				$serviceEnt = \Entity\Service::select($idService);

				switch ($formData['invoiceOption'][$idService]) {

					case 'total': case 'totalWithPenalty':
						$sio = [
							'idReservationService'	=> $idService,
							'invoiceContent'		=> 'total',
							'quantityType'			=> $formData['totalQuantityType'][$idService]??'one',
						];
						// add fam details
						if ($serviceEnt->statusFin=='PI') {
							if ($formData['finalizeAdvanceMode'][$idService]) {
								$sio['finalizeAdvanceMode'] = $formData['finalizeAdvanceMode'][$idService];
							} elseif ($formData['forceFinalizeAdvanceMode']) {
								$sio['finalizeAdvanceMode'] = $formData['forceFinalizeAdvanceMode'];
							}
						}
						$sios[] = $sio;

						if ($formData['invoiceOption'][$idService]=='totalWithPenalty') {
							$reconvertedValue = $this->reconvertManualValuesForCustomRates($formData['totalWithPenaltyValue'][$idService], $formData['totalWithPenaltyCurrency'][$idService], $serviceEnt);
							$sios[] = [
								'idReservationService'	=> $idService,
								'invoiceContent'		=> 'penalty',
								'value'					=> $reconvertedValue,
								'currency'				=> $formData['totalWithPenaltyCurrency'][$idService],
							];
						}

					break;

					case 'advance':
						$reconvertedValue = $this->reconvertManualValuesForCustomRates($formData['advanceValue'][$idService], $formData['advanceCurrency'][$idService], $serviceEnt);
						$sios[] = [
							'idReservationService'	=> $idService,
							'invoiceContent'		=> 'advance',
							'value'					=> $reconvertedValue,
							'currency'				=> $formData['advanceCurrency'][$idService],
						];
					break;

					case 'reverseAdvance':
						$sios[] = [
							'idReservationService'	=> $idService,
							'invoiceContent'		=> 'reverseAdvance',
						];
					break;

					case 'reverseTotal':
						$sios[] = [
							'idReservationService'	=> $idService,
							'invoiceContent'		=> 'reverseTotal',
						];
					break;

					case 'refund':
						$sio = [
							'idReservationService'	=> $idService,
							'invoiceContent'		=> 'refund',
							'refundDetails'			=> [
								'supplierTotal'			=> $this->reconvertManualValuesForCustomRates($formData['refundFareUsedSupplierPriceTotal'][$idService], $formData['refundFareUsedSupplierPriceCurrency'][$idService], $serviceEnt),
								'supplierVat'			=> $this->reconvertManualValuesForCustomRates($formData['refundFareUsedSupplierPriceVat'][$idService], $formData['refundFareUsedSupplierPriceCurrency'][$idService], $serviceEnt),
								'supplierCurrency'		=> $formData['refundFareUsedSupplierPriceCurrency'][$idService],
								'commissionTotal'		=> $this->reconvertManualValuesForCustomRates($formData['refundFareUsedCommissionTotal'][$idService], $formData['refundFareUsedCommissionCurrency'][$idService], $serviceEnt),
								'commissionCurrency'	=> $formData['refundFareUsedCommissionCurrency'][$idService],
								'serviceFeeTotal'		=> $this->reconvertManualValuesForCustomRates($formData['refundServiceFeeTotal'][$idService], $formData['refundServiceFeeCurrency'][$idService], $serviceEnt),
								'serviceFeeCurrency'	=> $formData['refundServiceFeeCurrency'][$idService],
								'taxTotal'				=> $this->reconvertManualValuesForCustomRates($formData['refundFareUsedTaxTotal'][$idService], $formData['refundFareUsedTaxCurrency'][$idService], $serviceEnt),
								'taxVat'				=> $this->reconvertManualValuesForCustomRates($formData['refundFareUsedTaxVat'][$idService], $formData['refundFareUsedTaxCurrency'][$idService], $serviceEnt),
								'taxCurrency'			=> $formData['refundFareUsedTaxCurrency'][$idService],
								'penaltyTotal'			=> $this->reconvertManualValuesForCustomRates($formData['refundPenaltyTotal'][$idService], $formData['refundPenaltyCurrency'][$idService], $serviceEnt),
								'penaltyCurrency'		=> $formData['refundPenaltyCurrency'][$idService],
								'returnServiceFee'		=> $formData['refundReturnServiceFee'][$idService],
							]
						];
						// add fam details
						if ($serviceEnt->statusFin=='PI') {
							if ($formData['finalizeAdvanceMode'][$idService]) {
								$sio['finalizeAdvanceMode'] = $formData['finalizeAdvanceMode'][$idService];
							} elseif ($formData['forceFinalizeAdvanceMode']) {
								$sio['finalizeAdvanceMode'] = $formData['forceFinalizeAdvanceMode'];
							}
						}
						$sios[] = $sio;
					break;

					case 'reverseRefund':
						$sios[] = [
							'idReservationService'	=> $idService,
							'invoiceContent'		=> 'reverseRefund',
						];
					break;

					case 'penalty':
						$reconvertedValue = $this->reconvertManualValuesForCustomRates($formData['penaltyValue'][$idService], $formData['penaltyCurrency'][$idService], $serviceEnt);
 						$sios[] = [
							'idReservationService'	=> $idService,
							'invoiceContent'		=> 'penalty',
							'value'					=> $reconvertedValue,
							'currency'				=> $formData['penaltyCurrency'][$idService],
						];
					break;

					case 'reverseTotalWithPenalty':
						$sios[] = [
							'idReservationService'	=> $idService,
							'invoiceContent'		=> 'reverseTotal',
						];
						$reconvertedValue = $this->reconvertManualValuesForCustomRates($formData['reverseTotalWithPenaltyValue'][$idService], $formData['reverseTotalWithPenaltyCurrency'][$idService], $serviceEnt);
						$sios[] = [
							'idReservationService'	=> $idService,
							'invoiceContent'		=> 'penalty',
							'value'					=> $reconvertedValue,
							'currency'				=> $formData['reverseTotalWithPenaltyCurrency'][$idService],
						];
					break;

				}

			}
		}

		$invoiceInsertEnt->servicesInvoiceOptions = $sios;

		// fulfillment date
		if ($formData['determineFulfillmentDate']) {
			$invoiceInsertEnt->fulfillmentDate = $formData['fulfillmentDate'] = $invoiceInsertEnt->get_defaultFulfillmentDate();
		} else {
			$invoiceInsertEnt->fulfillmentDate	= $formData['fulfillmentDate']?:null;
		}

		// rewrite invoice details (when submitted)
		if ($formData['currencyType']) {
			$invoiceInsertEnt->invoiceRowCurrencyTypes	= $formData['currencyType'];
			$invoiceInsertEnt->invoiceRowCurrencyDates	= $formData['currencyDate'];
			$invoiceInsertEnt->invoiceRowMeasureUnits	= $formData['measureUnit'];
			$invoiceInsertEnt->invoiceRowHiding			= (array)$formData['hidden'];
		}

		if ($formData['rowDescriptionScheme']) {
			$invoiceInsertEnt->rowDescriptionScheme = $formData['rowDescriptionScheme'];
		} elseif ($formData['description']) {
			$invoiceInsertEnt->invoiceRowDescriptions	= $formData['description'];
		}


		// process remarks and also copy remarks from the services
		$invoiceInsertEnt->remarks = $formData['remarks'];
		if ($formData['copyServiceRemarks']) {
			$copyRemarks = $invoiceInsertEnt->get_invoiceRemarksFromServices();
			if ($copyRemarks) {
				$formData['remarks'] = $formData['remarks']."\n".$copyRemarks;
			}
		}
		$invoiceInsertEnt->remarks = $formData['remarks'];

		// OTHER SETTINGS/CONFIGS NEED THE ROWS TO BE CREATED /////////////////////////////////////
		// loading of invoice rows is done only if needed (updating the currency type/date)
		if ($formData['forceCurrencyType'] or $formData['forceCurrencyDate'] or $formData['forceHideRowsComponentsFromProfile']) {
			$invoiceRows = $invoiceInsertEnt->createInvoiceRows();
		}

		// overwrite the currency type and currency date of all invoice rows
		if ($formData['forceCurrencyType']) {
			$invoiceRowCurrencyTypes = [];
			foreach($invoiceRows as $rowKey=>$nada) {
				$invoiceRowCurrencyTypes[$rowKey] = $formData['forceCurrencyType'];
			}
			$invoiceInsertEnt->invoiceRowCurrencyTypes = $invoiceRowCurrencyTypes;
		}
		if ($formData['forceCurrencyDate']) {
			$invoiceRowCurrencyDates = [];
			foreach($invoiceRows as $rowKey=>$nada) {
				$invoiceRowCurrencyDates[$rowKey] = $formData['forceCurrencyDate'];
			}
			$invoiceInsertEnt->invoiceRowCurrencyDates = $invoiceRowCurrencyDates;
		}

		// forces the hidden/shown rows on the invoice based on corporate profile, but also consideres the app default, for non-set values in corporate profile
		if ($formData['forceHideRowsComponentsFromProfile']) {
			// loads calculation settings (for cases in which the corporate profile is set to "app default"
			$invoiceVatsCfgObj = \App\Config\Services_Calculations_InvoiceVat::getInstance();
			// here we collect  corporate profile or calculation details regarding hidden rows per component
			$hideRows = [];
			foreach($invoiceRows as $rowKey=>$rowDetails) {
				// for 'discount', 'serviceFee' and 'tax', we check if there is a corporate profile
				if (
					in_array($rowDetails['rowType'], ['discount', 'serviceFee', 'tax'])
					and key_exists($rowDetails['serviceCode'], $formData['forceHideRowsComponentsFromProfile'])
					and key_exists($rowDetails['rowType'], $formData['forceHideRowsComponentsFromProfile'][$rowDetails['serviceCode']])
				) {
					$hideRows[$rowKey] = $formData['forceHideRowsComponentsFromProfile'][$rowDetails['serviceCode']][$rowDetails['rowType']];
				}
				// load calculation default
				else {
					$hideRows[$rowKey] = $invoiceVatsCfgObj->getHidden($rowDetails['serviceCode'], $rowDetails['location'], $rowDetails['calculationType'], $rowDetails['rowType']);
				}
			}
			$invoiceInsertEnt->invoiceRowHiding = $hideRows;
		}

	}


	/**
	 * Calculates the invoice due date based on the current criteria: client, invoice date and service code.
	 * @param string $invoiceDate - invoice date (ISO date)
	 * @param string $idClient - idCorporate|idCorporateOffice or 0|idCustomer
	 * @param string $serviceCode - the service code (usually the first one from the invoice)
	 */
	private function calculateDueDateFromProfile($invoiceDate, $idClient, $serviceCode)
	{

		// get client details
		[$idCorp, $idCust] = explode('|', $idClient);
		if ($idCorp) {
			$dueDateProfile = new \App\Utils\InvoiceDueDate\Corporate($idCorp);
		} else {
			$dueDateProfile = new \App\Utils\InvoiceDueDate\Customer($idCust);
		}
		// determine due date
		return $dueDateProfile->calculateDueDate($invoiceDate, $serviceCode);

	}

	/**
	 * Returns the invoice due date based on client profile, invoice date, etc.
	 * @param array $R
	 */
	protected function actionGetDueDate($R)
	{

		$dueDate = $this->calculateDueDateFromProfile($R['invoiceDate'], $R['idClient'], $R['serviceCode']);

		// output the details
		$output = new \stdClass();
		$output->dueDate = $dueDate;
		return new \Engine\Mvc\Response\Json($output);

	}

	/**
	 * If the currency type and date are updated locally ($CFG['Invoices']['InvoiceInsert']['defaultValues']['currencyDate']
	 * or $CFG['Invoices']['InvoiceInsert']['defaultValues']['currencyType']) we have to reconvert the
	 * manually inputted prices so that we get the same result; this happens because the class Invoice_CreateInvoiceRowsAbstract
	 * will always use the offer currency details when converting these values to
	 * @param float $value
	 * @param string $currency
	 * @param \Entity\Service $serviceEnt
	 * @param string
	 * @return float
	 */
	private function reconvertManualValuesForCustomRates($value, $currency, \Entity\Service $serviceEnt)
	{

		if (!$value) {
			return 0;
		}
		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();

		// if no custom configs - do not do anything
		if (!$CFG['Invoices']['InvoiceInsert']['defaultValues']['currencyType'] and !$CFG['Invoices']['InvoiceInsert']['defaultValues']['currencyDate']) {
			return $value;
		}

		$offerEnt = $serviceEnt->chosenOffer;

		$currDate = $CFG['Invoices']['InvoiceInsert']['defaultValues']['currencyDate'] ? : $offerEnt->currencyDate;
		$currType = $CFG['Invoices']['InvoiceInsert']['defaultValues']['currencyType'] ? : $offerEnt->currencyType;

		// convert from manually set currency to offer currency
		$convertedAmount1 = \App\CurrencyExchange\Rates::getInstance($currDate, $currType)->convert($value, $currency, $offerEnt->totalCurrency, null);

		// and then back to original (manual) currency (but with different currency details)
		$convertedAmount2 = \App\CurrencyExchange\Rates::getInstance($offerEnt->currencyDate, $offerEnt->currencyType)->convert($convertedAmount1, $offerEnt->totalCurrency, $currency, null);

		return $convertedAmount2;

	}

	/**
	 * Load the default bank account
	 *
	 * Do this based on the invoice currency:
	 * 	1. If we have MICE Project, we check it's currency and load it:
	 * 		- if it matches the invoice currency or if it is blank
	 * 	2. Else if we have a corporate and a bank account in its profile, we check it's currency and load it:
	 * 		- if it matches the invoice currency or if it is blank
	 *	3. Else if the current office has a default bank account, we check it's currency and load it:
	 * 		- if it matches the invoice currency or if it is blank
	 *  4. Else we search for any bank account in the current office that matches the invoice currency or has the currency blank
	 *  5. If none of the above check were successful, we load the agency default bank account
	 *
	 * @param string $invoiceCurrency
	 * @param int $miceProjectProfile
	 * @param int $idCorp
	 * @return int
	 */
	private function getDefaultBankAccountId(string $invoiceCurrency, int $miceIdBankAccount, int $idCorp) : int
	{
		// load bank account form MICE project and check currency
		if ($miceIdBankAccount) {
			$agencyBankAccountEnt = \Entity\AgencyBankAccount::select($miceIdBankAccount);
			if (in_array($agencyBankAccountEnt->currency, [$invoiceCurrency, ''])) {
				return $miceIdBankAccount;
			}
		}

		// load bank account from corporate profile and check currency
		if ($idCorp) {
			$idAgencyBankAccount = \App\EntityPreference\Corporate::getInstance($idCorp)->agencyBankAccount;
			if ($idAgencyBankAccount) {
				$agencyBankAccountEnt = \Entity\AgencyBankAccount::select($idAgencyBankAccount);
				if (in_array($agencyBankAccountEnt->currency, [$invoiceCurrency, ''])) {
					return $idAgencyBankAccount;
				}
			}
		}

		// load bank account form current office and check currency
		$idAgencyBankAccount = \Entity\Office::current()->idDefaultBankAccount;
		if ($idAgencyBankAccount) {
			$agencyBankAccountEnt = \Entity\AgencyBankAccount::select($idAgencyBankAccount);
			if (in_array($agencyBankAccountEnt->currency, [$invoiceCurrency, ''])) {
				return $idAgencyBankAccount;
			}
		}

		// if the office default bank account is not in the invoice currency, we search for a bank account associated ot the office in that currency
		$agencyBankAccountObj = new \App\EntityList\AgencyBankAccounts();
		$agencyBankAccountObj->setFilter('bas.currency', 'IN', [$invoiceCurrency, '']);
		$agencyBankAccountObj->setFilter('off.idOffice', '=', \Entity\Office::current()->getId());
		$agencyBankAccountObj->setOrderBy('bas.currency', 'DESC');
		$agencyBankAccountObj->setLimit(1);
		$agencyBankAccountArray = $agencyBankAccountObj->exportRecords();

		if ($agencyBankAccountArray) {
			return $agencyBankAccountArray[0]['id'];
		}

		// load bank account from agency level (we do not need to check the currency for this one)
		return \Entity\AgencyBankAccount::getDefaultId();
	}
}
