<?php
namespace App\Amadeus\StructureParser;

/**
 * The main responsible for converting from the parsed data to the output format ready to be put into the DB, or loaded into TINA
 *
 * In order to add  a new transaction type:
 * 	- add here, in getTransactions() and/or setParsedStructure() the relevat to handle them correctly
 * 	- add in \App\Amadeus\Storage\Operation->getType() the relevant code to make the correct detection
 *  - add in transaction folders, or add a new transaction folder, where you interpret the input data, and out it in a TINA-compatible way
 *
 * @author Lucian
 */
class Processor
{
	/**
	 * The parsed file path
	 * @var string
	 */
	protected $filePath = null;

	/**
	 * The "cached" idFile, so we wont keep hitting the DB if RFND cases(where we dont have the PNR number)
	 * @var string
	 */
	protected $idFile = null;

	/**
	 * \App\Amadeus\FileParser\InternalParsedStructure output
	 * @var array
	 */
	protected $parsedStructure = null;

	/**
	 * The original parser, from where we get our parsedStructure
	 * @var \App\Amadeus\FileParser\ParsedFile
	 */
	protected $parser = null;

	/**
	 * A list of internal transactions
	 * @var array:\App\Amadeus\Storage\Operation
	 */
	protected $transactions = [];


	/**
	 * A list of internal flags
	 * @var array
	 */
	protected $internalStats = [];

	/**
	 * Cleanup allocated items, preventing memory leaks
	 */
	public function destroy()
	{
		foreach($this->transactions as $idx=>$transaction){
			$this->transactions[$idx]->destroy();
			unset($this->transactions[$idx]);
		}
		$this->transactions = [];

		$this->parser->destroy();	// this should be destroyed elsewere
		$this->parser = null;

		$this->parsedStructure = null;
		$this->filePath = null;
	}


