<?php
namespace App\Utils\DocsSpecialObjects;

class InvoiceRowsFakeRowsAndSpecialVatTax
{
	/**
	 * Services codes for which the special tax can appear
	 * @var array
	 */
	private $serviceCodes = [];

	/**
	 * Invoice id
	 * @var int
	 */
	private $idInvoice;

	/**
	 * Include or not fake rows when paying with cc
	 * @var boolean
	 */
	private $includeFakeRows;

	/**
	 * Name of the special tax (ex: K7, EC)
	 * @var array
	 */
	private $specialTaxCodes;

	/**
	 * In case we have a manual service we take the tax value and currency from 2 remarks
	 * @var string
	 */
	private $specialTaxRemarkValue;

	/**
	 * In case we have a manual service we take the tax value and currency from 2 remarks
	 * @var string
	 */
	private $specialTaxRemarkCurrency;

	/**
	 * Invoice rows
	 * @var array
	 */
	private $invoiceRows;

	/**
	 * Instances array for multiton
	 * @var array
	 */
	static private $instance = [];

	/**
	 * @param int $idInvoice
	 * @param boolean $includeFakeRows
	 */
	private function __construct(int $idInvoice, $includeFakeRows)
	{
		$this->idInvoice = $idInvoice;
		$this->includeFakeRows = $includeFakeRows;
	}

	/**
	 * Singleton instantiation.
	 * @param int $idInvoice
	 * @param bool $includeFakeRows
	 * @return $this
	 */
	static public function getInstance(int $idInvoice, bool $includeFakeRows)
	{
		$key = sprintf("%s-%s", $idInvoice, ($includeFakeRows ? '1' : '0'));
		if (!array_key_exists($key, self::$instance)) {
			self::$instance[$key] = new self($idInvoice, $includeFakeRows);
		}

		return self::$instance[$key];
	}

	/**
	 * Populates output rows
	 */
	public function process() :void
	{
		if (!\Entity\Invoice::select($this->idInvoice)->recordExists()) {
			throw new \Exception('The invoice id received is not valid.');
		}

		if ($this->invoiceRows === null) {
			$this->populateRows();
		}
	}

	/**
	 * Get the invoice rows used for display in the special objects
	 * @throws \Exception
	 * @return array
	 */
	public function getInvoiceRows() : array
	{
		if ($this->invoiceRows === null) {
			throw new \Exception('You need to call process method before.');
		}

		return $this->invoiceRows;
	}

	/**
	 * Set special tax name
	 */
	public function setSpecialTaxCodes(array $specialTaxCodes) :void
	{
		$this->specialTaxCodes = $specialTaxCodes;
	}

	/**
	 * Set remark name for special tax value
	 */
	public function setSpecialTaxRemarks(string $specialTaxRemarkValue, string $specialTaxRemarkCurrency) :void
	{
		$this->specialTaxRemarkValue = $specialTaxRemarkValue;
		$this->specialTaxRemarkCurrency = $specialTaxRemarkCurrency;
	}

	/**
	 * Set service codes
	 * @param array $serviceCodes
	 */
	public function setServiceCodes(array $serviceCodes)
	{
		$this->serviceCodes = $serviceCodes;
	}

