<?php

namespace Entity;

class Supplier extends \App\Entity\GeneralAbstract
{

	/**
	 * Indicate if the operations available for the current entity must be done inside a transaction or not.
	 * @var Boolean
	 */
	protected $saveInsideTransaction = true;

	protected $addAndUpdateDateTimeFormat = 'timestamp'; // TODO: to be eliminated after the table columns are renamed and replaced with datetime


	/**
	 * (non-PHPdoc)
	 * @see \App\Entity\SelectAbstract::setTableName()
	 */
	protected function setTableName(): void
	{
		global $DBTSuppliers;
		$this->tableName=$DBTSuppliers;
	}

	/**
	 * Static method for mark duplicated suppliers
	 * @param Int $id
	 */
	static public function markDuplicate($id)
	{
		return new self('markDuplicate', $id);
	}

	/**
	 * Static method for unmark duplicated suppiers
	 * @param Int $id
	 */
	static public function unmarkDuplicate($id)
	{
		return new self('unmarkDuplicate', $id);
	}

	/**
	 * (non-PHPdoc)
	 * @see \App\Entity\SelectAbstract::getAttribute()
	 */
	protected function getAttribute($var)
	{
		$_LANG = \App\Loader_Lang::getInstance()->getAll();

		// CHECK EVERY POSSIBLE VALUES THAT $var MAY HAVE -----------------------------------------
		switch ($var) {

			// company full
			case 'companyFull': {
				if (!isset($this->fields['companyFull'])) {
					$this->fields['companyFull']=trim($this->fields['companyPrefix'].' '.$this->fields['company'].' '.$this->fields['companySuffix']);
				}
				return $this->fields['companyFull'];
			}

			// trust
			case 'trust': {
				return Trust::select($this->fields['idTrust'], $this->selectForUpdate);
			}

			// country
			case 'country': {
				return \Entity\Country::select($this->fields['countryCode'], $this->selectForUpdate);
			}

			// city
			case 'city': {
				return \Entity\City::select($this->fields['cityId'], $this->selectForUpdate);
			}

			// paymentPossibilities
			case 'paymentPossibilities': {
				if (!is_array($this->fields['paymentPossibilities'])) {
					if ($this->fields['paymentPossibilities']) {
						$this->fields['paymentPossibilities']=explodeClean('|', $this->fields['paymentPossibilities']);
					} else {
						$this->fields['paymentPossibilities']= [];
					}
				}
				return $this->fields['paymentPossibilities'];
			}

			// paymentPossibilitiesNames
			case 'paymentPossibilitiesNames': {
				if (!is_array($this->fields['paymentPossibilitiesNames'])) {
					// initialize as am empty array
					$this->fields['paymentPossibilitiesNames']= [];
					// scan pay pos and get their names
					if ($this->fields['paymentPossibilities']) {
						// get the list of fops
						$ppList=$this->__get('paymentPossibilities');
						// store the names of the fops
						foreach($ppList as $p) {
							$this->fields['paymentPossibilitiesNames'][]=\App\Config\FormsOfPayment::getInstance()->get($p);
						}
					}
				}
				return $this->fields['paymentPossibilitiesNames'];
			}

			// processed remarks
			case 'processedRemarks': {
				if (!is_array($this->fields['processedRemarks'])) $this->fields['processedRemarks']=$this->getUnserializedProcessedRemarks();
				return $this->fields['processedRemarks'];
			}

			// realSupplier
			case 'realSupplier': {
				return Supplier::select($this->fields['idRealSupplier'], $this->selectForUpdate);
			}

			// the duplicated children of the current supplier
			case 'duplicatedSuppliersList': {
				if (!array_key_exists($var, $this->fields)) {
					$this->fields[$var] = [];
					$listObj = new \App\EntityList\Suppliers();
					$listObj->setFilter('idRealSupplier', '=', $this->id);
					foreach ($listObj->exportRecords() as $supp) {
						$this->fields[$var][] = $supp['id'];
					}
				}
				return $this->fields[$var];
			}

			case 'fullDuplicatedSuppliersGroup': {
				if ($this->getAttribute('duplicatedSuppliersList')) { // parent
					$this->fields['fullDuplicatedSuppliersGroup'] = array_merge([$this->id], $this->getAttribute('duplicatedSuppliersList'));
				}
				elseif ($this->fields['idRealSupplier']) { // child
					$this->fields['fullDuplicatedSuppliersGroup'] = $this->getAttribute('realSupplier')->fullDuplicatedSuppliersGroup;
				}
				else { // none
					$this->fields['fullDuplicatedSuppliersGroup'] = [$this->id];
				}

				return $this->fields['fullDuplicatedSuppliersGroup'];
			}

			// supplier codes (iata/tktCode/icao/sirena), @see $CFG['travel']['standardCodes']
			case 'codes': {
				if (!isset($this->fields['codes'])) {
					global $DBTSupplierCodes;
					$db=\DB::getInstance();
					$this->fields['codes']=$db->GetAssoc("SELECT `code`, `value` FROM `{$DBTSupplierCodes}` WHERE idSupplier='{$this->id}'");
					if ($db->ErrorNo()) {
						throw new \DBException("Could not get supplier codes");
					}
				}
				return $this->fields['codes'];
			}

			// iata code for supplier
			case 'codeIata': {
				if (!isset($this->fields[$var])) {
					$codes = $this->__get('codes');
					$this->fields[$var] = (string)$codes['iata'];
				}
				return $this->fields[$var];
			}
			// iata code for supplier
			case 'codeTktCode': {
				if (!isset($this->fields[$var])) {
					$codes = $this->__get('codes');
					$this->fields[$var] = (string)$codes['tktCode'];
				}
				return $this->fields[$var];
			}

			// bank accounts
			case 'bankAccounts': case 'defaultBankAccountDetails': {
				if (!array_key_exists('bankAccounts', $this->fields)) {
					$supplierBankAccountObj = new \App\EntityList\SupplierBankAccounts();
					$supplierBankAccountObj->setColumns(['defaultAccount', 'account', 'currency', 'idBank', 'idBankOffice']);
					$supplierBankAccountObj->setFilter('suppBankAccounts.idSupplier', '=', $this->id);
					$supplierBankAccountArray = $supplierBankAccountObj->exportRecords();
					$basList = [];
					$defaultBa = [];
					foreach ($supplierBankAccountArray as $supplierBankAccount) {
						$basList[$supplierBankAccount['id']] = $supplierBankAccount;
						if ($supplierBankAccount['defaultAccount']) {
							$defaultBa = $supplierBankAccount;
						}
					}
					$this->fields['bankAccounts'] = $basList;
					$this->fields['defaultBankAccountDetails'] = $defaultBa;
				}

				return $this->fields[$var];
			}

			// default bank account derails
			case 'defaultBankAccount_account': {
				if (!array_key_exists($var, $this->fields)) {
					$baDet = $this->getAttribute('defaultBankAccountDetails');
					$this->fields[$var] = $baDet['account'];
				}
				return $this->fields[$var];
			}
			case 'defaultBankAccount_bank': {
				$baDet = $this->getAttribute('defaultBankAccountDetails');
				return Bank::select($baDet['idBank'], $this->selectForUpdate);

			}
			case 'defaultBankAccount_bankOffice': {
				$baDet = $this->getAttribute('defaultBankAccountDetails');
				return BankOffice::select($baDet['idBankOffice'], $this->selectForUpdate);
			}

			// the list of contact persons
			case 'contactPersonsList': {
				if (!array_key_exists($var, $this->fields)) {
					$contactPersons = new \App\EntityList\ContactPersons();
					$contactPersons->setColumns(['lastName', 'firstName', 'email']);
					$contactPersons->setFilter('idCompany', '=', $this->id);
					$contactPersons->setFilter('type', '=', 'supplier');
					$contactPersons->setOrderBy('lastName', 'ASC');
					$contactPersons->setOrderBy('firstName', 'ASC');
					$contactPersonsArray = $contactPersons->exportRecords();

					$ctcPersList = [];
					foreach ($contactPersonsArray as $contactPersonInfo) {
						$ctcPersList[$contactPersonInfo['id']] = [
							'lastName' => 	$contactPersonInfo['lastName'],
							'firstName' => 	$contactPersonInfo['firstName'],
							'email' =>		$contactPersonInfo['email'],
						];
					}

					$this->fields[$var] = $ctcPersList;
				}
				return $this->fields[$var];
			}

			// balance details (debit)
			case 'debitBalanceDetails': {
				if ($this->id) {
					return (array)\App\PartnerBalance\Supplier::getInstance($this->id)->getFullBalance();
				}
				return [];
			}

			// balance in equiv currency
			case 'equivDebitBalance': {
				if ($this->id) {
					return \App\PartnerBalance\Supplier::getInstance($this->id)->getEquivBalance();
				}
				return 0;
			}

			// balance details (debit)
			case 'creditBalanceDetails': {
				if ($this->id) {
					return \App\PartnerBalance\ImpDocBalance\Supplier::getInstance($this->id)->getFullBalance();
				}
				return [];
			}

			// balance in equiv currency
			case 'equivCreditBalance': {
				if ($this->id) {
					return \App\PartnerBalance\ImpDocBalance\Supplier::getInstance($this->id)->getEquivBalance();
				}
				return 0;
			}


			// addUser
			case 'addUser': {
				return User::select($this->fields['idAddUser'], $this->selectForUpdate);
			}

			// updateUser
			case 'updateUser': {
				return User::select($this->fields['idUpdateUser'], $this->selectForUpdate);
			}

			// address details
			case 'addressDetails': {
				if (!is_array($this->fields['addressDetails'])) {
					if ($this->fields['addressDetails']) {
						$this->fields['addressDetails'] = unserialize($this->fields['addressDetails']);
					} else {
						$this->fields['addressDetails'] = [];
					}
				}
				return $this->fields['addressDetails'];
			}

			// address components
			case 'address_main_streetPrefix': case 'address_main_streetName': case 'address_main_streetNumber': case 'address_main_district':
			case 'address_main_buildingNumber': case 'address_main_buildingEntrance': case 'address_main_floor': case 'address_main_apartment':
		 	case 'address_main_zipCode': case 'address_main_cityId': case 'address_main_countryCode':
			case 'address_delivery_streetPrefix': case 'address_delivery_streetName': case 'address_delivery_streetNumber': case 'address_delivery_district':
			case 'address_delivery_buildingNumber': case 'address_delivery_buildingEntrance': case 'address_delivery_floor': case 'address_delivery_apartment':
			case 'address_delivery_zipCode': case 'address_delivery_cityId': case 'address_delivery_countryCode': {
				$addressDetails = $this->getAttribute('addressDetails');
				return $addressDetails[$var];
			}

			// translations for street prefix
			case 'address_main_streetPrefixName': {
				$_LANG = \App\Loader_Lang::getInstance()->getAll();
				return $_LANG['addressDetails']['streetPrefixPossibleValues'][$this->getAttribute('address_main_streetPrefix')];
			}
			case 'address_delivery_streetPrefixName': {
				$_LANG = \App\Loader_Lang::getInstance()->getAll();
				return $_LANG['addressDetails']['streetPrefixPossibleValues'][$this->getAttribute('address_delivery_streetPrefix')];
			}

			// city/country for elements of detailed address
			case 'address_main_city': {
				return \Entity\City::select($this->getAttribute('address_main_cityId'), $this->selectForUpdate);
			}
			case 'address_main_country': {
				return \Entity\Country::select($this->getAttribute('address_main_countryCode'), $this->selectForUpdate);
			}
			case 'address_delivery_city': {
				return \Entity\City::select($this->getAttribute('address_delivery_cityId'), $this->selectForUpdate);
			}
			case 'address_delivery_country': {
				return \Entity\Country::select($this->getAttribute('address_delivery_countryCode'), $this->selectForUpdate);
			}

			// composed address details:
			case 'composedAddress':  {
				$addressComponents = ['streetPrefix', 'streetName', 'streetNumber', 'buildingNumber', 'buildingEntrance', 'floor', 'apartment', 'cityId', 'countryCode', 'district', 'zipCode',];
				$composedAddress = '';
				foreach (['main'] as $addrType) {
					foreach($addressComponents as $addrComp) {
						$key = 'address_'.$addrType.'_'.$addrComp;
						if ($this->getAttribute($key)) {
							if ($addrComp == 'streetPrefix') {
								$composedAddress.= $_LANG['addressDetails']['streetPrefixPossibleValues'][$this->getAttribute($key)].' ';
							}
							elseif ($addrComp == 'countryCode') {
								$composedAddress.= \Entity\Country::select($this->getAttribute($key))->name.'; ';
							}
							elseif ($addrComp == 'cityId') {
								$composedAddress.= \Entity\City::select($this->getAttribute($key))->name.'; ';
							}
							else {
								$composedAddress.= $_LANG['addressDetails'][$addrComp].' '.$this->getAttribute($key).'; ';
							}
						}
					}

				}

				$this->fields[$var] = $composedAddress;
				return $this->fields[$var];
			}

			case 'composedDeliveryAddress':  {
				$addressComponents = ['streetPrefix', 'streetName', 'streetNumber', 'buildingNumber', 'buildingEntrance', 'floor', 'apartment', 'cityId', 'countryCode', 'district', 'zipCode',];
				$composedAddress = '';
				foreach (['delivery'] as $addrType) {
					foreach($addressComponents as $addrComp) {
						$key = 'address_'.$addrType.'_'.$addrComp;
						if ($this->getAttribute($key)) {
							if ($addrComp == 'streetPrefix') {
								$composedAddress.= $_LANG['addressDetails']['streetPrefixPossibleValues'][$this->getAttribute($key)].' ';
							}
							elseif ($addrComp == 'countryCode') {
								$composedAddress.= \Entity\Country::select($this->getAttribute($key))->name.'; ';
							}
							elseif ($addrComp == 'cityId') {
								$composedAddress.= \Entity\City::select($this->getAttribute($key))->name.'; ';
							}
							else {
								$composedAddress.= $_LANG['addressDetails'][$addrComp].' '.$this->getAttribute($key).'; ';
							}
						}
					}

				}

				$this->fields[$var] = $composedAddress;
				return $this->fields[$var];
			}

			// id group elements
			case 'idGroupElements': {
				if (!array_key_exists($var, $this->fields)) {
					global $DBTSupplierGroups;
					$db = \DB::getInstance();
					$sql = "SELECT `idGroup`, `idGroupElement` FROM `{$DBTSupplierGroups}` WHERE idSupplier=?";
					$this->fields[$var] = $db->GetAssoc($sql, [$this->id]);

					if ($db->ErrorNo()) {
						throw new \DBException("Could not load group elements for supplier");
					}
				}
				return $this->fields[$var];
			}

			// group elements names
			case 'groupElementsNames': {
				$idGroupElements = $this->getAttribute('idGroupElements');
				if ($idGroupElements) {
					$groupsList = [];
					foreach ($idGroupElements as $idGroupElem) {
						$groupElemDetails = \App\Config\SupplierGroups::getInstance()->getGroupElementDetailsById($idGroupElem);
						$groupsList[] = sprintf("%s:%s", $groupElemDetails['groupName'], $groupElemDetails['groupElementName']);
					}
					$this->fields[$var] = implode('; ', $groupsList);
				}
				else {
					$this->fields[$var] = '';
				}

				return $this->fields[$var];
			}

			// the rest of the fields (most of them are just copied)
			default: {
				return parent::getAttribute($var);
			}

		}

	}

