Как исключить создание нежелательных движений по регистру «Себестоимость товаров» при закрытии месяца в 1С:ERP?

Программист 1С v8.3 (Управляемые формы) 1С:ERP Управление предприятием Управленческий учет
← На главную

Мы сталкиваемся с распространенной ситуацией в 1С:ERP, когда после доработки документов, которые по определенным условиям не должны формировать движения по себестоимости, система при закрытии месяца "досоздает" эти движения. Это приводит к некорректным остаткам, появлению отрицательной себестоимости или другим ошибкам в учете (в таких случаях помогает автоматическое устранение отрицательных остатков). Рассмотрим, как эффективно и безопасно решить эту проблему, предотвратив создание лишних записей в регистре «Себестоимость товаров».

Понимание проблемы: почему движения создаются повторно?

Система 1С:ERP при закрытии месяца выполняет сложный комплекс регламентных операций, одной из которых является расчет себестоимости товаров. Этот механизм анализирует все движения товаров за отчетный период. Если по каким-то причинам документ-регистратор не сформировал ожидаемые движения по регистру Себестоимость товаров (например, из-за вашей доработки, которая исключает эти движения при наличии определенной «галки»), то для обеспечения целостности учета и корректного расчета финансового результата система может воспринять это как ошибку и попытаться «досоздать» эти недостающие движения. Именно это приводит к появлению нежелательных записей и, как следствие, к отрицательным остаткам в отчетах.

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

Оценка рисков и альтернативные подходы

Прежде чем приступить к решению, крайне важно осознать, что мы вмешиваемся в один из самых критичных и сложных механизмов 1С:ERP — расчет себестоимости. Любое некорректное изменение может привести к серьезным проблемам в учете, некорректной финансовой отчетности и сложностям при обновлениях.

Рассмотрим подходы, которые не рекомендуются или требуют крайней осторожности:

  1. Прямое изменение модулей расчета себестоимости: Это наиболее рискованный путь. Модули типа УправлениеЗапасамиПартионныйУчет или процедуры, отвечающие за документ Расчет себестоимости выпуска, содержат крайне сложную логику, и изменение их напрямую практически гарантирует проблемы при обновлении и непредсказуемое поведение системы.

  2. Использование сторнирования движений: Документ «Сторнирование движений» позволяет отменить записи. Однако, его применение требует глубокого понимания влияния на последующие расчеты. Каждое сторнирование — это дополнительный документ и усложнение аналитики. Также, если движения создаются повторно при каждом закрытии месяца, это может стать постоянной ручной работой.

  3. Настройка видов запасов/хозяйственных операций: В 1С:ERP заложена гибкая система настроек для видов запасов и хозяйственных операций. Теоретически, можно попытаться использовать штатные настройки, например, выделив отдельные виды запасов, которые не должны иметь себестоимости. Однако, для такой специфической задачи, как полное исключение себестоимости для *части* перемещений (только при наличии «галки»), это редко дает желаемый результат без дополнительных доработок и может потребовать значительной перестройки аналитики учета.

Мы будем стремиться к решению, которое минимально затрагивает типовую логику и является наиболее контролируемым.

Решение: использование подписки на событие «ПередЗаписью» регистра накопления

Наиболее корректным и безопасным способом решения нашей задачи является использование подписки на событие ПередЗаписью регистра накопления Себестоимость товаров. Этот подход позволяет точечно вмешаться в процесс записи данных в регистр, не затрагивая сложную логику расчета себестоимости, которая выполняется внутри регламентных операций. Мы будем перехватывать попытку записи движений и, при выполнении нашего условия (наличие «галки» в документе), предотвращать их запись.

Принцип работы метода

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

