Welcome to TechNet Blogs Sign in | Join | Help

Обновление статьи о закрытии склада.

Выложил обновленную версию статьи о принципах работы с себестоимостью в Dynamics AX. Обновление сделано с учетом новых возможностей Dynamics AX 2009.
Posted by denisfed | 0 Comments

Пример создания нового складского документа

Выложил статью c примером создания нового складского документа в DAX. В статье рассматривается пример с созданием объекта в DAX объекта "клипборд резервов". Предполагается что во всех обычных складских документах появляется две новых операции - "Снять резервы в клипборд" и "Резервировать из клипборда". Приведен пример реализации данного объекта с поддержкой интеграции с модулем логистики - системой складских проводок и складских остатков.

Кроме того, в конце статьи приведен пример полностью программного (без использования журналов или других складских документов) создания приходов, списаний и переносов номенклатуры в DAX.

Об архитектуре разноски в ГК и корреспонденции счетов

Выложил статью "Немного об архитектуре разноски в ГК и проблеме корреспонденции счетов". Хотя в статье довольно много информации об API работы с главной книгой, в первую очередь статья была написана, чтобы рассказать о том, откуда вообще выросла проблема реализации корреспонденции счетов при локализации западных ERP систем. Кроме того - рассматриваются некоторые общие подходы по интеграции модулей DAX с главной книгой.

Dynamics AX 4 и IMTS

Выложил статью Dynamics AX 4 и IMTS посвященную проблеме блокировок в логистическом модуле Dynamics AX.

Новые поля в складских проводках

 

Общие вопросы

При внедрении DAX часто возникает примерно следующая задача: Клиент говорит что-нибудь типа "Мне нужно добавить в заказы (складские журналы, закупки, производственные заказы и т.п.) новое поле - направление продаж (код продавца, номер автомобиля для отгрузки, идентификатор кредитной линии клиента и тп). Кроме того - мне нужно уметь строить отчеты (обычные или OLAP) по складским списаниям (приходам) в разрезе этого нового поля".

Первое что приходит в голову - это просто добавить новое поле в строки заказов и потом переделать некоторые складские отчеты таким образом, чтобы они строились по соединению (join) таблицы складских проводок с исходным документом. Проблема в том, что в реальности постоянно возникают ситуации, при которых клиент, задним числом вспоминает что неплохо бы это поле добавить не только в заказы (например), но и в складские журналы списания или переноса. В этой ситуации внедренцу приходится либо плодить кучу отчетов - по одному для каждого вида складских документов, либо строить какой-то хитромудрый отчет, который собирает данные из множества таблиц. Очевидно, что оба подхода имеют свои минусы и почти не имеют плюсов.

Второй, более продвинутый вариант - это добавление нового поля либо в таблицу складских проводок (inventTrans) либо в таблицу складской аналитики (inventDim). Надо понимать, что с точки зрения нагрузки на систему, вариант добавления в таблицу inventDim новых полей значительно более затратный. Это связано с тем, что с ростом числа записей в таблице inventDim, растет и таблица inventSum (запасы в наличии). Поскольку модуль логистики ПОСТОЯННО использует таблицу inventSum для получения текущего складского остатка, критически важно для производительности, чтобы эта таблица не разросталась. Поэтому, добавлять новые поля в складскую аналитику имеет смысл только при выполнении следующих условий:

1. Имеет смысл сальдирование по этой аналитике. Грубо говоря - количественный или денежный остаток в разрезе этой аналитики имеет экономический смысл.

2. Предполагается, что система должна проверять неотрицательность остатка в разрезе этой складской аналитики.

Поэтому - реально добавлять новые поля в складскую аналитику приходится достаточно нечасто. Например - ни код продавца, ни номер автомобиля явно не проходят по обоим условиям. Мне приходилось добавлять, например, складскую аналитику "Материально ответственное лицо" или "Вид продукции" (своя, комисионная, давальческая) и некоторые другие. Причем добавлять ее приходилось в первую очередь даже не для отчетности, а для контроля - чтобы нечаянно не списать с аналитики "МОЛ" или "Вид продукции" больше номенклатуры, чем на нее изначально было оприходовано.