	/**
	 * (non-PHPdoc)
	 * @see \App\Entity\GeneralAbstract::setAttribute()
	 */
	protected function setAttribute($var, $value)
	{

		// check that operation is okay
		if ($this->operation!='insert' and $this->operation!='update' and $this->operation!='markDuplicate') {
			throw new \Exception('You can use __set magic method only with insert/update operation in `'.__CLASS__.'` class (error while setting the `'.$var.'` attribute.');
		}

		// CHECK EACH POSSIBLE FIELD VALUE --------------------------------------------------------
		switch ($var) {

			// simple fields
			case '_dontExport': case 'canDelete': case 'alias': case 'company': case 'companyPrefix': case 'companySuffix': case 'idTrust': case 'category':
			case 'zipCode': case 'countryCode': case 'cityId': case 'regNo': case 'fiscalCode': case 'vatRegistered': case 'vatCertificateNumber':
			case 'incomeTaxPayer': case 'address': case 'deliveryAddress': case 'telephone': case 'fax': case 'email': case 'website':
			case 'paymentPossibilities': case 'comments': case 'locked': case 'lockedComments': case 'codes': case 'bankAccounts':
			case 'address_main_streetPrefix': case 'address_main_streetName': case 'address_main_streetNumber': case 'address_main_district':
			case 'address_main_buildingNumber': case 'address_main_buildingEntrance': case 'address_main_floor': case 'address_main_apartment':
		 	case 'address_main_zipCode': case 'address_main_cityId': case 'address_main_countryCode':
			case 'address_delivery_streetPrefix': case 'address_delivery_streetName': case 'address_delivery_streetNumber': case 'address_delivery_district':
			case 'address_delivery_buildingNumber': case 'address_delivery_buildingEntrance': case 'address_delivery_floor': case 'address_delivery_apartment':
			case 'address_delivery_zipCode': case 'address_delivery_cityId': case 'address_delivery_countryCode': case 'accountingNumber': {
				$this->fields[$var]=$value;
				break;
			}

			// the list of duplicated suppliers (the current supplier being the "parent" of all these children
			case 'duplicatedSuppliers': {
				if ($this->operation!='update' and $this->operation!='markDuplicate') {
					throw new \Exception("The `{$var}` field must be set only for `update` / `markDuplicate` operation in ".__METHOD__);
				}
				if (!is_array($value)) {
					throw new \Exception("The `{$var}` field must be set as array in ".__METHOD__);
				}
				$this->fields[$var]=$value;
				break;
			}

			// process remarks
			case 'remarks': {
				$this->fields['remarks']=$value;
				$this->fields['processedRemarks']=\App\Config\Remarks::process($value, 'supplier');
				break;
			}

			// groups
			case 'idGroupElements': {
				if($this->operation != 'update') {
					throw new \Exception("The fields related to the supplier group details can only be set on update.");
				}
				$this->fields[$var] = $value;
				break;
			}

			// the field is not valid
			default: {
				throw new \Exception("The field '$var' is not valid for the `".__CLASS__."` entity, operation `{$this->operation}`.");
			}

		}

	}