Разберем по шагам, как реализовать это решение:

  1. Создание подписки на событие

    Откроем конфигуратор и перейдем в раздел «Общие» -> «Подписки на события».

    Создадим новую подписку на событие, например, с именем ПриЗаписиСебестоимостиТоваровПеред (или любое другое осмысленное имя, указывающее на ее назначение). Нам потребуется указать следующие параметры:

    • Имя: ПриЗаписиСебестоимостиТоваровПеред
    • Источник: В качестве источника нам необходимо выбрать все объекты, которые могут инициировать запись движений в регистр Себестоимость товаров. В нашем случае, поскольку мы хотим влиять на записи, которые формируются *для* документов «Перемещение товаров» и «Пересортица товаров», нам нужно выбрать сам регистр накопления. * Развернем узел «Регистры накопления» и выберем СебестоимостьТоваров. * Примечание: В некоторых случаях может потребоваться также указывать документы-регистраторы, но для события ПередЗаписью именно самого регистра, мы получаем набор записей, который будет записываться. Если документ-регистратор будет РасчетСебестоимостиВыпуска, но движения в наборе будут относиться к Перемещению, то логика будет сложнее, как мы увидим далее. Но на основании вашего сообщения, что «этот вариант помог», мы будем ориентироваться на то, что в момент записи, регистратором является именно наш модифицированный документ, либо из набора можно получить ссылку на него.
    • Событие: Выберем ПередЗаписью. Это событие сработает непосредственно перед попыткой записи набора движений в базу данных.
    • Обработчик: Создадим новый общий модуль (если у вас уже есть общий модуль для ваших доработок, используйте его, например, НашиДоработкиПереопределяемый или УправлениеЗапасамиНашиДоработки) и в нем экспортируемую процедуру. Назовем ее, например, ОбработкаПередЗаписьюСебестоимостиТоваров.

    Наш обработчик будет принимать два параметра: Источник (набор записей регистра) и Отказ (булевый флаг, который можно установить в Истина, чтобы полностью отменить запись).

  2. Реализация обработчика подписки

    Откроем созданный общий модуль и добавим в него процедуру, которая будет обрабатывать событие ПередЗаписью. В этой процедуре нам необходимо выполнить следующие действия:

    • Убедиться, что Источник действительно является набором записей регистра Себестоимость товаров.
    • Получить ссылку на документ-регистратор из набора записей.
    • Проверить тип документа-регистратора (Перемещение товаров или Пересортица товаров).
    • Обратиться к объекту документа-регистратора, чтобы проверить наличие нашей "галки" (например, реквизита НеДелатьДвиженияПоСС).
    • Если все условия выполняются (документ нужного типа и галка установлена), очистить набор записей регистра с помощью метода Очистить(), предотвратив таким образом их запись в базу данных.

    Рассмотрим пример кода, который может быть использован в обработчике. Предположим, что в документах Перемещение товаров и Пересортица товаров есть булевый реквизит НеДелатьДвиженияПоСС:

    
    Процедура ОбработкаПередЗаписьюСебестоимостиТоваров(Источник, Отказ) Экспорт
                
        // Убеждаемся, что Источник - это набор записей регистра "Себестоимость товаров".
        // Это важно, так как подписка может быть назначена на несколько типов источников.
        Если НЕ ТипЗнч(Источник) = Тип("НаборЗаписейРегистраНакопления.СебестоимостьТоваров") Тогда
            Возврат;
        КонецЕсли;
                
        // Получаем документ-регистратор из набора записей.
        // Регистратор - это документ, который инициировал создание движений.
        ДокументРегистратор = Источник.Отбор.Регистратор.Значение;
                
        // Если регистратор не заполнен или это не ссылка на документ, выходим.
        Если ДокументРегистратор = Неопределено ИЛИ НЕ (ДокументРегистратор ССЫЛКА ДокументСсылка) Тогда
            Возврат;
        КонецЕсли;
                
        // Проверяем тип документа-регистратора и наличие нашей галки.
        // Если регистратором является "Перемещение товаров" и в нем установлена галка "НеДелатьДвиженияПоСС".
        Если ТипЗнч(ДокументРегистратор) = Тип("ДокументСсылка.ПеремещениеТоваров") Тогда
            Попытка
                // Получаем объект документа для доступа к реквизитам.
                ДокументОбъект = ДокументРегистратор.ПолучитьОбъект();
                // Проверяем наличие реквизита и его значение.
                Если ДокументОбъект.Свойство("НеДелатьДвиженияПоСС") И ДокументОбъект.НеДелатьДвиженияПоСС Тогда
                    // Если галка установлена, очищаем весь набор записей.
                    // Это предотвратит запись любых движений по себестоимости для этого документа.
                    Источник.Очистить();
                    Возврат; // Завершаем выполнение, так как записи удалены.
                КонецЕсли;
            Исключение
                // Обработка возможной ошибки, если документ был удален или недоступен.
                // Можно записать в журнал регистрации, но не препятствовать другим движениям.
            КонецПопытки;
                    
        // Если регистратором является "Пересортица товаров" и в нем установлена галка "НеДелатьДвиженияПоСС".
        ИначеЕсли ТипЗнч(ДокументРегистратор) = Тип("ДокументСсылка.ПересортицаТоваров") Тогда
            Попытка
                ДокументОбъект = ДокументРегистратор.ПолучитьОбъект();
                Если ДокументОбъект.Свойство("НеДелатьДвиженияПоСС") И ДокументОбъект.НеДелатьДвиженияПоСС Тогда
                    Источник.Очистить();
                    Возврат; // Завершаем выполнение.
                КонецЕсли;
            Исключение
                // Обработка возможной ошибки.
            КонецПопытки;
        КонецЕсли;
                
        // Важный момент для Расчета себестоимости выпуска:
        // Если документ "РасчетСебестоимостиВыпуска" сам является регистратором,
        // но при этом формирует движения, относящиеся к "исключенным" перемещениям,
        // то логика может быть значительно сложнее.
        // В типовом регистре "Себестоимость товаров" нет прямого измерения,
        // которое бы указывало на "исходный" документ, если регистратором является
        // "РасчетСебестоимостиВыпуска".
        // Однако, поскольку в вашем случае этот метод помог, это может означать,
        // что при досоздании движений при закрытии месяца, в качестве Регистратора
        // все равно используется ссылка на исходное "Перемещение товаров" или "Пересортицу товаров".
        // В ином случае (если регистратором будет "РасчетСебестоимостиВыпуска"),
        // вам потребуется добавить дополнительное измерение в регистр "Себестоимость товаров"
        // (например, "ДокументОснование"), в которое будет записываться ссылка на исходное перемещение.
        // Тогда логика внутри обработчика будет проверять это дополнительное измерение:
        /*
        ИначеЕсли ТипЗнч(ДокументРегистратор) = Тип("ДокументСсылка.РасчетСебестоимостиВыпуска") Тогда
            Для Каждого ЗаписьРегистра Из Источник Цикл
                Если ЗаписьРегистра.Свойство("ДокументОснование")
                   И ТипЗнч(ЗаписьРегистра.ДокументОснование) = Тип("ДокументСсылка.ПеремещениеТоваров") Тогда
                    Попытка
                        ДокументОснованиеОбъект = ЗаписьРегистра.ДокументОснование.ПолучитьОбъект();
                        Если ДокументОснованиеОбъект.Свойство("НеДелатьДвиженияПоСС") И ДокументОснованиеОбъект.НеДелатьДвиженияПоСС Тогда
                            // Удаляем конкретную запись из набора.
                            // Важно: Источник.Удалить(ЗаписьРегистра) нужно использовать осторожно,
                            // так как это может изменить коллекцию во время итерации.
                            // Лучше собрать записи для удаления и удалить их после цикла.
                            // Для простоты примера, если мы полностью уверены, что весь набор
                            // состоит только из таких записей, можно очистить весь Источник.
                            // Но если РасчетСебестоимостиВыпуска формирует и другие, нужные записи,
                            // то нужно удалять поштучно или модифицировать их.
                            // В данном случае, автор подтвердил, что "этот вариант помог",
                            // что подразумевает более простой сценарий.
                                    
                            // В реальной ситуации, если нужно удалять только часть записей,
                            // стоит использовать временный список:
                            // Переменная: ЗаписиДляУдаления = Новый СписокЗначений();
                            // В цикле: ЗаписиДляУдаления.Добавить(ЗаписьРегистра);
                            // После цикла: Для Каждого ЗаписьУдалить Из ЗаписиДляУдаления Цикл Источник.Удалить(ЗаписьУдалить.Значение); КонецЦикла;
                                    
                            // Но вернемся к простому решению, которое, как выяснилось, сработало.
                        КонецЕсли;
                    Исключение
                        // Обработка ошибки
                    КонецПопытки;
                КонецЕсли;
            КонецЦикла;
        */
                
    КонецПроцедуры
    

    Обратите внимание: мы используем метод ПолучитьОбъект() для доступа к реквизитам документа. Это может быть ресурсоемкой операцией при большом количестве движений, но для проверки одной «галки» обычно приемлемо. Также важно предусмотреть обработку исключений (Попытка...Исключение), если, например, документ-регистратор был удален, но остались его движения.

  3. Проверка результата

    После реализации подписки и обновления конфигурации базы данных, обязательно проведите тщательное тестирование. Для сверки данных используйте остатки товаров организаций с себестоимостью. Мы рекомендуем следующие шаги:

    1. Повторите процедуру закрытия месяца.
    2. Проанализируйте отчеты по движениям документа Перемещение товаров и Пересортица товаров. Убедитесь, что для документов с установленной «галкой» движения по регистру Себестоимость товаров отсутствуют.
    3. Проверьте отчеты по регистру Себестоимость товаров в целом (используя сравнение прибыли, себестоимости, цен и выручки в двух периодах), убедитесь, что нежелательные движения больше не формируются и отрицательные остатки отсутствуют.
    4. Проведите проверку целостности данных (здесь пригодится сравнение товаров на складах организаций и себестоимости товаров), чтобы убедиться в отсутствии других, скрытых проблем.