Для всех остальных случаев - правильнее добавлять новое поле в inventTrans. Причем я для себя выработал следующий приблизительный критерий, позволяющий принимать решение - добавлять новое поле в inventTrans или попытаться обойтись хитрым запросом (с джойном inventTrans и исходного документа): Добавлять новое поле в inventTrans следует если выполняется одно из следующих условий:

1. Новый аттрибут есть более чем в одном типе складского документа

2. Новый аттрибут будет заполняться для более чем 25% складских проводок данного вида (то есть - либо приходов, либо расходов)

3. Новый аттрибут будет использоваться для вычисления дополнительных количеств в таблице остатков в наличии (Об этом подходе я напишу в другой раз. Основная идея состоит в том, чтобы в остатках в наличии иметь не только поле, допустим, "Зарезервировано", но еще и дополнительное поле "Зарезервировано в журналах перемещения".)

Наивный подход к реализации заполнения нового поля в складских проводках состоит в том, чтобы переопределить методы insert() или update() таблиц inventTrans и, допустим, SalesLine таким образом, чтобы при обеспечить синхронизацию этого нового поля между двумя таблицами. Надо сказать, что во первых этот метод приводит к серьезному возрастанию нагрузки не систему во время обновления, во вторых - очень сильно не вяжется с идеологией логистического модуля DAX.

Цель данной заметки состоит как раз в том, чтобы дать приблизительное описание той инфраструктуры которая обеспечивает интерфейс между исходными логистическими документами (заказами, закупками, складскими журналами, производственными заказами и т.п.) и стандартными механизмами логистических операций DAX, а затем дать простой пример того как можно обеспечить копирование аттрибутов из исходных документов в складские проводки.

Немного о программной инфраструктуре логистики

На мой взгляд - инфраструктура логистического модуля, это очень наглядный пример мощи объектно ориентированного подхода. При проектировании классов этого модуля разработчиками было принято очень простое решение:

1. Классы отвечающие за выполнение типовых логистических операций - резервирования, комплектации, регистрации, физической и финансовой разноски ничего не знают о том, на основании какого исходного документа выполняются эти операции.

2. Вся логика, связанная со специфическими операциями по конкретному складскому документу инкапуслирована в иерархию классов (inventMov_*).  При этом каждому виду складского документа соответствует ОДИН конкретный класс в этой иерархии.

3. При выполнении логистических операций классы InventUpd* либо запрашивают у классов InventMov* нужную им информацию (например код номенклатуры или заказанную дату), либо вызывают методы классов inventMov, чтобы те выполнили некторые операции, специфичные для данного складского документа.

Рассмотрим несколько конкретных примеров:

В иерархии классов inventMovement имеется метод mustBeAutoReserved. Этот метод вызывается классов inventUpd_expected, отвечающим за создание и обновление складских проводок в статусе "Заказано"/"В заказе" для того чтобы определить - не следует ли автоматически резервировать данную проводку списания. В классе inventMovement этот метод возвращает значение false. В классе inventMov_sales (отвечающим за модуль заказов) - возвращает значение в зависимости от режима авторезервирования в шапке заказа, а в классе InventMov_QuarantineOrder (отвечающем за карантинный заказ) этот метод всегда возвращает значение true, чтобы по карантинному заказу у нас товар автоматически резервировался на карантинном складе.

Метод inventMovement.updateLedgerFinancial() используется для создания проводок в ГК по приходным операциям. В большинстве случаев, эта операция выполняется методом самого базового класса, который вытаскивает из дочернего класса (относящегося к данному виду приходного документа) данные о счете и коррсчете проводки (через методы accountBalanceSheet() и accountOperations()), финансовой аналитике (через метод Dimension()) и разноске в ГК (методы postingBalanceSheet() и postingOperations()). Для закупок, этот метод был переопределен в классе inventMov_purch(), поскольку по закупкам разноска по коррсчету делается совсем другим классом (vendVoucher), который также создает запись в проводках по поставщику (vendTrans). Поэтому - в методе inventMov_purch.updateLedgerFinancial() создется только вторая половина проводки - приход на инвентарный счет (10.x или 41.x).

