<?php defined('BASEPATH') OR exit('No direct script access allowed');

require_once(APPPATH . "interfaces/Transaction_processor_interface.php");

require_once(APPPATH . 'processors/TransFlag.php');

// require_once APPPATH . 'processors/Base_processor.php';
require_once APPPATH . 'processors/Parent_processor.php';

require_once(APPPATH . "validator/CompanyValidator.php");
require_once(APPPATH . "validator/TransSaveValidator.php");
require_once(APPPATH . "validator/DateValidator.php");

class Transaction_processor extends Parent_processor implements Transaction_processor_interface
{
	public $updatedOn = null;

	// private $ci;
	public function __construct(&$params)
	{
		$this->updatedOn = gmdate('Y-m-d H:i:s');

		parent::__construct($params);
		// $this->ci =& get_instance();
	}

	protected function setupValidators($data)
	{
		parent::setupValidators($data);

		$this->addValidator(new CompanyValidator($this->params));
		$this->addValidator(new TransSaveValidator($this->params));
		$this->addValidator(new DateValidator($this->params));

		if (!in_array($data['tType'], array(SALPMT, PURPMT, JOURNAL_CONTRA, JOURNAL_VOUCHER)))
		{
			require_once(APPPATH . "validator/TransDuplicateValidator.php");
			$this->addValidator(new TransDuplicateValidator($this->params));
		}

		if (!in_array($data['tType'], array(EST,SALORD,PUREST,PURORD)))
		{
			require_once(APPPATH . "validator/TransReferedValidator.php");
			$this->addValidator(new TransReferedValidator($this->params));
		}
		// $this->addValidator(new TransExistanceValidator($this->params));
	}

	public function save(&$data, &$result)
	{
		$this->CI->load->library("trans_table");

		$this->validate($data);

		$this->init_save($data);

		$this->CI->db->trans_start();

		try {

			$this->pre_save($data);

			$this->execute_save($data, $result);

			$this->post_save($data, $result);

		} catch(Exception $ex) {

			$this->CI->db->trans_rollback();

			throw $ex;
		}

		$this->CI->db->trans_complete();

		$this->post_commit($data, $result);
	}