	/**
	 * (non-PHPdoc)
	 * @see \App\Entity\GeneralAbstract::save_filterSaveData()
	 */
	protected function save_filterSaveData(Array $saveData)
	{
		// skip this method for other than insert/update operations
		if ($this->operation!='insert' and $this->operation!='update' and $this->operation!='markDuplicate') {
			return $saveData;
		}

		// eliminate some fields that should not be saved
		unset($saveData['bankAccounts']);
		unset($saveData['_dontExport']);
		unset($saveData['codes']);
		unset($saveData['duplicatedSuppliers']);
		// set remarks
		if (array_key_exists('remarks', $saveData)) {
			$saveData['processedRemarks'] = $this->getSerializedProcessedRemarks();
		}
		// set can delete
		if ($this->operation=='insert' and !isset($saveData['canDelete'])) {
			$saveData['canDelete']=1;
		}
		// process paymentPossibilities
		if (array_key_exists('paymentPossibilities', $saveData)) {
			if ($saveData['paymentPossibilities']) $saveData['paymentPossibilities']='|'.implode('|', $saveData['paymentPossibilities']).'|';
			else $saveData['paymentPossibilities']=null;
		}
		// not vat registered - empty the certificate number
		if (array_key_exists('vatRegistered', $saveData) and !$saveData['vatRegistered']) {
			$saveData['vatCertificateNumber']=null;
		}
		// clean locked comments
		if (array_key_exists('locked', $saveData) and !$saveData['locked'] and !array_key_exists('lockedComments', $saveData)) {
			$saveData['lockedComments']='';
		}

		// address components
		$saveData['addressDetails'] = [];
		$addressComponents = ['streetPrefix', 'streetName', 'streetNumber', 'buildingNumber', 'district', 'buildingEntrance', 'floor', 'apartment', 'zipCode', 'cityId', 'countryCode'];
		$anyAddrCompSet = false;
		foreach (['main', 'delivery'] as $addrType) {
			foreach($addressComponents as $addrComp) {
				$key = 'address_'.$addrType.'_'.$addrComp;

				// at least on field is set
				if (array_key_exists($key, $saveData)) {
					$anyAddrCompSet = true;
				}
				// if field has a value - save it
				if ($saveData[$key]) {
					$saveData['addressDetails'][$key] = $saveData[$key];
				}
				unset($saveData[$key]);
			}
		}


		// if at least one address component is set - compose the db table field
		if ($anyAddrCompSet) {
			if ($saveData['addressDetails']) {
				$saveData['addressDetails'] = serialize($saveData['addressDetails']);
			} else {
				$saveData['addressDetails'] = '';
			}
		} else {
			unset($saveData['addressDetails']);
		}

		// groups
		unset($saveData['idGroupElements']);

		// return the new save data
		return $saveData;
	}


