1. Типы скидокТак или иначе, всем приходится сталкиваться с ситуацией, когда некоторые услуги продаются клиенту по индивидуальной цене. В лучшем случае в организации есть бизнес-логика предоставления скидок, в худшем это делается наобум. Если подходить к вопросу системно, то для учёта скидок нужно решить 3 вопроса:
- Политика скидок (кому сколько и при каких условиях)
- Механизм начисления (техническая сторона)
- Отчетность
Первая часть индивидуальна для каждого предприятия и реализуется на уровне бизнес-логики. В биллинг тащить это не хочется. Но из первой части можно понять, какие бывают типы скидок, чтобы их начислять.
У меня получился такой список:
а) Скидки на подключение (разовые)
б) Фиксированные скидки на периодические услуги (цена минус X р.)
в) Процентные скидки на периодические услуги
г) Индивидуальная цена на услугу
д) Скидка на сумму счета
а) Начисление реализуется либо тупо путём занесения в BG расходов нужной суммы (но тогда пропадает отчетность), либо например двумя расходами: расход со стандартной ценой минус расход другого типа со скидкой. В любом случае, сделать workaround вокруг расходов можно.
От биллинга тут ничего не требуется. б) Выглядит как-то странно, но менеджеры могут придумать и не такое
. Можно тупо завести абонплату "скидка" с ценой в -1р. и вешать на договор с количеством, равным X. Забавно, но работает
От биллинга тут ничего не требуется. в) Тут
нужна реализация в биллинге, см. ниже.
г) Слишком общая постановка, кроме персональных тарифов ничего не придумаешь.
От биллинга тут ничего не требуется. д) вычисляется только после создания счета => можно реализовать скриптами.
От биллинга тут ничего не требуется.Почему нельзя просто использовать персональные тарифы? За годы их использования имеем следующие проблемы:
- Отсутствует прозрачность (=> контроль) за предоставлением скидок.
- Сложности с внедрением новых тарифов (при введении новых тарифов или изменении старых приходится поддерживать персональные)
- Сложности с построением отчетов по тарифам
Скидка по своей сути - это некая сущность клиентского договора, имеющая период действия и набор параметров, а также влияющая на наработку.
2. Реализация процентных скидок в BGBillingТ.о. нам нужна некая сущность договора, по которой можно было бы начислять скидки как процент от наработки по другим услугам. Нужно указать: date1, date2, sids (через запятую), % скидки.
Предлагаю следущую схему:
- Скидка - это абонплата, процент указывается в количестве абонплаты:
Вложение:
npays-discount.jpg [ 35.26 КБ | Просмотров: 34369 ]
- В тарифном плане вместо узла "стоимость" указывается новый тип узла "скидка" (java-код ниже):
Код:
mysql> select * from mtree_node where id=48;
+----+-------------+----------+----------+----------+-----+
| id | parent_node | mtree_id | type | data | pos |
+----+-------------+----------+----------+----------+-----+
| 48 | 47 | 3 | discount | sids&5,6 | 0 |
+----+-------------+----------+----------+----------+-----+
1 row in set (0.00 sec)
(5 - услуга 'Доступ в интернет', 6 - услуга 'Доступ в интернет (локальный трафик)')
Вложение:
npays-discount-tariff-tree.jpg [ 28.91 КБ | Просмотров: 34369 ]
Узел работает только для месячной абонплаты (для простоты).
В узле discount задаётся список услуг, для которых считается скидка. Услуги можно указывать вообще любые.
Узел возвращает цену абонплаты равной = select -sum(summa)/100 from contract_account where sid in (<sids>) and yy=<yy> and mm=<mm> and cid=<cid>
Т.е. 1% от суммы наработки договора по нужным нам услугам в месяце, за который вычисляем абонплаты.
!!! Важно !!! Узел работает в режиме "безусловно". Т.е. если мы поставили скидочную абонплату в середине месяца, то скидка все равно насчитается как % от наработки по услугам за весь месяц. Иначе нельзя, т.к. наработка по услугам хранится с точностью до месяца. Т.о. скидки предоставляются по месяцам.
Всё просто и удобно
Запускаем начисление абонплат и получаем скидку на 15 ноября = 30% * 100р. * (15 дней/30 дней) = 15р.
Вложение:
npays-discount-account.jpg [ 20.53 КБ | Просмотров: 34369 ]
Вот код класса узла [для 5.2]:
Код:
package ru.bitel.bgbilling.modules.npay.tariff.server;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Calendar;
import ru.bitel.bgbilling.kernel.tariff.tree.server.DefaultTariffTreeNode;
import ru.bitel.common.ParameterMap;
import ru.bitel.common.Utils;
public class MonthModeDiscountByServicesTariffTreeNode extends DefaultTariffTreeNode<NPayTariffRequest, NPayTariffContext>
{
private String sids;
public MonthModeDiscountByServicesTariffTreeNode(int id, ParameterMap parameterMap)
{
super(id, parameterMap);
this.sids = parameterMap.get("sids", "");//список sid-ов через запятую
}
protected int executeImpl(Long treeNodeId, NPayTariffRequest req, NPayTariffContext ctx)
{
BigDecimal cost = BigDecimal.ZERO;
//Берем наработку по услугам из 'sids'
//Не нашел метода в API, чтобы получить наработку по списку услуг без субдоговоров, считаем руками:
String query =
"SELECT sum(summa) FROM contract_account WHERE yy=? AND mm=? AND sid IN (" +
this.sids + ") AND cid=?";
Connection con = req.getConnection();
try {
PreparedStatement ps = con.prepareStatement(query);
ps.setInt(1, req.getTime().get(Calendar.YEAR));
ps.setInt(2, req.getTime().get(Calendar.MONTH)+1);
ps.setInt(3, req.cid);
ResultSet rs = ps.executeQuery();
if (rs.next())
{
cost = Utils.maskNull(rs.getBigDecimal(1)).divide(BigDecimal.valueOf(100)).negate();
}
rs.close();
ps.close();
} catch (SQLException e) {
req.addError("Error while obtaining discount service account for sids: "+this.sids+". Node: "+this.fingerprint + ". Error:"+e.getMessage());
e.printStackTrace();
}
req.serviceCost.cost = cost;
return 1;
}
}
Прописываем в npay.xml:
Код:
<node type="discount" class1="" title="Discount" class2="ru.bitel.bgbilling.modules.npay.tariff.server.MonthModeDiscountByServicesTariffTreeNode"/>
<!--... -->
<node type="month_mode" class1="bitel.billing.module.services.npay.MonthCalculateModeTariffTreeNode" title="Iany?iue ?a?ei niyoey" class2="ru.bitel.bgbilling.modules.npay.tariff.server.CalculateModeTariffTreeNode">
<node type="month_cost"/>
<node type="add_cost"/>
<node type="month_period"/>
<node type="month_if"/>
<node type="month_if_account"/>
<node type="mult_cost"/>
<node type="month_option"/>
<node type="discount"/>
</node>
<node type="month_period" class1="bitel.billing.module.tariff.PeriodTariffTreeNode" title="Ia?eia" class2="ru.bitel.bgbilling.kernel.tariff.tree.server.PeriodTariffTreeNode">
<node type="month_if"/>
<node type="month_cost"/>
<node type="month_if_account"/>
<node type="mult_cost"/>
<node type="month_option"/>
<node type="discount"/>
</node>
<node type="month_if" class1="bitel.billing.module.services.npay.AmountRangeTariffTreeNode" title="Oneiaea ii iauaio oneoae" class2="ru.bitel.bgbilling.modules.npay.tariff.server.AmountRangeTariffTreeNode">
<node type="month_period"/>
<node type="month_cost"/>
<node type="mult_cost"/>
<node type="discount"/>
</node>
<!--... -->
<node type="month_option" category="directory" class1="ru.bitel.bgbilling.modules.npay.tariff.client.OptionTariffTreeNode" title="Iioey" class2="ru.bitel.bgbilling.modules.npay.tariff.server.OptionTariffTreeNode">
<node type="month_cost"/>
<node type="add_cost"/>
<node type="month_period"/>
<node type="month_if"/>
<node type="month_if_account"/>
<node type="mult_cost"/>
<node type="discount"/>
</node>
Для 5.1 я думаю принципиальных отличий не должно быть.
3. Недостатки - % скидки может быть только целым
- % скидки нельзя указать отрицательным (продать дороже)
Собственно, вопрос к разработчикам - можете реализовать это официально?
В виде костыля неудобно поьзоваться по двум причинам:
- Лень ковырять клиент, чтобы добиться поддержки отображения и редактирования узла discount
- Приходится переопределять npay.xml в локальной директории => нужно будет следить за его обновлениями