Пересчеты и бонусы
Материал из BiTel WiKi
Версия от 07:44, 8 июня 2010; Cromeshnic (Обсуждение | вклад)
Содержание |
Задача
Необходимо реализовать систему, позволяющую осуществлять перерасчеты клиентам за прошлые месяцы на произвольную сумму без необходимости внесения изменений в тарифный план. Сумма пересчета должна быть отражена в детализации счета клиента. Перерасчет может быть `размазан` на несколько месяцев, если сумма перерасчета больше месячной наработки
Решение
- Сумму перерасчета будем задавать в виде расхода определенного типа. Для этого добавляем два новых типа расхода: "Перерасчет" (редактируемый) и "Перенос перерасчета" (нередактируемый)
- Заводим в модуле bill новую позицию счета:
bill.pos.<n>.title=Перерасчет за предыдущие месяцы bill.pos.<n>.name=Перерасчет за предыдущие месяцы bill.pos.<n>.summ=CHARGE($month,<ct1>,<ct2>)
Где n - номер позиции, ct1, ct2 - наши новые типы расходов
Схема использования
- В течение месяца менеджеры вносят расход "Перерасчет" на договора
- В следующем месяце после глобального пересчета всех услуг и прямо перед закрытием периода запускается глобальный скрипт (см ниже), выполняющий перенос остатка суммы перерасчета на следующий месяц для договоров, у которых наработка оказалась меньше суммы перерасчета (в т.ч. по всем субдоговорам). Также скрипт помещает все такие договоры в специальную группу, скажем "Перенос перерасчета". Для таких договоров сумма счета окажется нулевой, поэтому нужно обрабатывать их отдельно.
- Закрываем период и выставляем счета
- Ставим в настройке счета галку "Создавать при сумме счета <=0" и выставляем нулевые счета для группы "Перенос перерасчета"
- Убираем галку
- Очищаем группу у всех договоров в ней
- Самое важное происходит вне биллинга - отрицательная позиция счета должна отразиться в детализации клиента. Как это сделать организационно - вопрос индивидуальный и решается с бухгалтерией. Например, в виде "бонуса".
Функция в библиотеке скриптов 'bonus', производящая перенос перерасчета для конкретного договора
public String ProcessContractBonus(java.sql.Connection con, int cid, int uid, int CHARGE_TYPE, int CHARGE_CORR_TYPE, long gr){ bu = new BalanceUtils(con); cm = new ContractManager(con); c = cm.getContractByID(cid); //Пропускаем зависимые субдоговора if(c.isDependSub()){ return("Пропускаем зависимый субдоговор "+c.getTitle()+" ("+cid+")"); } out="id="+cid+"\ttitle="+c.getTitle()+"\t\t"; //берем текущий месяц и год Calendar calendarLastMonth = new GregorianCalendar(); calendarLastMonth.add(Calendar.DAY_OF_YEAR, -calendarLastMonth.get(Calendar.DAY_OF_MONTH)); //устанавливаем calendarLastMonth в последнее число предыдущего месяца calendarLastMonth.setTimeInMillis(Math.round(Math.floor(calendarLastMonth.getTimeInMillis()/86400000.0)*86400000)); //Отрезаем минуты и часы if(c.getDateTo()!=null){ if(!c.getDateTo().after(calendarLastMonth.getTime())){ return("Пропускаем закрытый договор "+c.getTitle()+" ("+cid+")"); } } //берем сумму бонуса за предыдущий месяц минус наработку по договору за предыдущий месяц float sum = 0.00; float acc = 0.00; //очищаем предыдущие переносы бонуса с прошлого месяца на текущий для этого договора и его зависимых субдоговоров ps = con.prepareStatement("delete from contract_charge where ((month(dt)=? and year(dt)=? and summa>0) or (month(dt)=? and year(dt)=? and summa<0)) and pt=? and (cid in (select group_concat(id) from contract where scid=? and sub_mode=0) or cid=?);"); ps.setInt(1, calendarLastMonth.get(Calendar.MONTH)+1); ps.setInt(2, calendarLastMonth.get(Calendar.YEAR)); ps.setInt(3, (new GregorianCalendar()).get(Calendar.MONTH)+1); ps.setInt(4, (new GregorianCalendar()).get(Calendar.YEAR)); ps.setInt(5, CHARGE_CORR_TYPE); ps.setInt(6, cid); ps.setInt(7, cid); rs = ps.executeUpdate(); //Общий бонус за прошлый месяц ps = con.prepareStatement("select sum(ch.summa) from contract_charge ch join contract c on ch.cid=c.id where month(ch.dt)=? and year(ch.dt)=? and (ch.cid=? or (c.scid=? and c.sub_mode=0)) and (ch.pt=? or ch.pt=?)"); ps.setInt(1, calendarLastMonth.get(Calendar.MONTH)+1); ps.setInt(2, calendarLastMonth.get(Calendar.YEAR)); ps.setInt(3, cid); ps.setInt(4, cid); ps.setInt(5, CHARGE_TYPE); ps.setInt(6, CHARGE_CORR_TYPE); rs = ps.executeQuery(); if(rs.next()) { sum = rs.getFloat(1); } ps.close(); out+="бонус="+sum+"\t"; //наработка ps = con.prepareStatement("select cb.summa3 from contract_balance cb where cb.mm=? and cb.yy=? and cb.cid=?"); ps.setInt(1, calendarLastMonth.get(Calendar.MONTH)+1); ps.setInt(2, calendarLastMonth.get(Calendar.YEAR)); ps.setInt(3, cid); rs = ps.executeQuery(); if(rs.next()) { acc = rs.getFloat(1); } ps.close(); out+="наработка="+acc+"\t"; //Если сумма пересчета не меньше наработки, то счет будет нулевой - нужно выставить группу gr if(-sum-acc>=0){ ps = con.prepareStatement("update contract set gr=gr|pow(2,?) where id=?"); ps.setLong(1,gr); ps.setInt(2,cid); ps.executeUpdate(); }else{ ps = con.prepareStatement("update contract set gr=gr&~pow(2,?) where id=?"); ps.setLong(1,gr); ps.setInt(2,cid); ps.executeUpdate(); } //если сумма больше наработки по договору, if(-sum-acc>0){ out+="переносим="+(-sum-acc); chargeManager = new ChargeManager(con); //...то добавляем положительный расход суммой разности в прошлом месяце charge = new Charge(); charge.setChargeDate(calendarLastMonth.getTime()); charge.setChargeTypeID(CHARGE_CORR_TYPE); charge.setComment("переносим остаток бонуса на следующий месяц"); charge.setContractID(cid); charge.setID(-1); charge.setSumma(new BigDecimal(-sum-acc)); charge.setUserID(0); chargeManager.updateCharge(charge); EventProcessor.getProcessor().addEvent(new ChargeEvent(charge)); //Обновляем баланс в предыдущем месяце bu.updateBalance(calendarLastMonth.getTime(), cid); //...и такой же отрицательный - в будущем charge = new Charge(); charge.setChargeDate((new GregorianCalendar()).getTime()); charge.setChargeTypeID(CHARGE_CORR_TYPE); charge.setComment("Остаток бонуса с предыдущего месяца"); charge.setContractID(cid); charge.setID(-1); charge.setSumma(new BigDecimal(sum+acc)); charge.setUserID(0); chargeManager.updateCharge(charge); EventProcessor.getProcessor().addEvent(new ChargeEvent(charge)); //Обновляем баланс в текущем месяце bu.updateBalance(new Date(), cid); } return out; }
Пользовательская функция mysql superid:
CREATE FUNCTION `superid`(cid int(10) UNSIGNED) RETURNS int(10) UNSIGNED READS SQL DATA BEGIN declare r_cid int(10) UNSIGNED; SELECT IF(scid>0,scid,id) INTO r_cid FROM contract WHERE id=cid; RETURN r_cid; END
Глобальный скрипт переноса перерасчета для всех договоров
import bitel.billing.server.util.*; import bitel.billing.server.contract.bean.*; import java.math.*; import bitel.billing.server.util.*; import bitel.billing.server.script.bean.event.*; public void main( setup, con, conSlave ) { email = "programmer@someprovider.ru"; CHARGE_TYPE = 12; //тип расхода для начисления бонуса CHARGE_CORR_TYPE = 13; //тип расхода для переноса бонуса long SPEC_GROUP = 55; //Группа, в которую включаются все договора с пересчетом за последний месяц, не меньшим чем наработка includeBGBS( "bgbs://ru.bitel.bgbilling.kernel.script.common.bean.ScriptLibrary/bonus" ); //Убираем у всех группу SPEC_GROUP ps = con.prepareStatement("update contract set gr=gr&~pow(2,?)"); ps.setLong(1,SPEC_GROUP); ps.executeUpdate(); ps = con.prepareStatement("select superid(cid) ccid from contract_charge where year(dt)=year(now()-interval 1 month) and month(dt)=month(now()-interval 1 month) and (pt=? or pt=?) group by ccid;"); ps.setInt(1, CHARGE_TYPE); ps.setInt(2, CHARGE_CORR_TYPE); rs = ps.executeQuery(); report=""; while(rs.next()){ cid = rs.getInt(1); m=ProcessContractBonus(con, cid, 0, CHARGE_TYPE, CHARGE_CORR_TYPE, SPEC_GROUP); print(m); report+=m+"\n"; } try{ MailMsg msg = new MailMsg(setup); msg.sendMessage(email, "Перенос бонусов для всех договоров", report); }catch(e){ print("cannot send email"); } }
Отчет по пересчетам (csv)
Отчет не универсальный, необходимо указать в sql-запросе нужные mid модуля bill и типы платежей
.java-файл
import java.sql.*; import java.util.*; import java.util.regex.*; import java.sql.*; import bitel.billing.server.util.*; import bitel.billing.server.contract.bean.*; import bitel.billing.common.*; public void fillReport( con, filter, bitel.billing.server.reports.BGCSVReport.ReportResult result, pageSize, pageIndex ) { query = "select "+ "c.id, "+ "c.title, "+ "c.comment, "+ "(select sum(summa) from contract_charge join contract cc on cid=cc.id where pt=13 and summa<0 and month(dt)=month(?) and year(dt)=year(?) and (cid=c.id or (cc.scid=c.id and cc.sub_mode=0))) prev, "+ "(select sum(summa) from contract_charge join contract cc on cid=cc.id where pt=12 and month(dt)=month(?) and year(dt)=year(?) and (cid=c.id or (cc.scid=c.id and cc.sub_mode=0))) curr, "+ "(select sum(summa) from contract_charge join contract cc on cid=cc.id where pt=13 and summa>0 and month(dt)=month(?) and year(dt)=year(?) and (cid=c.id or (cc.scid=c.id and cc.sub_mode=0))) next, "+ "(select summa3 from contract_balance cb where cb.yy=year(?) and cb.mm=month(?) and cid=c.id ) engine, "+ "(select sum(summ) from bill_data_6 where cid=c.id and yy=year(?) and mm=month(?)-1) bills "+ "from "+ "contract c "+ "where "+ "c.scid<=0 "+ "or c.sub_mode=1 "+ "having (prev!=0 or curr!=0 or next!=0);"; ps = con.prepareStatement( query ); java.sql.Date month = TimeUtils.convertCalendarToSqlDate(filter.getCalendarParam( "month" )); ps.setDate(1, month); ps.setDate(2, month); ps.setDate(3, month); ps.setDate(4, month); ps.setDate(5, month); ps.setDate(6, month); ps.setDate(7, month); ps.setDate(8, month); ps.setDate(9, month); ps.setDate(10, month); rs = ps.executeQuery(); data = new ArrayList(rs.getFetchSize()); while(rs.next()){ map = new HashMap(); float expected_bill_summ = (rs.getFloat(4)==null? 0 : rs.getFloat(4)) + (rs.getFloat(5)==null? 0 : rs.getFloat(5)) + (rs.getFloat(6)==null? 0 : rs.getFloat(6)) + (rs.getFloat(7)==null? 0 : rs.getFloat(7)); float expected_next = (rs.getFloat(4)==null? 0 : rs.getFloat(4)) + (rs.getFloat(5)==null? 0 : rs.getFloat(5)) + (rs.getFloat(7)==null? 0 : rs.getFloat(7)); String expected_bill_summ_s; if(expected_bill_summ>0){ expected_bill_summ_s=new java.text.DecimalFormat("0.##").format((double)(expected_bill_summ)).toString(); }else{ expected_bill_summ_s="0"; } String expected_next_s; if(expected_next<0){ expected_next_s=new java.text.DecimalFormat("0.##").format((double)(-expected_next)).toString(); }else{ expected_next_s=null; } map.put("cid",rs.getString(1)); map.put("title",rs.getString(2)); map.put("comment",rs.getString(3)); map.put("prev",rs.getString(4)); map.put("curr",rs.getString(5)); map.put("next",rs.getString(6)); map.put("engine",rs.getString(7)); map.put("bills",rs.getString(8)); // map.put("expected_bill_summ",rs.getFloat(4)+rs.getFloat(5)+rs.getFloat(6)+rs.getFloat(7)); map.put("expected_bill_summ", expected_bill_summ_s); map.put("expected_next", expected_next_s); data.add(map); } result.setData( data ); }
.rep.xml-файл
<?xml version="1.0" encoding="UTF-8"?> <report title="Пересчеты за месяц" type="java"> <month name="month" title="Месяц"/> <fields> <item id="cid" title="cid"/> <item id="title" title="Договор"/> <item id="comment" title="Комментарий"/> <item id="prev" title="Перенос с предыдущего месяца"/> <item id="curr" title="Пересчеты за месяц"/> <item id="next" title="Факт. перенос на следующий месяц"/> <item id="expected_next" title="Предполагаемый перенос на следующий месяц"/> <item id="engine" title="Наработка"/> <item id="expected_bill_summ" title="Ожидаемая сумма счетов"/> <item id="bills" title="Фактическая сумма счетов"/> </fields> </report>
--Cromeshnic 07:39, 8 июня 2010 (UTC)