Метод inventMov.addRemainFinancialUnit() обновляет недопоставленное количество в исходном складском документе. В базовом классе inventMovement этот метод определен так, чтобы он ничего не делал если для данного типа складского документа обновление недопоставленного количества не требуется и чтобы он выдавал сообщение об ошибке если таковое обновление требуется. В классах inventMov_purch,inventMov_sales, inventMov_prodLine этот метод переопределен таким образом, чтобы он обновлял соответствующие количества в строке закупки, заказа и производственной спецификации.

Ну и так далее...

При создании экземпляра класса inventMovement используется constructor controlled inheritance: Для создания экземпляра класса используется метод inventMovement::construct(common table). При этом, логика внутри метода contruct (точнее даже метода constructNoThrow, который вызывается из construct), на основании информации о типе таблицы со строкой исходного складского документа, создает нужный экземпляр конкретного наследника класса inventMovement. При этом, переданный экземпляр строки исходного складского документа храниться в переменной buffer, доступной из класса inventMovement и его наследников. Кроме того - существует еще один метод создания экземпляра класса inventMovement. В таблице inventTrans существует метод inventMovement, который находит нужную строку таблицы с исходным документом, а затем через inventMovement::construct() создает и возвращает соответствующий экземпляр объекта класса inventMovement.

Конкретный пример.

Заказчик – крупная торговая организация. Им для работы требуется четко контролировать складские резервы в двух дополнительных разрезах:

1. Менеджер по продажам (сейл), к которому относится резерв

2. Срок жизни резерва. Резервы, которые кто-то создал и потом в течении N-дней не продал – должны автоматически удаляться системой.

Если транслировать эту задачу в более приземленные термины, то нужно:

1. Добавить в таблицу складских проводок (inventTrans) поле "Сейл" и копировать туда поле "Ответственный продавец" из шапки заказа (возможно – и из некого дополнительного поля, которое мы добавим в шапку складского журнала – для резервов по складским журналам).

2. Добавить в таблицу складских проводок поле "Дата автоматического снятия резерва". При резервировании – в это поле должна заносится текущая дата + 5 дней. Желательно сделать механизм расчета автоматической даты снятия расширяемым, поскольку велика вероятность того, что в дальнейшем метод расчета срока жизни резерва будет зависеть от типа исходного документа, номенклатуры, клиента под которого ставиться резерв и тп.

3. Разработать процедуру удаления просроченных резервов. Здесь я эту тему рассматривать не буду - оставлю для самостоятельного изучения :)

Для начала попытаемся решить задачу с ответственным продавцом. Для этого:

· В классе inventMovement создадим метод salesResponsible(), возвращающий значение типа emplId. В базовом классе этот метод будет возвращать пустую строку.

· В классе inventMov_sales (связанном со строкой заказа) переопределяем этот метод таким образом, чтобы он возвращал значение salesResponsible из шапки соответствующего заказа.

· Добавляем поле salesResponsible в таблицу inventTrans

· Изменяем метод inventMovement.initInventTransFromBuffer() таким образом, чтобы он инициализировал новое поле значением, полученным из метода inventMovement.salesResponsible().

Первичное тестирование данная доработка пройдет. Если при создании заказа заполнить в шапке поле "Ответственный продавец", то при создании строк оно попадет в складские проводки. Но вот если попробовать изменить это поле у уже созданного заказа, то в складских проводках так и останется старое значение. Почему это происходит ? Давайте попробуем изменить в шапке заказа поле "Дата заказа" и протрассировать метод inventUpd_estimated.updateNow(), который где-то в своих недрах должен изменить значение поля dateExpected таблицы складских проводок. При трассировке довольно быстро натыкаешься на код метода updateFieldsChange(), который вызывает метод инициализации полей складской проводки (inventMovement.initInventTransFromBuffer()) в том случае, если метод inventMovement.mustUpdateInventTransFields() вернул true. Если заглянуть в этот метод, то можно обнаружить следующий код:

return (this.transDate() != _movement_orig.transDate() ||

this.shippingDateRequested()!= _movement_orig.shippingDateRequested()||

this.transSchedTime() != _movement_orig.transSchedTime() ||

this.transItemBOMId() != _movement_orig.transItemBOMId() ||

this.transItemRouteId() != _movement_orig.transItemRouteId() ||

this.transIdReturn() != _movement_orig.transIdReturn() ||

this.projCategoryId() != _movement_orig.projCategoryId() ||

this.custVendAc() != _movement_orig.custVendAc() ||

this.assetId() != _movement_orig.assetId() ||

this.inventRefTransId() != _movement_orig.inventRefTransId()) ||

this.probabilityId() != _movement_orig.probabilityId().

Попросту говоря – система создает на основании старой (не измененной) копии строки исходного документа (доставаемой через buffer.Orig()) экземпляр класса InventMovement() и сравнивает значение некоторых методов, значения которых в дальнейшем попадают в складские проводки. Значит – для того чтобы добиться правильного поведения системы нам нужно:

1. Добавить в этот метод сравнение значений, возвращаемых методом salesResponsible()

2. Для того чтобы логика сравнения отработала нам придется добавить в СТРОКИ заказа копию поля salesResponsible. (Ведь метод пляшет от сравнения СТРОКИ ЗАКАЗА до и после обновления, а не от значения шапки заказа.). Нам придется переопределить метод обновления шапки заказа (salesTableType.update()) таким образом, чтобы при изменении ответственного продавца в шапке заказа, новое значение поля копировалось бы и в строки заказа. При этом – обновление строки заказа у нас будет вызывать обращение к inventUpd_estimated.updateNow(), порождая таким образом обновление информации и в складских проводках.

Теперь попробуем разобраться с датой автоматического снятия резерва.

· Для начала добавим в таблицу складских проводок новое поле dateExpired

· Поскольку хочется сделать механизм расчета даты автоматического снятия максимально гибким, создадим метод inventMovement.dateExpired(). Этот метод получает в качестве параметра дату создания резерва, а возвращает рассчитанную на ее основе дату снятия резерва (на первых порах – просто дату+5 дней).

· Подправим метод inventUpd_Reservation.updateReserveMore() (этот метод собственно и резервирует складские проводки), таким образом, чтобы в поле inventTrans,dateExpired записывалось значение, полученное из нового метода inventMovement.dateExpired().

· В методе inventUpdReservation.updateReserveLess() (он снимает резервы) вставляем очистку поля dateExpired – чтобы дата снятия резерва не была заполнена для проводок в статусе "Заказанно"

· Наконец – для того чтобы дата снятия резерва не стояла у уже отгруженных проводок – вставляем очистку даты резервирования в методе inventMovement.initInventTransPhysical(), который вызывается для заполнения полей inventTrans при физической разноске складских проводок. (Кстати – inventMovement.initInventTransFinancial выполняет аналогичную ситуацию при финансовой разноске). Поле salesResponsible я бы не стал зачищать в этом методе, поскольку достаточно удобно, когда это поле заполнено в проводках с любым статусом – а не только в проводках резервирования.

Если после выполнения этих модификаций поэкспериментировать с резервированием, выясняется что в целом все работает, однако есть один тонкий момент: Как известно, DAX умеет схлопывать записи в inventTrans в рамках одного номера лота, если КЛЮЧЕВЫЕ поля этих записей совпадают. В том случае, если галка "Автоматическое добавление" в параметрах модуля управления запасами установлена – это происходит автоматически – при любом обновлении inventTrans. Если галка не установлена, аналогичного эффекта можно добиться через пункт меню "Суммирование" в форме складских проводок. Так вот – если мы нарезервировали по одному и тому же лоту в разное время, с разными датами автоматического снятия резерва, система при схлопывании проводок может схлопнуть проводки с разными датами снятия резерва, подставив в результирующую проводку первую попавшуюся из нескольких дат. Для того чтобы предотвратить подобный эффект, нужно изменить метод inventTrans.setSumAmount(), так чтобы он дополнительно проверял совпадение даты резервирования и ответственного продавца в двух проводках.

NB. Кстати – если у вас на проекте есть проблемы с ростом inventTrans, посмотрите – включена ли галка "Автоматическое добавление". Если не включена – попробуйте оценить выигрыш от ее включения. Напишите джобик, который пробежится по существующим складским проводкам и проверит их на совпадение полей, проверяемых в методе inventTrans.setSumAmount. На моей практике – установка этой галки однажды позволила уменьшить число записей в inventTrans по заказам почти в два раза. Хотя с другой стороны – включение этой галки заведомо замедляет обновление inventTrans.

 

