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