1. Типы скидокТак или иначе, всем приходится сталкиваться с ситуацией, когда некоторые услуги продаются клиенту по индивидуальной цене. В лучшем случае в организации есть бизнес-логика предоставления скидок, в худшем это делается наобум. Если подходить к вопросу системно, то для учёта скидок нужно решить 3 вопроса:
 - Политика скидок (кому сколько и при каких условиях)
 - Механизм начисления (техническая сторона)
 - Отчетность
 Первая часть индивидуальна для каждого предприятия и реализуется на уровне бизнес-логики. В биллинг тащить это не хочется. Но из первой части можно понять, какие бывают типы скидок, чтобы их начислять.
У меня получился такой список:
 а) Скидки на подключение (разовые)
 б) Фиксированные скидки на периодические услуги (цена минус X р.)
 в) Процентные скидки на периодические услуги
 г) Индивидуальная цена на услугу
 д) Скидка на сумму счета
 а) Начисление реализуется либо тупо путём занесения в BG расходов нужной суммы (но тогда пропадает отчетность), либо например двумя расходами: расход со стандартной ценой минус расход другого типа со скидкой. В любом случае, сделать workaround вокруг расходов можно. 
От биллинга тут ничего не требуется. б) Выглядит как-то странно, но менеджеры могут придумать и не такое 

. Можно тупо завести абонплату "скидка" с ценой в -1р. и вешать на договор с количеством, равным X. Забавно, но работает 
 От биллинга тут ничего не требуется. в) Тут 
нужна реализация в биллинге, см. ниже.
 г) Слишком общая постановка, кроме персональных тарифов ничего не придумаешь. 
От биллинга тут ничего не требуется. д) вычисляется только после создания счета => можно реализовать скриптами. 
От биллинга тут ничего не требуется.Почему нельзя просто использовать персональные тарифы? За годы их использования имеем следующие проблемы:
 - Отсутствует прозрачность (=> контроль) за предоставлением скидок.
 - Сложности с внедрением новых тарифов (при введении новых тарифов или изменении старых приходится поддерживать персональные)
 - Сложности с построением отчетов по тарифам
Скидка по своей сути - это некая сущность клиентского договора, имеющая период действия и набор параметров, а также влияющая на наработку.
2. Реализация процентных скидок в BGBillingТ.о. нам нужна некая сущность договора, по которой можно было бы начислять скидки как процент от наработки по другим услугам. Нужно указать: date1, date2, sids (через запятую), % скидки.
Предлагаю следущую схему:
 - Скидка - это абонплата, процент указывается в количестве абонплаты:
Вложение:
			
			npays-discount.jpg [ 35.26 КБ | Просмотров: 47344 ]
		
		
	  - В тарифном плане вместо узла "стоимость" указывается новый тип узла "скидка" (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 КБ | Просмотров: 47344 ]
		
		
	 Узел работает только для месячной абонплаты (для простоты).
В узле 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 КБ | Просмотров: 47344 ]
		
		
	 Вот код класса узла [для 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 в локальной директории => нужно будет следить за его обновлениями