	/**
	 * Return a list of all transactions done in this file
	 * These include
	 *  - ticketed segments(TICK, MCO?)
	 *  - confirmed segments(IMR->hotel)
	 *  - unticketed segments(hotel, ticket, rail etc)
	 * @return array:\App\Amadeus\Storage\Operation
	 */
	public function getTransactions()
	{
		// return cached list of transactions, if we already have it set
		if($this->transactions){
			return $this->transactions;
		}

		$logs = [];

		$reportedUSegments = [];
		$reportedHSegments = [];
		$reportedEMDSegments = [];

		$transactions = [];

		$parsedStructure = $this->getParsedStructure();
		if (!$parsedStructure['I']) {
			$logs[] = sprintf(__METHOD__.": This file has no passengers. Ignoring the rest of the file");

			$this->transactions = $transactions;
			return $this->transactions;
		}

		/**
		 * Lookup ticketed H segments
		 * These lines contains the itinerary for a ticketed service
		 */
		if(isset($parsedStructure['H']) && $parsedStructure['AMD']['AMD']['ACTTAG']!='VOID') {
			// H lines only contain the itinerary, and are associated with all the passengers
			foreach($parsedStructure['I'] as $index => $I){
				if($I['FO']){ // when the FO line is present, it means we have a reissue, regardless of $I['K'][0]['ISSUE'] (which has the value 'R' in most cases)
					$transaction = new \App\Amadeus\Storage\Operation($this, "H:reissue", ['passenger' => $index, 'segments'=>array_keys($parsedStructure['H'])]);
				}else{
					$transaction = new \App\Amadeus\Storage\Operation($this, "H", ['passenger' => $index, 'segments'=>array_keys($parsedStructure['H'])]);
				}

				$reportedHSegments[$index]++;
				$transactions[] = $transaction;
			}
		}

		/**
		 * Lookup ticketed EMD segments
		 * These lines contain "miscellaneous documents"
		 */
		if(isset($parsedStructure['EMD']) && $parsedStructure['AMD']['AMD']['ACTTAG']!='VOID'){
			$reportedEMDPassengers = [];
			$groupedEMDIndexes = [];
			foreach ($parsedStructure['EMD'] as $index => $EMD) {
				/**
				 * only use the first association, thats how i had the examples
				 * 	group by passengers & TSDNBR
				 * 	initially i was only grouping by pax(this handled the case where one passengers had multiple copies of the same EMD segment, with the same EMD tktNo)
				 * 	now, i'm grouping by pax & TSDNBR(i wanted to handle separate tickets, not sure TSDNBR is the right tag to use, but seems to work and avoind futher lookups in the parsed structure)
				 */
				$key = sprintf("%s.%s.%s.%s.%s",
					$EMD['PSGASS'][0],
					$EMD['TSDNBR'],
					$EMD['EMDTYPE'],
					$EMD['REASON'],
					$EMD['RFISC']
				);

				$groupedEMDIndexes[$key][]= $index;

				$reportedEMDPassengers[$key]++;
				$reportedEMDSegments[$index]++;
			}

			foreach ($groupedEMDIndexes as $indexes) {
				$transaction = new \App\Amadeus\Storage\Operation($this, "EMD", ['EMD'=>$indexes, ]);
				if (!$transaction->getDocument()->shouldSkip()) {
					$transactions[] = $transaction;
				}
			}
		}

		/**
		 * Lookup unticketed U segments
		 * These lines may contain anything, from ticketing without ticket numbers to MCO segments, hotels etc
		 */
		if(isset($parsedStructure['U'])){
			$HsegmentsDetected = 	(bool)$reportedHSegments;	// if we have any H segments, then dont report the U segments (skip U-AIR if H lines are present)
			$EMDsegmentsDetected = 	(bool)$reportedEMDSegments;	// if we have any H segments, then dont report the U segments (skip U-AIR if H lines are present)
			$hasTSA = 				(bool)$parsedStructure['assoc:segment:pax-TSA'];
			$reportedEMDPassengers = [];
			foreach ((array)$parsedStructure['assoc:U:pax-segment'] as $paxIdx=>$segIdxArr) {
				$ticketingAdded = [];
				foreach ($segIdxArr as $segIdx) {
					$U = $parsedStructure['U'][$segIdx];
					$TSAIdx = null;
					if ($hasTSA) {
						$TSAIdx = $parsedStructure['assoc:segment:pax-TSA'][$segIdx][$paxIdx][0];
					}

					if ($U['_type']=='01') {	// all ticketing segments should get collapsed into one
						if (!$hasTSA || ($hasTSA && $TSAIdx!==null)) {
							if (!($HsegmentsDetected || $EMDsegmentsDetected) && !$ticketingAdded[$TSAIdx]) {
								$transaction = new \App\Amadeus\Storage\Operation($this, "U:ticketing", ['passenger' => $paxIdx, 'segment'=>$segIdx, 'TSA'=>$TSAIdx]);
								$transactions[] = $transaction;
								$ticketingAdded[$TSAIdx] = true;
							}
						}
						else {
							$logs[] = sprintf(__METHOD__.": Ignoring segment index '{$segIdx}' (TTONBR:'{$U['TTONBR']}' , SEGNBR:'{$U['SEGNBR']}') because its not referenced by a TSA segment");
						}

					}
					/*
					Seems like U-EMD segments should be ignored.
					@see pidgin@2013-03-29 16:07:00

					elseif($U['_type']=='19'){	// all U-EMD segments should get grouped by PSGASS
						if(!$reportedEMDPassengers[$EMD['PSGASS'][0]]){
							$transaction = new \App\Amadeus\Storage\Operation($this, "U", Array('passenger' => $paxIdx, 'segment'=>$segIdx, 'TSA'=>$TSAIdx));
							$transactions[] = $transaction;
							$reportedEMDPassengers[$EMD['PSGASS'][0]]++;
						}

					}*/elseif($U['_type']=='21') {	// for insurance segments, only count the number of U lines, ignoring the passengers
						/**
						 * for more details:
						 * @see DAMA:1020(AIR insurances with more than one pax)
						 * @see DAMA:1184(Problems with unticketed insurance segments) and DAMA:1474(Ignore the MCO and insurance unticketed segments)
						 */
						if (!$reportedUSegments[$segIdx]) {
							$transaction = new \App\Amadeus\Storage\Operation($this, "U", ['passenger' => $paxIdx, 'segment'=>$segIdx, 'TSA'=>$TSAIdx]);
							$transactions[] = $transaction;
							$reportedUSegments[$segIdx]++;
						}

					}
					elseif($U['_type']=='10') {	// ground transportation
						$transaction = new \App\Amadeus\Storage\Operation($this, "U", ['passenger' => $paxIdx, 'segment'=>$segIdx, 'TSA'=>$TSAIdx]);
						$transactions[] = $transaction;
						$reportedUSegments[$segIdx]++;
					}
					elseif($U['_type']=='17' && $U['FREE_']['_type']=='train') {	// ground transportation
						$transaction = new \App\Amadeus\Storage\Operation($this, "U", ['passenger' => $paxIdx, 'segment'=>$segIdx,]);
						$transactions[] = $transaction;
						$reportedUSegments[$segIdx]++;
					}
					elseif($U['_type']=='17' && $U['FREE_']['_type']=='hotel') {	// ground transportation
						$transaction = new \App\Amadeus\Storage\Operation($this, "U", ['passenger' => $paxIdx, 'segment'=>$segIdx,]);
						$transactions[] = $transaction;
						$reportedUSegments[$segIdx]++;
					}
					else {
						$skippedSegments = [
							/**
							 * skip surface segments alltogether, because they will mess up the itinerary detection, and are not used by TINA anyway
							 */
							'02',

							/**
							 * skip Unticketed Open segments,
							 * waiting for a response to MAIL:[AMA] Unticheted Open Segments for confirmation
							 */
							'03',

							/**
							 * skip Manual Tour Segment
							 * Not sure what they are used for
							 */
							'12',

							/**
							 * skip U-MCO segments, they have no use, and are only needed when ticketed
							 * @see DAMA:1160(Problems with unticketed MCO segments (ATC))
							 */
							'18',

							/**
							 * skip U-EMD segments, they have no use.
							 * The ticketed EMD segments are expected to come in a separate file, or as a "standard", ticketed line
							 */
							'19',

						];

						if ($HsegmentsDetected) {
							/**
							 * also ignore U-insurance when its in a AIR file..
							 * @see DAMA:1184(Problems with unticketed insurance segments) and DAMA:1474(Ignore the MCO and insurance unticketed segments)
							 */
							$skippedSegments[] = '21';
						}

						if (!in_array($U['_type'], $skippedSegments)) {
							// any other kind of transaction should be reported individually
							if(!$reportedUSegments[$segIdx]){
								if ($parsedStructure['assoc:U:U'][$segIdx] && count($parsedStructure['assoc:U:segment-pax'][$segIdx])==1) {
									$segIdx = $parsedStructure['assoc:U:U'][$segIdx][0];
									if(!$reportedUSegments[$segIdx]){
										$transaction = new \App\Amadeus\Storage\Operation($this, "U", ['passenger' => $paxIdx, 'segment'=>$segIdx, 'TSA'=>$TSAIdx]);
										$transactions[] = $transaction;
										$reportedUSegments[$segIdx]++;
									}
								}
								else {
									$transaction = new \App\Amadeus\Storage\Operation($this, "U", ['passenger' => $paxIdx, 'segment'=>$segIdx, 'TSA'=>$TSAIdx]);
									$transactions[] = $transaction;
									$reportedUSegments[$segIdx]++;
								}
							}
						}
					}	// if
				} // foreach $segIdxArr
			} // foreach $parsedStructure['assoc:U:pax-segment']

			/*
			foreach($parsedStructure['assoc:U:segment-pax'] as $segIdx=>$paxIdxArr){
				$transaction = new \App\Amadeus\Storage\Operation($this, "U", $segIdx);
				$transactions[] = $transaction;
			}
			*/
		}

		/**
		 * Lookup ticketed IVD segments
		 * These lines contain "insurance voucher data"
		 */
		if(isset($parsedStructure['IVD'])){
			if($parsedStructure['B']['ENTRY'] === 'IVP/CANCEL'){
				// this a VOID for an insurance voucher
				$transaction = new \App\Amadeus\Storage\Operation($this, "VOID:insurance", ['segment'=>0]);
				$transactions[] = $transaction;
			}
			else {
				$transaction = new \App\Amadeus\Storage\Operation($this, "IVD", ['segment'=>0]);
				$this->internalStats['is']['IVD'] = true;
				$transactions[] = $transaction;
			}
		}

		/**
		 * Lookup ticketed MCO segments
		 * These lines contain "miscellaneous documents"
		 */
		if(isset($parsedStructure['MCO']) && $parsedStructure['AMD']['AMD']['ACTTAG']!='VOID'){
			foreach($parsedStructure['MCO'] as $index => $MCO){
				$transaction = new \App\Amadeus\Storage\Operation($this, "MCO",  ['MCO'=>$index]);
				$transactions[] = $transaction;
			}
		}

		/**
		 * Lookup ticketed RFND segments
		 * These lines contain data about refunded tickets
		 */
		if(isset($parsedStructure['RFD'])){
			$fullRefundTransaction = new \App\Amadeus\Storage\Operation($this, "RFD", null);

			// trigger registered plugins
			\Engine\Plugins\Manager::getInstance()->executeEvent('Amadeus-getTransactionFromRefundRaw_alterObjects', [
				'this' => 					$this,
				'transactions' => 			&$transactions,
				'fullRefundTransaction' => 	&$fullRefundTransaction,
			]);

			/**
			 * This is a normal refund (ATC/FINANCIAL IMPACT refunds are included here)
			 * Refunding works by getting a list of all "refundable" past transactions and generationg synthetic refund operations for each of them.
			 *  - first, it will generate a full refund, and get the total amounts from there ($amountToDistribuite)
			 *  	THIS TRANSACTION WILL NEVER BE REPORTED
			 *  - then, loop through all "refundable" past transactions and generate a refund for them, until $amountToDistribuite is empty
			 *  	these are the reported refund transactions
			 */
			$originalTransactions = $transactions;

			$extraTransactions = $this->RFD_generateDistribuitedTransactionList($fullRefundTransaction);

			$this->internalStats['RFD'] = [];
			$amountToDistribuite = $fullRefundTransaction->getPrice()->passenger()->clampValues();
			$this->internalStats['RFD']['amountToDistribuite'] = $amountToDistribuite;
			$this->internalStats['RFD']['distribuitedAmounts'] = [];
			if ($extraTransactions) {
				$transactionsCount = count($extraTransactions);

				/*
				 * This is used to distribuite sums across multiple transactions, in the case where we have
				 * 	          supp   taxes penalty
				 * 	TICK       100   10          0
				 * 	REIS         5   10         10
				 * 	REIS         0   10         10
				 * --------------------------------
				 * 	RFND        90   15         --
				 *
				 * We can think of 3 ways to distribuite the sums from the RFND across the parent transactions:
				 * 	#1: start from the bottom->top and distribuite as much as possible on each transaction. This way some of the transactions will be refunded with amount: 0.
				 *  #2: start from the top->bottom and distribuite as much as possible on each transaction. This way some of the transactions will be refunded with amount: 0.
				 *  #3: calculate a percent, and distribuite that on every parent transaction. This way, *all* parent
				 *  	transactions will get refunded, and have an amount set on them.
				 *
				 * After some talks with Gabi & Diana, we concluded to use #2.
				 * If we'd want to go with version #1, we'd simply have to comment out the code array_reverse call below
				 * Version #3 would imply reimplementing the code in \App\Amadeus\StructureParser\Structures\Refund\Price->passenger_RFD_distribuited()
				 */
				$extraTransactions = array_values(array_reverse($extraTransactions));

				// generate "synthetic transactions"(refunds)
				$extraTransactions_docNumbers = [];
				foreach ($extraTransactions as $transactionIndex => $referenceTransaction) {
					// we cannot work with refunds for conjunction tickets, we need to re-map this $referenceTransaction to its 'root' transaction
					if (\Entity\AmadeusService::select($referenceTransaction)->autoIgnored=='conjunction') {
						$rootDoc = \Entity\AmadeusService::select($referenceTransaction)->transactionDocData;
						$prevTkts = \App\Amadeus\Storage\StorageHandler::selectByDocNumber($rootDoc->docNumber, $rootDoc->docDate);
						$prevTkt = reset($prevTkts);

						$referenceTransaction = $prevTkt['id'];

						$transaction = new \App\Amadeus\Storage\Operation($this, "RFD", [
							'distribuiteTransaction' => true,
							'referenceTransaction' => 	$referenceTransaction,
							'transactionIndex' => 		$transactionIndex,
							'transactionsCount' => 		$transactionsCount,
						]);

						$transaction->addLog(sprintf("This seems to be a refund for a conjunction ticket. Refunding %s@%s instead",$rootDoc->docNumber, $rootDoc->docDate));
					}
					else {
						$transaction = new \App\Amadeus\Storage\Operation($this, "RFD", [
							'distribuiteTransaction' => true,
							'referenceTransaction' => 	$referenceTransaction,
							'transactionIndex' => 		$transactionIndex,
							'transactionsCount' => 		$transactionsCount,
						]);
					}

					$this->internalStats['RFD']['distribuitedAmounts'][$referenceTransaction] = null;

					$extraTransactions_docNumbers[$referenceTransaction] = \Entity\AmadeusService::select($referenceTransaction)->docNumber;

					// force the price to get computed, otherwise it will get computed when needed(lazy style) and the prices depend on them being computed in a certain order
					$transaction->getPrice()->passenger();

					$transactions[] = $transaction;
				}

				$amountToDistribuite = $this->RFD_getAmountToDistribuite();
				$distribuitedAmountsSum = $this->RFD_getDistribuitedAmountsSum()->getClone();

				$remainingAmountsSum = $amountToDistribuite->getClone()->sub($distribuitedAmountsSum);
				/* @var $remainingAmountsSum \App\Amadeus\StructureParser\Types\Price */

				$threshhold = 0.0001;
				if (abs((float)$remainingAmountsSum->supplier)>$threshhold || abs((float)$remainingAmountsSum->tax->getAmount())>$threshhold || abs((float)$remainingAmountsSum->commission)>$threshhold) {
					/**
					 * When issuing refunds for reissues without the ATC line, the agent has the possibility to somehow input the refund values(manually), and he might enter them incorrectly
					 * 	(found lots of cases on J'Info & Morea servers)
					 * This would get handled every 2 weeks when the BSP report is generated, and the agency would settle its differences internally.
					 *
					 * Taking this into consideration, we should scan all reissues in the ticket chain, and if we dont have a reissue with an ATC line, we should put all the
					 * 	remaining amounts in there, ignoring the fact that the refunded amount should be smaller than the amount the client paid to the agency.
					 * @ian 28: If the ticket chain has a reissue with an ATC line this should be considered an invalid case and the parser/processor should stop, as the ATC lines
					 * 	should be generated automatically(according to AMA), and are more reliable than basic RFD lines
					 * @feb 21: the last remark wasn't correct it seems. I found a counterexample.
					 * 	@see HU/Morea/2013-09-19_084738_10.193.2.30__M_L_1909_10.AIR
					 * Basically, because of the currency exchange differences, it seems that the refund can be grater than the sum of all the tickets that were already issued,
					 * 	and the ATC line doesn't make any difference for this case
					 */

					// discard the last inserted "synthetic transactions"
					$transactions = $originalTransactions;
					$transactions[] = $fullRefundTransaction;	// add the transaction that represents the full refund attached to the last issued ticket (without doing any price-distribution)

					// this is a refund issued as a result of a reissue that didn't use the ATC line. according to the above comment, its possible to get invalid prices as a return result
					$transactions[0]->addLog(sprintf(
						"Not all all amounts from the RFD were distribuited, we were left with %.2f %s tax amount, %.2f %s supplier amount, %.2f %s commission not distribuited. When doing the distribution, we looked at transaction id: %s. I'm ignoring the price distribution and loading all values over the last issued ticket",
							$remainingAmountsSum->tax->getAmount(), $remainingAmountsSum->tax->getCurrency(),
							$remainingAmountsSum->supplier, $remainingAmountsSum->supplierCurrency,
							$remainingAmountsSum->commission, $remainingAmountsSum->commissionCurrency,
							implode(", ", $extraTransactions)
					));
				}

				else {
					// "inherit" fullRefundTransaction messages
					foreach ($fullRefundTransaction->getLogArr() as $fullRefundLogMsg) {
						$transactions[0]->addLog($fullRefundLogMsg);
					}

					// if the price distribution succeeded, validate the final prices
					foreach ($transactions as $transaction) {
						$transaction->getPrice()->passenger()->checkIntegrity($transaction, ['negativeTotal'=>true, 'negativeSupplier'=>true,]);
					}

					// log how much we distribuited until now
					$transactions[0]->addLog(sprintf(
						"All amounts from the RFD were distribuited: %.2f %s tax amount, %.2f %s supplier amount, %.2f %s commission, %.2f %s penalty. When doing the distribution, we looked at tickets: %s",
							$amountToDistribuite->tax->getAmount(), $amountToDistribuite->tax->getCurrency(),
							$amountToDistribuite->supplier, $amountToDistribuite->supplierCurrency,
							$amountToDistribuite->commission, $amountToDistribuite->commissionCurrency,
							$amountToDistribuite->penalty, $amountToDistribuite->penaltyCurrency,
							implode(", ", $extraTransactions_docNumbers)
					));
				}
			}
			else {
				$transactions[] = $fullRefundTransaction;	// add the transaction that represents the full refund attached to the last issued ticket(without doing any price-distribution at this point)

				$transactions[0]->addLog(sprintf("No parent transactions were found. Not distribuiting amounts!"));
			}
		}

		/**
		 * Lookup ticketed VOID-ed segments
		 * The VOID are differentiated by a certain tag in the AMD header
		 */
		// it seems that the only(real) possible value for $parsedStructure['AMD']['AMD']['ACTTAG'] is "VOID" or blank
		if($parsedStructure['AMD']['AMD']['ACTTAG']=='VOID'){
			/**
			 * when we have a VOID transaction, we can have no other transactions in it
			 * I encoutered this case in EE/Nautilus, 2011-01-04_115652_80.235.55.172__AIR00690.Txt,
			 * 	and i prefered to specifically check for VOID transaction when parsing IMR lines, instead of resetting the transactions [] like below
			 */
			#$transactions = [];

			$transaction = new \App\Amadeus\Storage\Operation($this, "VOID", null);
			$transactions[] = $transaction;
		}


		////////////////////////////////////////////////////////////////////////////
		// POST-PROCESSING /////////////////////////////////////////////////////////
		////////////////////////////////////////////////////////////////////////////

		/**
		 * skip U:ticketing segments when a ticketed IVD (IVD, not INS!!) is present in the same file
		 * ex: PNR: 2GKQMM
		 */
		if($this->internalStats['is']['IVD']){
			$ticketedInsurances = 	$this->getTicketedTransactions($this->getTransactionsByTransactionType($transactions, 'insurance'));
			$tickets =	$this->getTransactionsByTransactionType($transactions, 'ticketing');
			if($ticketedInsurances && $tickets){
				foreach($transactions as $k=>$tr){
					/* @var $tr \App\Amadeus\Storage\Operation */
					if($tr->getType() == 'ticketing' && !$tr->isTicketed()){
						unset($transactions[$k]);
					}
				}
			}
		}

		// reset array keys
		$transactions = array_values($transactions);

		// save the logs
		if ($logs) {
			$firstTransaction = reset($transactions);
			if ($firstTransaction) {
				foreach ($logs as $log) {
					$firstTransaction->addLog($log);
				}
			}
		}

		if (in_array($parsedStructure['AIRBLK']['AIROPT'], ['IA', ])) {
			$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();
			if ($CFG['amadeus']['FileParser']['AIR']['handle_IA']=='emptyFile') {
				$logs[] = sprintf(__METHOD__.": This file is an accounting file.");
				$this->internalStats['skippedTransactions'] = $transactions;
				$transactions = [];
			}
			else {	// $CFG['amadeus']['FileParser']['AIR']['handle_IA']=='error'
				// JIRA: TD-7529(Ignore from imported orders the file type IA (invoice accounting) when parsing files from Amadeus)
				// JIRA: TD-9605(Change the way we handle IA files for Amadeus and find another way to skip them)
				throw new \App\Utils\BaseParser\Structure\StructureParser\Types\Exception\ShouldIgnoreFile("This file is an accounting file. It should be ignored");
			}
		}

		// save the original components
		foreach ($transactions as $tr) {
			if ($tr->getPrice()->passenger()) {
				$paxPrice = $tr->getPrice()->passenger();

				$paxPrice->originalPriceArr['supplier'] = 				$paxPrice->supplier;
				$paxPrice->originalPriceArr['supplierCurrency'] = 		$paxPrice->supplierCurrency;

				$paxPrice->originalPriceArr['tax'] = 					$paxPrice->tax->getAmount();
				$paxPrice->originalPriceArr['taxCurrency'] = 			$paxPrice->tax->getCurrency();

				$paxPrice->originalPriceArr['commission'] = 			$paxPrice->commission;
				$paxPrice->originalPriceArr['commissionCurrency'] = 	$paxPrice->commissionCurrency;
				$paxPrice->originalPriceArr['commissionComponents'] = 	array_map(function($c) {
					return [
						'IOSource' =>		'raw',			// technically we could add stuff from plugins in here when overriding amounts in IO, it's specific to IO
						'category' => 		$c->category,
						'source' => 		$c->source,
						'amount' => 		$c->amount,
						'VAT' => 			$c->VAT,
						'currency' => 		$c->currency,
						'code' => 			$c->code,
					];
				}, $paxPrice->components->getComponentsByCategory('commission'));
			}
		}

		// trigger registered plugins
		\Engine\Plugins\Manager::getInstance()->executeEvent('Amadeus-getTransactions', [
			'this' => 			$this,
			'transactions' => 	&$transactions,
		]);

		$this->transactions = $transactions;
		return $this->transactions;
	}

