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