	public function init_save(&$data)
	{
		$ci =& get_instance();

		if (!empty($data["delPaymentId"]))
		{
			$isSuccess = false;
			foreach ($data["delPaymentId"] as $paymentTransId)
			{
				$isSuccess = $ci->TransModel->payment_delete($paymentTransId, $data["id"]);

				if (!$isSuccess)
				{
					throw new Exception("Can't delete payment.");
					break;
				}
			}

			if (!$isSuccess)
			{
				return false;
			}
		}

		if (!empty($data['isPos']))
		{
			$data['source'] = "P";
		}

		if (empty($data['conId']))
		{
			if (!in_array($data['tType'], array(EST,SALORD,SALRETAIL,CRNOTE,PUREST,PURORD,PURRETAIL,PURCRNOTE,EXPRETAIL,JOURNAL_VOUCHER,JOURNAL_CONTRA,SALPMT,PURPMT))
				|| (in_array($data['tType'], array(SALPMT,PURPMT)) && (empty($data['classCode']) || $data['classCode']!='XPNG'))
			)
			{
				if(!$this->_ensure_contact_exists_or_create($data))
				{
					return false;
				}
			}
			/*
			// Release branch : 227042bd9aa6351205bb6a8f241227b1d8243d4c on Jul 29 2025 18:28:43
			if (!empty($data['estId']))
			{
				if($this->_ensure_contact_exists_or_create($data))
				{
					$where = array(
						"cId" => $this->params->cId,
						"id" => $data['estId']
					);
					$ci->db->where($where)->update('trans', array("conId" => $data['conId']));
				}

				if (empty($data['conId']))
				{
					return false;
				}
			}
			else if ((!empty($data['isPos']) && $data['tType'] != SALRETAIL) || ($data['tType'] == CRNOTE  && !empty($data['refList'])))
			{
				if(!$this->_ensure_contact_exists_or_create($data))
				{
					return false;
				}
			}
			*/
		}

		if (!empty($data['estId']) && !empty($data['conId']))
		{
			$where = array(
				"cId" => $this->cId,
				"id" => $data['estId']
			);
			$this->db->where($where)->update('trans', array("conId" => $data['conId'])); 
		}

		$data['conId']	= !empty($data['conId']) ? $data['conId'] : 0;
		$data['info']	= !empty($data['info']) ? $data['info'] : "";

		if (!empty($data['isKot']) && !empty($data['guestno']))
		{
			$kot_info = array('guestno' => $data['guestno']);

			$data['info']			= is_string($data['info']) ? json_decode($data['info'], true) : $data['info'];
			$data['info']['kot']	= !empty($data['info']['kot']) ? $data['info']['kot'] : array();
			$data['info']['kot']	= array_merge($data['info']['kot'], $kot_info);
		}

		$data['info'] 	= !is_string($data['info']) ? json_encode($data['info'], JSON_UNESCAPED_SLASHES) : $data['info'];

		if (
			(!$this->params->isFranchise || $data['tType'] != PURORD) &&
			!empty($data['conId']) &&
			$data['conId'] == $this->params->bId
		){
			throw new Exception(lang('SAME_BRANCH_TRANS_RESTRICTED'));
			return false;
		}

		// If Sales Rep creating transaction then set sales rep id as default sRepId.
		if (get_user_data('roleCode',true) == 'R')
		{
			$data['sRepId'] = get_user_data('conId');
		}

		$data['updatedOn'] = $this->updatedOn;

		$cur_date = gmdate('Y-m-d');
		$data['tDate'] = !empty($data['tDate']) ? $data['tDate'] : $cur_date;

		if (isset($data['conTaxNo']))
		{
			$data['conTaxNo'] = strtoupper(trim($data['conTaxNo']));
		}

		// Supplier Invoice no /Buyer no should not duplicate for a supplier while make purchase bill/sales invoice.
		if (in_array($data['tType'], array(PURINV,PURRETAIL,EXPINV,EXPRETAIL,SALORD,SALINV)) && !empty($data['relNumber']))
		{
			$row = $this->CI->TransModel->duplicate_rel($data);
			if (!empty($row['id']))
			{
				throw new Exception(sprintf(lang('REL_NUMBER_DUPLICATE_TNO'), $row['tNo']));
				return false;
			}
		}

		$data['updatedBy'] = get_user_data('name');

		$this->_determine_contact_location_code($data);

		$this->get_tmp_trans($data);

		// this should be after get_tmp_trans.
		$this->_validate_transaction_type_change($data, $this->params->tmp_trans);

		parent::init_save($data);
	}