	/**
	 * For refund, get the list of distribuited parent transactions
	 * @param \App\Amadeus\Storage\Operation $transaction
	 */
	protected function RFD_generateDistribuitedTransactionList($transaction)
	{
		$rfdDocObj = 	new \App\Amadeus\StructureParser\Structures\Refund\Document($transaction);
		$doc = $rfdDocObj->document();

		$relatedTransactions = [];

		$prevTkts = \App\Amadeus\Storage\StorageHandler::selectByDocNumber($doc->docNumber);
		$prevTktId = null;
		foreach ($prevTkts as $tkt) {
			// skip operations except reissue and new. refunded is in here to make it easier in the development phase
			if (in_array($tkt['transactionStatus'], ['refunded', 'reissue', 'new', ]) && $tkt['id']) {
				$prevTktId = $tkt['id'];
				if ($prevTktId) {
					$loops = 100; // allow 100 loops in here, to avoid infinite loops
					do {
						$prevService = \Entity\AmadeusService::select($prevTktId);

						// stop at MCO's or EMD's.
						// initially we stopped only at reason = REFUNDABLE BALANCES (K), but after MAIL:[AMA] RFND pt un EMD cu reasonCode FINANCIAL IMPACT, i no longer do that
						if (in_array($prevService->serviceCode, ['EMD', 'MCO', ])) {
							$prevTktId = null;
							break;
						}

						// stop at refund operations, assume everything below it is already refunded
						if (in_array($prevService->transactionStatus, ['refund', 'refunded', 'voided', ])) {
							$prevTktId = null;
							break;
						}

						if (!in_array($prevService->serviceCode, ['ticketing', ])) {
							$prevTktId = null;
							break;
						}

						// add this to the final reservations list
						$relatedTransactions[] = $prevTktId;

						$prevTktId = null;
						if ($prevService->transactionReferences) {
							foreach ($prevService->transactionReferences as $ref) {
								if (in_array($ref['relationType'], ['reissue', ])  && $ref['id'] && !\Entity\AmadeusService::select($ref['id'])->autoIgnored && \Entity\AmadeusService::select($ref['id'])->docNumber) {
									$prevTktId = $ref['id'];
								}
							}
						}
					} while ($loops-- && $prevTktId);

					// check infinite loops
					if ($loops<=0) {
						throw \App\Amadeus\FileParser\ParserException::internal(sprintf("Infinite loop detected while trying to figure out the parent transactions for doc %s, in file %s", $doc->docNumber, $this->getFile()));
					}
				}
			}
		}

		return $relatedTransactions;
	}