Важные аспекты, которые необходимо учитывать

Внедряя подобные доработки, мы должны быть готовы к потенциальным последствиям и внимательно контролировать систему:

  1. Аналитика учета: Исключение движений по себестоимости для определенных документов может повлиять на всю цепочку учета и аналитики. Вам необходимо четко понимать, как будет формироваться себестоимость товаров при их дальнейшем выбытии (например, при продаже) с тех мест хранения или по тем аналитикам, куда они были перемещены без себестоимости. Если товар перемещается без себестоимости, а затем продается, то система может столкнуться с проблемой определения его стоимости, что может привести к некорректному расчету валовой прибыли. Возможно, потребуется дополнительная логика для определения "фиктивной" себестоимости или специальные корректирующие проводки.

  2. Отчетность: Тщательно проверьте все управленческие, финансовые и бухгалтерские отчеты, связанные с себестоимостью, валовой прибылью, финансовыми результатами и остатками товаров. Убедитесь, что внесенные изменения не приводят к искажению данных и что все показатели рассчитываются корректно с учетом новой логики.

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

  4. Последующие обновления конфигурации: Любые доработки, особенно затрагивающие ключевые механизмы типовой конфигурации, требуют повышенного внимания при обновлении системы. Вам потребуется адаптировать или повторно применить внесенные изменения под новые версии конфигурации. Будьте готовы к тому, что логика расчета себестоимости или структура документов/регистров может измениться, и ваш код потребует корректировки.

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

В заключение, хотя задача исключения документов из расчета себестоимости и является нетривиальной из-за сложности механизмов 1С:ERP, использование подписки на событие ПередЗаписью регистра накопления Себестоимость товаров представляет собой наиболее контролируемый и безопасный путь ее решения. Всегда помните о необходимости глубокого анализа бизнес-процесса и потенциальных последствий для всей системы учета при внесении любых изменений в ядро системы.

← На главную