Подводя общий итог рассмотренному примеру, можно дать следующую рекомендацию:

  1.  Если аттрибут копируется в складскую проводку из исходного документа, то необходимо дополнить метод inventMovement.initInventTrans* инициализацией нового поля из вновь созданного метода класса InventMovement.
  2. Если аттрибут тем или иным образом рассчитывается в процессе выполнения логистической операции, следует поместить инициализацию этого аттрибута в методы соответствующего класса inventUpd_*.

P.S. Рассмотренный пример (по крайней мере - с датой автоматического снятия резеров) хорошо работает только при отсутствии резервирования в заказанных. По логике вещей - автоматическое снятие резерва должно работать только для товара, уже находящегося на складе. Для товара в пути эта логика в принципе не работает, поскольку время обработки закупки поставщиком и транспортировки закупленного товара до его прибытия на склад достаточно непредсказуемо и, обычно, значительно больше типичного времени жизни резерва на складе. Поэтому, в случае использования резервирования в заказанных, надо во первых заблокировать удаление просроченных резервов в заказанных, а во вторых - подправить метод inventUpdate.updateDimReserveChange(), таким образом, чтобы при приходе товара, у складской проводки резервирования повторно инициализировалась дата автоматического снятия резерва. (Попросту говоря - этот метод при вызывается при физическом приходе товара, ищет по двум разным алгоритмам подходящую проводку в статусе "Зарезервировано в заказанных" и переводит ее в статус "Физически зарезервировано").

Update: Когда готовил контрольный пример для статьи, забыл об одном интересном нюансе. Для того чтобы снятие резервов работало интуитивно понятно для пользователя, первыми внутри данного лота должны сниматься резервы с наиболее ранней датой автоматического снятия. Для того чтобы система действовала именно таким образом, необходимо в методе inventUpd_reservation.updateReserveLess поставить сортировку по дате автоматического снятия в те несколько запросов, которые отбирают строки inventTrans для перевода в статус "Заказанно".

Себестоимость и длинные производственные заказы

 

Существует следующая достаточно типовая жалоба на производственный модуль DAX: "Мы не можем посчитать себестоимость списания материалов в производство до завершения производственного заказа. У нас на внедрении, цикл производства одного ПЗ занимает 2-3 недели. Получается что материал давно списан из цеховой кладовой, его уже распилили, нарезали и смонтировали, а по бухгалтерии он до сих пор числится на 10ом счету. Даже если мы включим разноску физических складских операций, они не будут включены в закрытие склада, соответственно - истинную себестоимость списания в производство нам не сосчитать до завершения ПЗ. Получается - наш бухгалтерский баланс отстает от реальности на 2-3 недели. Если ПЗ начат и завершен в разных отчетных периодах - это фатально...".

Давайте попробуем разобраться почему подобная ситуация возникает и как можно исправить ситуацию.

После того как заказ запущен в производство, материалы списываться на производственный заказ через журнал отгрузочных накладных. При этом в складских проводках образуются ФИЗИЧЕСКИЕ проводки списания. При приемке готовой продукции по журналу приемки, у нас также образуются ФИЗИЧЕСКИЕ приходные складские проводки. Кроме того - если мы по ПЗ разносим журналы карты маршрута или карты заданий, у нас в затратах по производственному заказу (таблица ProdCalcTrans, которую можно посмотреть из формы ПЗ по кнопке Запросы->Рассчет цены) накапливается информация о нематериальных затратах на производство по данному ПЗ. При этом делается проводка на сумму нематериальных затрат по дебету счета "Счет НЗП" и кредиту счета "Расход НЗП". В зависимости от настроек ПЗ, эти счета беруться из настроек рабочего центра, категории затрат по маршруту или производственных групп. Наконец, при завершении производственного заказа, система переводит проводки списания и оприходования в статус финансовых списаний и приходов и проводит эти списания и приходы по счетам ГК. Кроме того, проводки по списанию нематериальных затрат реверсируются, затем вместо них делаются проводки по ГК на те же суммы по счетам прихода и расхода из настроек калькуляции себестоимости.