	/**
	 * (non-PHPdoc)
	 * @see \App\Entity\GeneralAbstract::save_postProcess()
	 */
	protected function save_postProcess(Array $saveData)
	{

		// UPDATE THE DUPLICATED CHILDREN SUPPLIERS REFERENCES ////////////////////////////////////
		if ($this->operation=='update' and array_key_exists('duplicatedSuppliers', $this->fields)) {

			// STEP 1: CLEAN UP  THE OLD REFERENCES OF THIS SUPPLIER ------------------------------
			$this->save_executeSql_update($this->tableName, ['idRealSupplier'=>0], 'idRealSupplier', $this->id);

			// STEP 2: ADD THE NEW REFERENCES -----------------------------------------------------
			foreach($this->fields['duplicatedSuppliers'] as $idSupp) {
				$this->save_executeSql_update($this->tableName, ['idRealSupplier'=>$this->id], 'id', $idSupp);
			}

		}

		// UPDATE THE BANK ACCOUNTS ///////////////////////////////////////////////////////////////
		if ($this->operation=='insert' or $this->operation=='update') {
			$this->save_postProcess_bankAccounts();
		}

		// UPDATE SUPPLIER CODES //////////////////////////////////////////////////////////////////
		if ($this->operation != 'markDuplicate' and $this->operation != 'unmarkDuplicate') {
			$this->save_postProcess_manageCodes();
		}

		// UPON DELETE - eliminate associations with supplier services ////////////////////////////
		if ($this->operation=='delete') {
			global $DBTSuppliersProductsAssoc;
			$this->save_executeSql_delete($DBTSuppliersProductsAssoc, 'idSupplier', $this->id);
		}

		// MARK DUPLICATE ACTION //////////////////////////////////////////////////////////////////
		if ($this->operation == 'markDuplicate') {
			$this->save_postProcess_markDuplicate();
		}

		// UNMARK DUPLICATE ACTION ////////////////////////////////////////////////////////////////
		if ($this->operation == 'unmarkDuplicate') {
			$this->save_postProcess_unmarkDuplicate();
		}

		// SET GROUPS
		if($this->operation == 'update') {
			$this->save_postProcess_setGroups($this->fields);
		}
	}