	public function pre_save(&$data)
	{
		// TransFlag::$trans_values = $this->CI->trans_table->strip_fields($data);

		parent::pre_save($data);

		$this->CI->load->library("transSeries");

		if (!empty($data['id']))
		{
			$this->CI->transseries->handle_tNo_regeneration($data, $this->params->tmp_trans);

			// if (method_exists($this, 'reallocate_referenced_payments'))
			// {
			// 	$this->reallocate_referenced_payments($data);
			// }

			$this->CI->load->library("transRollback");
			$this->CI->transrollback->rollback_transaction($data['id'], $data);
		}
		else
		{
			
			$data['status'] = $data["isPmt"] ? $data['status'] : '';
			$d_types = TransFlag::$NonAccTranTypes;
			$d_types[] = SALINV;
			
			if (in_array($data['tType'], $d_types))
			{
				// default send status for goods receipts & delivery challan
				$data['status']	= in_array($data['tType'], array(PURRECP,DELCHALLAN,TRANSDEL,TRANSRPT,TRANSORD,JOBORD)) ? 'S' : 'D';
				$data['status']	= ((TransFlag::$is_kot_order && $data['status'] == 'D') || (!empty($data['source']) && $data['source'] == 'P')) ? 'S' : $data['status'];
				// set invoice status as send for customer credit validation
				// $values['status'] = in_array($values['tType'],array(PURRECP,DELCHALLAN,SALINV))?'S':'D';
				if (!empty($data['fStatus']))
				{
					// force to save as fstatus
					$data['status'] = $data['fStatus'];
				}
			}
			else if (in_array($data['tType'], array(PURINV,DBNOTE, PURDBNOTE, EXPINV)))
			{
				$data['status'] = 'S';
			}
			else if (in_array($data['tType'], array(PURRETAIL,SALRETAIL,EXPRETAIL)))
			{
				$data['status'] = 'P';
			}

			$details = !empty($data['details']) ? $data['details'] : array();

			if ($data['tType'] == EST && !empty($data['_isSaveAsRevision']))
			{
				// quotation version changes only if new revision
				if (!$this->CI->transseries->version_tNo($data, $details))
				{
					$hasError = true;
				}
			}
			else if (!$this->CI->transseries->create_tNo($data, $details))
			{
				$hasError = true;
			}

			if (!empty($data['send_status']))
			{
				// for invoice directly save in send status
				$data['status'] = 'S';
			}

			if (in_array($data['tType'], array(SALINV,SALPMT,CRNOTE,DBNOTE,DELCHALLAN,SALORD,EST,PURORD,PUREST,PURINV,PURDBNOTE,PURCRNOTE,PURRECP,JOBORD,TRANSDEL,TRANSRPT,TRANSORD)))
			{
				$dt = get_rights(TransFlag::$onlydraft[$data['tType']]);
				if ($dt && !empty($dt['actionRights']))
				{
					//Only Draft Status
					$data['status'] = 'D';
					if ($data['tType'] == SALRETAIL)
					{
						$insertPmd = true;
					}
				}
			}
		}
		
		if (!empty($data['removeCoupon']))
		{
			foreach($data['removeCoupon'] as $rCoupon)
			{
				if ($rCoupon["transId"])
				{
					$this->CI->db->delete("trans_coupons", $rCoupon);
				}
			}
		}
		/*
		//For import => Source = "I"
		if(!empty($data['source']) && $data['source'] == "I") {
			$values['tNo'] = $data['tNo'] ;
		}
		*/
	}

	protected function execute_save(&$data, &$result)
	{
		$ret = $this->CI->trans_table->save($data, $this->params, $result, $this);
		$data["id"] = $data["transId"] = $result["id"];
	}

	public function post_save(&$data, &$return)
	{
		parent::post_save($data, $return);

		if(isset($data['categoryCode']))
		{
			$return['categoryCode'] = $data['categoryCode'];
		}

		if (isset($data['classCode']))
		{
			$return['classCode'] = $data['classCode'];
		}

		$tmp_trans =& $this->params->tmp_trans;
		$transId = $data["transId"];

		// create log if tno modified
		if ($tmp_trans && $tmp_trans['tNo']!=$data['tNo'])
		{
			activity_log(array('tId' => $transId, 'aEvent' => 'E','aValue' => $data['tNo'],'aRelValue' => $data['tType'].' ['.$tmp_trans['tNo'].']'));
		}

		// trans related data
		$rel_data = array();
		$rel_fields = array('relDate', 'relNumber');
		foreach ($rel_fields as $key)
		{
			if (isset($data[$key]))
			{
				$rel_data[$key] = $data[$key];
			}
		}

		if ($rel_data)
		{
			$rel_data['transId'] = $transId;
			$rel_data['cId'] = $this->cId;
			$this->CI->db->delete('trans_rel', array('transId' => $transId, 'cId' => $this->cId));
			$this->CI->db->insert('trans_rel', $rel_data);
		}

		$this->create_online_payment_token($data);
	}