	/**
	 * Get the output rows which will later be used for display
	 */
	private function populateRows() :void
	{
		$invoiceEnt = \Entity\Invoice::select($this->idInvoice);
		$this->invoiceRows = [];

		foreach ($invoiceEnt->visibleRowsList as $id => $row) {

			// DETERMINE THE SPECIAL TAX VALUE (K7) for the current row (which may be a group) ////

			// gets the list of rows to be considered (parent + hidden children, if any)
			$invRowParent = \Entity\InvoiceRow::select($id);
			$rowsInGroup = array_merge([$id], array_keys($invRowParent->hiddenChildrenRowsList));

			// CALCULATES THE SPECIAL TAX VALUE (for each row in the group) -----------------------
			$rowGroupSpecialTaxAmount = 0;
			foreach ($rowsInGroup as $idRow) {

				$rowEnt = \Entity\InvoiceRow::select($idRow);
				// only taxes for ticketing (or similar service types) are considered
				if ($rowEnt->rowType == 'tax' and in_array($rowEnt->serviceCode, $this->serviceCodes)) {
					// determine the (abs) value of the special tax
					if ($rowEnt->refund and !$rowEnt->extraInfo['refundReverseService']) {
						$rowSpecialTaxAmount = $this->getSpecialTaxValueRefund($rowEnt->service);
					} else {
						$rowSpecialTaxAmount = $this->getSpecialTaxValueIssue($rowEnt->service);
					}
					// force sign for special tax
					if ($rowEnt->pricePrintValue < 0) {
						$rowSpecialTaxAmount *= -1;
					}
					// add current row specialTax component
					$rowGroupSpecialTaxAmount += $rowSpecialTaxAmount;
				}

			}

			// ADDS THE CURRENT ROW TO THE MAIN LIST //////////////////////////////////////////////

			// indicates if the group
			$groupSign = (($row['pricePrintValue'] >= 0 && $row['rowType'] != 'discount') || ($row['pricePrintValue'] < 0 && $row['rowType'] == 'discount')) ? 'positive' : 'negative';

			$this->invoiceRows[] = [
				'idRow'				=> $id,
				'serviceCode'		=> $row['serviceCode'],
				'description'		=> $row['description'],
				'measureUnit'		=> $row['measureUnit'],
				'quantity'			=> $row['quantity'],
				'amountBase'		=> $row['pricePrintValue'],
				'amountVat'			=> $row['vatPrintValue'],
				'amountTotal'		=> $row['pricePrintValue'] + $row['vatPrintValue'],
				'vatText'			=> $row['vatText'],
				'currency'			=> $invoiceEnt->currency,
				'rowType'			=> $row['rowType'],
				'vatPercent'		=> $row['vatPercent'],
				'specialTax'		=> $rowGroupSpecialTaxAmount,
				'fakeRow'			=> false,
				'groupSign'			=> $groupSign,
				'idDirectParentRow'	=> $id,
				'idVisibleParentRow' => $id,
			];


			// ADDS FAKE ROWS (IF OPTION IS SET) //////////////////////////////////////////////////
			if ($this->includeFakeRows) {
				// process parent
				$rows = $this->createTicketRows($id, $groupSign);
				$this->invoiceRows = array_merge($this->invoiceRows, $rows);

				// if we are in a group (we have children) process them as well
				if ($row['childrenRows']) {
					foreach ($row['childrenRows'] as $idRow) {
						$rows = $this->createTicketRows($idRow, $groupSign);
						$this->invoiceRows = array_merge($this->invoiceRows, $rows);
					}
				}
			}

		}

	}

