Организация системы отслеживания и отключения КТВ должников на BGBS с использованием CRM плагина

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

(Различия между версиями)
Перейти к: навигация, поиск
Строка 317: Строка 317:
Предполагаем, что событие таймера обработано в результате чего получено множество задач типа '''Обзвон должника'''.
Предполагаем, что событие таймера обработано в результате чего получено множество задач типа '''Обзвон должника'''.
 +
 +
{|
 +
|- valign=top
 +
| [[Изображение:ktv_debt_debt_call_task.png|thumb|300px|Задачи обзвона должника]]
 +
|}
 +
 +
Возможен непосредственный обзвон должников либо генерация квитанций и разнос их по квартирам. Как показывает практика, второй метод более эффективен. Для генерации квитанций подобного  вида:
 +
 +
{|
 +
|- valign=top
 +
| [[Изображение:ktv_debt_debt_kvit.png|thumb|300px|Квитанции]]
 +
|}
 +
 +
вы можете использовать шаблон отчета по задачам '''Квитанции'''. Сам XSL шаблон вложен в файл в конце данной статьи, для получения отчета необходимо выбрать тип шаблона над левым верхним углом таблицы задач, далее сохранить его в HTML файл и печатать. Непосредственная печать из биллинга невозможна, т.к. встроенный HTML компанент JAVA плохо поддерживает CSS, который используется для разделения страниц.
 +
 +
Шаблоны квитанций прописываются в конфигурации сервера биллинга следующим образом:
 +
<pre>
 +
register.task.report.format=register_tasks.xsl:Отчет по подключению;register_tasks_1.xsl:Отчет по обслуживанию;register_tasks_2.xsl:Отчет по должникам;register_tasks_3.xsl:Квитанции
 +
</pre>
 +
Файлы шаблонов вы можете загрузить здесь: [[Медиа:ktv_debt_xsl.zip]]

Версия 06:35, 25 сентября 2008

Ставится задача автоматического массового выявления, оповещения и отключения должников по КТВ.

В планировщике заданий добавляем генерацию события таймера. Событие генерируется каждые сутки в 0 часов 1 минуту.

Настройка задачи в планировщике

Общий алгоритм работы следующий:

  1. Раз в месяц по таймеру запускается задача выявления должников. В параметрах договоров-должников проставляется дата фиксации долга и создается задача на обзвон должников.
  2. Далее производится обзвон (либо разнос квитанций), задачи помечаются выполненными и обрабатываются.
  3. Следюущая обработка события таймера зафиксировав выполненную задачу обзвона и констатировав, что долг еще есть - создает задачу на отключение должника.
  4. При обработке задачи отключения должника автоматически закрывается абонентская плата, устанавливается группа договора долг.

Типы задач:

  1. Отключение должника (код 3 в данном примере)
  2. Обзвон должника (код 23 в данном примере)
  3. Подключение должника (код 24 в данном примере)

Необходимые параметры договора:

  1. Адрес, тип Адрес (код 9) в данном примере
  2. Дата фиксации долга, тип Дата (код 34 в данном примере)

Необходимые группы договоров:

  1. Должник - пометка договора, октлюченного за долг (код группы 15 в данном примере)

В справочнике групп решения CRM могут быть определены одна или несколько групп, группа может определяться в зависимости от номера квартала. В данном примере есть две группы решения с кодами 1 и 2.

В меню Автоматизация=>Скрипты поведения добавляем обработчик данного события.

import java.sql.*;
import java.util.*;
 
import bitel.billing.server.contract.bean.*;
import bitel.billing.server.script.bean.event.*;
import bitel.billing.server.util.*;
import ru.bitel.bgbilling.plugins.crm.server.dao.*;
import ru.bitel.bgbilling.plugins.crm.common.model.*;
 
// В зависимости от квартала клиента 
//   возможна передача различных групп решения проблемы.
int getTaskGroup() {
    quarter = "";
 
    query = 
        "SELECT quarter.title FROM contract_parameter_type_2 AS cp  " +
        " LEFT JOIN address_house AS house ON cp.hid=house.id  " +
        " LEFT JOIN  address_quarter AS quarter ON house.quarterid=quarter.id " +
        " WHERE cp.cid=? AND cp.pid=?";
    ps = con.prepareStatement( query );
 
    ps.setInt( 1, cid );
    ps.setInt( 2, TASK_ADDRESS_PARAM );
 
    rs = ps.executeQuery();
    if( rs.first() ) {
        quarter = rs.getString( 1 );
    }
 
    result = 0;
 
    if( guarter.equals( "1" ) )
    {
       result = GROUP_1;   
    }
    else
    {
       result = GROUP_2;
    }
 
    return result;
}
 