	/**
	 * Get the list of distribuited amounts
	 */
	public function RFD_getDistribuitedAmounts()
	{
		return $this->internalStats['RFD']['distribuitedAmounts'];
	}

	/**
	 * Get the sum of distribuited amounts
	 */
	public function RFD_getDistribuitedAmountsSum()
	{
		$distribuitedAmountsSum = new \App\Amadeus\StructureParser\Types\Price();
		$distribuitedAmountsSum->tax = new \App\Amadeus\StructureParser\Types\PriceTax();
		foreach ($this->RFD_getDistribuitedAmounts() as $dprice) {
			if ($dprice) {
				$distribuitedAmountsSum->add($dprice);
			}
		}

		return $distribuitedAmountsSum;
	}

	/**
	 * Get the amount we're expected to distribuite
	 */
	public function RFD_getAmountToDistribuite()
	{
		return $this->internalStats['RFD']['amountToDistribuite'];
	}

	/**
	 * Add a price item to the list of distribuited amounts
	 */
	public function RFD_addDistribuitedAmount($transactionId, $item)
	{
		$this->internalStats['RFD']['distribuitedAmounts'][$transactionId] = $item;
		return $this;
	}

	/**
	 * public Inernal function to sort the reservation list
	 * @param \App\Amadeus\Storage\Operation $a
	 * @param \App\Amadeus\Storage\Operation $b
	 * @return number
	 */
	public function getSortedTransactions_cmpByDocNumberTransactionType(\App\Amadeus\Storage\Operation $a, \App\Amadeus\Storage\Operation $b)
	{
		$ad = $a->getDocument()->document()->docNumber;
		$bd = $b->getDocument()->document()->docNumber;

		/**
		 * if both $a and $b docNumber is set, dont return
		 * if $ad is set, return 1
		 * if $bd is set, return -1
		 * else continues to the other comparisons
		 */
		$c = ($ad && $bd?0: ($ad?-1: ($bd?1: 0)));

		if($c!=0) return $c;

		// a list of sorting priorities, so we can make the insurance be above the hotel for example
		$sortPriorities = [
			'ticketing' => 	10,
			'hotel' => 		20,
			'car' => 		30,
			'insurance' => 	40,
			'misc' => 		50,
			'EMD' => 		55,
			'MCO' => 		55,
		];

		$at = $a->getType();
		$bt = $b->getType();

		$c = ($sortPriorities[$at]?$sortPriorities[$at]:100) - ($sortPriorities[$bt]?$sortPriorities[$bt]:100);
		if($c!=0) return $c;


		// sort by docNumber
		$ad = $a->getDocument()->document()->docNumber;
		$bd = $b->getDocument()->document()->docNumber;

		$c = strcmp($ad, $bd);
		if($c!=0) return $c;


		// sort by transactionCode, this will basically sort by passenger and then by segment number
		$ad = $a->getDocument()->document()->transactionCode;
		$bd = $b->getDocument()->document()->transactionCode;

		$c = strcmp($ad, $bd);
		if($c!=0) return $c;

		return 0;
	}