	public function post_commit(&$postData, &$result)
	{
		$ci =& get_instance();
	
		$this->_delete_attachments($postData);
		$this->_save_attachments($postData, $result);

		if (!empty($postData["btrId"]) && !empty($result["id"]))
		{
			$ci->load->library('banktrans_table');
			$res = $ci->banktrans_table->map_trans($this->params->cId, $postData["btrId"], $result["id"]);

			if ($res !== false )
			{
				$result["transCnt"] = !empty($res["transCnt"]) ? $res["transCnt"] : 1;
			}
		}

		parent::post_commit($data, $result);
	}

	private function get_tmp_trans(&$data)
	{
		$this->params->tmp_trans = array();

		if (empty($data['id']))
		{
			return true;
		}

		$tmp_trans = array();

		$this->db->select('t.id,t.tDate,t.totAmount,ta.dueAmount,t.conId, t.status,t.categoryCode,t.bId, t.tNo, t.tType, t.status,t.classCode,t.dueDate');
		$this->db->from('trans AS t');
		$this->db->join('trans_acc as ta','ta.transId=t.id and ta.dueAmount<>0','left');
		$this->db->where(array(
			't.id'	=> $data['id'],
			't.cId'	=> $this->params->cId
		));
		$tmp_trans = $this->db->get()->row_array();

		if (empty($tmp_trans))
		{
			throw new Exception(lang('TRANS_ERROR'));
			return false;
		}

		$this->params->tmp_trans = $tmp_trans;

		if (sal_mod_active(BRANCH_MOD) && $tmp_trans['categoryCode']=='RAL' && empty($data['isIndAcc']))
		{
			// reference account restrict edit
			throw new Exception(lang('REF_ACC_DISABLED'));
			return false;
		}

		$comp_conf	= get_company('config');

		if (!empty($comp_conf['freezeDate']) && strtotime($tmp_trans['tDate'])<=strtotime($comp_conf['freezeDate']))
		{
			throw new Exception(sprintf(lang('FREEZE_TRANSACTION_DATE'), $comp_conf['freezeDate']));
			return false;
		}
	}

	public function load_data($tType, $transId)
	{
		
	}

	public function process_related_transaction($newtType, $newTransId, $tType, $transId, &$newTransData, &$postData)
	{
		
	}

	private function _ensure_contact_exists_or_create(&$data)
	{
		if (!empty($data['conId']) || empty($data["conName"]))
		{
			return true;
		}

		$ci =& get_instance();

		if ($data['tType']==CRNOTE && empty($data["conName"]))
		{
			$data["conName"] = 'Retail Customer';
		}

		$data["conName"] = preg_replace("!\s+!", " ", $data["conName"]);

		$result = $ci->db->select("id")->from("contacts")->where(array(
			"cId" => $this->params->cId
		))->like("name", $data["conName"], "none")->get()->row_array();

		if (!empty($result) && !empty($result["id"]))
		{
			$data['conId'] = $result["id"];
			return true;
		}

		$contact = array(
			"cId"	=> $this->params->cId,
			"name"	=> $data['conName'],
			"company"	=> $data['conName'],
			"csType"	=> "C",
			"stateCode"	=> !empty($data['conStateCode']) ? $data['conStateCode'] : '',
			"stateName"	=> !empty($data['stateName']) ? $data['stateName'] : '',
			"stateId"	=> !empty($data['conStateId']) ? $data['conStateId'] : '',
			"isActive"	=> "1",
			"taxNo"		=> !empty($data['conTaxNo']) ? $data['conTaxNo'] : '',
			"conTaxScheme"	=> !empty($data['conTaxScheme']) ? $data['conTaxScheme'] : ''
		);

		if (!empty($data['contact']) && !empty($data['contact']['contactPersonMobile']))
		{
			$contact['contactPersonMobile'] = $data['contact']['contactPersonMobile'];
		}

		$contact['address'] = array();
		if (!empty($data['billingAddr']))
		{	
			$contact['address']["billing"] = $data['billingAddr'];
		}

		if (!empty($data['shippingAddr']))
		{
			if (empty($data['billingAddr']))
			{
				$contact['address']["billing"] = $data['shippingAddr'];
			}
			else
			{
				$contact['address']["shipping"] = $data['shippingAddr'];
			}
		}

		if (empty($contact['address']))
		{
			$contact['same_as_bill'] = true;
			$address = array(
				'addrType'	=> "A",
				'country'	=>  get_company('country'),
				'countryCode' =>  get_company('countryCode'),
				'stateCode'	=> !empty($contact['stateCode']) ? $contact['stateCode'] : get_company('stateCode'),
				'stateId'	=> !empty($contact['stateId']) ? $contact['stateId'] : get_company('stateId'),
				'stateName'	=> !empty($contact['stateName']) ? $contact['stateName'] : get_company('stateName')
			);
			$contact['address']["shipping"] = $contact['address']["billing"] = $address;
		}

		$ci->load->model('contactsModel');

		$conId = $ci->contactsModel->save($contact);

		if ($conId)
		{
			$data['conId'] = $conId;
			return true;
		}

		if ($this->contactsModel->message)
		{
			throw new Exception(lang('CONTACT_SAVE_ERROR') . " : " . $this->contactsModel->message);
		}

		return false;
	}