	/**
	 * This method creates 'fake' service price and tax rows for a ticket
	 * @param int $idInvoiceRow
	 * @return array
	 */
	private function createTicketRows(int $idInvoiceRow, string $groupSign)
	{

		// INITS AND VALIDATIONS ------------------------------------------------------------------

		$invoiceRowEnt = \Entity\InvoiceRow::select($idInvoiceRow);
		if ($invoiceRowEnt->rowType != 'serviceFee') {
			return [];
		}

		// this helps to skip the "returned service fee" from issue - we do not want to consider this row but only the posive (extra) serviceFee associated to the refund
		if ( $invoiceRowEnt->extraInfo['refundReverseService'] ) {
			return [];
		}

		$serviceEnt = $invoiceRowEnt->service;
		$offerEnt = $serviceEnt->chosenOffer;
		// the offer must still exist on the service
		if (!$offerEnt->recordExists() || $offerEnt->deleted) {
			return [];
		}
		// the offer must have the flag 'ignoreSupplierOnDocuments' set
		if (!$offerEnt->ignoreSupplierOnDocuments) {
			return [];
		}

		$invoiceEnt = \Entity\Invoice::select($this->idInvoice);
		$invoiceCurrency = $invoiceEnt->currency;
		$invoiceDate = $invoiceEnt->invoiceDate;

		$rows = []; // rows array
		$calculationDetails = \App\Config\Services_Calculations_InvoiceVat::getAdmin()->getCalculationDetails($invoiceRowEnt->serviceCode, $invoiceRowEnt->location, $invoiceRowEnt->calculationType);

		// PREPARE AMOUNTS TO BE INCLUDED AS FAKE ROWS ////////////////////////////////////////////

		// prepare offer service price
		if ($calculationDetails['servicePrice']['vatPercent']==-1) { // -1 VAT for taxes
			$origOfferServicePriceBase = \App\CurrencyExchange\Rates::getInstance($invoiceDate)->convert( $offerEnt->supplierWorkValue + $offerEnt->commissionWorkValue , $offerEnt->totalCurrency, $invoiceCurrency );
			$origOfferServicePriceVat = \App\CurrencyExchange\Rates::getInstance($invoiceDate)->convert( $offerEnt->supplierWorkVat + $offerEnt->commissionWorkVat , $offerEnt->totalCurrency, $invoiceCurrency );
			$origOfferServicePriceTotal = \App\Config\CurrencyDecimals::roundPrice( $origOfferServicePriceBase + $origOfferServicePriceVat, $invoiceCurrency);
		} else { // normal VAT percentage (0 or more)
			$offerServicePriceTotal = $offerEnt->supplierWorkValue + $offerEnt->supplierWorkVat + $offerEnt->commissionWorkValue + $offerEnt->commissionWorkVat;
			$origOfferServicePriceTotal = \App\CurrencyExchange\Rates::getInstance($invoiceDate)->convert($offerServicePriceTotal, $offerEnt->totalCurrency, $invoiceCurrency);
			$origOfferServicePriceBase = \App\Config\CurrencyDecimals::roundPrice( $origOfferServicePriceTotal * 100 / (100+$calculationDetails['price']['vatPercent']), $invoiceCurrency);
			$origOfferServicePriceVat = \App\Config\CurrencyDecimals::roundPrice( $origOfferServicePriceTotal - $origOfferServicePriceBase, $invoiceCurrency);
		}

		// prepare offer taxes
		if ($calculationDetails['tax']['vatPercent']==-1) { // -1 VAT for taxes
			$origOfferTaxBase = \App\CurrencyExchange\Rates::getInstance($invoiceDate)->convert($offerEnt->taxWorkValue, $offerEnt->totalCurrency, $invoiceCurrency);
			$origOfferTaxVat = \App\CurrencyExchange\Rates::getInstance($invoiceDate)->convert($offerEnt->taxWorkVat, $offerEnt->totalCurrency, $invoiceCurrency);
			$origOfferTaxTotal = \App\Config\CurrencyDecimals::roundPrice( $origOfferTaxBase + $origOfferTaxVat, $invoiceCurrency );
		} else { // normal VAT percentage (0 or more)
			$origOfferTaxTotal = \App\CurrencyExchange\Rates::getInstance($invoiceDate)->convert($offerEnt->taxWorkValue + $offerEnt->taxWorkVat, $offerEnt->totalCurrency, $invoiceCurrency);
			$origOfferTaxBase = \App\Config\CurrencyDecimals::roundPrice( $origOfferTaxTotal * 100 / (100+$calculationDetails['tax']['vatPercent']), $invoiceCurrency);
			$origOfferTaxVat = \App\Config\CurrencyDecimals::roundPrice( $origOfferTaxTotal - $origOfferTaxBase, $invoiceCurrency);
		}

		// prepare fareUsed price and tax
		$refundFareUsedServicePriceTotal = $refundFareUsedServicePriceBase = $refundFareUsedServicePriceVat = 0;
		$refundFareUsedTaxTotal = $refundFareUsedTaxBase = $refundFareUsedTaxVat = 0;
		$refundPenaltyTotal = $refundPenaltyBase = $refundPenaltyVat = 0;
		if ($invoiceRowEnt->refund) {
			if ($serviceEnt->idImportedService) {
				$impServiceEnt = $serviceEnt->importedService;
				$impServiceObj = $impServiceEnt->object;
				if ($impServiceObj and $impServiceEnt->systemStatus=='refunded') {
					$rfndDetObj = $impServiceObj->refundDetails;
					if ($rfndDetObj) {
						// fareUsed - service price
						$rfndFareUsedServicePriceOrig = $rfndDetObj->fareUsedSupplierValue + $rfndDetObj->fareUsedSupplierVat + $rfndDetObj->fareUsedCommissionValue;
						$refundFareUsedServicePriceTotal = \App\CurrencyExchange\Rates::getInstance($invoiceDate)->convert($rfndFareUsedServicePriceOrig, $rfndDetObj->currency, $invoiceCurrency);
						$refundFareUsedServicePriceBase = \App\Config\CurrencyDecimals::roundPrice( $refundFareUsedServicePriceTotal * 100 / (100+$calculationDetails['price']['vatPercent']), $invoiceCurrency);
						$refundFareUsedServicePriceVat = \App\Config\CurrencyDecimals::roundPrice( $refundFareUsedServicePriceTotal - $refundFareUsedServicePriceBase, $invoiceCurrency);
						// fareUsed - tax
						$refundFareUsedTaxTotal = \App\CurrencyExchange\Rates::getInstance($invoiceDate)->convert($rfndDetObj->fareUsedTaxValue + $rfndDetObj->fareUsedTaxVat, $rfndDetObj->currency, $invoiceCurrency);
						$refundFareUsedTaxBase = \App\Config\CurrencyDecimals::roundPrice( $refundFareUsedTaxTotal * 100 / (100+$calculationDetails['tax']['vatPercent']), $invoiceCurrency);
						$refundFareUsedTaxVat = \App\Config\CurrencyDecimals::roundPrice( $refundFareUsedTaxTotal - $refundFareUsedTaxBase, $invoiceCurrency);
						// penalty
						$refundPenaltyTotal = \App\CurrencyExchange\Rates::getInstance($invoiceDate)->convert($rfndDetObj->penaltyValue, $rfndDetObj->currency, $invoiceCurrency);
						$refundPenaltyBase = \App\Config\CurrencyDecimals::roundPrice( $refundPenaltyTotal * 100 / (100+$calculationDetails['penalty']['vatPercent']), $invoiceCurrency);
						$refundPenaltyVat = \App\Config\CurrencyDecimals::roundPrice( $refundPenaltyTotal - $refundPenaltyBase, $invoiceCurrency);
					}
				}
			}
		}

		// FAKE PRICE ROW /////////////////////////////////////////////////////////////////////////

		// determine the values based on current row type
		$fakeServicePriceValue = $fakeServicePriceVat = 0;
		if ($invoiceRowEnt->refund) { // handle refunds case
			if ($invoiceRowEnt->pricePrintValue < 0) { // handle credit note for refunds
				$fakeServicePriceValue = $origOfferServicePriceBase - $refundFareUsedServicePriceBase;
				$fakeServicePriceVat = $origOfferServicePriceVat - $refundFareUsedServicePriceVat;
			} else { // initial refund invoice
				$fakeServicePriceValue = - $origOfferServicePriceBase + $refundFareUsedServicePriceBase;
				$fakeServicePriceVat = - $origOfferServicePriceVat + $refundFareUsedServicePriceVat;
			}
		} else { // issue (non-refund) case
			if ($invoiceRowEnt->pricePrintValue < 0) { // handle credit note cases
				$fakeServicePriceValue = - $origOfferServicePriceBase;
				$fakeServicePriceVat = - $origOfferServicePriceVat;
			} else { // initial invoice cases
				$fakeServicePriceValue = $origOfferServicePriceBase;
				$fakeServicePriceVat = $origOfferServicePriceVat;
			}
		}

		// inject new row
		if ($fakeServicePriceValue) {
			$servicePriceRow = [
				'idRow'				=> 0,
				'serviceCode'		=> $invoiceRowEnt->serviceCode,
				'description'		=> $this->processInvoiceRowDescription($serviceEnt, 'price'),
				'measureUnit'		=> $invoiceRowEnt->measureUnit,
				'quantity'			=> 1,
				'amountBase'		=> $fakeServicePriceValue,
				'amountVat'			=> $fakeServicePriceVat,
				'amountTotal'		=> $fakeServicePriceValue + $fakeServicePriceVat,
				'vatText'			=> $calculationDetails['price']['vatText'],
				'currency'			=> $invoiceCurrency,
				'rowType'			=> 'servicePrice',
				'vatPercent'		=> $calculationDetails['price']['vatPercent'],
				'specialTax'		=> 0,
				'fakeRow'			=> true,
				'groupSign'			=> $groupSign,
				'idDirectParentRow'	=> $idInvoiceRow,
				'idVisibleParentRow' => $invoiceRowEnt->hiddenOnRowId ? : $idInvoiceRow,
			];

			$rows[] = $servicePriceRow;
		}

		// FAKE TAX ROW ///////////////////////////////////////////////////////////////////////////

		// determine the values based on current row type
		$fakeTaxValue = $fakeTaxVat = 0;
		if ($invoiceRowEnt->refund) { // handle refunds case
			if ($invoiceRowEnt->pricePrintValue < 0) { // handle credit note for refunds
				$fakeTaxValue = $origOfferTaxBase - $refundFareUsedTaxBase;
				$fakeTaxVat = $origOfferTaxVat - $refundFareUsedTaxVat;
			} else { // initial refund invoice
				$fakeTaxValue = - $origOfferTaxBase + $refundFareUsedTaxBase;
				$fakeTaxVat = - $origOfferTaxVat + $refundFareUsedTaxVat;
			}
		} else { // issue (non-refund) case
			if ($invoiceRowEnt->pricePrintValue < 0) { // handle credit note cases
				$fakeTaxValue = - $origOfferTaxBase;
				$fakeTaxVat = - $origOfferTaxVat;
			} else { // initial invoice cases
				$fakeTaxValue = $origOfferTaxBase;
				$fakeTaxVat = $origOfferTaxVat;
			}
		}

		// calculate special tax for fake row
		$specialTax = 0;
		if (in_array($invoiceRowEnt->serviceCode, $this->serviceCodes)) {
			if ($invoiceRowEnt->refund) {
				$specialTax = $this->getSpecialTaxValueRefund($serviceEnt);
			} else {
				$specialTax = $this->getSpecialTaxValueIssue($serviceEnt);
			}

			// check sign for special tax (on credit notes)
			if ($fakeTaxValue < 0) {
				$specialTax *= -1;
			}
		}

		if ($fakeTaxValue or $specialTax) {
			$taxRow = [
				'idRow'				=> 0,
				'serviceCode'		=> $invoiceRowEnt->serviceCode,
				'description'		=> $this->processInvoiceRowDescription($serviceEnt, 'tax'),
				'measureUnit'		=> $invoiceRowEnt->measureUnit,
				'quantity'			=> 1,
				'amountBase'		=> $fakeTaxValue,
				'amountVat' 		=> $fakeTaxVat,
				'amountTotal'		=> $fakeTaxValue + $fakeTaxVat,
				'vatText'			=> $calculationDetails['tax']['vatText'],
				'currency'			=> $invoiceCurrency,
				'rowType'			=> 'tax',
				'vatPercent'		=> $calculationDetails['tax']['vatPercent'],
				'specialTax'		=> $specialTax,
				'fakeRow'			=> true,
				'groupSign'			=> $groupSign,
				'idDirectParentRow'	=> $idInvoiceRow,
				'idVisibleParentRow' => $invoiceRowEnt->hiddenOnRowId ? : $idInvoiceRow,
			];

			$rows[] = $taxRow;
		}

		// FAKE PENALTY ROW ///////////////////////////////////////////////////////////////////////////

		if ($serviceEnt->idImportedService && $invoiceRowEnt->refund && $serviceEnt->importedService->systemStatus == 'refunded') {

			if ($refundPenaltyBase) {
				// apply the correct sign based on invoice row type
				if ($invoiceRowEnt->pricePrintValue >= 0) { // on normal rows (issue)
					$fakePenaltyBase = $refundPenaltyBase;
					$fakePenaltyVat = $refundPenaltyVat;
				} else { // on credit notes
					$fakePenaltyBase = -$refundPenaltyBase;
					$fakePenaltyVat = -$refundPenaltyVat;
				}

				$penaltyRow = [
					'idRow'				=> 0,
					'serviceCode'		=> $invoiceRowEnt->serviceCode,
					'description'		=> $this->processInvoiceRowDescription($serviceEnt, 'penalty'),
					'measureUnit'		=> $invoiceRowEnt->measureUnit,
					'quantity'			=> 1,
					'amountBase'		=> $fakePenaltyBase,
					'amountVat'			=> $fakePenaltyVat,
					'amountTotal'		=> $fakePenaltyBase + $fakePenaltyVat,
					'vatText'			=> $calculationDetails['penalty']['vatText'],
					'currency'			=> $invoiceCurrency,
					'rowType'			=> 'penalty',
					'vatPercent'		=> $calculationDetails['penalty']['vatPercent'],
					'specialTax'		=> 0,
					'fakeRow'			=> true,
					'groupSign'			=> $groupSign,
					'idDirectParentRow'	=> $idInvoiceRow,
					'idVisibleParentRow' => $invoiceRowEnt->hiddenOnRowId ? : $idInvoiceRow,
				];

				$rows[] = $penaltyRow;
			}
		}

		return $rows;
	}

