Как стать автором
Обновить
0

К вопросу расчета себестоимости

Время на прочтение5 мин
Количество просмотров6.1K
Несколько дней назад проводил партнерский семинар — обучение новых сотрудников. Касались вопроса расчета итогов (регистров) системы, в частности расчета себестоимости.
Вечером один из слушателей прислал ссылку на статью, подробно рассматривающую проблематику расчета себестоимости в MS Axapta.
С просьбой прокомментировать основные различия в подходах.

Автор честно потратил за два дня не менее 8-ми часов, разбираясь в тексте.
В результате был вынужден признать поражение — я НЕ смог разобраться как оно работает.
Невероятное количество специальных случаев, настроек, десятки страниц описания обработки особенностей работы с каждым счетом, процедуры переноса себестоимости, миграции партий.
Потратив два дня на попытки разобраться в этом тексте, я так и не получил ясного понимания, что же мне надо сделать, если я хочу учитывать товар не только на складе, а и, например, на водителе. Или же хочу считать себестоимость не только для товара (это могут быть рекламации в гарантии, объекты основных средств или еще что угодно — да те же деньги).
Кроме того, некоторое удивление вызвало наличие специальных разделов с описанием закрытия склада по услугам и описанием ошибок списания на округлении. Зачем вообще списывать услуги со склада?
Двух-этапная система для борьбы с остатками округления тоже не уложилась в голове.
Пассажи типа
Правильнее было бы накапливать сумму ошибок на каком-то выделенном счете, а потом, при трансформации баланса, закрывать этот счет в ручную. Для того чтобы добиться такого эффекта — необходимо подправить метод inventAdj::errorAccountOperation(), таким образом чтобы он возвращал нужный вам счет ошибок округления. Я бы, наверное, использовал для этого счета отклонений от стандартной себестоимости. Если standard costing используется — то на эти счета как раз и нужно отклонения списывать, а если не используется — то эти счета в настройке складских разносок не заняты и их можно приспособить под списание ошибок и округлений. Если эта схема вам подходит — достаточно поменять в методе InventAdj::errorAccountOperation() значения InventAccountType::InventProfit и InventAccountType::InventLoss на InventAccountType::InventStdProfit и InventAccountType::InventStdLoss соответственно.

с одной стороны — внушают уважение, с другой — вселяют ужас.

Изучение оного документа, однако, дало идею публикации на Хабре касательно того, как устроен расчет себестоимости у нас.
Я не стал расписывать подробности, не влияющие на понимание сути процесса, при желании их можно найти в выдержке из документации.

Расчет себестоимости, наверное, один из самых сложных для понимания процессов в нашей системе, и он же — один из самых упорядоченных. IMHO.

В качестве спойлера картинка:



В нашем случае расчет себестоимости — это процесс вычисления стоимости конкретной учетной единицы при каждом ее движении.
Возможно, звучит непонятно, попробуем пояснить.
Мы используем итоги (похожи на регистры в 1С) — некоторый аналог многомерного куба, ну или его реляционное представление. Соответственно, итог отображает текущее состояние измеримых показателей компании.
Хрестоматийный пример — остатки товара на складе. Итог, с измерениями склад и товар и переменными количество и сумма. Таблица фактов (являющаяся частью итога) содержит собственно данные о изменениях — мы их называем транзакциями, бухгалтера — проводками. Система расчета себестоимости не ограничивается собственно расчетом себестоимости, а на уровне платформы генерализирована до расчета значений переменных итогов в соответствии с теми или иными алгоритмами. Такие алгоритмы (или драйвера в нашей терминологии) привязываются к итогам (по одному на итог).

Вернемся к первой диаграмме.
В процессе расчета участвует специальный сервис (в данном случае не виндовый сервис, а сервис в терминах сервера приложений как некий класс) TotalCalculator, который зачитывает транзакции из таблицы фактов, передает их соответствующим драйверам, и применяет изменения в таблице фактов.
В свою очередь транзакции содержат значения для измерений и дельты для переменных. Некоторые из измерений и переменных могут не содержать значение в момент создания транзакции. Если быть точным, то у нас есть 2 таблицы фактов — оперативная и детализированная, которые отличаются тем, что детализированная заполняется в процессе работы калькулятора итогов. При этом возникает некоторая монопольность доступа, что позволяет, например, использовать bitmap indexes, ну и проделывать другие трюки для оптимизации.
Незаполненное значение переменной в транзакции обозначает, что оно должно быть вычислено позднее. Для примера — при списании товара со склада оперативно (без выполнения алгоритма FIFO) вычислить себестоимость, с которой списывается товар невозможно. Поэтому указывается значение переменной количество, а сумма не указывается.
В свою очередь, при покупке товара (упрощая) стоимость известна, и равна закупочной стоимости. В этом случае в транзакции будут указаны и количество и сумма.

Таким образом, драйвер для итога остатки на складе реализует алгоритм FIFO.
Ну а драйвер для итога остатки на расчетных счетах реализует модифицированный FIFO для себестоимости денег с учетом наличия овердрафта (это предполагает наличие транзакций, которые списывают деньги, которые не были ранее оприходованы). Правильная реализация и организация драйверов была бы невозможна, если бы не поддержка двойной записи — каждая транзакция знает парную ей. В паре одна транзакция всегда исходящая, другая входящая (это определяется знаками значений переменных, точнее у исходящей проводки всегда отрицательные, у входящей — положительные).
Драйвер получает на вход пару проводок, и его задача вернуть рассчитанную проводку.
Соответственно алгоритм расчета себестоимости по FIFO выглядит примерно так:
Если входящая проводка и сумма указана, запомним эту партию в очереди партий
Если входящая проводка и сумма НЕ указана, она должна быть указана в парной исходящей проводке
Если исходящая проводка, то возьмем стоимость из первой в очереди партии. Вернем проводку с полученной себестоимостью.

Для себестоимости денег бывает так, что исходящая транзакция расходует деньги при пустой очереди партий. В этом случае мы используем текущий (на день транзакции) курс ЦБ РФ (в варианте для России).

Аналогично для итогов с другим назначением драйвера используют другие алгоритмы. В реальной конфигурации Ultima eCommerceERP достаточно не более 10 драйверов. Таким образом, информация о расчете итогов хранится в аккуратно структурированном виде, разложенном в несколько драйверов, каждый из которых реализует четко оговоренный функционал.

Как результат:
  • Логика расчета себестоимости изолирована в драйверах итогов
  • Упрощается модульное тестирование функционала
  • Безопасный теперь код пишется быстрее
  • Снижен «входной порог» разработчика для внесения изменений, увеличивая количество разработчиков, которые могут реализовать функционал


Касательно производительности — в одной из инсталляций системы таким образом обрабатывается несколько миллионов транзакций в час.
Теги:
Хабы:
+3
Комментарии4

Публикации

Изменить настройки темы

Информация

Сайт
www.ultimaerp.com
Дата регистрации
Дата основания
Численность
51–100 человек
Местоположение
Россия

Истории