Продажа пакетов минут на направления

Материал из BiTel WiKi

Перейти к: навигация, поиск

Решение функционирует на биллинге версии 6.0 и новее.

С помощью тарифных опций пользователь активирует пакеты с секундами на все направления вызовов. При необходимости решение может быть доработано с тем, чтобы секунды выдавались по конкретным префиксам/направлениями. Секунды с пакетов списываются в первую очередь, далее идёт тарификация по тарифу с уменьшением баланса. При параллельной активации нового пакета до истечения срока действия существующего - секунды из существующего пакета переносятся в новый. Действует всегда один последний пакет.

1. В справочнике заводятся необходимые тарифные опции, указываются режимы и стоимости их активации.

Тарифные опции

2. В конфигурации сервера установить флаг:

tariffOption.dontCheckOnActivateAlreadyActivated=0

Это позволит активировать пакет до истечения аналогичного.

3. Создать скрипт поведения, определить в нём обработчики событий с помощью класса VoipBundleProcessor.

События скриптов

4. Секунды пакета будут учитываться в отдельном поле таблицы contract_tariff_option, добавленить его в базу:

ALTER TABLE contract_tariff_option ADD _bundle_sum INT NOT NULL

5. Класс VoipBundleProcessor в динамическом коде.

package ru.bitel.bgbilling.modules.inet.dyn;
 
import ru.bitel.bgbilling.kernel.event.Event;
import ru.bitel.bgbilling.kernel.script.server.dev.EventScriptBase;
import ru.bitel.bgbilling.server.util.Setup;
import ru.bitel.common.sql.ConnectionSet;
import ru.bitel.bgbilling.modules.voiceip.radius.event.*;
import bitel.billing.server.processor.voiceip.*;
import ru.bitel.bgbilling.kernel.tariff.option.server.bean.*;
import java.util.*;
import java.math.*;
import java.sql.*;
import ru.bitel.bgbilling.kernel.tariff.option.server.event.*;
import ru.bitel.bgbilling.kernel.tariff.option.common.bean.*;
import ru.bitel.bgbilling.kernel.network.radius.*;
import bitel.billing.server.radius.H323;
import ru.bitel.common.*;
 