	/**
	 * Saves the bank accounts into the db (for insert/update operations).
	 */
	private function save_postProcess_bankAccounts()
	{

		// check operation
		if ($this->operation!='insert' and $this->operation!='update') {
			throw new \Exception('You can call `'.__METHOD__.'` only for insert/update operations.');
		}
		// if no bank accounts - skip this method
		if (!array_key_exists('bankAccounts', $this->fields)) {
			return true;
		}

		// DETERMINE WHICH BANK ACCOUNTS MUST BE ADDED, WHICH UPDATED AND WHICH DELETED -----------
		if ($this->operation=='update') $oldBas=Supplier::selectForUpdate($this->id)->bankAccounts;
		else $oldBas= [];
		// determine the update bas list (list of bas that should be added/updated/deleted)
		$updateBas= [];
		foreach($this->fields['bankAccounts'] as $ba) {
			// current bank account is updated
			if ($ba['id'] and $oldBas[$ba['id']]) {
				$updateBas[]=$ba+ ['_operation'=>'update'];
				unset($oldBas[$ba['id']]);
			}
			// current bank account is inserted
			if (!$ba['id']) {
				$updateBas[]=$ba+ ['_operation'=>'insert'];
			}
		}
		// the rest of the oldBas must be deleted
		if ($oldBas) {
			foreach($oldBas as $id=>$ba) {
				$updateBas[]= ['id'=>$id, '_operation'=>'delete'];
			}
		}

		// EXECUTE THE ACTUAL SQLS ON BANK ACCOUNTS -------------------------------------------
		if ($updateBas) {
			global $DBTSuppliersBankAccounts;
			foreach($updateBas as $ba) {
				// operation = delete
				if ($ba['_operation']=='delete') {
					$this->save_executeSql_delete($DBTSuppliersBankAccounts, 'id', $ba['id']);
				}
				// operation = insert/update
				else {
					$prepVars= [];
					$prepVars['account']=$ba['account'].'';
					if ($ba['defaultAccount']) {
						$prepVars['defaultAccount']=1;
					} else {
						$prepVars['defaultAccount']=0;
					}
					$prepVars['currency']=$ba['currency'].'';
					$prepVars['idBank']=$ba['idBank']+0;
					$prepVars['idBankOffice']=$ba['idBankOffice']+0;
					if ($ba['_operation']=='insert') $prepVars['idSupplier']=$this->id;
					// compose&exec sql
					if ($ba['_operation']=='update') {
						$this->save_executeSql_update($DBTSuppliersBankAccounts, $prepVars, 'id', $ba['id']);
					} else {
						$this->save_executeSql_insert($DBTSuppliersBankAccounts, $prepVars);
					}
				}
			}
		}

	}