Почему так сделано ? С приходными проводками - все понятно. Пока заказ не завершен, на него могут быть отнесены новые затраты, соответственно - посчитать окончательную себестоимость прихода мы не можем и делать финансовые приходы по складу рановато. С проводками списания, как мне кажется,такая ситуация вызвана скорее техническими проблемами. При закрытии склада, в случае корректировки себестоимости по финансовой складской проводке списания в производство, необходимо также скорректировать и приходную финансовую складскую проводку по производству. А у нас этой проводки до завершения ПЗ просто нету. Мне кажется - в принципе можно было бы попытаться изменить процедуру закрытия склада таким образом, чтобы эта ситуация обрабатывалась (Например - накапливать сумму коррекций где-нибудь в шапке ПЗ, чтобы потом использовать при создании финансовой складской проводки по приходу из производства). Тем не менее - разработчики процедуры закрытия склада нашли более простое решение этой проблемы, о котором мы и поговорим ниже. Ну и наконец - я совершенно не понимаю, почему реверсируются и заново проводятся проводки списания нематериальных затрат. Судя по коду - предварительные (это те которых потом реверсируют) и окончательные проводки в принципе не могут различаться по суммам. Так что это - либо какое-то требование западного учета, либо идея из серии "reserved for future use"  и разработчики планируют в дальнейшем сделать какой-то механизм который позволит пересчитывать окончательные нематериальные затраты и они будут отличаться от предварительных (которые мы по журналу карт маршрута или карт заданий разносим).

Возникает вопрос - что же делать ? То что у нас себестоимость прихода из ПЗ не рассчитывается до завершения ПЗ - полбеды. В общем-то можно как-то объяснить почему мы ее не считаем до накопления всех затрат по ПЗ. Но вот что с себестоимостями списаний делать ? В закрытие склада они не попадают, коррекции на них не проводяться и к концу периода мы можем получить неадекватное сальдо на 10ом счете...

Дело в том, что распространенное мнение, что закрытие и пересчет склада работают только с финансовыми складскими проводками, начиная с версии 3.0sp2 не соответствует истине. Дело в том, что начиная с этой версии, рассчет себестоимости обрабатывает ФИЗИЧЕСКИЕ складские проводки по той номенклатуре, у которой в группе складских моделей стоит галочка "Включать физическую себестоимость".

Система позволяет сопоставить друг с другом физическую и финансовую проводку (например - приход по отборочной накладной и списание по накладной, или, как в нашем случае, приход по накладной и списание в производство по отгрузочной накладной). Кардинальное отличие от обычного сопоставления финансовых проводок состоит в том, что если одна из сопоставляемых проводок - физическая, то данные о сопоставлении не записываются в таблицу сопоставлений, сопоставление делается только в памяти, как при выполнении процедуры пересчета склада. Кроме того, если в результате сопоставления произошла коррекция по физической расходной складской проводке, то коррекция не протягивается по цепочке себестомости. Тем не менее, коррекции себестоимости списания записываются в таблицу складских сопоставлений и если по данному списанию у нас выполнялась разноска в ГК по физической складской проводке, то в результате разноски в ГК закрытия или пересчета в главную книгу попадет и сумма коррекции. Надо отметить, что коррекции физических проводок списания записываются в таблицу складских сопоставлений с специальной моделью (поле settleModel) -"Физическое значение". Соответственно - эти коррекции можно легко вычленить при анализе результатов закрытия/пересчета склада.

Значит для того, чтобы у нас корректно списывалась себестоимость по складским проводкам списания по ПЗ нам нужно сделать следующее:

· Надо для всех материалов включить в настройках складской модели режим "Включать физическую себестоимость"

· Для того чтобы у нас делались проводки по ГК при выполнении физических списаний нужно не только включить разноску физических запасов в параметрах складских моделей но и включить галку "Разнести отгрузочную накладную в главную книгу" в параметрах модуля производство.