// коды групп решения задач
GROUP_1 = 1;
GROUP_2 = 2;
 
// код типов задач на отключения и обзвон
DISCONNECT_TASK = 3;
CALL_TASK = 23;
 
// код параметра договора "Дата фиксации долга"   
DEBT_FIX_PARAM = 34; 
 
TASK_ADDRESS_PARAM = 9;
// сколько дней ждать после обзвона   
DAYS_AFTER_CALL = 3;
// группа "Должник"
GROUP_DOLG = 15;
// группы "ЕРКЦ", "Условно расторгнут", "VIP" - договора с такими группами не наблюдаются
GROUP_ERKC = 43;
GROUP_USL_RAST = 21;
GROUP_VIP = 12;
 
// размер ежемесячного платежа (абонплаты) обычного
MONTHLY_CHARGE = 130;
// размер ежемесячного платежа (абонплаты) уменьшенного
MONTHLY_CHARGE_CHEAP = 105;
// дата подлкючения к ЕРКЦ
ERKC_ACTIVE_DATE_PARAM = 52;
 
cid = event.getContractID();
time = event.getGenerateTime();
 
print( "cid=" + cid );
 
// проверка флага события таймера, обрабатываем только события с флагом = 1
if( event.getFlag() != 1 ) {
    print( "Flag != 1, skipping.." );
    return;
}
 
bu = new BalanceUtils( con );   
rtm = new RegisterTaskManager( con );
cm = new ContractManager( con );
cpu = new ContractParamUtils( con );
tm = new ContractTariffManager( con );
 
contract = cm.getContractByID( cid );
 
// Проверки ---------------------------------------------------------------------------------------------------------------
 
// Договора ЕРКЦ не контролируются на долги, ЕРКЦ занимается этим сам
erkcGroup = ( contract.getGroups() & (1L<<GROUP_ERKC) ) > 0;
 
if( erkcGroup ) {
    print( "erkc group" );
    erkcActiveDate = cpu.getDateParam( cid, ERKC_ACTIVE_DATE_PARAM );
    if ( erkcActiveDate == null ) {
        error( "ERKC Activation Date is not set" );
        return;
    }
    else {
        if ( time.before( erkcActiveDate ) ) {
            print( "ERKC is not yet activated. Activation date: " + erkcActiveDate.get( Calendar.DAY_OF_MONTH ) + "." + ( erkcActiveDate.get( Calendar.MONTH ) + 1 ) + "." + erkcActiveDate.get( Calendar.YEAR ) );
        }
        else {
            print( "ERKC activated. No debts controlled" );
            //return;
        }
    }
}
 
// Условно расторгнутые
uslRastorg = ( contract.getGroups() & (1L<<GROUP_USL_RAST) ) > 0;
if( uslRastorg ) {
    print( "usl rastorg group" );
    return;
}
 
// Должники
dolgGroup = ( contract.getGroups() & (1L<<GROUP_DOLG) ) > 0;
if( dolgGroup ) {
    print( "dolg group" );
    return;
}
 
// VIP-клиенты
vipGroup = ( contract.getGroups() & (1L<<GROUP_VIP) ) > 0;
if( vipGroup ) {
    print( "vip group" );
    return;
}
 
// Кредитные договора
if( contract.getBalanceMode() == Contract.CREDIT_BALANCE_MODE ) {
    print( "credit mode" );
    return;
}
 
// Конец проверок ---------------------------------------------------------------------------------------------------------
 
debt = false;
float balance = bu.getBalance( time, cid );
 
// Выбираем лимит под тариф
tp = tm.getContractTariff( cid, time );
if( tp == null ) {
    error("no active tariff plans" );
    return;
}
tpid = tp.getTariffPlanID();
 
switch( tpid ) {
    case 10:
      monthlyCharge = MONTHLY_CHARGE;
      break;
    case 12:
      monthlyCharge = MONTHLY_CHARGE_CHEAP;
      break;
    case 14:
      monthlyCharge = MONTHLY_CHARGE_CHEAP;
      break;
    default:
      monthlyCharge = MONTHLY_CHARGE;
      break;
}
 
// до 20-го числа за должников считаем тех, у кого баланс меньше 3-х абонок,
// после 20-го -- меньше 2-х абонок
if( time.get( Calendar.DATE ) < 20 ) {
    print("do 20");
    if( balance < - 3 * monthlyCharge + .01 ) {
        debt = true;
    }
}
else {
    print("posle 20");
    if( balance < - 2 * monthlyCharge + .01 ) {
        debt = true;
    }
}
 