	/**
	 * Gets invoice row default description schema and replaces formulas with values
	 * @param \Entity\Service $serviceEnt
	 * @param String $rowType
	 * @return string
	 */
	private function processInvoiceRowDescription(\Entity\Service $serviceEnt, $rowType)
	{
		$offerEnt = $serviceEnt->chosenOffer;
		$invoiceEnt = \Entity\Invoice::select($this->idInvoice);

		$invoiceRowsScheme = null;
		if ($invoiceEnt->idCorporate) {
			$invoiceRowsScheme = \App\EntityPreference\Corporate::getInstance($invoiceEnt->idCorporate)->invoiceRowsScheme;
		}

		// get invoice rows texts templates
		if ($invoiceRowsScheme) { // get description from the scheme associated to the company in profile
			$invoiceRowTextTemplate = \App\Config\InvoiceRowsDescription::getInstance()->getSchemesForService($offerEnt->serviceCode, $invoiceRowsScheme);
		}
		else { // if not take the default schema
			$invoiceRowTextTemplate = \App\Config\InvoiceRowsDescription::getInstance()->getSchemesForService($offerEnt->serviceCode);
		}

		// replace service fields into the row description
		$pivotFields = \App\GenDocument\Vars\Values_Service::getInstance($serviceEnt->getId());
		$textProcessor = new \Engine\GenDocument\TextProcessor\TextProcessor(new \Engine\GenDocument\TextProcessor\DefaultTextHandler(), $pivotFields);
		$textProcessor->reset();

		return trim($textProcessor->parse($invoiceRowTextTemplate[$rowType]));
	}

