Как продублировать движения регистра накопления с изменением структуры данных и пересчетом валюты?

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

При кастомизации конфигураций на платформе «1С:Предприятие 8» (особенно таких как УПП, Комплексная автоматизация 1.1 или Управление торговлей 10.3) часто возникает задача параллельного ведения учета. Например, когда необходимо дублировать движения основного регистра партионного учета ПартииТоваровНаСкладах в новый вспомогательный регистр, но с изменением структуры данных (разделением стоимости на закупку и доп. расходы) и в другой валюте (например, пересчет из USD в EUR).

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

Выбор архитектурного решения: Модуль набора записей или Подписка на события

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

  1. Процедуры в модуле набора записей исходного регистра (например, в модуле регистра ПартииТоваровНаСкладах в процедурах ПередЗаписью или ПриЗаписи).
  2. Подписка на события для объекта РегистрНакопленияНаборЗаписей.[ИмяРегистра] на событие ПередЗаписью или ПриЗаписи.

Рассмотрим их подробнее. Изменение модуля самого регистра кажется более простым решением, так как вся логика сосредоточена внутри одного объекта. Однако с точки зрения поддержки и обновления конфигурации этот подход имеет серьезный недостаток — при каждом обновлении вам придется вручную объединять код модуля регистра.

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

Почему таблица движений или набор записей оказываются пустыми в ПередЗаписью?

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

При записи набора движений платформа 1С выполняет двухфазную запись, если включено свойство «Записывать выбранные» или если документ перепроводится:

  1. Первый вызов (Очистка): Платформа записывает пустой набор записей с установленным флагом Замещение = Истина. Это необходимо для того, чтобы удалить старые движения документа из базы данных. В этот момент коллекция ЭтотОбъект (или параметр Источник в подписке) действительно пуста, так как ее цель — очистить регистр.
  2. Второй вызов (Запись новых данных): Платформа заполняет набор записей новыми движениями и производит запись. Только во время этого второго вызова в наборе записей присутствуют реальные строки.

Кроме того, переменная мТаблицаДвижений — это локальное свойство модуля документа или модуля регистра, которое инициализируется далеко не во всех механизмах проведения (например, при использовании универсальных процедур типа ОбщегоНазначения.ЗаписатьТаблицыДокументаВРегистр эта переменная может вообще не заполняться).

Чтобы гарантированно получить записываемые движения, мы должны обращаться непосредственно к самому объекту набора записей (коллекции строк), предварительно отсекая фазу очистки регистра. Посмотрим на пример реализации обработки в подписке на события или модуле набора записей:


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

КонецПроцедуры

Реализация дублирования и трансформации данных

Разберем по шагам, как перенести данные из исходного набора записей в новый регистр-приемник с изменением структуры колонок. Допустим, нам нужно перенести движения, разделив общую стоимость на «Закупку» и «Доп. расходы», а также пересчитать суммы в евро.

Для определения того, какая часть суммы относится к доп. расходам, нам потребуется анализировать документ-регистратор. Если регистратором выступает документ ПоступлениеТоваровИУслуг, то вся сумма относится к чистой закупке. Если регистратор — ПоступлениеДопРасходов или ГТД по импорту, то сумма должна быть отнесена в колонку доп. расходов.

Рассмотрим пример кода трансформации данных:


// Создаем набор записей для нового регистра-приемника
НаборПриемник = РегистрыНакопления.ПартииТоваровНаСкладахЗакупкаДопРасходы.СоздатьНаборЗаписей();
НаборПриемник.Отбор.Регистратор.Установить(Источник.Отбор.Регистратор.Значение);

// Получаем ссылку на документ-регистратор для анализа его типа
РегистраторСсылка = Источник.Отбор.Регистратор.Value;
ТипРегистратора = ТипЗнч(РегистраторСсылка);

// Определяем курс евро на дату документа
КурсЕвро = ПолучитьКурсВалюты(Справочники.Валюты.НайтиПоКоду("978"), РегистраторСсылка.Дата);

Для Каждого ЗаписьИсточника Из Источник Цикл
    
    НоваяЗапись = НаборПриемник.Добавить();
    ЗаполнитьЗначенияСвойств(НоваяЗапись, ЗаписьИсточника);
    
    // Пересчитываем базовые показатели в Евро
    СуммаВЕвро = ЗаписьИсточника.Стоимость / КурсЕвро;
    
    // Разделяем закупку и доп. расходы в зависимости от типа документа
    Если ТипРегистратора = Тип("ДокументСсылка.ПоступлениеДопРасходов") 
       Или ТипРегистратора = Type("ДокументСсылка.ГТДИмпорт") Тогда
        
        НоваяЗапись.СуммаДР = СуммаВЕвро;
        НоваяЗапись.КоличествоДР = ЗаписьИсточника.Количество;
        НоваяЗапись.Сумма = 0;
        НоваяЗапись.Количество = 0;
        
    Иначе
        
        НоваяЗапись.Сумма = СуммаВЕвро;
        НоваяЗапись.Количество = ЗаписьИсточника.Количество;
        НоваяЗапись.СуммаДР = 0;
        НоваяЗапись.КоличествоДР = 0;
        
    КонецЕсли;
    
КонецЦикла;

// Записываем сформированные движения в новый регистр
НаборПриемник.Записать(Истина);

Критическая проблема валютного ФИФО-учета («Проблема копеек»)

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

Представим ситуацию:

  1. Приход: Поступило 10 единиц товара стоимостью 100 USD. Курс EUR/USD на дату прихода составлял 0.9. В евровом регистре зафиксировано 90 EUR.
  2. Расход: Спустя месяц мы списываем эти 10 единиц товара. В исходном регистре списывается 100 USD себестоимости. Но курс EUR/USD на дату списания изменился и стал равным 0.8.
  3. Ошибка: Если мы просто пересчитаем списываемые 100 USD по новому курсу 0.8, то в евровом регистре спишется 80 EUR.

Результат: На складе физически осталось 0 штук товара, по валюте USD остаток равен 0, а по валюте EUR завис неликвидный остаток в размере 10 EUR! При регулярных операциях база данных быстро «засорится» копеечными и рублевыми хвостами, которые никогда не спишутся автоматически.

Как решить проблему валютного списания партий:

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

Рекомендации по заполнению исторических данных

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

При написании обработки для первоначального заполнения нового регистра помните о важнейшем правиле: обработка документов должна происходить строго в хронологическом порядке.

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

← На главную