Себестоимость и закрытие склада 2009
О чем все это?
По моему опыту общения с консультантами и разработчиками, внедряющими DAX, вопросы,связанные с закрытием склада и расчетом себестоимости, стали одними из самых больных на внедрениях. Люди относятся к закрытию склада как к черному ящику, который в зависимости от фазы луны выдает самые разнообразные результаты и отрабатывает за самое разное время. На мой взгляд – это вызвано тем, что информация по процедуре расчета себестоимости разбросана по разным местам документации, кроме того некоторые тонкости этой процедуры вообще нигде не описаны и их приходится изучать методом проб и ошибок или изучая исходные тексты процедуры закрытия склада.
В данной статье я попытался дать достаточно подробное объяснение того, как работает данная процедура и разобрать некоторые другие темы связанные с себестоимостью.
Документ рассчитан на достаточно опытных консультантов и архитекторов DAX, уже знакомых с основными идеями модуля торговли, уже потренировавшимися несколько раз в процедуре закрытия склада и накопивших вопросы по данной тематике.
С момента написания исходного текста статьи прошло уже почти два года. За это время и сама Аксапта шагнула вперед (вышла версия 2009, которая довольно радикально отличается от 4ой версии) да и появилась необходимость слегка дополнить исходную статью теми тонкостями, которые я когда-то не стал рассматривать. Все дополнения в статье я буду выделять курсивом.
Зачем вообще нужно закрытие склада?
Основной задачей процедуры закрытия склада является вычисление истинной себестоимости списания номенклатуры. DAX работает с себестоимостью следующим образом: В момент выполнения операции списания, система не пытается точно сосчитать себестоимость списания по данному складскому движению в соответствии с настройками складской модели. Вместо этого, система списывает некоторую оценочную себестоимость, в целом – более или менее похожую на истинную себестоимость. (Подробнее – в разделе "Оценочная себестоимость.”) Далее – при выполнении закрытия склада рассчитывается истинное значение себестоимости в соответствии с настройками складской модели. После выполнения расчета истинной себестоимости система рассчитывает коррекцию –разницу между оценочной себестоимостью и истинной. Эта коррекция записывается в таблицу складских проводок и разносится в ГК. Почему так сделано ? В общем случае, в момент выполнения списания, система может не содержать всех данных, необходимых для расчета истинной себестоимости. Например , при работе в режиме отрицательного склада, мы можем списывать номенклатуру до того как ее приход проведен по системе; при использовании модели "средняя за период» невозможно посчитать себестоимость до того момента, как период будет закончен и все приходы будут проведены по системе и так далее; по модели LIFO мы аналогичным образом не можем посчитать себестоимость расхода до тех пор пока у нас не будет проведен последний приход за период и т.д.
Время от времени, начинающие консультанты на внедрении, приходили ко мне с предложением как-то подправить аксапту, чтобы бухгалтера могли руками вводить в документы списания мгновенную себестоимость и в дальнейшем при закрытии склада, система не трогала такие складские проводки и не пыталась рассчитывать по ним истинную себестоимость. Так вот – подобный подход СИЛЬНЕЙШИМ образом противоречит идеологии метода расчета себестоимости, используемой в DAX. Истинная себестоимость списания ВСЕГДА должна рассчитываться системы на основании сопоставления приходных и расходных складских проводок.
После того как система посчитала истинную себестоимость за период, выполнение любых складских движений за этот период запрещается. Это вполне логично – поскольку вставка новых приходов или расходов задним числом, может привести к нарушению логики уже созданных сопоставлений приходов с расходами и появлению некорректной истинной себестоимости. Если нам нужно просто предварительно посчитать себестоимость списания для текущего состояния складских проводок, без блокировки дальнейших складских движений за период, достаточно вместо процедуры закрытия склада запустить процедуру пересчета.
Итак – закрытие склада состоит из следующих этапов:
1. Сопоставление приходных и расходный складских проводок. Расчет первичной себестоимости списания по расходным проводкам.
2. Прогон коррекции себестоимости по графу движения складских запасов
3. Разноска в ГК.
Рассмотрим каждый из этих этапов подробнее.
Сопоставление приходных и расходных проводок
Расчет истинной себестоимости списания в Аксапте всегда производится на основании сопоставления расходной складской проводки с приходной. При этом сопоставление происходит для проводок, с совпадающим кодом номенклатуры и аналитиками финансового склада.
Для модели FIFO или LIFO, правильность такого подхода вполне очевидна: Например если мы купили 2 штуки по 10 рублей и 5 штук по 14 рублей, то для того чтобы посчитать себестоимость списания 3 штук, надо сопоставить их с первым приходом общей суммой 20 рублей и одной штукой из второго прихода – суммой 14 рублей. Таким образом, в нашем примере, себестоимость списания по расходной проводке на 3 штуки составит 34 рубля. В дальнейшем ,я буду оперировать понятиями количества и суммы сопоставления. При этом сумма сопоставления рассчитывается как себестоимость приходной складской проводки на дату закрытия склада помноженная на отношения сопоставленного количества к количеству в исходной проводке. Естественно – сумма сопоставления округляется в соответствии с настройками округления в справочнике валют.
Как сопоставляются приходы и расходы по модели FIFO– интуитивно понятно, поэтому я не буду об этом подробно рассказывать. Единственное что стоит отметить - система не проверяет соответствие порядка приходов и расходов. Скажем – если мы провели по системе закупку изделия 15ым числом, а списание 5ым (предположим, что у нас никогда не было других движений по этому изделию). В этом случае – эти два прихода будут молча сопоставлены по модели FIFO, что вообще говоря – противоречит здравому смыслу. Подробнее о причинах появления отрицательного остатка и негативных последствиях этого явления я расскажу в разделах “Почему возникает отрицательный складской остаток?” и "Прогон себестоимости”
Модели LIFO или LIFO на дату в DAX было бы правильнее назвать LILO (Last In – Last out). Работают они как и FIFO , но только с точностью до наоборот. Система бежит по списку несопоставленных расходов, начиная с самого последнего и сопоставляет с каждым из них самый последний несопоставленный приход. “LIFO на дату” отличается от LIFO тем, что с расходом может сопоставляться только тот приход, который уже был на складе в момент выполнения расхода. Существование двух этих методов вызвано следующим соображением: Представим себе ситуацию, при которой у нас товар закупался 1ым,5ым и 31ым числами, а продавался, допустим, 10 и 18ым числами. С точки зрения строгой бухгалтерской идеи LIFO, у нас гарантировано должен быть списан товар, закупленный 31 числа (Поскольку по LIFO последние приходы списываются в первую очередь). С другой стороны – получается, что у нас отгрузка от 18 числа сопоставляется с приходом от 31ого – что выглядит как-то уж очень не интуитивно. Вот разработчики системы и реализовали эти два подхода, чтобы удовлетворить обе точки зрения. Первый из них – более правилен с бухгалтерской точки зрения , второй – с обывательской.
При изучении исходных текстов версии 2009, вскрылась забавная особенность. Похоже что использование механизма LILO было ошибкой. Программист писавший механизм LIFO, судя по всему, просто скопировал кусок кода с механизма FIFO и в ОБА оператора выборки проводок (и для приходных и для расходных) добавил ключевое слово reverse (вызывающее перебор результатов в обратном порядке -от более поздних дат к более ранним). В итоге - сопоставления шли в режиме LILO, то есть - более поздние расходы сопоставлялись с более поздними приходами, затем чуть более ранние расходы с чуть более ранними приходами и так далее. В версии 2009 эта ошибка была исправлена. Забавно, что несмотря на то что ошибка эта существовала примерно с лета 2003 года, исправлена она была только в году в 2007. Похоже что модель LIFO настолько непопулярна во всем мире, что эту ошибку просто никто не замечал 4 года.
Теперь рассмотрим сопоставление в режиме "Средняя” и “Средняя на дату”. В давние былинные времена – до выхода версии Axapta 3.0 sp2 (В локализованной версии – этот service pack ,был выпущен под номером 3), в этом режиме КАЖДЫЙ расход периода сопоставлялся с КАЖДЫМ несопоставленным приходом (в случае Средней на дату – с каждым несопоставленным приходом, с датой меньшей или равной дате расхода). Получалось ,что если скажем у нас на начало месяца на складе уже лежали два несопоставленных прихода и еще 6 приходов пришло за месяц, то каждый из расходов (а их у нас, допустим было 40 штук) сопоставлялся с каждым из этих 8 приходов. В результате – у нас в таблице сопоставлений образовывалось 8*40*2 записей (на два результат умножен из за некоторых особенностей архитектуры сопоставления при которой на каждое сопоставление рождается две записи). Нетрудно сделать вывод, что такой рост таблицы не способствовал быстрому выполнению процедуры закрытия. Поэтому в Axapta 3.0Sp2 был реализован некий компромиссный вариант расчета средней себестоимости. Был введен порог для количества или суммы для каждого сопоставления. Грубо говоря - система, при сопоставлении каждого из расхода с приходами, начинает процесс с тех из несопоставленных приходов, у которых больше несопоставленное количество и стремиться сопоставить каждый раз не менее порогового количества. Если по данному приходу или расходу несопоставленным осталось меньше порогового количества – система сопоставляет столько, сколько осталось – независимо от порога. Пороговое количество может быть задано несколькими способами:
1. Можно указать минимальный процент сопоставляемого количества в параметрах закрытия. В этом случае – система будет стремиться сопоставлять в одном сопоставлении не менее чем N% данного прихода.
2. В поле “минимальное среднее количество” номенклатурной карточке
3. В поле "Минимальное среднее количество” параметров модуля управления запасами. Данный параметр работает только для тех номенклатур, у которых не указанное значение по пункту 2.
Наконец – существует еще один способ косвенно ограничить сопоставляемое количество: Указав при закрытии склада минимальную сумму сопоставления, можно заставить систему стремиться не создавать сопоставлений с суммой сопоставления (то есть – долей себестоимости прихода, сопоставленной с расходом в данном сопоставлении) меньше заданного порогового значения.
Таким образом – в обычных ситуациях (закупочная цена не очень сильно меняется за период, закупки происходят значительно реже, чем продажи), алгоритм расчета средней будет давать очень незначительные отклонения от истинной средней стоимости, рассчитанной, грубо говоря, с помощью Excel, делением себестоимости всех несопоставленных приходов на количество. Но если представить себе ситуацию при которой мы обычно закупаем за период 2-3 партии по тысяче штук, продаем несколько тысяч партий по 1-2 штуки, но почему-то в данном конкретном периоде мы внезапно закупили дополнительно одну партию из 5 штук по цене в два раза выше обычной, то есть большие шансы что те несколько расходов , которые будут сопоставлены с этой странной партией будут иметь завышенную себестоимость списания.
Со времен написания оригинальной статьи в расчете средней произошло радикальное изменение. В версии 2009 она наконец-то считается по менее странному алгоритму. Если процедура расчета себестоимости обнаруживает что в текущем периоде у нас есть ТОЛЬКО ОДНА открытая приходная проводка, то все расходные проводки сопоставляются с ней и в целом все работает также как и раньше. НО: Если у нас в закрываемом периоде есть НЕСКОЛЬКО открытых проводок, то все работает совсем по другому:
Создается фиктивная операция переноса для данной номенклатуры и данного сочетания аналитик финансового склада. То есть - появляются операция списания и оприходования для данной номенклатуры, которые выглядят и обрабатываются закрытием склада как перенос, но по сути складскую аналитику не меняют; значения поля inventDimId у обоих проводок одинаково.
Все незакрытые приходы в периоде сопоставляются с расходом по переносу
Все незакрытые расходы в периоде сопоставляются с приходом по переносу.
Таким образом, фактически в себестоимости этого фиктивного переноса мы имеем себестоимость всех приходов за период, а за счет сопоставления всех расходных проводок с приходной проводкой по переносу, система будет рассчитывать себестоимость каждой из расходной проводок как пропорцию между количеством в расходной проводке b общим количеством открытых приходов помноженным на общую себестоимость приходов. Таким образом, наконец-то, Аксапта научилась рассчитывать среднюю себестоимость за период. Приведенное здесь описание относится к средней за период. Средняя на дату рассчитывается более хитрым способом. Фактически - расчет средней на дату сведен к алгоритму средней за период, при которой каждый новый приход создает очередной период. То есть - как только система натыкается на очередной приход, она создает новый фиктивный перенос и сопоставляет незакрытый остаток на приходе по старому фиктивному переносу со списанием по новому фиктивному переносу. Также с новым фиктивным переносом сопоставляются приходы на очередную дату и так далее.
В общем лично я не тестировал производительность получившейся схемы. Но из общих соображений думаю что закрытие по средней за период будет работать с примерной такой же скоростью что и закрытие по FIFO, закрытие по средней на дату будет проигрывать закрытию по FIFO процентов 20-30 (что все равно значительно меньший проигрыш, чем это было в старой схеме по средней).
Особо отмечу, что при расчете себестоимости для сопоставления используется сальдо приходной проводки на дату закрытия. То есть – если мы закупили товар 15 числом, потом 25 числом доначислили накладные расходы на закупку, то при закрытии склада 20ым числом для расчета суммы сопоставлении будет использоваться себестоимость из исходной закупки – без суммы накладных расходов.
Прогон себестоимости
Итак – сопоставление и приходных и расходных проводок закончен. Истинная себестоимость посчитана. Зачем нужен какой-то дополнительный этап?
Представим себе, следующую ситуацию:
· 1 января закупили номенклатуру на сумму 2000 рублей
· 5 числа – перенесли ее на соседний склад. Очевидно, что мгновенная себестоимость расхода, а вместе с ней и себестоимость прихода на соседний склад составит 2000 рублей
· 10 числа – продали номенклатуру с соседнего склада. Мгновенная себестоимость списания опять-таки составит 2000 рублей
· 20 числа – на первоначальную закупку доначислили расходы за транспортировку в сумме 400 рублей
· Модель FIFO
Если мы закрываем склад 30ым числом, то система у нас сопоставит расход от 5 числа с приходом от 1ого и посчитает истинную себестоимость списания по складскому переносу, равную 2400 рублям. Естественно – возникает задача как-то добиться того, чтобы себестоимость прихода на соседний склад и себестоимость списания с такового по продаже также была скорректирована до 2400 рублей.
Эта задача решается следующим образом:
При любом обновлении расходной складской проводки, система проверяет – нет ли для данного списания, связанного с ним прихода? (Связанные приходы существуют для складских проводок списания, порожденных из журнала переноса;журнала карантинного заказа; заказа на перемещение; производственного заказа; журнала спецификаций,заказа на перенос и фиктивного переноса . Кроме того – связанный приход существует для тех складских проводок расхода, у которых заполнен номер возвращаемого лота. ) Если данный расход действительно имеет связанный приход, то сумма коррекции записывается в таблицу “Коррекции уровня лота” (inventCostListTrans), вместе с номером лота связанного прихода и некоторой другой служебной информацией.
Далее на стадии прогона себестоимости, система считывает из этой таблицы информацию о корректируемых приходных проводках, проводит коррекцию себестоимости по этим приходным проводкам, а затем корректирует и себестоимости списания в расходных складских проводках, сопоставленных с корректируемой приходной проводкой. Если скорректированные подобным образом расходные проводки, в свою очередь также имеют связанные приходные проводки, то процесс прогона себестоимости повторяется.
Таким образом – процесс прогона себестоимости по сути, является итерационным. Фактически – система через таблицу складских сопоставлений строит граф движения материалов от их начального оприходования до окончательного списания, проходя при этом через все цепочки переноса и сборки номенклатуры.
Как определить число итераций прогона себестоимости, необходимое для полного закрытия склада ? Вопрос сложный. В теории – число итераций прогона себестоимости должно равняться максимальному числу переносов данной номенклатуры между разными складскими аналитиками через журнал переноса, плюс уровень вложенности спецификаций, плюс число переносов номенклатуры через журнал карантинного заказа и тп. В реальности – на число итерации кардинальным образом влияет наличие циклов в графе прогона себестоимости. Если их нет – закрытие отрабатывает за 5-7-10-15 итераций, если они есть, то время, требуемое для полной прогонки себестоимости становится труднопредсказуемым.
Почему в графе прогона себестоимости образуются циклы:
Представим себе следующую ситуацию
1 числа закупили 1 штуку номенклатуры по цене 200 рублей (зак1)
20 числа закупили еще 4 штуки - по цене 250 рублей (зак2)
Потом – задним числом провели 5ым числом перенос 2 штук со склада 1 на склад 2. (пер1)
Потом - задним числом перенесли 6ым числом эти две штуки обратно (пер2)
25 числа – все продали (зкз1)
Ну и наконец – 27ым числом доначислили 70 рублей накладных расходов на первую закупку.
Закрываем склад 31 числом по модели FIFO. Аналитика финансового склада - склад
До закрытия склада имеем следующую картину себестоимостей и количеств в проводках:
Документ |
Дата |
Количество |
Себестоимость |
Склад |
Зак1 |
01.01.2007 |
1 |
270 |
1 |
Пер1 |
05.01.2007 |
-2 |
-480 |
1 |
Пер1 |
05.01.2007 |
2 |
480 |
2 |
Пер2 |
06.01.2007 |
-2 |
-480 |
2 |
Пер2 |
06.01.2007 |
2 |
480 |
1 |
Зак2 |
20.01.2007 |
4 |
1000 |
1 |
Зкз1 |
25.01.2007 |
-5 |
-1200 |
1 |
По итогам сопоставления по модели FIFO имеем следующую картину:
|
|
|
| ||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
| ||||||
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Обратим внимание на то, что у нас в грАфе прогона себестоимости образовался цикл: списание по первому переносу сопоставилось с приходом по второму переносу. А приход по второму переносу, в свою очередь зависит от списания по первому переносу. В поле "Коррекция” для приходов и расходов будет указана сумма коррекции себестоимости, накопленная на данной итерации закрытия склада. Посмотрим - к чему это приведет. Далее – каждая таблица будет соответствовать одной итерации прогона себестоимости:
Итерация 1:
|
|
|
| ||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
| ||||||
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Итерация 2:
|
|
|
| ||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Итерация 3:
|
|
|
| ||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Итерация 4:
|
|
|
| ||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Итерация 5:
|
|
|
| ||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Итерация 6:
|
|
|
| ||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
| |||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Ну и так далее. Если бы у нас не было ситуации с переносом между складами при отсутствии товара на складе, то закрытие склада отработало бы за две итерации. В нашем же примере – на закрытие потребуется порядка 15-20 итераций. Кроме того – можно спрогнозировать, что в итоге у нас себестоимость списания по заказу так и не дотянет до правильного значения в 1270 рублей, при этом дельта будет списана с проводок по переносу в виде округления.
Таким образом, если возникла ситуация при которой в какой-то точке периода, за который мы закрываем склад, возникла ситуация отрицательного складского остатка, то это может привести к чрезмерно большому числу итераций по закрытию склада и недостаточной точности результата. Подробнее про причины этого явления написано в разделе "Почему возникает отрицательный складской остаток?”
Кроме того – стоит помнить, что при использовании модели средней себестоимости за период или на дату, у нас, в общем случае, КАЖДАЯ приходная проводка, сопоставляется с КАЖДОЙ расходной, соответственно - циклы в графе прогона себестоимости образуются даже если складской остаток не падал ниже ноля в течении закрываемого периода. Для версии DAX 2009 это соображение по прежнему справедливо. При закрытии склада по средней - у нас по прежнему все приходы сопоставляются со всеми расходами. То что теперь это делается не через множество складских сопоставлений, а через фиктивный перенос экономической смысл проблемы не изменяет.
Поскольку ситуация, при которой закрытие склада длится неопределенно долгое время, случается, к сожалению не так уж редко, в процедуре закрытия склада имеются два параметра, которые позволяют явно или неявно ограничивать число итераций прогона себестоимости.
К слову сказать, система не прогоняет себестоимость через складские проводки попавшие в процедуру "Корректировки в наличии". Если при прогонке себестоимости, система натыкается на подобную проводку, сумма коррекции немедленно списывается в округления и дальнейшая корректировка не проходит. Это логично - раз уж мы переоценили остаток, значит проводка с этим остатком начинает новую жизнь и все себестоимости предыдущих приходов никакого отношения к ней не имеют.
1. В параметрах закрытия склада имеется поле "максимальная пропускная способность”. Именно в этом поле и указывается максимальное число итераций, которое система может выполнить на стадии прогонки себестоимости. Если при выполнении процедуры прогонки себестоимости добрались до итерации, указанной в этом поле, то при выполнении этой итерации, коррекция себестоимости (пришедшая с предыдущей итерации в таблице InventCostListTrans) по данной приходной проводке не переносится на сопоставленный с данным приходом расход, а списывается с прихода на счет прибылей и убытков.
2. Также в параметрах закрытия склада имеется поле "Минимальная коррекция пропускной способности”. Если значение коррекции себестоимости, пришедшая с предыдущей итерации в таблице inventCostListTrans, меньше этого значения, то эта коррекция не переносится связанный с данным приходом расход, а просто списывается с прихода на счет прибылей и убытков. Хочу подчеркнуть, что этот параметр работает для КАЖДОЙ строки таблицы inventCostListTrans. То есть – если у нас в inventCostListTrans имеется три коррекции на 2,10 и 80 копеек, значение параметра "минимальная коррекция пропускной способности” установлена в 5 копеек, то списание в прибыли и убытки будет выполнено только для первой из коррекций. По остальным коррекциям итерации прогонки будут продолжаться.
Подробнее об списании коррекций – в разделе "О списании ошибок округления и корректировок при прогонке себестоимости.”
Имеется любопытная особенность прогонки себестоимости о которой я здесь не написал, но которая часто вызывает недоумение у пользователей. Классический вопрос - "У меня была закрытая проводка, но она каким-то образом оказалась модифицирована при закрытии склада. Почему ?" Ответ на этот вопрос состоит из нескольких шагов:
Проводка по складу помечается как закрытая при выполнении двух условий: Должны совпадать количество в проводке и сопоставленное количество; Должны совпадать себестоимость в проводке и сопоставленная себестоимость (то есть costAmountPosted+costAmountAdjustment==costAmountSettled). (если быть честным - требуется не абсолютное равенство, а лишь разница меньше чем некая дельта). Соответственно - если мы по каким-то причинам переоцениваем закрытую проводку (все равно - просто переоценкой лота в соответствующей форме или потому что у нас эта ЗАКРЫТАЯ проводка попала в цикл графа прогона себестоимости), такая проводка немедленно становится открытой.
На самом деле - еще до запуска процедуры сопоставления приходов и расходов, система сканирует все открытые и хотя бы частично сопоставленные по количеству приходные проводки, расcчитывает по ним разницу между себестоимостью и сопоставленной себестоимостью и отправляет найденную разницу во все тот же граф прогона себестоимости. (занимается этим, кстати, метод inventCostItemDim.updateReceiptAdjustment(), который вызывается ДО метода inventCostItemDim.updateModel(), который занимается собственно сопоставлением приходов и расходов.
Я когда-то задумывался, а нельзя ли было решить эту проблему по другому ? Например - при попытке корректировать закрытую проводку, вместо всяких прогонок себестоимости автоматически списывать сумму коррекции на счет прибылей и убытков. Ну то есть - откорректировали себестоимость проводкой Д 10 К 25.транзит и тут же списали ее проводкой Д 91. округления К 10. Однако у этого подхода есть один очень существенный минус. Если наша приходная проводка закрыта, это совсем не означает что товар уже совсем окончательно и безповоротно продан или списан за пределы хозяйствующего субъекта. Его запросто могли просто переместить на соседний склад. Тогда у нас есть закрытые друг на друга проводки закупки и списания по переносу и вполне себе открытая проводка прихода на соседний склад. Теперь - если мы доначисляем накладные расходы на транспортировку и растаможку (а они иногда могут быть сопоставимы с закупочной стоимостью), они у нас, при подобном подходе, не попадут на себестоимость окончательного списания за пределы организации (то есть продажи скорее всего), а автоматически попадут на счета прибылей и убытков в том периоде в котором мы закупили. В итоге - маржа по продаже, которая у нас когда-то все-таки случится, будет неправдоподобно высокой, поскольку в затраты по продаже не попали заготовительные расходы. Так что - похоже что альтернативы предложенному подходу просто не существует.
Ну и в завершение темы, следует сказать, что одной из дополнительных составляющих прогона себестоимости в версии 2009, стала коррекция и перерасчет непрямых затрат по производственным заказам. Об этих непрямых затратах и их вычислении я напишу ниже, в секции " Структура себестоимости и непрямые затраты ".
Разноска в ГК
Прежде всего, хочу отметить, что разноска в ГК выполняется только в том случае, если в параметрах закрытия склада была установлена галка "Обновить главную книгу». Смысла не устанавливать эту галку я не вижу, поскольку начиная еще с версии Axapta 2.5Sp1Hf1, разноска стала выполняться достаточно быстро по сравнению со всеми остальными этапами закрытия склада и отключение этой галки не приводит к серьезному повышению быстродействия этой процедуры.
На предыдущих этапах закрытия склада была заполнена таблица складских сопоставлений (InventSettlement) в которой помимо собственно сопоставленных количеств, сопоставленных сумм и сумм коррекций находится также информация, необходимая для создания бухгалтерских проводок по выполненной коррекции – счет, коррсчет, аналитика, разноска для счета и коррсчета. Поэтому процедура разноски закрытия склада работает достаточно просто. Она группирует суммы коррекции в разрезе этих бухгалтерских параметров, а потом делает соответствующие проводки в ГК. Поэтому – нет особой необходимости в процедуре, которая бы позволяла выверять проводки в ГК по закрытию склада со складскими проводками. Вся информация по счетам аналитикам и тп находится в таблице складских сопоставлений. Если у вас при закрытии склада сгенерировались проводки в ГК с какими-то неожиданными аналитиками или счетами – ищите эти счета и аналитики в таблице сопоставлений. Ну а из этой таблицы уже можно перейти к исходным складским проводкам и попытаться понять – на основании каких операций были сгенерированы эти странные проводки.
Кроме того – на разноску влияет параметр процедуры закрытия склада, называющийся спецификация. В оригинальной (нелокализованной) версии DAX, этот параметр позволял процедуре разноски группировать проводки не только по бухгалтерским параметрам, но и по коду номенклатуры, по которой была выполнена коррекция или коду ее номенклатурной группы. То есть – даже если операции с разными номенклатурами породили коррекции с одинаковыми аналитиками, счетами и корсчетами, в ГК эти коррекции попадали двумя отдельными проводками. К сожалению – этот режим оказался несовместимым с механизмом корреспонденции счетов, используемым в российской версии DAX. Поэтому – если режим корреспонденции счетов включен, то группировка проводок по номенклатуре или номенклатурным группам невозможна. У меня этот факт не смущает, поскольку, как я уже сказал, выверять проводки в ГК по процедуре закрытия гораздо удобнее и легче с помощью таблицы складских сопоставлений (inventSettlement).
Любопытный момент: Как система догадывается что для корректировок тех складских проводок, которые не порождали разносок в главную книгу (например - складских проводок переноса) не надо делать проводок в главную книгу и в момент закрытия склада ? В момент разноски исходной складской проводки, система записывает в связанную со складской проводкой таблицу InventTransPosting информацию о счете и коррсчете разноски, использовавшейся аналитике и, самое главное, о том была ли вообще какая-то разноска в главную книгу (поле isPosted), или счета и аналитика были заполнены исключительно для общности. Далее - при создании сопоставлений и коррекций складской проводки при закрытии склада, система использует для заполнения счета, коррсчета и финансовой аналитики данные из inventTransPosting. При этом, если по исходной складской проводке не было создано проводок ГК (isPosted==NoYes::No), то в складском сопоставлении (inventSettlement) поля счетов и разносок не будут заполнены и класс, разносящий складские сопоставления эти строки таблицы просто проигнорирует.
Кроме того, в версии 2009 кроме разноски коррекций в главную книгу, система на заключительном этапе рассчитывает и списывает отклонения между стандартной и фактической себестоимостью (для новой модели стандартной себестоимости), возникшие в результате прогонки коррекций. Подробнее об этом написано в разделе "Новая стандартная себестоимость"
О параллельном закрытии склада с нескольких рабочих мест.
Наиболее значительным изменением в процедуре закрытия склада со времен выхода первой локализованной версии 2.1 в 2000ом году, стала реализация параллельного закрытия склада с нескольких рабочих станций впервые выпущенная в составе международного Service Pack 2 для Axapta 3.0. На мой взгляд - если до выхода этого пакета обновления низкая производительность процедуры закрытия склада иногда становилась неразрешимой проблемой, то с его выходом – большая часть проблем производительности была снята.
Итак – рассмотрим процедуру закрытия склада в многопользовательском режиме поподробнее:
Того клиента DAX, на котором была первоначально запущена процедура закрытия склада мы в дальнейшем будем называть мастер-клиентом. Всех остальных клиентов – клиентами – помощниками. Напомню, что запуск клиента-помошника выполняется нажатием кнопки Рассчет->Помошь в расчете. При этом в списке закрытий склада должно быть выбрано незаконченное закрытие склада.
В момент запуска процедуры закрытия склада на мастер-клиенте, он записывает параметры закрытия склада в таблицу “Закрытие запасов” (InventClosing) для того, чтобы эту информацию могли бы потом использовать клиенты-помощники. После этого мастер-клиент пишет в таблицу “Список рассчета”(inventCostList) коды той номенклатуры, по которой имеются незакрытые операции. Далее – и мастер-клиент и клиенты-помошники начинают сопоставлять открытые складские проводки по каждой из номенклатур, попавших в эту таблицу. Данный процесс считается нулевой итерацией закрытия склада. Как я уже написал в разделе "Прогон себестоимости”, во время сопоставления, система порождает записи в таблице InventCostListTrans, которые в дальнейшем будут использоваться для итераций прогона себестоимости. Надо сказать, что если в таблицу inventCostListTrans попала хотя бы одна запись для некоторой номенклатуре, то запись об этой номенклатуре попадает также и в таблицу InventCostList. Это необходимо для координации совместной работы нескольких клиентов на стадии прогона себестоимости.
На стадии прогона себестоимости, система действует по похожему алгоритму. Каждый клиент бежит по таблице InventCostList и по каждой из попавших в эту таблицу номенклатур обрабатывает коррекции себестоимости (прогоняет их по графу сопоставлений), попавшие в таблицу inventCostListTrans на прошлой итерации закрытия склада.
Разноска закрытия склада в ГК выполняется только с мастер-клиента.
Если во время выполнения закрытия склада нажать кнопку Рассчет->Останов расчета, то закрытие склада будет приостановлено. После этого можно будет повторно нажать кнопку Рассчет->Помощь в закрытии склада и закрытие склада будет продолжена. В этом случае – мастер-клиентом станет тот клиент, который первый присоединится к незавершенной процедуре закрытия склада.
Если вы используете при параллельном закрытии склада больше 2-3 клиентов, то стоит контролировать загрузку сервера БД и AOS. После того как одна из них добралась процентов до 60-70, подключать к закрытию новых клиентов уже не имеет смысла. Кроме того, опыт показывает, что хотя классы закрытия склада выполняются на клиенте, с одной рабочей станции можно запустить 2 или даже 3 клиента, поскольку часть времени они не создают нагрузку на процессор, поскольку ждут завершения операции на AOS или БД. Хочу также заметить, что на стадии сопоставления система создает значительно бОльшую нагрузку на сервер БД чем на стадии прогонки себестоимости. Поэтому – до момента начала прогонки себестоимости не стоит подключать к процедуре закрытия склада слишком большое количество клиентов.
Хотя авторы системы многопользовательского закрытия склада предназначали таблицы inventCostList и inventCostListTrans исключительно как некий внутрисистемный инструмент для взаимодействия между несколькими клиентами, в качестве побочного эффекта – эти таблицы являются прекрасным инструментом для мониторинга процесса закрытия склада. Поэтому прежде чем приступить к обсуждению того, как этот процесс мониторить – давайте посмотрим структуру данных таблиц:
InventCostList
Поле |
Описание |
ItemId |
Код номенклатуры по которой требуется выполнять закрытие |
Voucher |
Номер документа ГК – используется для связи с таблицей закрытия (inventClosing) |
CostNum,NumOfIteration |
Поля с обратными по отношению к друг другу значениями: NumOfIteration – номер текущей итерации прогонки себестоимости; CostNum – обратное значение. Грубо говоря – максимальное число итераций минус номер текущей итерации. |
InventCostListTrans
Поле |
Описание |
ItemId |
Код номенклатуры по которой требуется прогонка себестоимости |
InventTransId |
Номер приходного лота, на который требуется перенести коррекцию на очередной итерации прогонки себестоимости |
InventTransIdReturn |
Номер возвращаемого лота. Используется в тех случаях, если при возврате заказа (точнее – вообще складского списания) был указан номер возвращаемого лота (то есть – исходного лота по которому было выполнено списание). Для повышения производительности закрытия склада этот случай обрабатывается специальным образом: Вместо того чтобы брать сумму коррекции на данной итерации из поля adjustment, система просто рассчитывает себестоимость исходной проводки списания и приравнивает к ней себестоимость возврата |
VoucherPhysical |
Используется только для возвратов и карантинного заказа. Нужен для корректной связи с исходной проводкой, из которой был порожден данный возврат или карантинный заказ. |
Voucher |
Номер документа ГК – используется для связи с таблицей закрытия (inventClosing) |
NumOfIteration |
Номер итерации прогонки себестоимости. Нужен для того чтобы координировать действия клиентов и какой-нибудь клиент не начал обрабатывать итерацию N+1, пока все остальные трудятся еще над итерацией N |
Adjustment |
Собственно – сумма коррекции, которую нужно провести на по данной складской проводке на данной итерации. |
Считается, что стадия сопоставления приходов и расходов соответствует номеру итерации ноль. На этой итерации в таблице inventCostListTrans записей нет.
В версии 2009 подход к технической организации многопользовательского закрытия склада. Прежде всего - это связано с переходом на использование технологии нового пакетного сервера. Хотя тема эта интересная и сама по себе заслуживает отдельной статьи, я все-таки кратко рассмотрю основные идеи, стоящие за новой технологией пакетного сервера.
В версии 2009 пакетные задания исполняются не на выделенном компьютере (компьютерах) а на сервере приложений. Сделано это не из стремления сэкономить клиенту 800 долларов на выделенном пакетном сервере, а для того чтобы предоставить среду распараллеливания трудоемких вычислений. Понятно что число ядер в процессоре будет увеличиваться быстро, а производительность каждого ядра будет расти в лучшем случае на десятки процентов на каждое удвоение числа ядер. Поэтому если мы хотим обеспечить задел по производительности, необходимо подумать о возможности распараллеливать трудоемкие алгоритмы Dynamics AX на несколько нитей. Гораздо логичнее реализовать этот механизм на базе сервера приложений (который и так уже и многопользовательский и многопоточный), чем на базе старого пакетного сервера, выполняющегося на обычном клиенте DAX. Старый клиент и старый пакетный сервера, в принципе, остались, но понятно что они потихоньку отживут.
Новая пакетная инфраструктура поддерживает программный API для постановки заданий в очередь. (Класс BatchHeader). Можно указывать зависимости между заданиями или создавать несколько однотипных заданий исполняемых в паралель.
Новый API поддержан для некоторых особо вычислительно сложных задач. На мой взгляд - нормальная поддержка сделана для закрытия склада и сводного планирования, более или менее приличная - для разноски журналов главной книги и, в общем, достаточно рудиментарная - для разноски документов по заказу и закупке (насколько я понял - там она играет только в случае, когда мы разносим много накладных по множеству заказов/закупок. Время разноски ОДНОЙ накладной по ОДНОЙ многострочной закупке или заказу этот механизм не уменьшит. Впрочем - заказы и закупки на несколько тысяч строк встречаются довольно нечасто, так что возможно я просто придираюсь.)
В нашем конкретном случае, для того чтобы распараллелить закрытие склада в версии 2009, нам достаточно в параметрах модуля управления запасами указать группу пакетных заданий для запуска помощников (Helpers Batch Group) и число помощников (Extra Batch Helpers). Рекомендации по настройке числа помощников мне дать трудно,(все таки я на практике этот механизм не применял), но я бы начал эксперименты с числа ядер на сервере приложений, помноженного на два. Естественно - при этом в настройке пакетного сервера в форме администрирования серверов (Administration->Setup->Server Configuration->Batch Server Schedule) надо установить число нитей, не меньшее чем число помощников. (Конечно если у вас сервер в это время загружен обычной пользовательской нагрузкой - можно поставить число нитей и поменьше. Просто реальное число активных помощников будет равняться минимуму из значения в параметрах управления запасами и числом нитей для данного временного интервала в форме администрирования серверов).
Старый пользовательский интерфейс для останова/возобновления процедуры закрытия остался на месте, просто он теперь останавливает процессы-помощники на пакетном сервере, а не на обычных рабочих местах как в старой версии.
К слову сказать в версии 2009 и отмена закрытия склада происходит в параллельном режиме.
Как мониторить процесс закрытия склада?
Самый правильный способ мониторить закрытие склада –сразу после того как это закрытие было запущено, открыть в соседнем окне еще одного клиента DAX и залезть с помощью браузера таблиц в таблицу inventCostListTrans
Если отсортировать записи в этой таблице по номеру итерации, то сразу можно увидеть - какая итерация у вас сейчас идет. Как правило, при сколько-нибудь серьезной логистике, меньше чем итераций за 5-7 склад просто не закрывается. Если у вас склад закрывается за меньшее время, то скорее всего проблемы производительности этой процедуры вас просто не волнуют и вы можете пропустить остаток этого раздела. В течении этих самых первых 5-7 итераций, количество записей в таблице inventCostListTrans резко падает (обычно – с нескольких тысяч до пары сотен или даже нескольких десятков). После того, как эти итерации прошли – есть смысл начинать пристально следить за содержимым этой таблицы. Допустим – вы заметили пару лотов, по которым коррекция себестоимости в поле adjustment с каждой итерацией падает как-то уж слишком медленно. Скажем – если коррекция равняется 600 рублям и с каждой итерацией она уменьшается всего на 10 копеек – самое время попытаться понять – не случилось ли по данной номенклатуре какой-то беды с данными. Скорее всего – причина такого медленного прогона себестоимости состоит в том, что у вас остаток по данной номенклатуре в какой-то точке закрываемого периода падал ниже ноля, в результате чего в грАфе прогона себестоимости образовался цикл. Что делать в подобной ситуации ?
Если вы перфекционист или сумма прогоняемой коррекции как-то уж слишком высока (скажем – 6000 при месячном объеме продаж в 6000000), то нужно записать номера проблемных лотов на бумажку, после чего остановить процедуру закрытия склада, отменить незавершенное закрытие склада и начать разбираться с проблемными лотами. Если вы нашли ситуации отрицательного остатка, то нужно их аккуратно отсторнировать, примаркировать (или указать номер возвращаемого лота)неправильное списание к его сторно и попробовать повторить попытку закрытия склада. (Про маркирование и номера возвращаемых лотов подробнее написано в разделе "О возвратах”
Если же вы обнаружили что сумма прогоняемой коррекции небольшая по сравнению с оборотами за период (допустим 500 при обороте в 50000000, то можно просто подкорректировать (в браузере таблиц) поле MinTransferValue в таблице InventClosing. Это то самое поле, в котором хранится максимальная коррекция пропускной способности, заданная вами в начале процедуры закрытия склада. Если вы укажете там значение, большее чем максимальная из оставшихся в прогоне коррекций, то но очередной итерации прогонки себестоимости, зависшие коррекции просто будут списаны на счета прибылей и убытков. На реальный финансовый результат фирмы это не повлияет, но может слегка подпортить картину прибыльности (или убыточности) отдельных продаж.
Вот почему так важно, чтобы оценочная себестоимость списания не слишком сильно отличалась от истинной себестоимости. Ведь если разница была слишком большой, то в случае образования циклов в графе прогонки себестоимости, каждые несколько дополнительных рублей разницы могут обойтись вам в несколько минут дополнительного времени закрытия.
Кроме того, могу посоветовать разработать какой-нибудь отчет, который пробегал бы по открытым проводкам и выдавал сообщение о переходе остатка через ноль. Если вы будете запускать подобный отчет перед каждым закрытием, то у вас появится возможность привести данные в порядок, без необходимости ждать пока закрытие доберется до 20ой итерации.
В заключение хочу отметить, что существуют определенные ситуации, в которых переход остатков через ноль вызван не ошибками в вводе данных, а объективной спецификой бизнеса. В таком случае – единственный способ бороться с производительностью закрытия склада - это корректировка поля MinTransferValue, о которой я писал чуть выше. Конечно – в этом случае мы не сможем получить корректную себестоимость списания по каждой складской проводке, но по большому счету, в такой ситуации эта себестоимость и не будет иметь особого экономического смысла.
О пересчете склада
В DAX имеется не только процедура закрытия склада, которая рассчитывает окончательную себестоимость списания по каждой складской проводке за период (блокируя при этом дальнейшие складские движения в этом периоде), но и процедура пересчета склада. Эта процедура отличается от закрытия склада следующим образом:
1. Сопоставление приходов и расходов строится “в памяти”, то есть – данные о сопоставлении приходов и расходов не записываются в таблицу складских сопоставлений.
2. В случае необходимости корректировки себестоимости по расходной или приходной проводке, запись в inventSettlement создается, но в ней не заполняются сопоставленные количества и суммы. Происходит заполнение только суммы коррекции.
3. Как следствие первых двух пунктов –перед КАЖДОЙ итерацией прогонки себестоимости происходит операция сопоставления "в памяти” приходных и расходных проводок. Это логично – ведь если мы не пишем в таблицы БД данные о сопоставлении, то перед каждой прогонкой себестоимости нам нужно заново создать в памяти данные о сопоставлении, чтобы понять – с каким расходом сопоставлен корректируемый на данной итерации приход.
4. Можно проводить пересчет склада по отдельной номенклатуре или номенклатурной группе. Нужно только помнить о том, что если данная номенклатура списывалась по журналу спецификаций или журналу производства, то коррекция может затронуть и ту номенклатуру, в производство которой была списана данная номенклатура.
5. В отличие от закрытия склада – система не списывает при пересчете склада погрешности округления.
Если мы хорошенько подумаем о смысле 3 пункта, то можно сделать вывод о том, что пересчет склада будет почти гарантировано работать медленнее, чем закрытие. Ведь перед каждой итерацией прогонки себестоимости, мы вынуждены заново проходить стадию сопоставления приходных и расходных проводок. Мы конечно сэкономим немного времени за счет того что мы не тратим на каждой итерации время на запись данных по сопоставлению в таблицу БД, но скорее всего – сэкономленное время будет с лихвой перекрыто дополнительным временем, нужным на повторяющееся сопоставление.
Отмена закрытия склада
Алгоритм процедуры отмены закрытия/пересчета склада достаточно очевиден:
Система пробегается по старым (сделанным по закрытию/пересчету) записям в inventSettlement и помечает их как отмененные
Система копирует старые записи в новые (то есть записи отмены закрытия), инвертируя знак сопоставленного количества, сопоставленной суммы и суммы коррекции
Система вычитает из полей исходной складской проводки сумму сопоставления, сопоставленное количество и сумму коррекции (если сопоставление было сделано по проводке в статусе физической операции - вычитается только сумма коррекции, причем вычитается из физической суммы по проводке)
Порожденные складские сопоставления разносятся процедурой разноски складских сопоставлений, похожей на описанную в разделе "Разноска в ГК". До версии 2009 и закрытие и пересчет и коррекция и отмена всех этих процедур выполнялась одним и тем же классом. В версии 2009 классы разноски операции и классы разноски отмены операции разделили. (Точнее говоря - класс разноски отмены отнаследован от класса разноски операции). Это связано с тем что в 2009 версии, процедуры закрытия склада нагружены дополнительной семантикой разноски отклонений (для новой стандартной себестоимости) и непрямых затрат по производственным заказам (для непрямых затрат) и сохранить полностью единую логику для обоих классов стало невозможным.
Наверное, стоит также добавить, что процедуры отмены закрытия склада также используется и для отмены коррекции себестоимости запасов в наличии или себестоимости складских проводок.
Партионная себестоимость
Нигде в Dynamics AX нет специальной настройки для включения режима партионной себестоимости. Тем не менее – если в настройках группы складских аналитик поставить галку "Финансовый склад" для складских аналитик "Номер партии" или "Серийный номер", то при закрытии склада по ЛЮБОЙ модели мы и получим режим партионной себестоимости, поскольку сопоставление приходов и расходов будет идти только в рамках одного номера партии или серийного номера. Разумнее всего, для той номенклатуры, у которой включен партионный учет, ставить модель закрытия склада по FIFO, поскольку с технической точки зрения она быстрее всего работает.
Один лот – одна себестоимость
Архитектура модуля расчета себестоимости в DAX неявным образом предполагает что элементарной единицей списания или приходования себестоимости является один складской лот. То есть – даже если по одному лоту у нас имеется несколько строк в таблице складских проводок, при расчете с себестоимости система все равно будет считать все себестоимость по лоту в целом.
С первого взгляда – подход логичный, но он содержит в себе некую ловушку, в которую регулярно попадают начинающие внедренцы. Допустим – мы включили по одной из номенклатур партионный учет (включаем нужную галку в настройках групп складской аналитики) и у нас имеется две партии товара по 10 рублей (2шт) и 15 рублей (3шт).
Далее, создаем журнал переноса на 5 штук. Как известно, если мы не указали в настройках групп складской аналитики номер партии, как первичную аналитику, то мы можем не вводить номер партии в строки журнала переноса. При выполнении резервирование по такому журналу, система автоматически разобьет приходные и расходные складские проводки, подставив в каждую из них нужный номер партии. Это удобно для пользователей, поскольку позволяет им не задумываться над подбором номера партии, если в данной ситуации нас не волнует то, какие именно партии будут списаны.
Только вот после разноски такого журнала, мы обнаружим, что во всех приходных проводках стоит одинаковая цена - 13 рублей. В момент разноски журнала, система использует себестоимость списания ЛОТА с первого склада в качестве себестоимости прихода на второй склад. Себестоимость списания лота с первого склада – 65 рублей. Соответственно – при расчете себестоимостей прихода по двум складским проводкам данного лота будет использоваться эта сумма, которая будет распределена между приходными складскими проводками пропорционально их количествам. Закрытие склада не исправит ситуацию, поскольку коррекция себестоимости в таблице InventCostListTrans также привязана к номеру лота, соответственно – на стадии закрытия коррекция будет распределена по приходным проводкам пропорционально их количествам. Вот именно в такой ситуации и возникают жалобы на тему "Аксапта испортила мою себестоимость".
Когда я первый раз столкнулся с подобной ситуацией, я с наскока пытался решить ее, переделав процедуру разноски журнала переноса и закрытия склада. Только вот оказалось, что не очень понятно – как решать эту задачу в общем случае. Например – мы ведь можем по переносу списывать несколько партий и создавать из них одну. Как мы вообще должны обрабатывать ситуацию, при которой у нас в результате переноса меняется не только аналитика физического склада (например - склад или номер ячейки), но и аналитика финансового склада?
Возникновение подобной ситуации, говорит о попытке решить бизнес-проблему айтишным путем. Ведь если вдуматься – ситуация абсурдная: С одной стороны - бухгалтерия пытается вести партионный учет. С другой стороны – бизнес-процессы организованы так, что логистики не знают или не хотят знать – какой номер партии им подставлять и пытаются добиться, чтобы эти номера система подставляла автоматически. В такой ситуации, существует два варианта решения проблемы:
1. Можно настаивать на том, чтобы пользователи вводили номера партий в ручную. В такой ситуации спонсор проекта со стороны заказчика должен жестко разъяснить пользователям – почему так важна партионная себестоимость.
2. Можно настаивать на том, чтобы бухгалтера перешли на использование модели FIFO. Если уж все так хотят чтобы система автоматически подставляла номера партий – так пусть и подставляет. Если аналитика "Номер партии" не будет указана как аналитика финансового склада, то после закрытия склада мы и получим информацию, связывающую расходы с номерами партий в приходах. Только в данной ситуации у нас в складской аналитике расходных проводок не будет номеров партий; Нам придется собирать эту информацию из таблицы складских сопоставлений, связывающей расходы с приходами.
Складские сопоставления и как с ними работать.
В данном разделе пойдет речь о таблице складских сопоставлений (inventSettlement). Строго говоря – использование термина "сопоставление” (Settlement) в названии этой таблицы, поскольку, эта таблица несет двоякий смысл: она может описывать и сопоставление прихода с расходом, и коррекцию себестоимости. Хотя для расходных складских проводок коррекция себестоимости ВСЕГДА порождается на основании сопоставления, для приходных складских проводок коррекция себестоимости происходит совершенно независимо от сопоставления с расходными. Кроме того – если коррекция себестоимости расхода делается на основании пересчета, а не закрытия склада, то коррекция себестоимости будет помещена в эту таблицу без данных о сопоставленном количестве. Давайте внимательнее рассмотрим список полей этой таблицы:
Поле |
Описание |
TransRecId |
Ссылка на складскую проводку к которой относится данная запись |
InventTransId |
Номер лота, по которому прошла коррекция/сопоставление. |
Itemid |
Код номенклатуры по которой прошла коррекция/сопоставление |
ItemGroupId |
Номенклатурная группа номенклатуры по которой прошла коррекция/сопоставление |
TransDate |
Дата коррекции/сопоставления |
Voucher |
Документ ГК, по которому прошла коррекция/сопоставления |
SettleTransId |
Идентификатор коррекции сопоставления. По большому счету – некий внутренний счетчик. Поле очень любопытно в том случае, если данная запись была порождена закрытием склада. В этом случае- записи в этой таблице, относящиеся к расходной и приходной проводке будут иметь одинаковое значение этого поля. |
QtySettled |
Сопоставленное количество. В том случае если данная запись порождена корректировкой прихода или корректировкой расхода, порожденной пересчетом (не закрытием) склада, то данное поле будет пустым. |
CostAmountSettled |
Сопоставленная сумма. В том случае если данная запись порождена корректировкой прихода или корректировкой расхода, порожденной пересчетом (не закрытием) склада, то данное поле будет пустым. |
CostAmountAdjustment |
Сумма коррекции. Если в результате сопоставления при закрытии склада коррекция себестоимости расхода отсутствовала – поле нулевое. Если запись связанна с приходом и порождена закрытием склада – поле нулевое. |
BalanceSheetAccount |
Складской счет, на который будет разнесена данная коррекция |
OperationsAccount |
Коррсчет, на который будет разнесена данная коррекция. Обычно – счет затрат (operations) |
BalaceSheetPosting |
Тип разноски в ГК, который будет использоваться при разноске на складской счет |
OperationsPosting |
Тип разноски в ГК, который будет использоваться при разноске на корсчет. |
Dimension |
Финансовая аналитика которая будет использоваться при разноске в ГК |
Canceled |
Признак отмены данного сопоставления. Если вы отменяете закрытие или пересчет склада - у исходного складского сопоставления в это поле ставится true. Фактически – это признак сторнирования сопоставления |
SettleModel |
Принцип сопоставления. В первом приближении – соответствует складской модели. |
Posted |
Признак того, что информация по данной строке была обработана модулем разноски в ГК |
SettleType |
Тип сопоставления. |
При выполнении закрытии склада информация о сопоставлении заносится в эту таблицу следующим образом: и для сопоставляемого расхода и для сопоставляемого прихода создается запись в таблице складских сопоставлений, при этом поле SettleTransId у этих двух записей будет одинаковым.
В случае коррекции себестоимости прихода (например – при разноске накладных расходов по накладной или переоценке складских запасов) – создается одна запись. Аналогичным образом – одна запись создается и при коррекции себестоимости расходных проводок (при пересчете склада).
Поля, связанные с разноской в ГК заполняются информацией из таблицы складской разноски (InventTransPosting), связанной с корректируемой (сопоставляемой) складской проводкой. Если запись в этой таблице по каким-то причинам отсутствует – в качестве складского счета берется счет складского прихода или расхода из таблицы настроек складской разноски по данной номенклатуре, в качестве корсчета – счет прибылей и убытков из той же таблицы. Отмечу что в саму таблицу inventTransPosting информация попадает из финансовой аналитики исходного документа и остается в данной таблице даже если этот документ удален.
Надо сказать – что далеко не всегда коррекция себестоимости приводит к необходимости делать проводки в ГК. Скажем – при коррекции себестоимости прихода или расхода по журналу переноса никакой проводки в ГК делать не надо – поскольку по исходному журналу – такой проводки тоже не было. Этот момент отслеживается с помощью поля isPosted в таблице InventTransPosting. Если данное поле не было установлено в true, то счета и корсчета в inventSettlement не заполняются и подсистема разноски сопоставлений в ГК эти коррекции/сопоставления игнорирует. Так что если вы занимаетесь выверкой проводок в ГК по закрытию с себестоимостью, то следует игнорировать записи с пустыми счетами и корсчетами. Наконец – хочу уточнить что поле posted в таблице inventSettlement не имеет никакого отношения к факту разноски или неразноски данного сопоставления(коррекции) в ГК. Это поле заполняется в момент обработки сопоставления классом разноски сопоставлений в ГК и говорит только о том, что эта запись была обработана этим классом.
Немного о складских проводках
Имеет смысл рассказать о тех полях в складских проводках, которые относятся к себестоимости и закрытию склада.
Поле |
Описание |
CostAmountPosted |
Для приходных складских проводок – себестоимость прихода (в случае закупки – без учета накладных расходов). Для расходных проводок – оценочная себестоимость списания |
CostAmountAdjustment |
Сумма корректировки себестоимости. ВСЕГДА должна равняться сумме поля costAmountAdjustment складских сопоставлений, связанных с этой проводкой. |
CostAmountSettled |
Сума сопоставленной по закрытию склада себестоимости. У закрытых складских проводок – равняется сумме costAmountPosted и costAmountAdjustment |
Qty |
Количество по складскому документу. Всегда считается в тех единицах измерения, которые были указаны как единица измерения склада |
qtySettled |
Сопоставленное по закрытию склада количество |
costAmountPhysical |
Себестоимость по физическим операциям (попросту говоря – по отборочным накладным). Для проводок списания – почти всегда равняется оценочной себестоимости. Для приходных проводок – себестоимости из приходного документа. Вообще – использовать значения из этого поля для каких-то отчетов – дурной тон. Оно предназначено чтобы ПРИБЛИЗИТЕЛЬНО (плюс-минус трамвайная остановка) оценить складские движения по которым не было еще проведено реальное финансовое складское движение. |
CostAmountStd |
Стандартная себестоимость, для данной номенклатуры. Если для данной номенклатуры не включен в группе складских моделей флажок стандартной себестоимости – поле нулевое. |
CostAmountOperations |
Для расходов – не заполняется. Для приходов – почти всегда равняется costAmountPosted с обратным знаком. Единственное исключение – закупка номенклатуры, у которой выключен в группе складских моделей флажок "Разносить финансовые операции”. В этом случае – в costAmountPosted пишется ноль, а в costAmountOperations – закупочная себстоимость. В общем случае – в этом поле хранится сумма по корсчету складской операции. |
ValueOpen |
Признак того – закрыта данная складская проводка или не закрыта. Закрыта может быть только финансовая складская проводка. Проводка помечается как закрытая только в том случае, если значение costAmountSettled равняется истинной себестоимости (сумме полей costAmountPosted и costAmountAdjustment ) и разница между qty и qtySettled меньше чем 0.00000001 |
DateClosed |
В момент когда проводка помечается как закрытая, система автоматически заполняет это поле датой последнего складского сопоставления, связанного с данной складской проводкой. |
О возвратах
В системе существует два принципиально разных механизма создания возвратов.
· Для возврата списаний, используется механизм под названием "номер возвращаемого лота”. Этот номер можно указывать, например, в строках заказов или складских проводок. Затем – этот номер возвращаемого лота копируется в складскую проводку по данному возврату (в поле inventTransIdReturn). В момент закрытия склада, наткнувшись на проводку прихода, у которой это поле заполнено, система на итерации прогона себестоимости корректирует себестоимость данного прихода до себестоимости возвращаемого расхода (естественно – с обратным знаком).
· Для возврата приходов используется механизм маркировки складских проводок. Именно об этом механизме я и расскажу поподробнее в оставшейся части раздела.
Итак – система позволяет примаркировать приходные и расходные проводки друг к другу. При этом, в связанные приходные и расходные проводки в поле inventrefTransId записывается ссылка на связанную проводку. При закрытии склада или пересчете, подобные примаркированные проводки не попадают в стандартные процедуры сопоставления приходных и расходных проводок и прогонки себестоимости. Вместо этого, подобные проводки сопоставляются непосредственно друг с другом независимо от используемой складской модели. Как следствие этого – себестоимость списания расходной проводки становится гарантировано равной себестоимости приходной проводки. Следует отметить, что и при маркировании, и при обработке примаркированных проводок в момент закрытия или пересчета склада, система проверяет совпадение складской аналитики финансового склада. То есть – если у вас, например, номер партии является аналитикой финансового склада, то примаркировать проводки с разными номерами партий вам не удастся. Даже если каким-то образом (например – модификацией полей в inventTrans напрямую) подобные проводки будут примаркированы друг к другу, то в момент закрытия/пересчета склада, система отменит подобные маркировки, очистив поле inventRefTransId в складских проводках.
Маркировать проводки можно как до их финансовой разноски, так и после. Нельзя маркировать открытую складскую проводку к закрытой, поскольку в момент закрытия/пересчета склада такие проводки не будут сопоставлены и смысл в маркировании теряется.
К сожалению, кроме расчета себестоимости, маркировка проводок используется в DAX и для других целей:
1. При резервировании в заказанных, маркировка позволяет связать данный резерв в заказанных с конкретной приходной проводкой в статусе "Заказано”. В дальнейшем – при складском или финансовом оприходовании этой складской проводки, именно примаркированная к ней расходная проводка будет переведена в статус "Зарезервировано”. Таким образом – мы можем привязать наш резерв в заказанных к конкретному приходу.
2. При работе модуля сводного планирования, маркировка используется для организации связи данных покрытия чистых потребностей со складскими проводками. Грубо говоря – если две складских проводки примаркированы друг к другу, то запись о данном приходе в таблице чистых потребностей (reqTrans) будет рассматриваться как покрывающая для записи по данному расходу. Аналогично – если мы будем создавать перенос или закупку на основании созданных при сводном планировании планового переноса или закупку, то проводки по данной закупке/переносу будут примаркированы к той расходной проводке, на основании которой появилась данная потребность.
Следует помнить о наличии данных механизмов, поскольку может возникнуть ситуация, когда последствия их работы (примаркированные проводки) фактически выпадут из стандартной процедуры расчета себестоимости о складской модели. При этом – фактически система начнет работать в режиме партионного учета, поскольку себестоимость конкретного расхода будет однозначно определятся себестоимостью конкретного расхода. (Вместо усреднения или расчета по модели FIFO например).
О списании ошибок округления и корректировок при прогонке себестоимости.
Представим себе следующую ситуацию: Допустим – мы закупили 3 единицы товара на общую сумму 10 рублей. Затем – каждую из этих единиц мы продали (отдельными заказами). Предположим также, что мы этот товар не переносили между складами и вообще никаких операций по нему кроме этой закупки и трех продаж, не выполняли. После закрытия склада – себестоимость списания по каждому из этих заказов будет равняться 33 рубля и 3 копейки. По приходу у нас возникнет интересная картина. Сопоставленное количество – будет равняться 3 (то есть – в общем-то, все списали и приходную проводку пора помечать как закрытую). В то же время – сопоставленная сумма будет равняться 9 рублям и 99 копейкам (то есть – приходную проводку нельзя помечать как закрытую, поскольку на проводке зависло сальдо в одну копейку). В подобной ситуации, система просто переоценит приходную проводку на одну копейку в корреспонденции с корсчетом прихода со счетом прибылей и убытков из настроек складской разноски для склада. (Следует отметить – что для закупок, корсчетом прихода является счет потребления по закупке из настроек складских разносок). То есть – после закрытия склада – у нас в приходной проводке в поле costAmountPosted будет указана исходная сумма (10 рублей), а в costAmountAdjustment - -1 копейка, при этом сама приходная проводка будет помечена как закрытая.
В том случае, если у нас происходит списание прогоняемой коррекции себестоимости (либо потому что она меньше заданного при закрытии склада порога точности, либо потому что у нас исчерпался счетчик итераций), система поступает аналогичным образом.
Допустим – у нас с предыдущей итерации прогона себестоимости на данный приход по журналу спецификации пришла коррекция в 3 копейки, которая меньше чем наш порог точности закрытия (Параметр закрытия – "Минимальная коррекция пропускной способности”. Система сгенерирует сопоставление, корректирующее себестоимость прихода по журналу спецификации в корреспонденции с корсчетом прихода на 3 копейки, а затем сразу же уценит этот приход на три копейки в корреспонденции со счетом прибылей и убытков.
Update: Уже после того как опубликовал первую версию статьи - посмотрел как сделаны округления на 4ой версии. Картина следующая: в версии 4.0sp1 в метод initInventSettlement класса inventCostItemDim добавлен новый (последний) параметр _errorAdjustment, который выставляется в true, в том случае если этот метод вызывается из метода createErrorAdjustment, занимающегося списанием ошибок округления и корректировок при пригонке себестоимости. В этом случае, в качестве коррсчета корректировки берется счет прибылей и убытков из настроек складской разноски для данной номенклатуры, а не коррсчет прихода, как обычно. В старой версии, в которой эта логика не была добавлена, списанные округления и корректировки зависали на коррсчетах прихода. Поскольку коррсчетов прихода в закрытии склада использовалось много, оценить и списать сумму ошибок при закрытии было нелегко. Поэтому, если вы работаете не более старых версиях, я рекомендую вам перенести эту доработку на ваше приложение. Кроме того, на мой взгляд, мысль списывать округление напрямую на счета прибылей и убытков тоже не очень удачная. Правильнее было бы накапливать сумму ошибок на каком-то выделенном счете, а потом, при трансформации баланса, закрывать этот счет в ручную. Для того чтобы добиться такого эффекта - необходимо подправить метод inventAdj::errorAccountOperation(), таким образом чтобы он возвращал нужный вам счет ошибок округления. Я бы, наверное, использовал для этого счета отклонений от стандартной себестоимости. Если standard costing используется - то на эти счета как раз и нужно отклонения списывать, а если не используется - то эти счета в настройке складских разносок не заняты и их можно приспособить под списание ошибок и округлений. Если эта схема вам подходит - достаточно поменять в методе InventAdj::errorAccountOperation() значения InventAccountType::InventProfit и InventAccountType::InventLoss на InventAccountType::InventStdProfit и InventAccountType::InventStdLoss соответственно.
Со времен написания оригинальной статьи, группа российской локализации добавила в настройках складских разносок специальный счет для списания округлений. Теперь туда можно поставить какой-нибудь подходящий счет (например - какие-нибудь субсчета 76ого, 91ого или даже 26ого) и в конце периода как-то закрывать накопившуюся погрешность округления.
Стандартная себестоимость.
Существует мнение, что закрытие склада игнорирует те номенклатуры, у которых в складской модели включен режим стандартной себестоимости. Это не так. Более того – если у нас за последнее время менялась стандартная себестоимость по данному изделию, то закрытие склада, например, по средней, усреднит себестоимость старых и новых приходов (сделанных по разным стандартным себестоимостям). Поэтому – очень рекомендуется (не с точки зрения целостности системы, а с точки зрения методики учета), при любом изменении стандартной стоимости изделия, проводить переоценку запасов таким образом, чтобы себестоимость запасов после переоценки равнялась их количеству, помноженному на новую стандартную стоимость.
Кроме того, хочу уточнить, что когда система списывает при приходе разницу между закупочной стоимостью и стандартной, она выполняет эту операцию как стандартную процедуру переоценки приходной складской проводки в корреспонденции со счетом отклонений. При этом – в costAmountPosted попадает закупочная стоимость, в costAmountAdjustment – сумма отклонения, ну и в costAmountStd – стандартная себестоимость (которая будет равнятся сумме закупочной себестоимости и отклонения).
В версии 2009 реализована полностью новая модель работы со стандартной себестоимостью. Та модель, которую я здесь описываю, осталась, но переименована в "Fixed Receipt Price".
Закрытие склада по услугам.
Надо заранее сказать, что настройка работы с услугами в Dynamics AX имеет некоторую неоднозначность.
1. Существует тип номенклатуры "Услуга”. Для номенклатур данного типа система не ведет записей в таблице запасов в наличии (inventSum), по ним нельзя выполнять операцию резервирования, в общем – система в целом не позволяет делать те операции, которые нельзя выполнять по тем сущностям, которые нельзя положить на склад.
2. Существует галка "разносить финансовые операции” в настройках складских моделей. Если эта галка выключена (а для услуг она в 99% случаев отключается), то при приходе данной номенклатуры по закупке, ее себестоимость относится на счета затрат. Во всех остальных вариантах приходов – никаких проводок по номенклатуре с данной складской моделью вообще не делается.
Соответственно – особого смысла закрытие склада для услуг никогда не имело. Собственно – закрытие склада и необходимо для того, чтобы мы могли посчитать точную себестоимость списания в затраты тех активов, которые мы когда-то отнесли на инвентарные счета. Услуги мы и так сразу списали на затратные счета. Возникает законный вопрос – зачем тратить время на складские сопоставления, если себестоимость по ним мы считать не будем ? В версии 4.0 (по крайней мере – в русской версии 4.0) закрытие склада по услугам было отключено. То есть – при выполнении закрытия склада – для всех номенклатур типа услуга сопоставленное количество автоматически приравнивается общему, а в таблицу складских сопоставлений пишется складское сопоставление с пустыми финансовыми суммами и моделью сопоставления "Складское сопоставление номенклатуры по услуге”.
Нужно помнить об этой особенности и если у вас на проекте использовалась номенклатура типа услуга с ВКЛЮЧЕННОЙ галкой "Разносить финансовые операции”, то нужно эту номенклатуру переделать из типа “услуга” в тип “Номенклатура". Впрочем – на реальных внедрениях, я случая подобной настройки не видел.
Update: После некоторых размышлений, я все-таки вспомнил ситуацию, при которой для услуг включалась галка "Разносить финансовые операции". Это делалось для тех услуг, которые включались в состав спецификации. При приходовании услуги делалась проводка Д20.промежуточный К60, а при сборке спецификации с данной услугой - Д20.производство К20.промежуточный. Вот такое услуги при переходе на версию 4.0 однозначно придется переделывать в номенклатуры, иначе их себестоимость просто не будет включена в себестоимость выпущенной готовой продукции.
Почему возникает отрицательный складской остаток?
Наверное все знают, что в Dynamics AX существуют настройки складской модели, разрешающие списание товара, до его приходования на склад. (“Отрицательный физический склад” и “отрицательный финансовый склад”). Хотя время от времени этот режим включают осознанно (например – у складской модели для услуг), но в большинстве случаев – этот режим сознательно выключен. Поэтому – начинающие консультанты очень часто недоумевают, обнаружив, что при выключенном режиме отрицательного склада, складской остаток, тем не менее, опускался ниже ноля на какой-то момент времени.
Это вызвано тем, что в момент выполнения списания (физического или финансового) система контролирует ТЕКУЩИЙ складской остаток. Поэтому – если товар пришел только вчера, а списание мы выполняем более ранней датой, система разрешает это сделать.
Почему так сделано ? Я, на одном из своих проектов, переделывал проверку количества при списаниях. Мне пришлось написать код, который проверяет остаток на складе (по финансовым складским операциям) на дату списания, а затем пробегает по ВСЕМ списаниям и приходам, произошедшим ПОСЛЕ выполненного списания. В том случае, если списание проходило текущей датой, система выполняла операцию чуть медленнее, чем до этой переделки. В то же время, если операция выполнялась, скажем, датой трехнедельной давности – задержка была гораздо более ощутимой – где-то процентов на 40-50, по сравнению со стандартной проверкой. Кроме того – в такой ситуации проверка остатков за период порождала достаточно много блокировок. Когда же я попытался реализовать подобный режим и для физических складских операций, я столкнулся с еще большей проблемой. Не понятно, что в этом случае делать с резервами. Может возникнуть ситуация, при которой менеджер по продажам СЕГОДНЯ поставил резерв под заказ, а система ему не дает проводить отгрузку по заказу задним числом, потому что на тот момент – на складе такого количества товара не было. В общем – на этом проекте я сделал контроль остатка по финансовым операциям и оставил старую проверку для физических операций. Поскольку списания задним числом на этом проекте происходило достаточно редко – проблем подобный подход не вызвал. Но в общем случае полную проверку остатка в реальном времени не сделать. Правильнее будет периодически проверять наличие отрицательного остатка на дату с помощью какого-нибудь отчета.
Оценочная себестоимость.
В общем случае – оценочная себестоимость рассчитывается как “мгновенная средняя» то есть текущий остаток в валюте для данной номенклатуры в разрезе аналитик финансового склада, деленный на текущее количество данной номенклатуры на складе в разрезе тех же аналитик. При этом – в расчет берутся только те количества и суммы, которые были проведены по накладной (но не по отборочной накладной).
В целом - вопрос точности расчета системой не является особо критичным. Чем меньше разница между истинной и оценочной себестоимостями – тем быстрее работает закрытие склада. Если у нас на складе лежит номенклатура, закупленная по цене 400, 600 и 700 рублей, то даже если мы каким-то образом разочек списали одну штуку по мгновенной себестоимости 1200 рублей, ничего страшного не произойдет. В худшем случае – у нас чуть дольше будет работать процедура закрытия склада. В тоже время , если у нас в такой ситуации списалось по оценочной стоимости в 100000000 рублей – то на время закрытия склада это может повлиять достаточно сильно и негативно. Кроме того,надо помнить, что если мы слишком много номенклатуры спишем по какой-то уж совсем бредовой оценочной себестоимости , то у нас может возникнуть ситуация при которой у нас на складе осталось 3 штуки изделий по себестоимости минус 600 рублей. Вылечить подобную ситуацию можно только закрытием или пересчетом склада.
Наконец, надо разобрать несколько специальных случаев. В том случае, если в настройках группы складских моделей установлена галка "Включать физическую себестоимость», то в расчет мгновенной средней включаются суммы и количества, проведенные не только по обычным накладным, но и по отборочным. Если у нас между оформлением отборочной накладной и обычной накладной имеется большой временной лаг, то включение этого режима позволяет несколько увеличить точность расчета мгновенной средней.
В том случае если при расчете мгновенной средней, выяснилось что на складе нет данной номенклатуры и мы не можем вообще посчитать среднюю мгновенную себестоимость ,то система пытается использовать вместо мгновенной средней, то значение стандартной цены из номенклатурной карточки (то есть – тоже самое поле что и для стандартной себестоимости).
Отдельным случаем является работа в режиме стандартной себестоимости (в версии 2009 это называется "Fixed Receipt Price"). В этом случае – вместо вычисления мгновенной средней, используется стандартная себестоимость, указанная в номенклатурном справочнике в поле "Цена» группы полей "Затраты». (В версии 3.0 эта группа полей называлась "Склад»). В принципе – эту цену можно настраивать не только в разрезе номенклатуры, но и для сочетания номенклатурных аналитик по данной номенклатуре. Но это – тема для отдельного обсуждения.
В том случае, если система рассчитывает себестоимость списания для возврата, то для расчета себестоимости списания используется себестоимость того прихода, по которому происходит возврат. При этом для определения ситуации возврата используется либо механизм маркировки проводок, либо указание возвращаемого лота.
"Странные проводки" по возврату закупок.
Часто приходится слышать жалобы начинающих консультантов, на "странные проводки" по возврату закупки. Скажем что-то типа "Почему по закупке у нас прошли проводки Д 10 К 60 1400руб, а по возврату Д 10 К 60 -1400руб; Д10 К10 -200руб?. Откуда вторая проводка?".
Вот причина этой ситуации: В проводки по поставщику мы должны поставить сумму возврата (1400рублей). В тоже время, при отсутствии маркирования, и оценочная и истинная себестоимость списания у нас может отличаться от этих 1400 рублей (В нашем случае – оценочная себестоимость – 1200 рублей). Вот DAX и пытается побороть эту проблему следующим способом:
1. Со складского счета на счет задолженности списывается сумма возврата (количество, помноженное на цену)
2. Складской счет дооценивается (или уценивается) на сумму разницы между мгновенной себестоимостью и суммой возврата, в корреспонденции со счетом потребления из настроек складских разносок. По какой-то непонятной причине, принято в это поле настройки разноски прописывать обычный складской счет, тот же самый что и в настройке счета прихода. Поэтому и появляется странная проводка Д 10 К 10.
Правильнее было бы прописывать в качестве счета потребления прописывать какой-нибудь рабочий счет типа 76.xxx. Тогда разноска возврата имела бы более понятный вид:
Д10 К60 -1400
Д76 К10 -200
В конце периода, после закрытия склада, у нас на счете 76.xxx накопился бы финансовый результат по нашим возвратам. Например – если мы регулярно ухитрялись возвращать поставщику залежалый товар выше себестоимости – у нас бы там накопилось кредитовое сальдо, в обратной ситуации – кредитовое. В конце периода этот счет я бы закрывал на прибыли и убытки, показывая корректировку финансовых результатов периода за счет возвратов поставщику.
Более того, если мы после разноски возвратов смогли примаркировать их к исходным закупкам (то есть – все возвраты прошли в том же периоде что и закупки), то после закрытия склада, у нас вообще не будет сальдо на этом счете, поскольку проведенные по закрытию склада коррекции отреверсируют все проводки со складского счета на счет потребления.
Новая стандартная себестоимость.
Вообще говоря - в версии 2009 был реализован целый комплекс изменений в системе расчета себестоимости, в первую очередь связанных с необходимостью поддержки правильной МСФОшной стандартной себестоимости. Часть из этих изменений, влияет не только на стандартную, однако чтобы не распылять материал по статье, я соберу описание всех этих новшеств в одном разделе.
Я не успел применить новый механизм на практике, так что все нижеописанное было собрано на основании анализа исходных текстов. Вполне возможно что я где-то ошибся в деталях. Относитесь к этому разделу как к путеводителю по концепциям новой функциональности, а не как к руководству пользователя.
Прежде чем двигаться дальше, замечу что старая стандартная себестоимость и ее механизмы остались на месте и включаются галочкой "Fixed receipt price" в настройках складской модели. Новая стандартная себестоимость включается выбором значения "Standard cost" в поле "Inventory Model" группы складских моделей.
Поддержка истории стандартной себестоимости.
В DAX2009 реализовано хранение истории стандартной себестоимости для данной номенклатуры. Для того чтобы просмотреть или изменить стандартную себестоимость для номенклатуры, надо в форме номенклатурного справочника нажать на кнопку "Price". В появившейся форме будет две закладки: Слева находится закладка с запланированными, но не активными стандартными ценами (таблица itemPriceSim); Справа находится закладка с уже активированными ценами (ItemPrice). Напрямую внести новую цену на правую закладку - не получится. Необходимо сначала внести новую цену в itemPriceSim и только потом активировать эту цену специальной кнопочкой. Для номенклатуры типа "Спецификация" (BOM) запрещается вводить новую цену в ручную. О причинах этого явления мы поговорим в следующем разделе, когда будем обсуждать учет отклонений.
Отрадно заметить, что в новой версии, процедура активации не просто подменяет цену в справочнике, но и автоматически переоценивает складские остатки на дату активации. Кроме того, рассказывая о новой стандартной стоимости, надо перечислить следующие любопытные факты:
При приходовании номенклатуры задним числом, номенклатура приходуется по той стандартной себестоимости, которая была активна на дату приходования
При приходовании номенклатуры задним числом, номенклатура проходит всю цепочку переоценок, если с даты прихода стандартная себестоимость менялась. Например - в марте у нас была стандартная себестоимость 70 рублей, с первого апреля она изменилась до 75 рублей. Если мы в апреле оприходуем номенклатуру датой 26 марта, то она будет оприходована по себестоимости 70 рублей, а потом автоматически переоценена до 75 (проводкой от 1ого апреля). Что нетипично для DAX - номер ваучера при этом будет использован от приходной операции (скажем - накладной по закупке), а дата - от операции переоценки (дата смены стандартной себестоимости). Хотя возможность использовать разные даты для одного ваучера поддерживается со времен версии 3.0, для аксаптовской бизнеc-логики это не типично.
Новая стандартная себестоимость поддерживается и для физических операций. То есть - после разноски отборочной накладной по закупке, поле costAmountPhysical в складской проводке будет посчитано на основании стандартной себестоимости, а не закупочной цены. НО: После разноски финансовой операции - корректировки по физическим операциям отменяются. То есть - до разноски накладной, в поле costAmountPhysical будет лежать стандартная себестоимость, после - обычная закупочная. Более того - местами, во внутренностях классов, занимающихся поддержкой новой стандартной себестоимости, отклонение как раз рассчитывается как разница между финансовой и физической себестоимостями (costAmountPosted+costAmountAdjustment-CostAmountPhysical).
После любой операции производится проверка корректности себестоимости остатка (остаток в валюте учета должен равняться количеству на текущую дату, помноженному на текущую стандартную себестоимость). Если себестоимость остатка некорректна (допустим - списывали или приходовали какое-то сильно дробное количество номенклатуры и набежали погрешности округления),система проведет автоматическую коррекцию остатка в наличии. Правда при этом коррекция погрешности не будет размазана по складским остаткам, а будет просто приклеена к текущей складской проводке. Кроме того - из соображений производительности - корректность остатка традиционно проверяется на текущую дату, а не на дату операции. Правда - мне кажется это не страшно, поскольку возникшая в прошлом периоде погрешность округления неизбежно приведен к такой же погрешности в текущем периоде.
Для номенклатуры, использующей новую стандартную себестоимость, запрещена коррекция остатков в наличии и складских проводок. Кроме того - подобная номенклатура не попадает в нормальную операцию закрытия склада (то есть - в сопоставления и прогонку себестоимости). Однако, если изделие по стандартной себестоимости было собрано из материалов с обычной себестоимостью (FIFO/LIFO/средняя), то закрытие склада автоматически спишет отклонения в себестоимости готовой продукции, порожденные прогонкой себестоимости.
Все проведенные коррекции складских проводок по новой стандартной себестоимости - как сделанные из за отклонений, так и из за смены значения стандартной себестоимости или округлений, по прежнему попадают в таблицу складских сопоставлений (inventSettlement).
Учет отклонений
Я несколько раз сталкивался с применением учета по стандартной себестоимости на своих проектах. Обычно, приводимое заказчиком обоснование применения стандартной себестоимости выглядело примерно так:"У нас вообще производственный цикл сложный, себестоимость по средней или по FIFO посчитать тяжело, вот мы и работаем по стандартной. Да и если не под запись говорить, то у нас в производственной службе полный бардак, чего они там творят мы в бухгалтерии не знаем и знать не хотим, будем работать по стандартной, потому что нам так проще". Я, конечно, слегка утрирую, но все равно - зачастую предприятия используют стандартную себестоимость только для упрощения работы учетчиков. Хотя на самом деле - основная идея использования схемы стандартной себестоимости в учете формулируется в двух простых положениях:
В себестоимость включаются только экономически обоснованные затраты, то есть затраты неизбежные и необходимые для получения требуемого продукта с требуемым качеством. Поскольку для каждого конкретной партии материалов/товара разбираться с экономической обоснованностью каждого включенного в себестоимость рубля затруднительно - производится нормирование себестоимости на следующий учетный период (не обязательно месяц - возможно это квартал или год). По хорошему - этот процесс должен быть также связан с бюджетным планированием.
Отклонения в себестоимости материалов и готовой продукции должны анализироваться по видам и источникам отклонений. Как именно анализировать отклонения - зависит от отрасли. Я бы предпочел принимать лекарства, выпущенные в строгом соответствии с производственными регламентами и без отклонений. В тоже время если в удобрении для моих домашних цветов будет на пару процентов больше калия и меньше азота, чем положено по регламенту - я довольно легко переживу. Да и цветам ничего не сделается от этих пары процентов...
Полное рассмотрение теории стандартной себестоимости выходит за рамки этой статьи. Если кому-то интересно - рекомендую почитать "Управленческий и производственный учет" Колина Друри. Нам для дальнейшего рассмотрения достаточно понимать, для чего вообще в новой версии DAX был сделан раздельный учет отклонений по видам.
Если посмотреть на разноску видов отклонений в настройках складской разноски , то с первого взгляда понятен смысл только одной разноски: Purchase Price Variance. Это - вся та же разница между закупочной ценой и стандартной ценой, которая поддерживалась и в предыдущих версиях Dynamics AX. Давайте рассмотрим и другие виды отклонений:
Inventory cost revaluation - счет разноски переоценки складских запасов при изменении учетной цены. С экономичeской точки зрения - это конечно не отклонение, но в целом понятно почему его поместили вту же настроечную форму что и нормальные отклонения.
Inventory Cost Change - счет разноски отклонений между стандартной ценой и ценой из строки складского журнала (фактически - аналог счета отклонений по закупке, но для складских журналов)
Rounding Variance - счет разноски округлений. (Не путать с округлениями остатка в наличии, которые гарантируют что денежный остаток на складе равен количеству помноженному на стандартную себестоимость). Дело в том, что при расчете отклонений, эти отклонения считаются с точностью, не ограниченной валютой учета (то есть - не округленной до копеек). А вглавную книгу, отклонения все таки разносятся округленные до копеек. Соответственно - чтобы не прибегать к механизму разноски погрешности округлений в самом модуле главной книги, классы разноски сами рассчитывают и разносят округления. (Если бы этот механизм не был реализован- обороты по ГК не бились бы с таблицей отклонений (InventCostTransVariance).
Гораздо интереснее отклонения , связанные с отклонениями в производственном модуле (из за чего весь сыр бор со стандартной и был затеян - судя по всему) :
Production Price Variance - отклонения порожденные разницей в себестоимости одной единицы списанной в производство продукции или выполненной производственной операций. То есть - даже если мы потратили большее КОЛИЧЕСТВО материала/выполнили большее количество операций, но по запланированной цене за штуку - отклонение не возникнет.
Production Quantity Variance - отклонения по количеству потребленных материалов. То есть - если мы потратили больше или меньше материалов(или выполнили больше или меньше операций), чем было запланировано, разница в количестве множится на запланированную себестоимость единицы потребляемых материалов/операций и получается Production Quantity Variance
Production Lot Size Variance - это такой достаточно странный для меня тип отклонений, связанный с размером лота (партии) списанной в производство. В DAX поддерживается константный способ расчета потребления материалов на маршруте.(Я так понимаю - это что-то вроде внутризаводских логистических расходов, связанных с доставкой одной партии материала в производство, которые, в первом приближении, не зависят от размера партии). Предположим что мы запланировали потратить 30 рублей этих константных расходов и списать 20 штук материалов в производство. Получается - по полтора рубля на штуку. В реальности - списали не 20 штук, а 22. Получается отклонение в 30 *22/20-30=3 рубля. Коротко говоря - это тоже отклонение в количестве, но связанное не с себестоимостью самого материала, а с накладняком, сопутствующим его списанию в производство.
Substitution Variance - фактически - все прочие производственные отклонения. C формально-экономической точки зрения - отклонения связанные с заменой материалов или выполняемых операций на другие.
Возникает вполне логичный вопрос - а как же система рассчитывает все эти отклонения ? Что считать за эталон стандартного количества, цены за единицу и тому подобного ? Ответ достаточно прост- эталоном стандартного производственного процесса является результат расчета спецификации. То есть - когда мы последний раз спецификацию рассчитывали, в таблицу BOMCalcTrans система сложила данные о всех потребляемых материалах, выполняемых операциях, количествах и их запланированных стоимостях. Соответственно - для того чтобы посчитать отклонения, нам достаточно сравнить эталонные данные из BOMCalcTrans с фактическими данными из ProdCalcTrans (Калькуляции производственного заказа). Логику расчета отклонений выполняет класс ProdStandardVariance. Наиболее любопытны его методы findOrCreateVarianceTrans, который интересен тем что он заполняет табличку отклонений, находя соответствие между плановыми и фактическими затратами и метод calcVariance, который собственно и рассчитывает отклонения.
Кроме того - становится понятным, почему для спецификаций запрещен ручное редактирование учетной цены в таблице itemPriceSim. Представим себе, что мы скалькулировали спецификацию, получили плановую себестоимость 950 рублей, а потом подправили ее до 1200. Теперь - если наша фактическая себестоимость будет 1100 рублей, то довольно затруднительно будет размазать -100 рублей отклонения, на основании данных, которые вообще-то говорят что отклонение составляет +150 рублей.
Развертка отклонений
Предполагается, что русское слово "развертка", используется как перевод английского термина roll-up. Я, правда, всерьез рассматривал термин "разблюдовка", но решил что он слишком радикальный. :)
Как я уже говорил, в Dynamics AX для производстве поддерживается всего 4 вида отклонений от стандартной стоимости в производственном модуле. Опыт показывает, что учета только по виду отклонений для реальной жизни недостаточно. Необходима возможность какой-то более тонкой настройки учета отклонений. Эту возможность предоставляет появившийся в DAX2009 группировка отклонений по видам затрат. Справочник видов затрат (Cost Group) находится в меню Inventory Management->Setup->Bill Of Materials->Cost Groups. Пока что нас в этом справочнике интересует только три поля - Cost Group (Группа затрат),Name(Название) и Cost Group Type (тип группы). Группы с типом Direct Material (Прямые материальные затраты) могут быть привязаны к номенклатуре. О других видах групп мы поговорим позднее. Группы типа Direct Manufactoring (Прямые производственные) к Группам Маршрутных Затрат (Route Cost Category). В момент расчета калькуляции (BOMCalcTrans) или калькуляции производственного заказа (ProdCalcTrans), информация о группе затрат, по номенклатуре или маршрутной операции породивших данную строку затрат, копируется в соответствующее поле таблицы калькуляции. В тот момент когда система рассчитывает отклонения (в классе ProdStandardVariance), во временную табличку с отклонениями также копируется информация о группе затрат. Поскольку в DAX2009, настройки разноски отклонений в форме настроек складской разноски привязаны к группам затрат, то система потом использует данные из временной таблички с отклонениями для определения счетов, на которые эти отклонения будут разносится.
Наконец, чтобы завершить тему отклонений и собственно стандартной стоимости, надо обсудить стандартную форму запроса о проводках по стандартной себестоимости. Она находится по адресу Inventory Management->Inquiries->Transactions->Standard Cost Transactions. В верхней части экранной формы находится содержимое таблицs InventCostTrans. Вообще-то, судя по всему, эта табличка задумывалась как некая мегалоидея для разделения количественных и суммовых движений по складу. В большинстве случаев, данные в эту таблицу попадают при создании физического или финансового движения по складу или при попытке переоценки складской проводки (которая тут же компенсируется списанием появившейся переоценки на счета отклонений). Однако же, в некоторых ситуациях, записи в этой табличке возникают независимо от конкретных складских движений. Например - при изменении стандартной себестоимости, туда записываются записи которые сначала как бы списывают старую себестоимость, а потом как бы приходуют новую. Честно говоря - я не вижу глубокого смысла в создании этой таблички: Большая часть данных все равно дублируется в/из складских проводок; Я слабо представляю как этот механизм можно будет адаптировать к номенклатуре, работающей не по стандартной себестоимости; Большая часть функциональности продолжает использовать суммовые поля inventTrans и т.п. Так что на мой взгляд - эта табличка - неверный шаг в правильном направлении. В нижней части экранной формы находится гораздо более интересная табличка (inventCostTransVariance). В этой табличке сохраняется подробная информация об отклонениях по складским движениям (та самая развертка отклонений). Сохраняется она, правда, не во всех случаях, а только если в параметрах модуля управления запасами включен параметр "Cost breakdown". Параметр Variance To Standard позволяет регулировать уровень детализации отклонений в этой таблице (группировать до видов отклонений или до видов отклонений+группы затрат).
Ну и наконец последний вопрос, связанный с новой стандартной себестоимостью. Поскольку номенклатура с новой моделью теперь совсем не участвует в закрытии склада, может быть есть смысл просто не закрывать склад, если у меня вся номенклатура (и готовая продукция и материалы) использует новую складскую модель ? Ответ - нет - все-таки закрывать склад все равно надо. Дело в том, что хотя номенклатура с новой складской моделью перeстала обрабатываться стандартной веткой процедуры закрытия склада, все таки закрытие склада выполняет некоторую полезную функцию по отношению даже к данной номенклатуре. В конце процедуры закрытия склада производится заполнение таблицы InventCostTransSum, которая представляет собой сумму таблицы inventCostTrans на дату закрытия склада. Данные из этой таблицы позволяют достаточно заметно ускорить процедуру смены стандартной себестоимости или снизить время выполнения некоторых отчетов по стандартной себестоимости. Так что если склад долго не закрывали, скорость выполнения этих процедур может заметно упасть.
Структура себестоимости и непрямые затраты
В данном разделе мы поговорим об интересном, перспективном, но малость недоделанном в версии 2009 механизме - Costing Sheet. Посмотреть на настройки этой функциональности можно в пункте меню Inventory Management->Setup->Bill Of Material->Costing Sheet Setup. В самом первом приближении - это просто дерево структуры затрат, на листовом уровне которого находятся обычные группы затрат (Тех самых, которые привязаны к номенклатуре и группам маршрутных затрат), а на ветках - разные уровни группировки этих затрат. Однако, если попробовать поиграться с добавлением новых узлов на листовой уровень, то можно обнаружить два любопытных типа затрат - Indirect Surcharge и Indirect Rate. Первый из них - это затраты выражающиеся в процентах от некоторого другого узла costing sheet. Второй - затраты в денежных единицах на количество строки в расчете спецификации/производственной спецификации (BOMCalcTrans/ProdCalcTrans), относящихся к указанной ветке иерархии затрат.
Указанные в costing sheet нормы непрямых затрат используются для расчета себестоимости по производственным заказам. Также как и для прямых затрат используется странная для российского учета схема: Во время разноски журналов отгрузочных накладных или журналов маршрутных заданий/операций, делается проводка по некоторым промежуточным счетам; Во время окончательной калькуляции производственного заказа, эта проводка сторнируется и делается проводка между окончательными счетами (на сей раз уже достаточно понятная с точки зрения российского учета - типа Д20 К25). Наконец - сумма непрямых затрат включается в себестоимость готовой продукции и добавляется к стоимости проводки по выпуску ( типа Д43 К20). Данные о непрямых затратах также записываются в появившуюся в версии 2009 таблицу prodIndirectTrans.
Это еще не все. Поскольку непрямые затраты могут зависеть от себестоимости материалов по производственному заказу, очевидно что пересчет себестоимости материалов при закрытии/пересчете склада, должен вызвать также и пересчет непрямых затрат. Так что если в ходе итерации закрытия склада произошло обновление складских проводок по производственному заказу, система вызывает (в методе inventCostItemDim.updateIndirectCosts) пересчет непрямых затрат по производственному заказу и если эти непрямые производственные затраты действительно изменились - добавляет новые записи в prodIndiectTrans и увеличивает сумму прогоняемой коррекции на сумму дополнительных непрямых затрат. Кроме того - штатная процедура разноски складских сопоставлений, вызываемая в конце процедуры пересчета/закрытия склада доработана таким образом чтобы разносить в главную книгу также и свежесозданные записи в prodIndirectTrans. Процедура отмены закрытия склада была, соответственно, доработана для отмены добавленных при закрытии непрямых затрат,
При расчете спецификации, система добавляет к прямым затратам по материалам и операциям, также и строки с указанными непрямыми затратами. Ну а поскольку, как мы уже знаем, на основании сравнения результатов калькуляции спецификации и результатов калькуляции производственного заказа система рассчитывает отклонения от стандартной себестоимости, получается что отклонения могут случаться не только по прямым затратам, но и по нормированным косвенным. Насколько я понимаю,c точки зрения российского учета - это достаточно странный подход...
Теперь немного о том, почему я называю этот механизм недоделанным. Совершенно очевидно, что в случае хоть сколько-нибудь развитого производства и нормально организованного производственного учета, необходима реализация возможности держать в системе несколько разных вариантов costing sheet, которые можно было бы привязывать к номенклатуре или даже к версии спецификации. С одной стороны - если посмотреть на структуру таблиц, описывающих costingSheet можно заметить наличие задела для поддержки существования нескольких вариантов структуры себестоимости. С другой стороны - в текущей версии эта поддержка не реализована. Я так подозреваю что в следующей версии DAX эту поддержку окончательно доведут до ума и она станет одним из важнейших механизмов работы с себестоимостью.
Comments
Anonymous
January 01, 2003
Текст статьи (с обновлениями для Dynamics AX 2009) переехал по адресу: Себестоимость и закрытие складAnonymous
June 24, 2010
Доброго дня, прошу дать ссылку на Hotfix for “Incorrect Cost Figures – KB946804 _40SP1SP2″