	private function _delete_attachments(&$data)
	{
		if (!empty($data['attach_delete']))
		{
			$ci =& get_instance();
			$ci->db->where_in('id', $data['attach_delete']);
			$ci->db->delete('trans_attachments', array(
				'cId' => $this->params->cId,
				'transId'	=> $data['id']
			));
		}
	}

	private function _save_attachments(&$data, &$result)
	{
		
		if (empty($data['attachments']))
		{
			return;
		}

		$ci =& get_instance();
		$ci->load->model('attachmentModel');

		$result['attachments'] = array();
		foreach ($data['attachments'] as $key=>$val)
		{
			$result['attachments'][] = $ci->attachmentModel->update_db($val, $result['id']);
		}
	}

	private function _determine_contact_location_code(&$data)
	{
		// if ($data['tType']==SALRETAIL || (empty($data['conId']) && empty($data['conStateId'])))
		if (empty($data['conId']) && empty($data['conStateId']))
		{
			$data['conStateId'] = get_company('stateId');
		}

		if (empty($data['conId']) && empty($data['conStateCode']))
		{
			$data['conStateCode'] 	= get_company('stateCode');
		}

		if(empty($data['conId']) && (!empty($data['billingAddr']) && $data['billingAddr']['countryCode']!=get_company('countryCode')))
		{
			$data['conStateCode'] 	= $data['conStateId'] = '';
		}

		$data['conLocCode'] = "I";

		if (!empty($data['conStateId']))
		{
			$data['conLocCode'] = get_company('stateId') == $data['conStateId'] ? 'I' : 'X';

			if (!empty($data['dstStateId']))
			{
				$data['conLocCode'] = $data['dstStateId'] == $data['conStateId'] ? 'I' : 'X';
			}
		}

		if (!empty($data['billingAddr']) && !empty($data['billingAddr']['countryCode']) && $data['billingAddr']['countryCode'] != get_company('countryCode'))
		{
			$data['conLocCode'] = 'X';
		}

		if ($data['conLocCode'] == 'X' && !empty($data["isSales"]) && !empty(get_company('config')['isCompositionScheme']))
		{
			throw new Exception(lang('COMPOSITE_EXTERNAL_RESTRICTED'));
			return false;
		}
	}

