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