	/**
	 * Get the reservations in a sorted order
	 */
	public function getSortedTransactions()
	{
		$transactions = $this->getTransactions();
		@usort($transactions, [$this, 'getSortedTransactions_cmpByDocNumberTransactionType']);
		return $transactions;
	}


	/**
	 * Check if a file is an AIR file (let the file processor determie this)
	 * @return bool
	 */
	public function isAIR()
	{
		return !$this->isIMR();
	}

	/**
	 * Check if a file is an IMR file (let the file processor determie this)
	 * @return bool
	 */
	public function isIMR()
	{
		return $this->parsedStructure['AIRBLK']['isIMR'];
	}

	/**
	 * Check if a file is an AIR file(has a ticketing transaction, and a ticket number for it)
	 * @return bool
	 */
	public function isTicketingFile()
	{
		$ticketedTransactions = $this->getTicketedTransactions($this->getTransactions());
		foreach($ticketedTransactions as $tr){
			/* @var $tr \App\Amadeus\Storage\Operation */
			if(in_array($tr->getType(), ['ticketing', 'refund', 'void']) && $tr->getDocument()->document()->docNumber){
				return true;
			}
		}
		return false;
	}

	/**
	 * Filter the input data and leave only ticketed transactions
	 * @param array:\App\Amadeus\Storage\Operation $transactions
	 * @return array:\App\Amadeus\Storage\Operation
	 */
	public function getTicketedTransactions($transactions)
	{
		$result = [];
		foreach($transactions as $tr){
			/* @var $tr \App\Amadeus\Storage\Operation */
			if($tr->isTicketed()){
				$result[] = $tr;
			}
		}
		return $result;
	}