	private function _validate_transaction_type_change(&$data, $tmp_trans)
	{
		$this->params->is_convert = false;

		if (!empty($data['id']))
		{
			if (
				array_diff(array(SALINV,SALRETAIL), array($tmp_trans['tType'], $data['tType'])) &&
				array_diff(array(PURINV,PURRETAIL), array($tmp_trans['tType'], $data['tType']))
			)
			{
				//tType change not between si & sr overwite with existing status
				$data['tType'] = $tmp_trans['tType'];
				$data['status'] = $data["isPmt"] ? $data['status'] : $tmp_trans['status'];
				$this->params->is_convert = false;
			}
			else
			{
				//tType change between si & sr
				$this->params->is_convert = true;
				$this->db->select('t.tNo, t.tType', false)->from('trans_payment AS tp');
				$this->db->join('trans as t','t.id=tp.transId');
				$this->db->where(array('tp.cId' => $this->params->cId, 'tp.allotTransId' => $data['id']));
				$tmp = $this->CI->db->get()->row_array();

				if (!empty($tmp['tNo']))
				{
					$ci =& get_instance();
					$modules = $ci->config->item("modules");
					throw new Exception(sprintf(lang('TRANSACTION_LINKED_WITH_OTHERS_CONVERT'),$modules[$data['tType']]['value'],$data['tNo'],$modules[$tmp['tType']]['value'],$tmp['tNo']));
					return false;
				}
			}
		}
	}

	public function prepare_expense_account_details(&$data)
	{
		if (empty($data['details']))
		{
			return;
		}

		TransFlag::$exp_acc_details[] = array(
			'accCode'	=> !empty($data['paymentAccCode']) ? $data['paymentAccCode'] : '',
			'debit'		=> 0,
			'credit'	=> $data['totAmount'],
			'desc'		=> '',
			'conId'		=> '',
			'isValid'	=> '',
			'accId'		=> !empty($data['paymentAccId']) ? $data['paymentAccId'] : '',
			'groupCode'	=> '',
			'taxTypeCode'	=> '',
			'accName'	=> ''
		);

		// check its account based non gst expenses 
		if (!in_array($data['tType'], array(EXPRETAIL,SALPMT,PURPMT)) || $data['classCode'] != 'XPNG')
		{
			return;
		}

		TransFlag::$items_insert = false;

		$payModes 	= !empty($data['payModes'])?$data['payModes']:array();

		if ($payModes)
		{
			// multi account deposite payment changes
			$tmp = TransFlag::$exp_acc_details[0];

			TransFlag::$exp_acc_details = array();

			foreach ($payModes as $pay_mode)
			{
				if (!empty($pay_mode))
				{
					$tmp['accId'] 		= $pay_mode['pAccId'];
					$tmp['accCode'] 	= !empty($pay_mode['pAccCode'])?$pay_mode['pAccCode']:'';
					$tmp['credit']		= $pay_mode['pAmount'];
					$tmp['conId']		= !empty($data['conId'])?$data['conId']:0;
					TransFlag::$exp_acc_details[] 	= $tmp;
				}
			}
		}
		else
		{
			TransFlag::$exp_acc_details[0]['conId'] = !empty($data['conId'])?$data['conId'] : TransFlag::$exp_acc_details[0]['conId'];//non gst expenses with contact id
		}

		if (!empty($data['tdsAmount']))
		{
			// expenses tdsamount
			TransFlag::$exp_acc_details[0]['credit'] -= $data['tdsAmount'];
			TransFlag::$exp_acc_details[] = array(
				'accCode' => 'TDSPAY',
				'debit' => 0,
				'credit' => $data['tdsAmount'],
				'desc' => '',
				'conId' => TransFlag::$exp_acc_details[0]['conId'],
				'isValid' => '',
				'accId' => $this->CI->trans_acc_table->getAccountId('TDSPAY', TransFlag::$default_acc),
				'groupCode' => '',
				'taxTypeCode' => '',
				'accName' => ''					
			);
		}

		$cnt_items = count($data['details']);

		for ($i=0;$i<$cnt_items;$i++)
		{
			$tmp_item = $data['details'][$i];

			if (empty($tmp_item['accId']) && empty($tmp_item['conId']))
			{
				$hasError = true;
				throw new Exception(lang('ACCOUNT_REQUIRED'));
				break;
			}

			if (!empty($tmp_item['itemId']))
			{
				TransFlag::$items_insert = true;
				break;
			}

			if (empty($tmp_item['taxableAmount']))
			{
				$tmp_item['taxableAmount'] = $tmp_item['itemTotal'];
			}

			TransFlag::$exp_acc_details[] = array(
				'accCode'	=> !empty($tmp_item['accCode']) ? $tmp_item['accCode'] : '',
				'debit'		=> $tmp_item['taxableAmount'],
				'credit'	=> 0,
				'desc'		=> empty($tmp_item['desc']) ? (empty($tmp_item['itemDesc']) ? "" : $tmp_item['itemDesc']) : $tmp_item['desc'],
				'conId'		=> !empty($tmp_item['conId'])?$tmp_item['conId']:0,
				'isValid'	=> '',
				'accId'		=> $tmp_item['accId'],							
				'groupCode'	=> !empty($tmp_item['groupCode'])?$tmp_item['groupCode']:'',
				'taxTypeCode'	=> '',
				'accName'	=> $tmp_item['itemName'],
				'prjconId'	=> (!empty($tmp_item['prjconId'])) ? $tmp_item['prjconId'] : 0,
				'tsType'	=> (!empty($data['tstype'])) ? $data['tstype'] : 't',
			);
		}
	}

