Пример написания драйвера итогов

Для примера рассмотрим ситуацию, в которой необходимо реализовать на складе, остатки которого учитываются на итоге Склад (Stock), сборку некоего продукта из деталей. Для этого следует создать в системе промежуточный итог Сборка (Production), на который документом со Склада будут списываться детали, и с которого будет приходоваться обратно на Склад собранный из них готовый продукт:

Склад -> Сборка // деталь;

Склад -> Сборка // еще деталь;

Склад -> Сборка // и еще одна деталь;

Сборка -> Склад // продукт сборки;

Себестоимость собранного из деталей продукта будет рассчитываться специальным драйвером итога, написать который и стоит задача. Базовым классом, от которого будет наследоваться драйвер итога, будет DefaultTotalDriver.

35_important

В драйвере итогов следует избегать обращений к базе данных.

При написании драйвера следует решить следующие задачи:

1.Проводки делаются документом, из которого можно определить, какие детали были использованы для сборки. Чтобы избежать обращения к базе данных, организуется очередь. Создать пустую очередь для очередного документа можно переопределив виртуальные методы драйвера BeginDocument/EndDocument.

2.Далее необходимо рассчитать все, что пришло со склада на сборку, посредством метода CalculateOutcomes:

public override ICollection<DetailedTransactionValue> CalculateOutcomes(

   TransactionValue outcome, IEnumerable<TransactionValue> incomes)

Его входные параметры:

outcome – проводка списания. Ее необходимо рассчитать до полной и, при необходимости, расслоить на несколько;

incomes – проводка приходования. Возможно уже расслоена парным драйвером и посчитана до полной.

Поскольку в сборке никакой собственной логики расслоения нет, его должен делать базовый драйвер по образцу парных движений. Но сперва следует рассчитать себестоимость. Если сразу вызвать метод базового драйвера Default, ему неоткуда будет взять Amount, и он выдаст исключение:

outcome.Amount = CalculateAmount(); // посчитать себестоимость

return base.CalculateOutcomes(outcome, incomes); // сделать расслоение

3.И, наконец, следует аналогично предыдущему пункту рассчитать себестоимость собранного продукта посредством метода CalculateIncomes:

public override ICollection<DetailedTransactionValue> CalculateIncomes(TransactionValue income, IEnumerable<TransactionValue> outcomes)

В результате получаем драйвер:

decimal docIncomeAmount = 0;

 

public override void BeginDocument(DateTime transactionDate, long docId)

{

   docIncomeAmount = 0;

  base.BeginDocument(transactionDate, docId);

}

 

public override ICollection<DetailedTransactionValue> CalculateOutcomes(

   TransactionValue outcome, IEnumerable<TransactionValue> incomes)

{

  var result = base.CalculateOutcomes(outcome, incomes);

 

  var stock = outcome as StockTransaction;

 

  if (stock != null && stock.Quantity < 0 && incomes.Count() > 0 &&

       (incomes.First() is ProductionTransaction))

   {

       docIncomeAmount -= result.Sum(x => (x as StockDetailedTransaction).Amount ?? 0);

   }

 

  return result;

}

 

public override ICollection<DetailedTransactionValue> CalculateIncomes(

   TransactionValue income, IEnumerable<TransactionValue> outcomes)

{

  var result = base.CalculateIncomes(income, outcomes);

 

  if (result.Count > 1)

   {

       AddError(result.First(), @"Single income Prod->Stock expected: {0}...{1}",

           result.First(), result.Last());

   }

 

  var stock = income as StockTransaction;

 

  if (stock != null && stock.Quantity > 0 && outcomes.Count() > 0 &&

       (outcomes.First() is ProductionDetailedTransaction))

   {

       (result.First() as StockDetailedTransaction).Amount = docIncomeAmount;

   }

 

  return result;

}

Валидатор транзакций для написанного драйвера (на наличие только одного продукта в документе):

public void ValidateTransactions(TransactionPairCollection transactionPairs,

   TransactionCollection transactions)

{

  long ProductID = -1;

  foreach (var pair in transactionPairs)

   {

       var outcome = pair.OutcomeValue as StockTransaction;

      if (outcome != null)

       {

          if (!outcome.IsValueNull("Amount"))

           {

              throw new UltimaException(@"Amount variable of an outcome stock

                  transaction mustn't be set.");

           }

       }

 

       var income = pair.IncomeValue as StockTransaction;

      if (income != null && pair.OutcomeValue.IsComplete)

       {

          if (!income.IsComplete)

           {

              throw new UltimaException(@"Income stock transaction must be

                  complete when outcome transaction is complete.");

           }

       }

 

      if(pair.OutcomeValue is ProductionTransaction)

       {

          if(ProductID == -1)

           {

               ProductID = (pair.OutcomeValue as ProductionTransaction).ProductID;

           }

 

          if((pair.OutcomeValue as ProductionTransaction).ProductID != ProductID)

           {

              throw new UltimaException(@"In document must be only one product");

           }

       }

 

      if(pair.IncomeValue is ProductionTransaction)

       {

          if(ProductID == -1)

           {

               ProductID = (pair.IncomeValue as ProductionTransaction).ProductID;

           }

 

          if((pair.IncomeValue as ProductionTransaction).ProductID != ProductID)

           {

              throw new UltimaException(@"In document must be only one product");

           }

       }

   }

}