	/**
	 * Calculates special tax value for issue.
	 * The returned value is converted to invoice currency.
	 * @param \Entity\Service $serviceEnt
	 * @return float
	 */
	private function getSpecialTaxValueIssue(\Entity\Service $serviceEnt) : float
	{

		$invoiceEnt = \Entity\Invoice::select($this->idInvoice);

		// check imported service first
		$specialTaxValue = 0; $specialTaxCurrency = '';
		if (!$specialTaxValue && !$specialTaxCurrency && $serviceEnt->idImportedService && $this->specialTaxCodes) {
			$chosenOffer = $serviceEnt->importedService->object->chosenOffer;
			if ($chosenOffer) {
				if ($chosenOffer->taxCodes) {
					foreach ($chosenOffer->taxCodes as $tax) {
						if (in_array($tax['code'], $this->specialTaxCodes)) {
							$specialTaxValue += $tax['value'];
							$specialTaxCurrency = $tax['currency'];
						}
					}
				}
			}

			$specialTaxValue = \App\CurrencyExchange\Rates::getInstance($invoiceEnt->invoiceDate)->convert($specialTaxValue, $specialTaxCurrency, $invoiceEnt->currency);
		}

		// if nothing found, checks remarks
		if (!$specialTaxValue && $this->specialTaxRemarkValue && $this->specialTaxRemarkCurrency && $serviceEnt->processedRemarks[$this->specialTaxRemarkValue] && strlen($serviceEnt->processedRemarks[$this->specialTaxRemarkValue]) && $serviceEnt->processedRemarks[$this->specialTaxRemarkCurrency] && strlen($serviceEnt->processedRemarks[$this->specialTaxRemarkCurrency])) {
			$specialTaxValue = \App\CurrencyExchange\Rates::getInstance($invoiceEnt->invoiceDate)->convert($serviceEnt->processedRemarks[$this->specialTaxRemarkValue], $serviceEnt->processedRemarks[$this->specialTaxRemarkCurrency], $invoiceEnt->currency);
		}

		return $specialTaxValue;
	}

