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