· Хотя, строго говоря, к проблеме списания номенклатуры в производство это не имеет отношения, но было бы полезно если бы при приемке ГП у нас тоже делались проводки по ГК. Для этого надо у ГП включить режим разноски физических операций в параметрах складской модели и галку "Отчет о разноске принятого в ГК" в параметрах складского модуля. Кроме того, для того чтобы система порождала правдоподобную себестоимость при физическом оприходовании ГП по операции приемки, надо поставить в параметрах модуля производства галку "Использовать оцененную себестоимость". Если эта галка включена – система будет рассчитывать себестоимость прихода на основании списка накопленных по ПЗ затрат в таблице ProdCalcTrans.

· Наконец – надо в настройке разноски по складу, прописать в качестве счета и корсчета отгрузочной накладной те же счета что и для расхода и корсчета расхода. (20.xx и 10.xx). Для счета и корсчета приемки, надо поставить те же счета что и прихода и корсчета прихода (43.xx и 20.xx)

· Для счетов нематериальных затрат, надо прописать одинаковые счета и корсчета и для НЗП и для счетов по калькуляции себестоимости. (скажем – 20.xx и 70.xx)

В таком случае – мы получаем следующую картину:

1. При списании номенклатуры по журналу отборочных накладных, получаем проводки Д 20.xx К 10.xx с правдоподобной суммой. (Фактически – мгновенной себестоимостью)

2. При приходовании номенклатуры по журналу приемки также получаем разумные проводки Д 43.xx К 20.xx с разумной себестоимостью.

3. При списании нематериальных затрат – проводки Д 20.xx К 70.xx

4. В случае если производственный заказ длиться долго и закрытие склада произошло раньше чем заказ был завершен – то при закрытии склада получаем проводки коррекции Д20.xx К 10.xx, которые доводят остаток на 10 счете до правильного значения.

5. При завершении заказа – получаем сначала реверсивные проводки по складу (реверс физической операции) Д 10.xx К 20.xx для расхода и Д20.xx К 43.xx для прихода. При этом в качестве суммы реверсивной проводки берется сумма С УЧЕТОМ накопленной при закрытии склада коррекции. Кроме того – реверсируются нематериальные затраты (Д 70 К 20). После этого – делаются обычные финансовые складские проводки прихода и списания с разноской в ГК (Д 20.xx К 10.xx и Д 43.xx К 20.xx) и проводки на нематериальные затраты (Д 20.xx К 70.xx).

6. При следующем закрытии склада у нас делаются обычные коррекции Д 20.xx К 10.xx и Д 43.xx К 20.xx как при обычном закрытии склада. При этом – прошлые проводки коррекции по закрытию склада уже не играют, поскольку при выполнении финансовой разноски складских проводок, они были отреверсированы.

В общем – получается чуток странноватая с точки зрения русского бухгалтера, но в целом вполне жизненная схема. Единственное что выглядит странным – это то что при завершении ПЗ, предварительные проводки не сторнируются, а реверсируются.

Но эту особенность можно достаточно легко вылечить. Для того чтобы реверс складских операций выполнялся как сторно, достаточно в методах updateFinancialIssue и updateFinancialReceipt класса inventUpd_financial найти кусок кода, который вызывывает метод разноски физических складских операций по ГК: movement.updateLedgerPhysical(ledgerVoucher,inventTrans,this,inventOnhand);

 До и после этого метода надо вставить следующий кусочек кода, который переключает режим сторнирования:

ledgerVoucher.findLedgerVoucherObject().parmCorrection(!ledgerVoucher.findLedgerVoucherObject().parmCorrection());

Наконец – для того чтобы заменить реверс начисления нематериальных затрат а сторно, достаточно вставить аналогичные куски кода в метод UpdateCalcLedger класса ProdJobType. В его начале есть следующий кусок:

if (prodRouteTrans.PostingWIPValuation && prodRouteTrans.Amount)

{

…………

}

Если в начале и конце кода, выполняемого по условию If () вставить знакомый нам кусок кода для включения/выключения сторнировки – реверс замениться сторнированием.

Нужно помнить, что включив в классе inventUpd_Financial сторнирование вместо реверсирования мы включим его не только для производства, но и для обычных заказов и закупок. Соответственно, в случае если у нас по отборочной накладной по заказу или закупке проводились проводки в главную книгу, то при оформлении обычной накладной, эти проводки будут не отреверсированы, а отсторнированы.