public class VoipBundleProcessor
	extends EventScriptBase
{
	private static Map<Integer, int[]> BUNDLES = new HashMap<Integer, int[]>();
	static
	{
		// код бандла (опции) - код направления + количество секунд в нём, только одна запись на каждый бандл!
		// bundle-test - 100 секунд
		BUNDLES.put( 1, new int[]{ 946, 5 } );
		BUNDLES.put( 2, new int[]{ 946, 10 } );
	}
 
	@Override
	public void onEvent( Event event, Setup setup, ConnectionSet connectionSet )
		throws Exception
	{
		if( event instanceof TariffOptionActivatedEvent )
		{
			ContractTariffOption contractOption = ((TariffOptionActivatedEvent)event).getContractOption();
 
			int[] bundle = BUNDLES.get( contractOption.getOptionId() );
			if( bundle == null )
			{
				return;
			}
 
			Connection con = connectionSet.getConnection();
 
			int amount = bundle[1];
 
			// выборка последнего действующего бандла и добавление 			
			String query = 
				"SELECT option_id, _bundle_sum FROM contract_tariff_option " +
				"WHERE cid=? AND time_from<=NOW() AND NOW()<=time_to AND id!=? " +
				"ORDER BY id DESC";
			PreparedStatement ps = con.prepareStatement( query );
			ps.setInt( 1, contractOption.getContractId() );	
			ps.setInt( 2, contractOption.getId() );
 
			ResultSet rs = ps.executeQuery();
			while( rs.next() )
			{
				bundle = BUNDLES.get( rs.getInt( 1 ) );
				if( bundle != null )
				{
					amount += rs.getInt( 2 );
					break;
				}
			}
			ps.close();
 
			// запись суммы в опцию
			query = "UPDATE contract_tariff_option SET _bundle_sum=? WHERE id=?";
			ps = con.prepareStatement( query );
			ps.setInt( 1, amount );
			ps.setInt( 2, contractOption.getId() );
			ps.executeUpdate();
			ps.close();
		} 
		else if( event instanceof SessionCreatingEvent ) 
		{
			VoiceIpSessionRealtime session	= ((SessionCreatingEvent)event).getSession();	
			int contractId = session.getContract().getId();
 
			int roundSessionTime = session.getRoundSessionTime();		
			int destCode = session.getDestCode();
			BigDecimal sessionCost = session.getSessionCost();		
 
			Connection con = connectionSet.getConnection();
 
 
			String query = 
				"SELECT id, option_id, _bundle_sum FROM contract_tariff_option " +
				"WHERE cid=? AND time_from<=NOW() AND NOW()<=time_to " +
				"ORDER BY id DESC";
			PreparedStatement ps = con.prepareStatement( query );
			ps.setInt( 1, contractId );
 
			ResultSet rs = ps.executeQuery();
			while( rs.next() )
			{
				int recordId = rs.getInt( 1 );
				int optionId = rs.getInt( 2 );
				int bundleRest = rs.getInt( 3 );
 
				int[] bundle = BUNDLES.get( optionId );
				if( bundle != null /*&& bundle[0] == destCode*/ )
				{
					int canTake = Math.min( roundSessionTime, bundleRest );
 
					print( "canTake: " + canTake );
 
					if( canTake > 0 )
					{			
						query = "UPDATE contract_tariff_option SET _bundle_sum=_bundle_sum-? WHERE id=?";
 
						PreparedStatement psUpdate = con.prepareStatement( query );
						psUpdate.setInt( 1, canTake );
						psUpdate.setInt( 2, recordId );
						psUpdate.executeUpdate();
						psUpdate.close();
 
						session.setRoundSessionTime( roundSessionTime - canTake );
						session.setSessionCost( new BigDecimal( sessionCost.floatValue() * session.getRoundSessionTime() / roundSessionTime ).setScale( 5, BigDecimal.ROUND_HALF_UP ) );
					}
					break;
				}
			}
			ps.close();
		}
		else if( event instanceof RadiusAuthenticationEvent ) 
		{
			RadiusAuthenticationEvent raEvent = (RadiusAuthenticationEvent)event;
 
			int contractId = raEvent.getContractId();			
			RadiusPacket response = (RadiusPacket)raEvent.getResponse();
 
			String returnCode = response.getStringAttribute( 9, H323.H323_return_code, "" );
 
			if( response.getCode() ==  RadiusPacket.ACCESS_ACCEPT ||
				// баланс < лимита
				returnCode.equals( "4" ) ||
				// нет денег на звонок
				returnCode.equals( "15" ) )
			{
				Connection con = connectionSet.getConnection();
 
				String query = 
					"SELECT option_id, _bundle_sum FROM contract_tariff_option " +
					"WHERE cid=? AND time_from<=NOW() AND NOW()<=time_to " +
					"ORDER BY id DESC";
				PreparedStatement ps = con.prepareStatement( query );
				ps.setInt( 1, contractId );
 
				ResultSet rs = ps.executeQuery();
				while( rs.next() )
				{
					int optionId = rs.getInt( 1 );
					int bundleRest = rs.getInt( 2 );
 
					int[] bundle = BUNDLES.get( optionId );
					if( bundle != null )
					{
						print( "bundleRest: " + bundleRest );
 
						if( bundleRest > 0 )
						{
							response.setCode( RadiusPacket.ACCESS_ACCEPT );	
							response.setStringAttribute( 9, H323.H323_credit_time, String.valueOf( Utils.parseInt( response.getStringAttribute( 9, H323.H323_credit_time, "0" ) ) + bundleRest ) );
							response.setStringAttribute( 9, H323.H323_return_code, "0" );
						}
						break;
					}
				}
				ps.close();
			}
		}
	}
}

Обрабатывает следующие события:

  • при активации пакета вносит остаток секунд в _bundle_sum, в зависимости от активированной опции и наличия аналогичных активированных без закрытого периода;
  • перед сохранением VoiceIP сессии её _округлённая_ длительность и стоимость уменьшаются, также уменьшается остаток в _bundle_sum;
  • при авторизации проверяется наличие опции, и если причиной запрета была нехватка средств - запрет заменяется на ACCEPT, в h323_credit_time передаётся время разговора с учётом опции.

Соответствие количества секунд опциям хранится в классе в поле BUNDLES. Также там есть заготовка под хранение кода направления, но в далее она не используется.

Личные инструменты