При разработке и модификации конфигураций на платформе 1С:Предприятие 8 часто возникает задача корректного формирования движений по регистрам накопления. Одной из распространенных и трудноуловимых проблем является дублирование записей в таких регистрах. Давайте рассмотрим эту проблему подробнее, выясним ее причины и предложим эффективные решения, основываясь на вашем запросе и рекомендациях экспертов.
Мы столкнулись с ситуацией, когда при проведении документа, содержащего схожие измерения (Номенклатура, Партия, Комиссионер) с уже существующими в регистре накопления, происходит дублирование записей. Количество дубликатов при этом пропорционально числу ранее проведенных похожих документов. Вы предполагаете, что проблема кроется в некорректном соединении более двух таблиц в запросе. Давайте проанализируем ваш запрос:
ВЫБРАТЬ
РасходнаяНакладнаяСписокНоменклатуры.Комиссионер,
РасходнаяНакладнаяСписокНоменклатуры.Комиссионер.Представление,
РасходнаяНакладнаяСписокНоменклатуры.Партия,
РасходнаяНакладнаяСписокНоменклатуры.Партия.Представление,
РасходнаяНакладнаяСписокНоменклатуры.Номенклатура,
РасходнаяНакладнаяСписокНоменклатуры.Номенклатура.Представление,
РасходнаяНакладнаяСписокНоменклатуры.Количество,
РасходнаяНакладнаяСписокНоменклатуры.Цена,
РасходнаяНакладнаяСписокНоменклатуры.Сумма,
РасходнаяНакладнаяСписокНоменклатуры.ПроцентВознаграждения
ПОМЕСТИТЬ втТЧТовары
ИЗ
Документ.РасходнаяНакладная.СписокНоменклатуры КАК РасходнаяНакладнаяСписокНоменклатуры
ГДЕ
РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
&Период,
ЕСТЬNULL(ОстаткиНоменклатурыОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток,
втТЧТовары.Комиссионер,
втТЧТовары.КомиссионерПредставление,
втТЧТовары.Партия,
втТЧТовары.ПартияПредставление,
втТЧТовары.Номенклатура,
втТЧТовары.НоменклатураПредставление,
втТЧТовары.Количество,
втТЧТовары.Цена,
втТЧТовары.Сумма,
втТЧТовары.ПроцентВознаграждения,
ОстаткиНоменклатуры.Цена КАК ЦенаОстаток,
ОстаткиНоменклатуры.ПроцентВознаграждения КАК ПроцентВознагражденияОстаток,
ОстаткиНоменклатуры.Сумма КАК СуммаОстаток
ИЗ
втТЧТовары КАК втТЧТовары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиНоменклатуры КАК ОстаткиНоменклатуры
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиНоменклатуры.Остатки КАК ОстаткиНоменклатурыОстатки
ПО ОстаткиНоменклатуры.Номенклатура = ОстаткиНоменклатурыОстатки.Номенклатура
И ОстаткиНоменклатуры.Партия = ОстаткиНоменклатурыОстатки.Партия
И ОстаткиНоменклатуры.Комиссионер = ОстаткиНоменклатурыОстатки.Комиссионер
ПО втТЧТовары.Комиссионер = ОстаткиНоменклатуры.Комиссионер
И втТЧТовары.Партия = ОстаткиНоменклатуры.Партия
И втТЧТовары.Номенклатура = ОстаткиНоменклатуры.Номенклатура
Мы видим, что вы пытаетесь соединить таблицу движений регистра (РегистрНакопления.ОстаткиНоменклатуры) с виртуальной таблицей остатков (РегистрНакопления.ОстаткиНоменклатуры.Остатки) этого же регистра. Вот основные причины, почему это приводит к дублированию:
втТЧТовары <-- ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиНоменклатуры КАК ОстаткиНоменклатуры <-- ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиНоменклатуры.Остатки КАК ОстаткиНоменклатурыОстатки.
Это означает, что виртуальная таблица ОстаткиНоменклатурыОстатки соединяется с таблицей движений ОстаткиНоменклатуры, а затем результат этого соединения объединяется с втТЧТовары. Если для одной комбинации измерений (Номенклатура, Партия, Комиссионер) в таблице движений РегистрНакопления.ОстаткиНоменклатуры существует несколько записей (например, несколько приходов с разными ценами), то каждая строка из втТЧТовары будет дублироваться столько раз, сколько есть совпадений в таблице движений, а затем еще умножаться на количество совпадений из виртуальной таблицы Остатки (хотя в Остатки по измерениям обычно одна строка).
Период для виртуальной таблицы РегистрНакопления.ОстаткиНоменклатуры.Остатки(). Без явного указания даты, эта таблица рассчитает остатки на самый последний момент времени, что не всегда подходит для проверки остатков перед проведением текущего документа.КоличествоОстаток) и информацию о движениях (ЦенаОстаток, ПроцентВознагражденияОстаток) из таблицы движений. Эти данные имеют разную природу и требуют разных подходов к получению, особенно если Цена и ПроцентВознаграждения являются реквизитами, а не измерениями регистра. Если цена является реквизитом, то по одной комбинации измерений может быть несколько движений с разными ценами, что дополнительно умножает строки при соединении.Ваше замечание о контроле цены и процента ("Движения регистра я использую для контроля цены и процент, чтобы не проводилось при цене и проценте ниже того что было в приходной накладной") очень важно. Мы учтем это при построении правильного решения.
Чтобы избежать дублирования записей и корректно работать с регистрами накопления, мы рекомендуем следующие шаги:
Прежде чем формировать новые движения, всегда очищайте движения текущего документа. Это стандартная практика при проведении документов и помогает избежать накопления дубликатов, особенно при перепроведении. Если вы не используете стандартный механизм записи движений через Движения.РегистрНакопленияИмя.Записать(), а формируете их в запросе и добавляете программно, убедитесь, что перед этим вызывается:
Движения.ОстаткиНоменклатуры.Очистить();
Это удалит все движения, связанные с текущим документом, перед тем как будут записаны новые.
Виртуальная таблица Остатки(): Используйте ее, если вам нужны именно текущие остатки (количество, сумма) по измерениям на определенный момент времени. Она агрегирует все движения и дает итоговое значение.
Таблица движений РегистрНакопления.ИмяРегистра: Используйте ее, если вам нужны конкретные записи движений (например, чтобы найти максимальную цену прихода по партии или определенные характеристики движения).
Ключевой момент: Старайтесь не соединять напрямую таблицу движений и виртуальную таблицу остатков одного и того же регистра в одном запросе без очень специфичных и четко определенных условий, иначе вы почти гарантированно получите дублирование или некорректные данные.
При использовании виртуальной таблицы Остатки(), всегда указывайте параметр Период. Это позволяет получить остатки на конкретный момент времени, исключая движения текущего документа, если это необходимо. Например, для получения остатков на начало проведения документа:
РегистрНакопления.ОстаткиНоменклатуры.Остатки(&МоментВремениДокумента)
Разделяйте логику получения остатков, контроля цен/процентов и формирования новых движений. Это можно сделать с помощью временных таблиц:
втТЧТовары).Остатки(), используя данные из втТЧТовары для отбора, и помещаем их в новую временную таблицу (например, втТекущиеОстатки).МАКСИМУМ) и также помещаем их во временную таблицу (например, втКонтрольныеДанные).втТЧТовары с втТекущиеОстатки и втКонтрольныеДанные. Это позволит нам получить все необходимые данные без дублирования.Давайте применим эти принципы к вашему запросу и предложим улучшенную структуру. Мы разделим запрос на несколько частей, чтобы четко разграничить получение данных табличной части, остатков и контрольных значений.
Шаг 1: Получение данных табличной части документа (ваш втТЧТовары остается без изменений, это отличный старт):
ВЫБРАТЬ
РасходнаяНакладнаяСписокНоменклатуры.Комиссионер,
РасходнаяНакладнаяСписокНоменклатуры.Комиссионер.Представление КАК КомиссионерПредставление,
РасходнаяНакладнаяСписокНоменклатуры.Партия,
РасходнаяНакладнаяСписокНоменклатуры.Партия.Представление КАК ПартияПредставление,
РасходнаяНакладнаяСписокНоменклатуры.Номенклатура,
РасходнаяНакладнаяСписокНоменклатуры.Номенклатура.Представление КАК НоменклатураПредставление,
РасходнаяНакладнаяСписокНоменклатуры.Количество,
РасходнаяНакладнаяСписокНоменклатуры.Цена,
РасходнаяНакладнаяСписокНоменклатуры.Сумма,
РасходнаяНакладнаяСписокНоменклатуры.ПроцентВознаграждения
ПОМЕСТИТЬ втТЧТовары
ИЗ
Документ.РасходнаяНакладная.СписокНоменклатуры КАК РасходнаяНакладнаяСписокНоменклатуры
ГДЕ
РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка
;
Шаг 2: Получение остатков на момент проведения документа. Здесь мы используем виртуальную таблицу Остатки() с указанием параметра &МоментВремениДокумента (который должен быть передан как параметр запроса, например, Документ.МоментВремени() или Дата документа, в зависимости от потребностей). Также мы используем вложенные запросы для отбора по измерениям, что улучшает производительность.
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ОстаткиНоменклатурыОстатки.Комиссионер,
ОстаткиНоменклатурыОстатки.Партия,
ОстаткиНоменклатурыОстатки.Номенклатура,
СУММА(ОстаткиНоменклатурыОстатки.КоличествоОстаток) КАК КоличествоОстаток
ПОМЕСТИТЬ втОстаткиНаДокумент
ИЗ
РегистрНакопления.ОстаткиНоменклатуры.Остатки(
&МоментВремениДокумента, // Используем параметр МоментВремениДокумента
Номенклатура В (ВЫБРАТЬ втТЧТовары.Номенклатура ИЗ втТЧТовары)
И Партия В (ВЫБРАТЬ втТЧТовары.Партия ИЗ втТЧТовары)
И Комиссионер В (ВЫБРАТЬ втТЧТовары.Комиссионер ИЗ втТЧТовары)
) КАК ОстаткиНоменклатурыОстатки
СГРУППИРОВАТЬ ПО
ОстаткиНоменклатурыОстатки.Комиссионер,
ОстаткиНоменклатурыОстатки.Партия,
ОстаткиНоменклатурыОстатки.Номенклатура
;
Обратите внимание: если Цена является измерением регистра накопления, то в Остатки() будут отдельные строки для каждой уникальной цены, и тогда СУММА(КоличествоОстаток) будет агрегировать остатки по всем ценам, что может быть не то, что вам нужно. В этом случае, если цена важна для остатков, ее тоже нужно включить в группировку или использовать более сложную логику, возможно, через регистр сведений.
Шаг 3: Получение контрольных цен и процентов из движений. Для контроля цен и процентов (как вы упомянули, "чтобы не проводилось при цене и проценте ниже того что было в приходной накладной") мы будем работать непосредственно с таблицей движений. Мы найдем максимальную цену и процент вознаграждения для каждой комбинации измерений, которые были введены до текущего документа. Предполагаем, что регистр ведется по типу "Приход/Расход", и приходы являются источником "исходных" цен. Если это не так, условие ВидДвижения = &ВидДвиженияПриход следует адаптировать.
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ОстаткиНоменклатурыДвижения.Комиссионер,
ОстаткиНоменклатурыДвижения.Партия,
ОстаткиНоменклатурыДвижения.Номенклатура,
МАКСИМУМ(ОстаткиНоменклатурыДвижения.Цена) КАК МаксимальнаяЦенаПрихода,
МАКСИМУМ(ОстаткиНоменклатурыДвижения.ПроцентВознаграждения) КАК МаксимальныйПроцентПрихода
ПОМЕСТИТЬ втКонтрольныеПараметры
ИЗ
РегистрНакопления.ОстаткиНоменклатуры КАК ОстаткиНоменклатурыДвижения
ГДЕ
ОстаткиНоменклатурыДвижения.Комиссионер В (ВЫБРАТЬ втТЧТовары.Комиссионер ИЗ втТЧТовары)
И ОстаткиНоменклатурыДвижения.Партия В (ВЫБРАТЬ втТЧТовары.Партия ИЗ втТЧТовары)
И ОстаткиНоменклатурыДвижения.Номенклатура В (ВЫБРАТЬ втТЧТовары.Номенклатура ИЗ втТЧТовары)
И ОстаткиНоменклатурыДвижения.ВидДвижения = ВИДДВИЖЕНИЯ.Приход // Предполагаем, что есть такой вид движения
И ОстаткиНоменклатурыДвижения.Период < &МоментВремениДокумента // Важно: до текущего документа
СГРУППИРОВАТЬ ПО
ОстаткиНоменклатурыДвижения.Комиссионер,
ОстаткиНоменклатурыДвижения.Партия,
ОстаткиНоменклатурыДвижения.Номенклатура
;
Параметр &МоментВремениДокумента должен быть тем же, что и для остатков. ВИДДВИЖЕНИЯ.Приход – это пример. Убедитесь, что ваш регистр имеет тип движения "Приход". Если вы храните цену в регистре накопления, убедитесь, что она соответствует логике вашего контроля.
Шаг 4: Финальный запрос для формирования движений. Теперь мы соединяем все наши временные таблицы, чтобы получить полную картину и сформировать движения регистра. Выбираем все необходимые поля, включая информацию из документа, рассчитанные остатки и контрольные параметры.
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
&Период КАК Период, // Период движения (обычно Дата документа)
ДОБАВИТЬКДАТЕ(&Период, СЕКУНДА, 1) КАК МоментВремени, // Или МоментВремениДокумента
&Ссылка КАК Регистратор,
ВЫБОР КОГДА втТЧТовары.Количество > 0 ТОГДА ВИДДВИЖЕНИЯ.Приход ИНАЧЕ ВИДДВИЖЕНИЯ.Расход КОНЕЦ КАК ВидДвижения, // Пример определения вида движения
втТЧТовары.Комиссионер,
втТЧТовары.Партия,
втТЧТовары.Номенклатура,
втТЧТовары.Количество КАК Количество, // Количество из документа
втТЧТовары.Цена КАК Цена,
втТЧТовары.Сумма КАК Сумма,
втТЧТовары.ПроцентВознаграждения КАК ПроцентВознаграждения,
ЕСТЬNULL(втОстаткиНаДокумент.КоличествоОстаток, 0) КАК ТекущийОстатокКоличество, // Остаток на момент документа
ЕСТЬNULL(втКонтрольныеПараметры.МаксимальнаяЦенаПрихода, 0) КАК КонтрольнаяЦена,
ЕСТЬNULL(втКонтрольныеПараметры.МаксимальныйПроцентПрихода, 0) КАК КонтрольныйПроцент
ИЗ
втТЧТовары КАК втТЧТовары
ЛЕВОЕ СОЕДИНЕНИЕ втОстаткиНаДокумент КАК втОстаткиНаДокумент
ПО втТЧТовары.Комиссионер = втОстаткиНаДокумент.Комиссионер
И втТЧТовары.Партия = втОстаткиНаДокумент.Партия
И втТЧТовары.Номенклатура = втОстаткиНаДокумент.Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ втКонтрольныеПараметры КАК втКонтрольныеПараметры
ПО втТЧТовары.Комиссионер = втКонтрольныеПараметры.Комиссионер
И втТЧТовары.Партия = втКонтрольныеПараметры.Партия
И втТЧТовары.Номенклатура = втКонтрольныеПараметры.Номенклатура
ГДЕ
// Здесь можно добавить условия для контроля, например:
// втТЧТовары.Цена >= втКонтрольныеПараметры.МаксимальнаяЦенаПрихода ИЛИ втКонтрольныеПараметры.МаксимальнаяЦенаПрихода = 0
В этом финальном запросе вы получаете строку для каждого элемента табличной части документа. Для каждого такого элемента будут присоединены текущие остатки и контрольные параметры. Теперь на основе этих данных вы можете формировать движения регистра, например, с помощью объекта Движения в модуле проведения документа, или выполнить проверку условий для предотвращения проведения. Для отладки и проверки работы подобных составных пакетов запросов подойдёт инженерная консоль запросов для 1С.
Мы выяснили, что вы используете движения регистра для контроля цен и процентов. Если цена и процент являются реквизитами регистра накопления, а не измерениями, то для получения "последней" или "максимальной" цены для контроля, как мы показали выше, необходима агрегация. Однако, для хранения цен и процентов, которые могут меняться со временем, часто более подходящим инструментом является периодический регистр сведений.
ЦеныНоменклатурыКомиссионера.
Номенклатура, Комиссионер, Партия (если цена зависит от партии).Цена, ПроцентВознаграждения.По позиции регистратора или В пределах секунды.Тогда для получения актуальных цен и процентов для контроля или для записи в регистр накопления (если они там нужны как реквизиты), вы будете использовать виртуальную таблицу СрезПоследних этого регистра сведений:
ВЫБРАТЬ
ЦеныНоменклатурыКомиссионераСрезПоследних.Номенклатура,
ЦеныНоменклатурыКомиссионераСрезПоследних.Комиссионер,
ЦеныНоменклатурыКомиссионераСрезПоследних.Партия,
ЦеныНоменклатурыКомиссионераСрезПоследних.Цена,
ЦеныНоменклатурыКомиссионераСрезПоследних.ПроцентВознаграждения
ИЗ
РегистрСведений.ЦеныНоменклатурыКомиссионера.СрезПоследних(
&МоментВремениДокумента,
(Номенклатура В (ВЫБРАТЬ втТЧТовары.Номенклатура ИЗ втТЧТовары)
И Комиссионер В (ВЫБРАТЬ втТЧТовары.Комиссионер ИЗ втТЧТовары)
И Партия В (ВЫБРАТЬ втТЧТовары.Партия ИЗ втТЧТовары)))
) КАК ЦеныНоменклатурыКомиссионераСрезПоследних
Это позволит получить одну уникальную запись с ценой и процентом на определенный момент времени для каждой комбинации измерений, значительно упростив логику контроля.
ОстаткиНоменклатуры, то каждая уникальная комбинация (Номенклатура, Партия, Комиссионер, Цена) будет отдельной строкой. В этом случае, для контроля или выбора "правильной" цены, нужно будет в запросе к таблице движений использовать более сложную логику, возможно, с отбором по документу-основанию или выборкой максимальной цены по периоду.Дублирование записей в регистрах накопления — это часто следствие неправильно построенных запросов, особенно при смешивании разных типов данных (движений и остатков) или отсутствии четких условий соединения и отбора. Разделение запроса на логические части с использованием временных таблиц, явное указание периода для виртуальных таблиц остатков и правильный выбор источника данных (движения или остатки) помогут вам избежать этой проблемы. Для хранения и контроля цен рассмотрите возможность использования периодических регистров сведений, что является более элегантным и производительным решением в большинстве случаев. Для автоматического управления временем жизни созданных таблиц в таких пакетах можно использовать обработку для оптимизации временных таблиц в пакетных запросах.
Всегда помните о необходимости очистки движений документа перед их повторной записью, чтобы сохранить целостность данных в вашей информационной базе 1С.