if( erkcGroup ) {
	if( balance < - 6 * monthlyCharge + .01 ) {
		debt = true;
		print( "Debug ERKC < 6 month debt" );
	}
}
 
if( !debt ) {   
    return;
}
 
 
fixDate = cpu.getDateParam( cid, DEBT_FIX_PARAM );
if( fixDate == null ) {
   fixDate = (Calendar)time.clone();
   cpu.setDateParam( cid, DEBT_FIX_PARAM, fixDate );
}
 
print(  "fixDate=" +  TimeUtils.formatDate( fixDate ) );
 
callTask = null;
disconnectTask = null;
 
// список задач после даты фиксции долга
taskList = rtm.getAfterDateTaskList( cid, fixDate );
for( RegisterTask task : taskList )
{
   // найдена задача на обзвон 
   if( task.getTypeID() == CALL_TASK )
   {
     callTask = task;
   }
   // найдена задача на отключение 
   if( task.getTypeID() == DISCONNECT_TASK )
   {
     disconnectTask = task;
   }
}
 
print( "callTask = " + callTask + "; disconnectTask = " + disconnectTask );
 
// долг есть а задачи на обзвон нет - создание задачи на обзвон
if( callTask == null )
{
   callTask = new RegisterTask();    
 
   callTask.setContractID( cid );
   callTask.setAddressParamID( TASK_ADDRESS_PARAM );
 
   callTask.setOpenTime( time );
   callTask.setOpenUserID( 0 );
 
   callTask.setTypeID( CALL_TASK );
   callTask.setComment( "Долг: " + Utils.formatCost( balance ) ) ;
 
   groupId = getTaskGroup();
   callTask.setGroupID( groupId );
 
   print( "creating call task" )  ;
   rtm.updateTask( "new", callTask );
}
// статус задачи открыт - обновляем информацию о долге в комментарии задачи
else if ( callTask.getStatus() == RegisterTask.STATUS_OPEN  ) {
    callTask.setComment( "Долг: " + Utils.formatCost( balance ) ) ;
    rtm.updateTask(  String.valueOf( callTask.getID() ), callTask ); 
}
// задача дозвона выполнена
else if ( callTask.getStatus() == RegisterTask.STATUS_CLOSED ) {
    // задачи на отключения нет
	if( erkcGroup ) {
		print( "For ERKC orders don't need taks for turn off" ) ;
		return;
	}
    if ( disconnectTask == null ) {
        // после обзвона прошел срок - создание задачи отключения
        if ( TimeUtils.daysDelta( callTask.getExecuteDate(), time ) >= DAYS_AFTER_CALL ) {
           disconnectTask = new RegisterTask();
 
           disconnectTask.setContractID( cid );
           disconnectTask.setAddressParamID( TASK_ADDRESS_PARAM );
 
           disconnectTask.setOpenTime( time );
           disconnectTask.setOpenUserID( 0 );
 
           disconnectTask.setTypeID( DISCONNECT_TASK );
 
           groupId = getTaskGroup();
           disconnectTask.setGroupID( groupId ); 
           disconnectTask.setComment( "Долг: " + Utils.formatCost( balance ) ) ;
 
           print( "creating disconnect task." );
           rtm.updateTask( "new", disconnectTask );
        }
    }
    // открыта задача на отключение - обновление информации о долге в комментарии задачи
    else if ( disconnectTask.getStatus() == RegisterTask.STATUS_OPEN ) {
        disconnectTask.setComment( "Долг: " + Utils.formatCost( balance ) ) ;
        rtm.updateTask(  String.valueOf( disconnectTask.getID() ), disconnectTask );
    }
}

Предполагаем, что событие таймера обработано в результате чего получено множество задач типа Обзвон должника.

Задачи обзвона должника

Возможен непосредственный обзвон должников либо генерация квитанций и разнос их по квартирам. Как показывает практика, второй метод более эффективен. Для генерации квитанций подобного вида:

Квитанции

вы можете использовать шаблон отчета по задачам Квитанции. Сам XSL шаблон вложен в файл в конце данной статьи, для получения отчета необходимо выбрать тип шаблона над левым верхним углом таблицы задач, далее сохранить его в HTML файл и печатать. Непосредственная печать из биллинга невозможна, т.к. встроенный HTML компанент JAVA плохо поддерживает CSS, который используется для разделения страниц.

Шаблоны квитанций прописываются в конфигурации сервера биллинга следующим образом:

register.task.report.format=register_tasks.xsl:Отчет по подключению;register_tasks_1.xsl:Отчет по обслуживанию;register_tasks_2.xsl:Отчет по должникам;register_tasks_3.xsl:Квитанции

Файлы шаблонов вы можете загрузить здесь: Медиа:ktv_debt_xsl.zip

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