	/**
	 * Manages supplier codes (on insert/update/delete operations).
	 * @throws \Exception - on invalid operation
	 */
	private function save_postProcess_manageCodes()
	{

		global $DBTSupplierCodes;

		// check operation
		if ($this->operation!='insert' and $this->operation!='update' and $this->operation!='delete') {
			throw new \Exception('You can call '.__METHOD__.' only for insert/update/delete operations');
		}

		// IF OPERATION = delete - DELETES THE SUPPLIER CODES -------------------------------------
		if ($this->operation=='delete') {
			$this->save_executeSql_delete($DBTSupplierCodes, 'idSupplier', $this->id);
		}

		// check supplier codes (for insert/update)
		if (!array_key_exists('codes', $this->fields)) {
			return true;
		}


		// UPDATE SUPPLIER CODES ------------------------------------------------------------------

		// delete the old codes
		if ($this->operation=='update') {
			$this->save_executeSql_delete($DBTSupplierCodes, 'idSupplier', $this->id);
		}

		// (re)insert the new codes
		foreach($this->fields['codes'] as $code=>$value) {
			if (strlen($value)) { // skip empty fields
				$fields= ['idSupplier'=>$this->id, 'code'=>$code, 'value'=>$value];
				$this->save_executeSql_insert($DBTSupplierCodes, $fields);
			}
		}

	}

