WebAction CustomSuspend
Материал из BiTel WiKi
Версия от 04:38, 22 июля 2010; Cromeshnic (Обсуждение | вклад)
Ниже приведён код вебэкшена, представляющего собой модификацию стандартной логики приостановления - экшена WebAction_ContractStatus
Реализация очень специфична и для конкретного провайдера скорее всего потребует изменений.
Содержание |
Логика
Приостановление доступно:
- Физ.лицам
- Не более x дней в течение y дней
- Приостановить договор можно только с завтрашнего дня
- Активировать можно либо с сегодняшнего, либо с завтрашнего дня
- Возможно взимание расхода за каждое приостановление. Если приостановление отменяется полностью, расход удаляется.
- Баланс должен быть >0 с учетом расхода за приостановление.
Скрины
Требования
- BGBilling v 5.0 (В 5.1 webaction придётся переделывать!)
Настройки
В настройках сервера (Сервис -> настройка) добавляем строки:
#Параметры для WebAction_CustomSuspend #Максимальное количество дней приостановления в течение custom.suspend.suspened.check.period custom.suspend.max.suspened.in.period=60 #Период, в течение которого клиент не может быть приостановлен более, чем custom.suspend.max.suspened.in.period дней custom.suspend.suspened.check.period=90 #Тип расхода для приостановления через Web custom.suspend.charge.type=14 #Стоимость приостановления custom.suspend.charge.summa=20
Код java
/** * */ package bitel.billing.server.contract; import bitel.billing.common.BGException; import bitel.billing.server.ActionBase; import bitel.billing.common.TimeUtils; import bitel.billing.server.contract.bean.BalanceUtils; import bitel.billing.server.contract.bean.Charge; import bitel.billing.server.contract.bean.ChargeManager; import bitel.billing.server.contract.bean.Contract; import bitel.billing.server.contract.bean.ContractManager; import bitel.billing.server.contract.bean.ContractStatus; import bitel.billing.server.contract.bean.ContractStatusManager; import bitel.billing.server.script.bean.event.ChargeEvent; import bitel.billing.server.script.bean.event.EventProcessor; //import bitel.billing.server.script.bean.event.EventProcessor; //import bitel.billing.server.script.bean.event.GetContractStatusChangeDatesEvent; import java.io.IOException; import java.math.BigDecimal; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Calendar; import java.util.Date; //import java.util.List; import org.w3c.dom.Element; //import ru.bitel.bgbilling.common.BGIllegalArgumentException; /** * @author Semen Koshechkin * * web params: * * action=CustomSuspend * command= * Suspend ({to_date|(to_day,to_month,to_year)}) * Activate ([from=tomorrow]) */ public class WebAction_CustomSuspend extends ActionBase { private static final int CANCHANGE_DISABLED = 1; private static final int CANCHANGE_DISABLED_BADSTATUS = 2; private static final int CANCHANGE_DO = 3; private static final int CANCHANGE_CANCEL_ALLOW = 4; // private static final int CANCHANGE_CANCEL_DENY = 5; private static final int CANCHANGE_CANCEL_ALLOW_ONLY_TODAY = 6; private static final int CANCHANGE_DISABLED_LOW_ACCOUNT = 7; private int maxDays; //Максимальное количество дней приостановления в течение checkDays //Параметр глобального конфига: //custom.suspend.max.suspened.in.period private int checkDays; //Период, в течение которого клиент не может быть приостановлен более, чем maxDays дней //Параметр глобального конфига: //custom.suspend.suspened.check.period private int chargeType; //Берем расход за приостановление //Параметр глобального конфига: //custom.suspend.charge.type private float chargeSumm;//Сумма расхода за приостановление @Override public void doAction() throws SQLException, BGException { int cid= this.cid; if (cid <= 0) { setParamsError(); return; } this.maxDays = this.setup.getIntValue("custom.suspend.max.suspened.in.period", 60); this.checkDays = this.setup.getIntValue("custom.suspend.suspened.check.period", 90); this.chargeType = this.setup.getIntValue("custom.suspend.charge.type", -1); this.chargeSumm = this.setup.getFloatValue("custom.suspend.charge.summa", 0); //Выполняем действия if ("Suspend".equals(getParameter("command"))) { ActionCustomSuspendSuspend(cid); if(!rootNode.getAttribute("status").equals("error")){ try { this.response.sendRedirect("webexecuter?action=CustomSuspend"); } catch (IOException e) { e.printStackTrace(); } } } if ("Activate".equals(getParameter("command"))) { ActionCustomSuspendActivate(cid); if(!rootNode.getAttribute("status").equals("error")){ try { this.response.sendRedirect("webexecuter?action=CustomSuspend"); } catch (IOException e) { e.printStackTrace(); } } } //Строим список статусов ContractStatusManager statusManager = new ContractStatusManager(this.con); Element table = createElement(this.rootNode, "statuses"); //Проверяем наличие в будущем статусов, отличных от 0 и 4 boolean isManualFutureStatuses = false; Integer canchange = null; for (ContractStatus status : statusManager.getStatusList(cid)) { Element row = createElement(table, "status"); //row.setAttribute("period", TimeUtils.formatPeriod(status.getDate1(), status.getDate2())); row.setAttribute("date1", TimeUtils.format(status.getDate1(), "dd.MM.yyyy")); row.setAttribute("date2", TimeUtils.format(status.getDate2(), "dd.MM.yyyy")); row.setAttribute("status", ContractStatus.statusToString(status.getStatus())); row.setAttribute("statusint", String.valueOf(status.getStatus())); //row.setAttribute("comment", status.getComment()); if(status.getDate2()==null){ row.setAttribute("future", "2");//Текущий период }else{ if (TimeUtils.dateBeforeOrEq(new Date(), TimeUtils.convertCalendarToDate(status.getDate2()))) { row.setAttribute("future", "2");//Текущий период } } if(status.getDate1()==null){continue;} if (!TimeUtils.dateBefore(new Date(), TimeUtils.convertCalendarToDate(status.getDate1()))) { continue; } row.setAttribute("future", "1"); if ((status.getStatus() != 0) && (status.getStatus() != 4)) { isManualFutureStatuses = true; } if(status.getStatus() == 4) { canchange = CANCHANGE_CANCEL_ALLOW; //Есть что отменять } } //Пишем текущий статус ContractManager contractManager = new ContractManager(this.con); Contract contract = contractManager.getContractByID(cid); int statusint = contract.getStatus(); if((isManualFutureStatuses==false)&&(statusint==4))//Если в будущем нет статусов кроме 0 и 4, но текущий статус - приостановлен, то ... { canchange = CANCHANGE_CANCEL_ALLOW; //... можно его отменить //При этом, если завтрашний статус != "приостановлен", то отменять можно только сегодня Calendar tomorrow = Calendar.getInstance(); tomorrow.add(Calendar.DATE, 1); if(statusManager.getStatus(cid, tomorrow).getStatus()!=4){ canchange = CANCHANGE_CANCEL_ALLOW_ONLY_TODAY; } } this.rootNode.setAttribute("statusstr", ContractStatus.statusToString(statusint)); this.rootNode.setAttribute("statusint", String.valueOf(statusint)); //Запишем в xml сумму расхода за приостановление, если есть if(this.chargeSumm>0){ this.rootNode.setAttribute("suspendChargeSumm", String.valueOf(this.chargeSumm)); } if ((statusint != 0) && (statusint != 4)) { canchange = Integer.valueOf(CANCHANGE_DISABLED_BADSTATUS); } if(contract.getFc()==1) //Для юрлиц - нельзя { canchange = CANCHANGE_DISABLED; } //Дата, с которой можно менять статус (date1 для активации, деактивация всегда с завтрашнего числа) Element date1 = createElement(this.rootNode, "mindate"); Calendar now = Calendar.getInstance(); if (statusint == 0) { now.add(5, 1); } date1.setAttribute("day", String.valueOf(now.get(5))); date1.setAttribute("month", String.valueOf(now.get(2) + 1)); date1.setAttribute("year", String.valueOf(now.get(1))); if (statusint == 0){ //<--рекомендуемая максимальная дата для приостановления (date2) //Этот адовый кусок кода нужен только для того, чтобы показать пользователю максимальную дату, //до которой он может приостановить договор //..не спрашивайте, как это работает ;) Element date2 = createElement(this.rootNode, "date"); //Сначала устанавливаем эту дату для рассчетного периода от (now-checkDays) до собстна upperbound Calendar upperbound = Calendar.getInstance(); upperbound.add(Calendar.DATE, this.maxDays-this.SuspendedDays(cid) ); //lowerbound = upperbound - checkDays, определяем "нормальный" период проверки Calendar lowerbound = (Calendar)upperbound.clone(); lowerbound.add(Calendar.DATE, - this.checkDays); //lowestbound = now - checkDays Calendar lowestbound = Calendar.getInstance(); lowestbound.add(Calendar.DATE, - this.checkDays); //Теперь upperbound можно нарастить на количество дней приостановки между lowestbound и lowerbound upperbound.add(Calendar.DATE, this.SuspendedDays(lowestbound, lowerbound, cid)); //Это и есть максимальная дата, до которой можно приостановить наш конкретный договор date2.setAttribute("day", String.valueOf(upperbound.get(5))); date2.setAttribute("month", String.valueOf(upperbound.get(2) + 1)); date2.setAttribute("year", String.valueOf(upperbound.get(1))); //--/> } if (canchange == null) { //Проверяем текущий баланс BalanceUtils bu = new BalanceUtils(this.con); if(bu.getBalance(new Date(), cid).floatValue()-this.chargeSumm<0){ canchange = CANCHANGE_DISABLED_LOW_ACCOUNT; }else{ canchange = CANCHANGE_DO;//Всё ок } } this.rootNode.setAttribute("canchange", String.valueOf(canchange)); } private int SuspendedDays(Calendar date1, Calendar date2, int cid) throws SQLException { PreparedStatement ps = this.con.prepareStatement("select sum(1+datediff(LEAST(date2,date(now())),GREATEST(?,date1))) from contract_status where cid=? and status=4 and date2>=? and date1<=?"); ps.setDate(1, TimeUtils.convertCalendarToSqlDate(date1)); ps.setInt(2, cid); ps.setDate(3, TimeUtils.convertCalendarToSqlDate(date1)); ps.setDate(4, TimeUtils.convertCalendarToSqlDate(date2)); ResultSet rs = ps.executeQuery(); if(rs.next()) { return rs.getInt(1); }else { return 0; } } //Количество приостановленных дней от текущего времени private int SuspendedDays(int cid) throws SQLException { Calendar date2 = Calendar.getInstance(); Calendar date1 = (Calendar)date2.clone(); date1.add(5, -this.checkDays); return SuspendedDays(date1, date2, cid); } //Приостанавливаем public void ActionCustomSuspendSuspend(int cid) throws SQLException, BGException { int newStatus = 4; String comment = "Изменено пользователем"; Date date1 = null; //Приостанавливаем только с завтрашнего числа Calendar tomorrow = Calendar.getInstance(); tomorrow.add(5, 1); date1 = tomorrow.getTime(); //По какое число приостанавливаем Date date2 = getDateParameter("to_date"); if (date2 == null) { int day = getIntParameter("to_day", 0); int month = getIntParameter("to_month", 0); int year = getIntParameter("to_year", 0); if ((day > 0) && (month > 0) && (year > 0)) { Calendar dateCal = Calendar.getInstance(); dateCal.set(year, month - 1, day); date2 = dateCal.getTime(); } } if (cid <= 0) { setParamsError(); return; } //Проверяем текущий баланс BalanceUtils bu = new BalanceUtils(this.con); if(bu.getBalance(new Date(), cid).floatValue()-this.chargeSumm<0){ setErrorStatus("Недостаточно средств для приостановления"); return; } if (date2 == null) { setErrorStatus("не задана дата"); return; } if (!checkPeriod(date1, date2)) { return; } ContractManager contractManager = new ContractManager(this.con); Contract contract = contractManager.getContractByID(cid); if ((((contract.getStatus() != 0) || (newStatus != 4))) && (( (contract.getStatus() != 4) || (newStatus != 0)))) { setErrorStatus("нельзя менять статус кроме 'активен' <-> 'приостановлен'"); return; } /* if ((newStatus == 4) && (TimeUtils.dateBeforeOrEq(date1, new Date()))) { setErrorStatus("можно приостанавливать договор не раньше завтрашнего числа"); return; }*/ /*if ((newStatus == 0) && (TimeUtils.dateBefore(date1, new Date()))) { setErrorStatus("нельзя активировать раньше, чем сегодня"); return; }*/ //Проверка на suspend abuse Calendar tmp = TimeUtils.convertDateToCalendar(date2); tmp.add(Calendar.DATE, -this.checkDays); int expectedSuspendedDays = this.SuspendedDays(tmp, Calendar.getInstance(), cid)+TimeUtils.daysDelta(TimeUtils.convertDateToCalendar(date1), TimeUtils.convertDateToCalendar(date2)); if(expectedSuspendedDays>this.maxDays){ setErrorStatus("Договор не может быть приостановлен дольше "+this.maxDays +" дней в течение "+ this.checkDays+". Сократите период на "+(expectedSuspendedDays-this.maxDays) + " дн."); return; } ContractStatus status = new ContractStatus(); status.setContractId(cid); status.setStatus(newStatus); status.setDate1(TimeUtils.convertDateToCalendar(date1)); status.setDate2(TimeUtils.convertDateToCalendar(date2)); status.setComment(comment); if ((isDateCheckingEnabled("ActionContractStatus")) && (( (!checkDatesByClosedDate(null, date1)) || (!checkDatesByClosedDate(null, date2))))) { setErrorStatus("Устанавливаемый статус пересекается с закрытым периодом!"); return; } //Приостанавливаем ContractStatusManager statusManager = new ContractStatusManager(this.con); statusManager.changeStatus(status.clone(), this.userID, true, true); //Вносим расход, если нужно if(this.chargeType>=0) { ChargeManager chargeManager = new ChargeManager(con); Charge charge = new Charge(); charge.setChargeDate(date1); charge.setChargeTypeID(this.chargeType); charge.setComment("Приостановление через Web"); charge.setContractID(cid); charge.setID(-1); charge.setSumma(new BigDecimal(this.chargeSumm)); charge.setUserID(0); chargeManager.updateCharge(charge); EventProcessor.getProcessor().addEvent(new ChargeEvent(charge)); //Обновляем баланс в предыдущем месяце bu.updateBalance(date1, cid); //Обновляем баланс в личном кабинете - сбрасываем кэш contract_data, в котором хранятся данные о балансе this.request.getSession().setAttribute("contract_data", null); } } public void ActionCustomSuspendActivate(int cid) throws SQLException, BGException { //Дата, с которой будем активироваться: сегодня или завтра Calendar dt = Calendar.getInstance(); if("tomorrow".equals(getParameter("from"))) {//Включаемся завтра dt.add(Calendar.DATE, 1); } Date date1 = TimeUtils.convertCalendarToDate(dt); Date date2 = null; //Есть ли вообще что отменять в будущем? boolean futureSuspendExists=false; ContractStatusManager statusManager = new ContractStatusManager(this.con); //Перебираем будущие статусы: если есть статусы, отличные от 4 и 0, то отменять приостановление нельзя for (ContractStatus status : statusManager.getStatusList(cid)) { if (!TimeUtils.dateBefore(Calendar.getInstance(), status.getDate1())) { continue; } if ((status.getStatus() != 0) && (status.getStatus() != 4)) { setErrorStatus("запланировано изменение статуса на что-либо отличное от 'активен' или 'приостановлен'"); return; } if (status.getStatus() == 4) { futureSuspendExists=true; } } ContractManager contractManager = new ContractManager(this.con); Contract contract = contractManager.getContractByID(cid); if(contract.getFc()==1){ setErrorStatus("управление статусом недоступно"); return; } if ((contract.getStatus() != 4) && (contract.getStatus() != 0)) { setErrorStatus("нельзя менять статус кроме 'активен' <-> 'приостановлен'"); return; } if (!futureSuspendExists && contract.getStatus() != 4) { setErrorStatus("нет периодов приостановления"); return; } String comment = "Изменено пользователем (отмена смены статуса)"; ContractStatus status = new ContractStatus(); status.setContractId(cid); status.setStatus(0); status.setDate1(TimeUtils.convertDateToCalendar(date1)); status.setDate2(TimeUtils.convertDateToCalendar(date2)); status.setComment(comment); if ((isDateCheckingEnabled("ActionContractStatus")) && (( (!checkDatesByClosedDate(null, date1)) || (!checkDatesByClosedDate(null, date2))))) { setErrorStatus("Устанавливаемый статус пересекается с закрытым периодом!"); return; } //Устанавливаем статус statusManager.changeStatus(status.clone(), this.userID, true, true); //Удаляем расходы с даты date1 до бесконечности if(this.chargeType>=0) { ChargeManager chargeManager = new ChargeManager(con); BalanceUtils bu = new BalanceUtils(this.con); PreparedStatement ps = con.prepareStatement("select id, dt from contract_charge where cid=? and pt=? and dt>=?"); ps.setInt(1, cid); ps.setInt(2, this.chargeType); ps.setDate(3, TimeUtils.convertDateToSqlDate(date1)); ResultSet rs = ps.executeQuery(); while(rs.next()){ chargeManager.deleteCharge(rs.getInt(1)); bu.updateBalance(TimeUtils.convertSqlDateToDate(rs.getDate(2)), cid); } //Обновляем баланс в личном кабинете - сбрасываем кэш contract_data, в котором хранятся данные о балансе this.request.getSession().setAttribute("contract_data", null); } } }
XSL template
<xsl:template name="CustomSuspend"> <xsl:call-template name="error"/> <!-- canchange: CANCHANGE_DISABLED = 1 CANCHANGE_DISABLED_BADSTATUS = 2 CANCHANGE_DO = 3 CANCHANGE_CANCEL_ALLOW = 4 CANCHANGE_CANCEL_ALLOW_ONLY_TODAY = 6 CANCHANGE_DISABLED_LOW_ACCOUNT = 7 --> <xsl:choose> <!-- если canchange=1 --> <xsl:when test="/data/@canchange=1"> Вы не можете менять статус </xsl:when> <xsl:when test="/data/@canchange=2"> Ваш текущий или будущий статус не позволяет воспользоваться приостановлением услуг </xsl:when> <xsl:when test="/data/@canchange=3"> <!-- Можно приостановить --> <form method="post" action="{$WEBEXECUTER}"> <xsl:call-template name="module"/> <input type="hidden" name="action" value="CustomSuspend" /> <input type="hidden" name="command" value="Suspend" /> <table cellspacing="0" class="filter" style="font-size:12px"> <tr> <td> Приостановить услуги </td> <td>с <xsl:if test="/data/mindate"> <b> <xsl:value-of select="/data/mindate/@day"/>.<xsl:value-of select="/data/mindate/@month"/>.<xsl:value-of select="/data/mindate/@year"/> </b> </xsl:if> <xsl:if test="not(/data/mindate)"> <b>завтрашнего числа</b> </xsl:if> </td> <td> по <select name="to_day"> <option value='0'><xsl:if test="/data/date/@day = '0'"><xsl:attribute name="selected">1</xsl:attribute></xsl:if>--</option> <xsl:call-template name="day_list"/> </select> </td> <td> <select name="to_month"> <xsl:call-template name="month_list"/> </select> </td> <td> <select name="to_year"> <xsl:if test="/data/mindate/@year != /data/date/@year"> <option value="{/data/mindate/@year}"><xsl:value-of select="/data/mindate/@year"/></option> </xsl:if> <option value="{/data/date/@year}" selected="1"><xsl:value-of select="/data/date/@year"/></option> </select> </td> <xsl:if test="/data/@suspendChargeSumm"> <td>за <b><font color="red"><xsl:value-of select="/data/@suspendChargeSumm"/>р.</font></b> единовременно</td> </xsl:if> <td> <xsl:call-template name="submit"> <xsl:with-param name="title" select="'Выполнить'"/> <xsl:with-param name="guid" select="'_set'"/> </xsl:call-template> </td> </tr> </table> </form> </xsl:when> <xsl:when test="/data/@canchange=4"> <!-- Можно активировать договор --> <!-- Вы можете активировать договор <b> <a href="{$WEBEXECUTER}?action=CustomSuspend&command=Activate">сегодня</a></b> или <b><a href="{$WEBEXECUTER}?action=CustomSuspend&command=Activate&from=tomorrow">завтра</a></b> --> <table style="font-size: 12px;"> <tr> <td>Вы можете <b>активировать</b> договор</td> <td> <xsl:call-template name="button"> <xsl:with-param name="align" select="'left'"/> <xsl:with-param name="onclick">location.href='<xsl:value-of select="$WEBEXECUTER"/>?action=CustomSuspend&command=Activate'</xsl:with-param> <xsl:with-param name="title" select="'Сегодня'"/> </xsl:call-template> </td> <td>или</td> <td> <xsl:call-template name="button"> <xsl:with-param name="align" select="'left'"/> <xsl:with-param name="onclick">location.href='<xsl:value-of select="$WEBEXECUTER"/>?action=CustomSuspend&command=Activate&from=tomorrow'</xsl:with-param> <xsl:with-param name="title" select="'Завтра'"/> </xsl:call-template> </td> </tr> </table> </xsl:when> <xsl:when test="/data/@canchange=6"> <!-- Можно активировать договор только сегодня --> <!-- Вы можете активировать договор <b> <a href="{$WEBEXECUTER}?action=CustomSuspend&command=Activate">сегодня</a></b> --> <table style="font-size: 12px;"> <tr> <td>Вы можете <b>активировать</b> договор</td> <td> <xsl:call-template name="button"> <xsl:with-param name="align" select="'left'"/> <xsl:with-param name="onclick">location.href='<xsl:value-of select="$WEBEXECUTER"/>?action=CustomSuspend&command=Activate'</xsl:with-param> <xsl:with-param name="title" select="'Сегодня'"/> </xsl:call-template> </td> </tr> </table> </xsl:when> <xsl:when test="/data/@canchange=7"> <!-- Низкий остаток на балансе --> <p>Недостаточно средств для приостановления услуг. Баланс должен быть не меньше <xsl:if test="/data/@suspendChargeSumm"><b><xsl:value-of select="/data/@suspendChargeSumm"/>р</b></xsl:if><xsl:if test="not(/data/@suspendChargeSumm)"><b>0</b></xsl:if>. <xsl:if test="/data/contract_data/contract/@balance_rest"> Ваш текущий баланс = <b><xsl:value-of select="/data/contract_data/contract/@balance_rest"/>р</b>.</xsl:if></p> <!-- Если у провайдера используется обещанный платеж, то нужно указать об этом--> <p>Обещанный платеж не влияет на текущий баланс.</p> <!-- Если у провайдера используется логика дебетовых абонплат, то нужно указать об этом--> <xsl:if test="/data/@balance_mode='1'"><p>При отсутствии средств на счету услуги приостанавливаются автоматически раз в сутки статусом "<font color="red">закрыт</font>".</p></xsl:if> </xsl:when> <xsl:otherwise> Вы не можете менять статус. </xsl:otherwise> </xsl:choose> <br/> <b>Текущий статус: <font> <xsl:if test="/data/@statusint='0'"><xsl:attribute name="color">green</xsl:attribute></xsl:if> <xsl:if test="/data/@statusint='3'"><xsl:attribute name="color">red</xsl:attribute></xsl:if> <xsl:value-of select="/data/@statusstr"/> </font> </b> <br/> <br/> <b>История изменения статусов:</b> <br/> <br/> <div class="report"> <table cellspacing="1" width="400px"> <thead> <tr> <td>С даты</td> <td>По дату</td> <td>Статус</td> </tr> </thead> <tbody> <xsl:for-each select="statuses/status"> <xsl:sort select="position()" data-type="number" order="descending"/> <tr> <xsl:if test="@future=2"><xsl:attribute name="style">font-weight:bold;</xsl:attribute></xsl:if> <td><xsl:value-of select="@date1"/></td> <td><xsl:value-of select="@date2"/></td> <td> <xsl:if test="@statusint='0'"><xsl:attribute name="style">color:green;</xsl:attribute></xsl:if> <xsl:if test="@statusint='3'"><xsl:attribute name="style">color:red;</xsl:attribute></xsl:if> <xsl:value-of select="@status"/> </td> </tr> </xsl:for-each> </tbody> </table> </div> </xsl:template>
--Cromeshnic 04:31, 22 июля 2010 (UTC)