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