	/**
	 * Filter the input data and get only transactions matching $transactionType
	 * @param array:\App\Amadeus\Storage\Operation $transactions
	 * @param string $transactionType
	 * @return array:\App\Amadeus\Storage\Operation
	 */
	public function getTransactionsByTransactionType($transactions, $transactionType)
	{
		$result = [];
		foreach($transactions as $tr){
			/* @var $tr \App\Amadeus\Storage\Operation */
			if($tr->getType()==$transactionType){
				$result[] = $tr;
			}
		}
		return $result;
	}

	/**
	 * Filter the input data and get only transactions matching $transactionCode
	 * @param  array:\App\Amadeus\Storage\Operation $transactions
	 * @param string $transactionCode
	 * @return \App\Amadeus\Storage\Operation|NULL
	 */
	public function getTransactionByTransactionCode($transactions, $transactionCode)
	{
		foreach($transactions as $tr){
			/* @var $tr \App\Amadeus\Storage\Operation */
			if($tr->getDocument()->document()->transactionCode==$transactionCode){
				return $tr;
			}
		}

		return null;
	}


	/**
	 * Set the parser
	 * @param \App\Amadeus\FileParser\ParsedFile $parser
	 */
	public function setParser(\App\Amadeus\FileParser\ParsedFile $parser)
	{
		// TODO: is this were we should validate the input data?
		$this->validateParser($parser);

		$this->parser = $parser;
		$this->setParsedStructure($this->parser->getStructure()->getStructure());
		return $this;
	}

	/**
	 * Validate the input data in the parser
	 * @param \App\Amadeus\FileParser\ParsedFile $parser
	 * @throws \App\Amadeus\FileParser\ParserException
	 */
	protected function validateParser(\App\Amadeus\FileParser\ParsedFile  $parser){
		$structure = $parser->getStructure();

		// check that the file has actually ended with the correct tag
		if(!$structure->END){
			throw \App\Amadeus\FileParser\ParserException::internal(sprintf("No ENDX line found. This file is invalid"));
		}
	}

	/**
	 * Get back the original parser
	 */
	public function getParser()
	{
		return $this->parser;
	}

