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