	/**
	 * mark duplicated suppliers
	 */
	private function save_postProcess_markDuplicate()
	{
		foreach ($this->fields['duplicatedSuppliers'] as $idDuplicatedSupplier ) {
			$this->save_executeSql_update($this->tableName, ['idRealSupplier'=>$this->id], 'id', $idDuplicatedSupplier );
		}
		$this->messages->addSuccessCode('markDuplicate_ok');
	}

	/**
	 *unmark duplicated Suppliers
	 */
	private function save_postProcess_unmarkDuplicate()
	{
		$supplier = \Entity\Supplier::selectForUpdate($this->id);

		// the supplier is duplicated
		if ($supplier->idRealSupplier ) {
			$this->save_executeSql_update($this->tableName, ['idRealSupplier'=>0], 'id', $this->id);
		}
		// the supplier has duplicated
		else {
			$this->save_executeSql_update($this->tableName, ['idRealSupplier'=>0], 'idRealSupplier', $this->id);
		}
		$this->messages->addSuccessCode('unmarkDuplicate_ok');
	}

	/**
	 * Set groups
	 */
	private function save_postProcess_setGroups($saveData)
	{
		if (!array_key_exists('idGroupElements', $this->fields)) {
			return ;
		}

		global $DBTSupplierGroups;

		// delete current values - execute SQL
		$this->save_executeSql_delete($DBTSupplierGroups, 'idSupplier', $this->id);

		// insert new values
		if ($this->fields['idGroupElements']) {
			$idGroupElements = array_filter($this->fields['idGroupElements']);

			foreach ($idGroupElements as $idGroup=>$idGroupElement) {
				// execute SQL
				$this->save_executeSql_insert($DBTSupplierGroups, [
					'idSupplier' 		=> $this->id,
					'idGroup' 			=> $idGroup,
					'idGroupElement' 	=> $idGroupElement,
				]);
			}
		}
	}

