Продажа пакетов минут на направления
Материал из 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(); } } } }
Обрабатывает следующие события:
- при активации пакета вносит остаток секунд в _budle_sum, в зависимости от активированной опции и наличия аналогичных активированных без закрытого периода;
- перед сохранением VoiceIP сессии её _округлённая_ длительность и стоимость уменьшаются, также уменьшается остаток в _bundle_sum;
- при авторизации проверяется наличие опции, и если причиной запрета была нехватка средств - запрет заменяется на ACCEPT, в h323_credit_time передаётся время разговора с учётом опции.
Соответствие количества секунд опциям хранится в классе в поле BUNDLES. Также там есть заготовка под хранение кода направления, но в далее она не используется.