	public function determine_category_code(&$data)
	{
		// categoryCode = B2B where (conTaxNo != "" & tType=S*)
		// categoryCode = BCS where (conTaxNo = "" & vartotTaxable < 2.5L  & tType=S*)
		// categoryCode = BCL where (conTaxNo = "" & vartotTaxable >= 2.5L  & tType=S*)
		if ($data['tType'] == EST ||
			$data['tType'] == SALINV ||
			$data['tType'] == SALRETAIL ||
			$data['tType'] == ADVRPT ||
			$data['tType'] == CRNOTE ||
			$data['tType'] == DBNOTE ||
			($data['tType'] == JOURNAL_VOUCHER && !empty($sundry['SD']))
		)
		{
			if ( !empty($data['classCode']) && ($data['classCode'] == 'EXPWP' || $data['classCode'] == 'EXPWOP'))
			{
				$data['categoryCode'] = 'EXP';
			}
			else if (!empty($data['conTaxNo']) && ((!empty($data['conStateCode']) && $data['conStateCode'] != 'OT') || $data['totAmount'] > 250000 ))
			{
				$data['categoryCode'] = 'B2B';
			}
			else
			{
				$data['categoryCode'] = ($data['conLocCode']=='X' && $data['totAmount'] > 250000) ? 'B2CL' : 'B2CS';
			}
			$data['categorySubCode'] = !empty($data['rcm']) ? 'RCM' : '';
		}

		// categoryCode = COM where (conTaxNo != "" & tType=P* & isReverse=true)
		// categoryCode = URD where (conTaxNo = "" & tType=P*)
		if (
			$data['tType'] == PURORD ||
			$data['tType'] == PURINV ||
			$data['tType'] == PURRETAIL ||
			$data['tType'] == EXPRETAIL ||
			$data['tType'] == PURADVPMT ||
			$data['tType'] == PURCRNOTE ||
			$data['tType'] == PURDBNOTE ||
			($data['tType'] == JOURNAL_VOUCHER && !empty($sundry['SC']))
		)
		{
			if (!empty($data['conTaxScheme']) && $data['conTaxScheme']=='C')
			{
				$data['categoryCode'] = 'COM';
			}
			else if (!empty($data['conTaxNo']))
			{
				$data['categoryCode'] = 'RD';
			}
			else if (!empty($data['rcm']))
			{
				$data['categoryCode'] = 'RCM';
			}
			else if(!empty($data['classCode']) && $data['classCode']=='IMP')
			{
				$data['categoryCode'] = 'IMP';
			}
			else
			{
				$data['categoryCode'] = 'URD';
			}
			$data['categorySubCode'] = !empty($data['categorySubCode'])? $data['categorySubCode'] : '';				
		}

		if (in_array($data['tType'],array(PURORD,DELCHALLAN,PURRECP)) && !$this->params->isFranchise && !$this->params->isMaterial)
		{
			//CategoryCode
			// TL - If same gstin branch/warehouse transfer
			// TS - If transfer to SalesRep
			// ignore for else
			$data['categoryCode'] = '';

			$pan = !empty(get_company('config')['pan']) ? get_company('config')['pan'] : '';

			if (!empty($data['conId']))
			{
				$this->CI->db->select('csType,taxNo,perTax');
				$this->CI->db->from('contacts');
				$this->CI->db->where(array(
					'cId'	=> $this->params->cId,
					'id'	=> $data['conId']
				));
				$this->CI->db->where_in('csType', array('B','L','R'));
				$br_tmp = $this->CI->db->get()->row_array();

				if ($br_tmp)
				{
					$data['categoryCode'] = 'TL';
					$data['categoryCode'] = $br_tmp['csType'] == 'R' ? 'TS' : $data['categoryCode'];
				}
			}
			else if (!empty($data['perTax']) && $data['perTax'] == $pan)
			{
				$data['categoryCode'] = sal_mod_active(BRANCH_MOD) ? 'TL' : $data['categoryCode'];
			}
		}
	}