	/**
	 * (non-PHPdoc)
	 * @see \App\Entity\GeneralAbstract::save_updateCache()
	 */
	protected function save_updateCache()
	{
		// delete entity Trust cache
		\App\Entity\Cache::getInstance()->delete('Trust', $this->fields['idTrust']);

		parent::save_updateCache();
	}


	/**
	 * Exports the supplier (XML).
	 * @see \App\Entity\GeneralAbstract::save_finishOperation()
	 */
	protected function save_finishOperation()
	{
		parent::save_finishOperation();

		// export the supplier XML
		if (!$this->fields['_dontExport']) {

			// get operation; it will remain null if the supplier should not be exported
			$operation = null;
			if ($this->operation=='insert') {
				$operation = 'add';
			}
			if ($this->operation=='update' or $this->operation=='markDuplicate' or $this->operation=='unmarkDuplicate') {
				$operation = 'update';
			}
			if ($this->operation=='delete') {
				$operation = 'delete';
			}

			// does the actual export
			if ($operation) {
				try {
					$supplierExport = new \App\ExportData\ExportFile\Supplier($this->id, $operation);
					$supplierExport->writeFile();
				} catch (\App\MessagesException $e) {
					$this->messages->removeSuccess();
					\Log::getInstance()->critical(sprintf('The supplier XML was not generated. Error: %s', $e->getMessagesCollector()->getMessageBulk()));
					$this->messages->addErrorCode('xmlExportFailed');
				}
			}
		}
	}
}