Наконец, чтобы завершить тему себестоимости по производству, надо упомянуть о галке "Обновить производство" в параметрах закрытия/пересчета склада. Если эта галка установлена, то данные о коррекции проводок списания в производство попадают не только в ГК и таблицу складских сопоставлений, но и в поле realCostAdjustment (Корректировка) той записи таблицы ProdCalcTrans (Рассчет), которая привязана к корректируемой складской проводке списания, а также в поле Adjustment (корректировка) журнала отгрузочных накладных. Кроме того – при этом обновляется сумма корректировки в той записи таблицы ProdCalcTrans, которая привязана к проводке по приходу готовой продукции по ПЗ.

Как сторнировать журнал спецификаций?

Вчера бывший коллега задал мне именно этот вопрос.Самый банальный подход - просто создать аналогичный журнал, подставив туда количества с обратным знаком  - не работает. Во первых - в процедуре редактирования и разноски этого журнала есть куча проверок, которые не дадут этого сделать. Во вторых - этот журнал предназначен для СБОРКИ изделий, но никак не для РАЗБОРКИ таковых. В третьих - если подумать - у нас ведь стоит задача не разобрать чего-то, а просто отсторнировать исходный журнал.

На самом деле, задача сторнировки журнала спецификаций решается с помощью журнала складских проводок. Оприходованную по журналу спецификаций номенклатуру надо списать в складском журнале на коррсчет прихода из настроек складской разноски по производству. После этого - надо это списание примаркировать к приходу по журналу спецификаций. Списанную по журналу спецификаций номенклатуру надо оприходовать с коррсчета расхода из настроек складской разноски по производству. При этом - обязательно нужно указать номер возвращаемого лота, прописав туда ссылку на тот лот, по которому эта номенклатура списывалась по журналу спецификаций. Себестоимость в этих страках журнала можно поставить нулевую, поскольку после закрытия склада или пересчета, система поставит по складским проводкам правильную себестоимость, равную себестоимости списания по журналу спецификации.

Posted by denisfed | 1 Comments

Перемещаем товар программно

На проектах время от времени возникает ситуация, при которой заказчик хочет приделать какой-то свой собственный интерфейс к складским перемещениям. Классический пример - для автоматизации транспортной компании нужно сделать какой-то модуль, который бы отслеживал перемещение транспорта и накапливал бы затраты на перемещение, а также  попутно переносил бы в логистическом модуле товар между складами, каждый из которых соответствует путевым точкам или маршрутам от точки до точки. Обычно, в таком случае, в таком случае разработчики приписывают к данному модулю механизм, который создает и разносит журналы переноса. Этот механизм получается достаточно тяжеловесным и неудобным. Кроме того - есть шансы что при неаккуратной настройке журналов переноса этот механизм интеграции с логистикой сломается и будет выдавать непонятные пользователю сообщения (скажем - об отсутствии имени складского журнала или номерной серии для нумерации журналов).

Существует гораздо более изящный и удобный механизм, позволяющий перемещать товар между складскими аналитиками. Этот механизм - использование функции InventUpd_Financial::updateVirtuelTransfer(). Вот список ее параметров:

 

static void updateVirtuelTransfer(
ItemId _itemId,
InventDimId _fromInventDimId,
InventDimId _toInventDimId,
TransDate _transDate,
InventQty _qty, // Positive
NoYes _mustBeQuarantineControlled
)

Думаю - по названию параметров их смысл и так понятен - номенклатура, складская аналитика-источник, складская-аналитика приемник, дата, количество (положительное) и признак использования карантинного склада (я бы его всегда выключал). Для того чтобы это все работало - достаточно настроить в параметрах модуля управления запасами номерную серию "Операция журнала". (В третьей версии этот параметр, кажется, назывался "Документ ГК по журналу").

Себестоимость и закрытие склада

Текст статьи (с обновлениями для Dynamics AX 2009) переехал по адресу:

Себестоимость и закрытие склада 

Posted by denisfed | 1 Comments
 
Page view tracker