	/**
	 * Calculates special tax value for refund. Starts from the original special tax and subtracts the
	 * fareUsed specil tax (if any) - this will only check refund details from imported service.
	 * The returned value is converted to invoice currency.
	 * @param \Entity\Service $serviceEnt
	 * @return float
	 */
	private function getSpecialTaxValueRefund(\Entity\Service $serviceEnt) : float
	{

		// extracts the original special tax from issue
		$originalSpecialTax = $this->getSpecialTaxValueIssue($serviceEnt);

		// calculate the special tax from refund details
		$fareUsedSpecialTaxValue = 0; $fareUsedSpecialTaxCurrency = '';
		if ($serviceEnt->idImportedService && $this->specialTaxCodes) {
			$refundDetails = $serviceEnt->importedService->object->refundDetails;
			if ($refundDetails) {
				if ($refundDetails->fareUsedTaxCodes) {
					foreach ($refundDetails->fareUsedTaxCodes as $tax) {
						if (in_array($tax['code'], $this->specialTaxCodes)) {
							$fareUsedSpecialTaxValue += $tax['value'];
							$fareUsedSpecialTaxCurrency = $tax['currency'];
						}
					}
				}
			}

			// convert to invoice currency
			$invoiceEnt = \Entity\Invoice::select($this->idInvoice);
			$fareUsedSpecialTaxValue = \App\CurrencyExchange\Rates::getInstance($invoiceEnt->invoiceDate)->convert($fareUsedSpecialTaxValue, $fareUsedSpecialTaxCurrency, $invoiceEnt->currency);
		}

		// the returned value is always the original special tax minus specialTax from fareUsed (if found in imported service, otherwise it's 0)
		return $originalSpecialTax - $fareUsedSpecialTaxValue;
	}