	/**
	 * Set the parsed structure to use while parsing
	 * @param array $parsedStructure
	 * @return \App\Amadeus\StructureParser\Processor
	 */
	protected function setParsedStructure($parsedStructure)
	{
		$this->transactions = [];

		if($parsedStructure['I']){
			// extract associations for H lines
			if(isset($parsedStructure['H'])){
				foreach($parsedStructure['H'] as $segIdx=>$segment){
					// H lines dont have a PSGASS tag... assuming all passengers are associated with all segments
					foreach($parsedStructure['I'] as $psgIdx=>$pax){
						$parsedStructure['assoc:H:segment-pax'][$segIdx][] = $psgIdx;
						$parsedStructure['assoc:H:pax-segment'][$psgIdx][] = $segIdx;
					}
				}
			}

			// extract associations for IVD lines
			if(isset($parsedStructure['IVD'])){
				$segIdx = 0;	// we can only have 1 IVD segment in the file, from what i saw in the examples
				// IVD lines dont have a PSGASS tag... assuming all passengers are associated with all segments
				foreach($parsedStructure['I'] as $psgIdx=>$pax){
					$parsedStructure['assoc:IVD:segment-pax'][$segIdx][] = $psgIdx;
					$parsedStructure['assoc:IVD:pax-segment'][$psgIdx][] = $segIdx;
				}
			}

			if(isset($parsedStructure['U'])){
				foreach($parsedStructure['U'] as $segIdx=>$segment){
					if ($segment['PSGASS']) {
						// this U line has some passengers explicitly associated
						foreach ($segment['PSGASS'] as $PSGRNBR) {
							foreach ($parsedStructure['I'] as $psgIdx=>$pax) {
								if ($pax['PSGRNBR']==$PSGRNBR) {
									$parsedStructure['assoc:U:segment-pax'][$segIdx][] = $psgIdx;
									$parsedStructure['assoc:U:pax-segment'][$psgIdx][] = $segIdx;
								}
							}
						}
					}
					else {
						// this line doesn't have anything associated with it, it means all passengers are associated
						foreach ($parsedStructure['I'] as $psgIdx=>$pax) {
							$parsedStructure['assoc:U:segment-pax'][$segIdx][] = $psgIdx;
							$parsedStructure['assoc:U:pax-segment'][$psgIdx][] = $segIdx;
						}
					}
				}

				foreach ($parsedStructure['U'] as $segIdx1=>$segment1) {
					foreach ($parsedStructure['U'] as $segIdx2=>$segment2) {
						if (	$segment1['FREE_']
							 && $segment2['FREE_']
							 && $segment1['FREE_']['confirmationNumber']
							 && $segment2['FREE_']['confirmationNumber']
							 && $segment1['FREE_']['confirmationNumber']==$segment2['FREE_']['confirmationNumber']) {

							$parsedStructure['assoc:U:U'][$segIdx1][]= $segIdx2;
						}
					}
				}
			}

			if(isset($parsedStructure['MCO'])){
				foreach($parsedStructure['MCO'] as $docIdx => $doc){
					// we should first check for PSGASS, because on BG/Fiesta's server i found 2013-12-04_124220_10.10.2.105__AIR00001.Txt, which has an invalid segment associated, but correct passsegers
					$associationsDone = false;
					$associationsBroken = false;

					if($doc['SEGASS']){
						foreach($doc['SEGASS'] as $SEGNBR){
							if ($parsedStructure['U']) {
								foreach ($parsedStructure['U'] as $segIdx=>$seg) {
									if ($seg['SEGNBR']==$SEGNBR) {
										$parsedStructure['assoc:MCO:doc-segment'][$docIdx][] = $segIdx;
										$parsedStructure['assoc:MCO:segment-doc'][$segIdx][] = $docIdx;
										$associationsDone = true;
									}
								}
							}
							elseif ($parsedStructure['H']) {
								foreach ($parsedStructure['H'] as $segIdx=>$seg) {
									if ($seg['SEGNBR']==$SEGNBR) {
										$parsedStructure['assoc:MCO:doc-segment'][$docIdx][] = $segIdx;
										$parsedStructure['assoc:MCO:segment-doc'][$segIdx][] = $docIdx;
										$associationsDone = true;
									}
								}
							}
							else {
								$associationsBroken = 'segment';
								$this->getParser()->addLog("found an MCO line associated, but the associated U or H line is missing(associated SEGNBR:{$SEGNBR}, MCO TSDNBR: {$doc['TSDNBR']})");
							}
						}
					}

					if ($doc['PSGASS']) {
						// this MCO line has some passengers explicitly associated
						foreach($doc['PSGASS'] as $PSGNBR){
							if($parsedStructure['I']){
								foreach($parsedStructure['I'] as $paxIdx=>$pax){
									if($pax['PSGRNBR']==$PSGNBR){
										$parsedStructure['assoc:MCO:doc-pax'][$docIdx][] = $paxIdx;
										$parsedStructure['assoc:MCO:pax-doc'][$paxIdx][] = $docIdx;
										$associationsDone = true;
									}
								}
							}else{
								$associationsBroken = 'passenger';
								$this->getParser()->addLog("found an MCO line associated, but the associated I line is missing(searched PSGNBR: {$PSGNBR}, MCO TSDNBR: {$doc['TSDNBR']})");
							}
						}
					}

					if ($associationsBroken && !$associationsDone) {
						throw new \App\Amadeus\StructureParser\StructureException("found an MCO line associated, but i canot find the associated segments and/or passengers. Check the logs");
					}

					if ($associationsBroken) {
						$this->getParser()->addLog("the associations finally succeeded, even if the associations for '{$associationsBroken}' failed");
					}

					if (!$associationsDone) {
						// this line doesn't have anything associated with it, it means all segments are associated
						if($parsedStructure['U']){
							foreach($parsedStructure['U'] as $segIdx=>$seg){
								$parsedStructure['assoc:MCO:doc-segment'][$docIdx][] = $segIdx;
								$parsedStructure['assoc:MCO:segment-doc'][$segIdx][] = $docIdx;
								$associationsDone = true;
							}
						}
					}
				}
			}

			if(isset($parsedStructure['EMD'])){
				foreach($parsedStructure['EMD'] as $docIdx => $doc){
					// we should first check for PSGASS, because on BG/Fiesta's server i found 2013-12-04_124220_10.10.2.105__AIR00001.Txt, which has an invalid segment associated, but correct passsegers
					$associationsDone = false;
					$associationsBroken = false;

					if ($doc['SEGASS']) {
						// this EMD line has some segments explicitly associated
						// 	i'm assuming here that EMD SEGASS is linked to the U lines, and not H or other lines...
						foreach ($doc['SEGASS'] as $SEGNBR) {
							if ($parsedStructure['U']) {
								foreach ($parsedStructure['U'] as $segIdx=>$seg) {
									if ($seg['SEGNBR']==$SEGNBR) {
										$parsedStructure['assoc:EMD:doc-segment'][$docIdx][] = $segIdx;
										$parsedStructure['assoc:EMD:segment-doc'][$segIdx][] = $docIdx;
										$associationsDone = true;
									}
								}
							}
							elseif ($parsedStructure['H']) {
								foreach ($parsedStructure['H'] as $segIdx=>$seg) {
									if ($seg['SEGNBR']==$SEGNBR) {
										$parsedStructure['assoc:EMD:doc-segment'][$docIdx][] = $segIdx;
										$parsedStructure['assoc:EMD:segment-doc'][$segIdx][] = $docIdx;
										$associationsDone = true;
									}
								}
							}
							else {
								// TODO: these could refer to H lines also, not only U lines...
								$associationsBroken = 'segment';
								$this->getParser()->addLog("found an EMD line associated, but the associated U or H line is missing(associated SEGNBR:{$SEGNBR}, EMD's TSDNBR: {$doc['TSDNBR']})");
								#throw new \App\Amadeus\StructureParser\StructureException("found an EMD line associated, but the associated U or H line is missing(associated SEGNBR:{$SEGNBR})");
							}
						}
					}

					if ($doc['PSGASS']) {
						// this EMD line has some passengers explicitly associated
						foreach($doc['PSGASS'] as $PSGNBR){
							if($parsedStructure['I']){
								foreach($parsedStructure['I'] as $paxIdx=>$pax){
									if($pax['PSGRNBR']==$PSGNBR){
										$parsedStructure['assoc:EMD:doc-pax'][$docIdx][] = $paxIdx;
										$parsedStructure['assoc:EMD:pax-doc'][$paxIdx][] = $docIdx;
										$associationsDone = true;
									}
								}
							}else{
								$associationsBroken = 'passenger';
								$this->getParser()->addLog("found an EMD line associated, but the associated I line is missing(searched PSGNBR: {$PSGNBR}, EMD's TSDNBR: {$doc['TSDNBR']})");
								#throw new \App\Amadeus\StructureParser\StructureException("found an EMD line associated, but the associated I line is missing(PSGNBR: {$PSGNBR}). ");
							}
						}
					}

					if ($associationsBroken && !$associationsDone) {
						throw new \App\Amadeus\StructureParser\StructureException("found an EMD line associated, but i canot find the associated segments and/or passengers. Check the logs");
					}

					if ($associationsBroken) {
						$this->getParser()->addLog("the associations finally succeeded, even if the associations for '{$associationsBroken}' failed");
					}

					if (!$associationsDone) {
						// this line doesn't have anything associated with it, it means all segments are associated
						if($parsedStructure['U']){
							foreach($parsedStructure['U'] as $segIdx=>$seg){
								$parsedStructure['assoc:EMD:doc-segment'][$docIdx][] = $segIdx;
								$parsedStructure['assoc:EMD:segment-doc'][$segIdx][] = $docIdx;
								$associationsDone = true;
							}
						}
					}
				}
			}


			// lookup TSA segments. They split the file into multiple logical transactions
			if(isset($parsedStructure['I'])){
				foreach($parsedStructure['I'] as $psgIdx=>$pax){
					if($pax['TSA']){
						// this U line has some passengers explicitly associated
						foreach($pax['TSA'] as $TSAIdx=>$TSA){
							if($TSA['SEGASS']){
								// convert from internal IMR notation to absolute, PHP-ish index notation(SEGASS refers to U->SEGNBR, and we need internal, indexed array notation)
								if($parsedStructure['U']){
									foreach($parsedStructure['U'] as $segIdx=>$seg){
										if(in_array((int)$seg['SEGNBR'], $TSA['SEGASS'])){
											$parsedStructure['assoc:pax:TSA-segment'][$psgIdx][$TSAIdx][] = $segIdx;
											$parsedStructure['assoc:segment:pax-TSA'][$segIdx][$psgIdx][] = $TSAIdx;
										}
									}
								}else{
									/**
									 * This actually happens on LT/Gaublija "2012-07-03_131228_82.135.209.236__0703_131643_00.txt", "2011-07-11_160150_82.135.209.236__0711_160137_00.txt" for example
									 * Maybe this error should be ignorable
									 */
									#throw new \App\Amadeus\StructureParser\StructureException("found PSGASS, but no U line to associate with");

									$this->parser->addLog(__METHOD__." : TSA line: found SEGASS, but no U line to associate with");
								}
							}else{
								// NOTE: this code path was never tested(coverage tests confirms this)
								// is no segment references were setup, assume "all segments"
								$referencedSegments = array_keys($parsedStructure['U']);
								foreach($referencedSegments as $segIdx){
									$parsedStructure['assoc:pax:TSA-segment'][$psgIdx][$TSAIdx][] = $segIdx;
									$parsedStructure['assoc:segment:pax-TSA'][$segIdx][$psgIdx][] = $TSAIdx;
								}
							}
						}
					}

					if ($pax['PPI']) {
						foreach ($pax['PPI'] as $PPIIdx=>$PPI) {
							if ($PPI['SEGASS']) {
								// convert from internal IMR notation to absolute, PHP-ish index notation(SEGASS refers to U->SEGNBR, and we need internal, indexed array notation)
								if ($parsedStructure['IVD']) {
									// associate IVD segments witl all defined passengers
									$referencedSegments = [0];
									foreach($referencedSegments as $segIdx){
										if (!in_array($segIdx, $parsedStructure['assoc:IVD:pax-segment'][$psgIdx])) {
											$parsedStructure['assoc:IVD:pax-segment'][$psgIdx][] = $segIdx;
											$parsedStructure['assoc:IVD:segment-pax'][$segIdx][] = $PPIIdx;
										}
									}
								}
								elseif ($parsedStructure['U']) {
									foreach ($parsedStructure['U'] as $segIdx=>$seg) {
										if (in_array((int)$seg['ELMNBR'], $PPI['SEGASS'])) {
											if (!in_array($segIdx, $parsedStructure['assoc:U:pax-segment'][$psgIdx])) {
												$parsedStructure['assoc:U:pax-segment'][$psgIdx][] = $segIdx;
												$parsedStructure['assoc:U:segment-pax'][$segIdx][] = $psgIdx;
											}
										}
									}
								}
								else {
									$this->parser->addLog(__METHOD__." : PPI line: found SEGASS, but no IVD or U line to associate with");
								}
							}
							else {
								// NOTE: this code path was never tested (didn't do coverage tests though)
								// is no segment references were setup, assume "all segments"
								$referencedSegments = [0];
								foreach($referencedSegments as $segIdx){
									if (!in_array($segIdx, $parsedStructure['assoc:IVD:pax-segment'][$psgIdx])) {
										$parsedStructure['assoc:IVD:pax-segment'][$psgIdx][] = $segIdx;
										$parsedStructure['assoc:IVD:segment-pax'][$segIdx][] = $PPIIdx;
									}
								}
								$referencedSegments = array_keys($parsedStructure['U']);
								foreach($referencedSegments as $segIdx){
									if (!in_array($segIdx, $parsedStructure['assoc:U:pax-segment'][$psgIdx])) {
										$parsedStructure['assoc:U:pax-segment'][$psgIdx][] = $segIdx;
										$parsedStructure['assoc:U:segment-pax'][$segIdx][] = $PPIIdx;
									}
								}
							}
						}
					}
				}
			}
		}

		$this->parsedStructure = $parsedStructure;

		return $this;
	}