	public function update_trans_billable(&$data)
	{
		// While saving expense, if all transItems have same billable for contact, save only transId & update transItemId as 0.
		if (empty($data['prjconId']))
		{
			return;
		}

		$transId = $data["transId"];

		$this->CI->db->where(array(
			'cId'		=> $this->params->cId,
			'transId'	=> $transId,
			'transItemId'	=> 0
		));
		$this->CI->db->delete('trans_billable');

		$this->CI->db->insert('trans_billable', array(
			'cId' => $this->params->cId,
			'transItemId' => 0,
			'prjconId' => $data['prjconId'],
			'invTransId' => 0,
			'transId' => $transId,
			'tsType' => empty($data['tsType']) ? 't' : $data['tsType']
		));
	}

	public function create_online_payment_token(&$data)
	{
		$mod_codes = explode(",", get_company("salModCodes"));

		if (in_array("EC", $mod_codes) && in_array($data['tType'], array(SALINV, EST, DBNOTE, SALORD)))
		{
			$obptoken = $this->enc($data["transId"] . "." . $this->params->cId);

			$this->CI->db->insert('trans_fields', array(
				"transId" => $data["transId"],
				"fId" => 0,
				"fName" => "obptoken",
				"fValue" => $obptoken
			));
		}
	}

	private function enc($str, $token=null)
	{
		if ($token)
		{
			$public_salt = substr($token, 0, 8);
			$str = $str . "." . $public_salt;
			if ($token == ($public_salt . substr(hash("sha256", $str), 8)))
			{
				return true;
			}

			return false;
		}

		$public_salt = substr(str_shuffle("0123456789abcdef"), 0, 8);
		$str = $str . "." . $public_salt;
		$token = $public_salt . substr(hash("sha256", $str), 8);

		return $token;
	}

	public function verify_transaction_state_codes(&$data)
	{
		if (!empty($data['isExport']))
		{
			return;
		}

		/*check state and statecode match*/
		$this->CI->db->select('t.id, if(t.conStateCode != s.stateCode,1,0) as errStcode, if(t.conTaxNo && substring(t.conTaxNo,1,2)!=s.tinCode,1,0) as errTin',false)->from('trans as t');
		$this->CI->db->select('min(if(ta.stateId=t.conStateId, 0, 1)) AS stDiff',false);
		$this->CI->db->join('trans_addr as ta','ta.transId=t.id');
		$this->CI->db->join('states as s','t.conStateId=s.id');
		$this->CI->db->where(array(
			't.id' => $data["transId"],
			't.cId' => $this->params->cId,
			's.countryCode' => 'IN'
		));
		$this->CI->db->where_not_in('t.tType', array('SP','PP'));

		$row = $this->CI->db->get()->row_array();

		if (!empty($row['errStcode']))
		{
			throw new Exception(lang('STATE_ID_CODE_MISMATCH'));
			return false;
		}

		if (!empty($row['stDiff']))
		{
			throw new Exception(lang('POS_STATE_MISMATCH'));
			return false;
		}
	}
}