	public function getVatValuePositive()
	{
		if ($this->invoiceRows === null) {
			throw new \Exception('You need to call process() method before.');
		}

		$totalValue = 0;
		foreach ($this->invoiceRows as $row) {
			if ($row['groupSign']=='positive') {
				$totalValue += $row['amountVat'];
			}
		}

		return $totalValue;
	}

	public function getVatValueNegative()
	{
		if ($this->invoiceRows === null) {
			throw new \Exception('You need to call process() method before.');
		}

		$totalValue = 0;
		foreach ($this->invoiceRows as $row) {
			if ($row['groupSign']=='negative') {
				$totalValue += $row['amountVat'];
			}
		}

		return $totalValue;
	}

	public function getBaseValuePositive()
	{
		if ($this->invoiceRows === null) {
			throw new \Exception('You need to call process() method before.');
		}

		$totalValue = 0;
		foreach ($this->invoiceRows as $row) {
			if ($row['groupSign']=='positive') {
				$totalValue += $row['amountBase'];
			}
		}

		return $totalValue;
	}

	public function getBaseValueNegative()
	{
		if ($this->invoiceRows === null) {
			throw new \Exception('You need to call process() method before.');
		}

		$totalValue = 0;
		foreach ($this->invoiceRows as $row) {
			if ($row['groupSign']=='negative') {
				$totalValue += $row['amountBase'];
			}
		}

		return $totalValue;
	}

	public function getTotalValuePositive()
	{
		if ($this->invoiceRows === null) {
			throw new \Exception('You need to call process() method before.');
		}

		$totalValue = 0;
		foreach ($this->invoiceRows as $row) {
			if ($row['groupSign']=='positive') {
				$totalValue += $row['amountTotal'];
			}
		}

		return $totalValue;
	}

	public function getTotalValueNegative()
	{
		if ($this->invoiceRows === null) {
			throw new \Exception('You need to call process() method before.');
		}

		$totalValue = 0;
		foreach ($this->invoiceRows as $row) {
			if ($row['groupSign']=='negative') {
				$totalValue += $row['amountTotal'];
			}
		}

		return $totalValue;
	}

	public function getTotalSpecialTax()
	{
		if ($this->invoiceRows === null) {
			throw new \Exception('You need to call process() method before.');
		}

		$totalSpecialTax = 0;
		foreach ($this->invoiceRows as $row) {
			$totalSpecialTax += $row['specialTax'];
		}

		return $totalSpecialTax;
	}

}