	/*---------------------------------------------------------------------------------------------------------------------*/

	/**
	 * Get the internal structure being used
	 * @return array
	 */
	public function getParsedStructure()
	{
		return $this->parsedStructure;
	}


	/**
	 * Set the filename used to parse the file
	 * @return \App\Amadeus\StructureParser\Processor
	 */
	public function setFile($filePath)
	{
		$this->filePath = $filePath;

		return $this;
	}

	/**
	 * Get the parsed filename
	 * @return string
	 */
	public function getFile()
	{
		return $this->filePath;
	}


	/**
	 * Build an ID unique to each file, to be able to detect already-parsed files
	 * @return string
	 */
	public function getFileId()
	{
		if ($this->idFile) {
			return $this->idFile;
		}

		if (trim($this->parsedStructure['AMD']['MUC']['RECLOC']['RECLOC'])) {
			$this->idFile = sprintf("%s%s%s%s",
				$this->parsedStructure['AMD']['MUC']['RECLOC']['RECLOC'],
				$this->parsedStructure['AIRBLK']['NETID'],
				$this->parsedStructure['AIRBLK']['ADDR'],
				$this->parsedStructure['AMD']['AMD']['AIRSEQ']
			);
		}
		else {
			$oldDocNumber = $this->parsedStructure['I'][0]['T']['TKTNBR'];
			$transactions = \App\Amadeus\Storage\StorageHandler::selectByDocNumber($oldDocNumber);
			$lastTransaction = end($transactions);
			$lastTransaction = \Entity\AmadeusService::select($lastTransaction?$lastTransaction['id']:null);

			$this->idFile = sprintf("%s%s%s%s",
				\App\Amadeus\StructureParser\Parser\Document::extractTransactionCode($lastTransaction->transactionCode)->RECLOC,
				$this->parsedStructure['AIRBLK']['NETID'],
				$this->parsedStructure['AIRBLK']['ADDR'],
				$this->parsedStructure['AMD']['AMD']['AIRSEQ']
			);
		}

		return $this->idFile;
	}


	/**
	 * Get the number of transactions in this file
	 * @return number
	 */
	public function getTransactionsCount()
	{
		return count($this->getTransactions());
	}

	/**
	 * Get the internally marked skipped transactions
	 * @return array
	 */
	public function getSkippedTransactions()
	{
		// dummy call, to make sure the internalStats are filled in
		$this->getTransactions();
		return (array)$this->internalStats['skippedTransactions'];
	}

	/**
	 * Set the internal list of transactions, in case we need to alter the internal list
	 */
	public function setTransactions($transactions)
	{
		$this->transactions = $transactions;
		return $this;
	}

}
