При работе в 1С часто возникает задача, когда изменения в одном документе должны автоматически приводить к корректировкам в другом, связанном документе. Рассмотрим типичную ситуацию: вы изменяете цену номенклатуры в документе "Заказ поставщику" и хотите, чтобы система автоматически нашла эту же номенклатуру в "Заказе клиента", который был создан на основании этого заказа поставщику, и выполнила определенные действия, например, проверила или скорректировала его цену. Однако, в процессе реализации этой логики многие сталкиваются с ошибками, такими как "сравнение типов" или некорректное получение данных текущей строки табличной части.
Давайте подробно разберем, как правильно реализовать эту логику, избегая распространенных ошибок и используя эффективные методы платформы 1С.
Основная проблема заключается в том, что при изменении строки табличной части документа "Заказ поставщику" необходимо:
Начальные попытки часто приводят к ошибкам по следующим причинам:
Элементы.Товары.ТекущаяСтрока в управляемых формах возвращает идентификатор текущей строки, а не саму строку или ее данные. Попытка прямого индексирования Объект.Товары[Элементы.Товары.ТекущаяСтрока] приводит к ошибкам, так как коллекция табличной части ожидает числовой индекс, а не идентификатор.
Элементы.Товары.ТекущиеДанные, которое предоставляет удобный доступ к данным текущей строки, доступно только на клиенте. При попытке использовать его в серверной процедуре возникает ошибка "Текущие данные таблицы недоступны на сервере". Это означает, что для серверной обработки нам нужно передавать данные с клиента.
СтрокаКлиента.Номенклатура с СтрокаПоставщика.Номенклатура). Или если одна из переменных, участвующих в сравнении, не была инициализирована или получила неожиданное значение.
Давайте разберем по шагам правильное решение.
Для того чтобы получить данные измененной строки табличной части "Заказа поставщику" и передать их для обработки на сервер, мы должны использовать идентификатор строки.
Мы будем работать в модуле формы документа "Заказ поставщику". Допустим, у нас есть обработчик события ПриИзменении для колонки "Цена" в табличной части "Товары".
Элементы.Товары.ТекущаяСтрока.
НайтиПоИдентификатору() коллекции табличной части объекта для получения самой строки табличной части. Этот метод позволяет найти нужную строку по ее уникальному идентификатору.
Посмотрим на пример кода:
// Это процедура-обработчик события ПриИзменении для колонки "Цена" в табличной части "Товары".
// Например, ТоварыЦенаПриИзменении
Процедура ТоварыЦенаПриИзменении(Элемент)
// Прежде всего, убедимся, что есть выделенная строка.
// Элементы.Товары.ТекущаяСтрока возвращает УНИКАЛЬНЫЙ ИДЕНТИФИКАТОР строки,
// а не индекс или саму строку.
Если Элементы.Товары.ТекущаяСтрока <> Неопределено Тогда
// Вызываем серверную процедуру, передавая ей идентификатор текущей строки.
// Это позволяет избежать ошибок доступа к клиентским данным на сервере.
ОбработатьИзменениеЦеныНаСервере(Элементы.Товары.ТекущаяСтрока);
КонецЕсли;
КонецПроцедуры
// Эта процедура выполняется на сервере.
// Она принимает ИдентификаторСтроки, который был получен на клиенте.
&НаСервере
Процедура ОбработатьИзменениеЦеныНаСервере(ИдентификаторТекущейСтроки)
// Используем метод НайтиПоИдентификатору() для получения объекта строки табличной части
// из коллекции Товары объекта документа (Объект - это ссылка на объект документа ЗаказПоставщику).
СтрокаПоставщика = Объект.Товары.НайтиПоИдентификатору(ИдентификаторТекущейСтроки);
// Важно всегда проверять, что строка была найдена.
Если СтрокаПоставщика = Неопределено Тогда
Сообщить("Ошибка: Не удалось найти строку Заказа поставщика по идентификатору.");
Возврат;
КонецЕсли;
// Теперь СтрокаПоставщика - это полноценный объект строки табличной части,
// и мы можем обращаться к его реквизитам.
Сообщить("Номенклатура в Заказе поставщика: " + СтрокаПоставщика.Номенклатура);
Сообщить("Цена в Заказе поставщика: " + СтрокаПоставщика.Цена);
Сообщить("Назначение строки поставщика: " + СтрокаПоставщика.Назначение);
// Переходим к следующему шагу: поиск Заказа клиента.
// ...
КонецПроцедуры
Таким образом, мы корректно получили данные измененной строки "Заказа поставщику" в серверном контексте.
Как правило, между документами "Заказ поставщику" и "Заказ клиента" устанавливается связь через реквизит "Назначение" в табличной части "Заказа поставщику" или через реквизит "ДокументОснование" в самом "Заказе поставщику". В данном случае, судя по сообщению автора, связь хранится в реквизите Назначение каждой строки табличной части.
Проанализируем ситуацию:
СтрокаПоставщика.Назначение напрямую содержит ссылку на документ "Заказ клиента" (тип ДокументСсылка.ЗаказКлиента), то получение объекта будет простым.
ПолучитьЗначениеРеквизита(ИзмененнаяЦена.Назначение, "Заказ") намекает на такую косвенную связь, где Назначение является объектом, содержащим реквизит "Заказ". Однако для большинства типовых конфигураций (например, УТ, КА) Назначение чаще всего является прямой ссылкой на документ-основание. Мы рассмотрим более распространенный сценарий с прямой ссылкой, но имейте в виду, что вам может потребоваться адаптировать этот шаг под вашу специфическую конфигурацию.
Посмотрим на пример кода для получения объекта "Заказ клиента":
// Продолжение серверной процедуры ОбработатьИзменениеЦеныНаСервере
// ...
// Предположим, что СтрокаПоставщика.Назначение содержит прямую ссылку на ЗаказКлиента.
СсылкаЗК = СтрокаПоставщика.Назначение;
// Проверяем тип полученной ссылки, чтобы избежать ошибок.
Если ТипЗнч(СсылкаЗК) <> Тип("ДокументСсылка.ЗаказКлиента") Тогда
Сообщить("Ошибка: Реквизит 'Назначение' не содержит ссылку на 'Заказ клиента'.");
Возврат;
КонецЕсли;
// Получаем объект документа "Заказ клиента" для дальнейшей работы с ним.
// Объект документа позволяет изменять его табличные части и реквизиты.
ОбъектЗК = СсылкаЗК.ПолучитьОбъект();
Если ОбъектЗК = Неопределено Тогда
Сообщить("Ошибка: Не удалось получить объект Заказ клиента.");
Возврат;
КонецЕсли;
Сообщить("Найден связанный Заказ клиента: " + ОбъектЗК.Номер + " от " + ОбъектЗК.Дата);
// Теперь у нас есть объект ЗаказПоставщика (Объект) и объект ЗаказКлиента (ОбъектЗК).
// Переходим к следующему шагу: поиск номенклатуры в Заказе клиента.
// ...
Важно помнить, что если Назначение хранит ссылку на другой вид документа или справочника, вам нужно будет сначала получить этот объект, а затем из него извлечь ссылку на "Заказ клиента".
После того как мы получили объект "Заказ клиента", нам нужно найти в его табличной части "Товары" строки, соответствующие номенклатуре из нашего "Заказа поставщику". Для этого мы можем использовать методы НайтиСтроки() или Найти().
НайтиСтроки(): Этот метод является наиболее гибким и рекомендуемым, так как он возвращает массив всех строк, соответствующих заданным критериям отбора. Это особенно полезно, если одна и та же номенклатура может встречаться в "Заказе клиента" несколько раз (например, с разными характеристиками, сериями или просто в разных строках). Метод принимает структуру, где ключ – это имя реквизита табличной части, а значение – искомое значение.
Найти(): Этот метод возвращает только первую найденную строку, соответствующую критерию отбора, или Неопределено, если строка не найдена. Он полезен, если вы уверены, что номенклатура может встретиться в табличной части только один раз, или если вас устраивает обработка только первого совпадения.
Мы будем использовать метод НайтиСтроки() как более универсальный.
// Продолжение серверной процедуры ОбработатьИзменениеЦеныНаСервере
// ...
// Создаем структуру для отбора. Ключ - имя реквизита табличной части,
// Значение - искомое значение номенклатуры.
СтруктураОтбора = Новый Структура("Номенклатура", СтрокаПоставщика.Номенклатура);
// Используем метод НайтиСтроки() для поиска всех строк в табличной части "Товары" Заказа клиента,
// у которых реквизит "Номенклатура" совпадает с номенклатурой из Заказа поставщика.
МассивНайденныхСтрокКлиента = ОбъектЗК.Товары.НайтиСтроки(СтруктураОтбора);
Если МассивНайденныхСтрокКлиента.Количество() > 0 Тогда
Сообщить("В Заказе клиента найдено " + МассивНайденныхСтрокКлиента.Количество()
+ " строк с номенклатурой '" + СтрокаПоставщика.Номенклатура + "'.");
Для Каждого СтрокаКлиента Из МассивНайденныхСтрокКлиента Цикл
// Здесь СтрокаКлиента - это объект строки табличной части Заказа клиента,
// который соответствует номенклатуре из Заказа поставщика.
Сообщить(" Обрабатываем строку Заказа клиента:");
Сообщить(" Текущая цена клиента: " + СтрокаКлиента.Цена);
Сообщить(" Текущее количество клиента: " + СтрокаКлиента.Количество);
// Пример дальнейших действий: сравнение цен и потенциальное обновление.
// Убедимся, что валюты совпадают, чтобы сравнение цен было корректным.
Если ОбъектЗК.Валюта = Объект.Валюта Тогда
// Проверяем, что цена поставщика не равна нулю, чтобы избежать деления на ноль.
Если СтрокаПоставщика.Цена <> 0 Тогда
Расчет = СтрокаКлиента.Цена / СтрокаПоставщика.Цена;
// Например, проверяем, что цена клиента не ниже цены поставщика более чем на 5%.
Если Расчет < 0.95 Тогда
Сообщить("ВНИМАНИЕ! Цена в Заказе клиента (" + СтрокаКлиента.Цена
+ ") для товара '" + СтрокаПоставщика.Номенклатура
+ "' МЕНЬШЕ цены Заказа поставщика (" + СтрокаПоставщика.Цена
+ ") более чем на 5%.");
// Здесь вы можете реализовать логику корректировки цены в Заказе клиента.
// Например, установить новую цену:
// СтрокаКлиента.Цена = СтрокаПоставщика.Цена * 1.1; // Наценка 10%
// СтрокаКлиента.Сумма = СтрокаКлиента.Цена * СтрокаКлиента.Количество;
// Сообщить("Цена клиента скорректирована на " + СтрокаКлиента.Цена);
Иначе
Сообщить("Цена в Заказе клиента соответствует требованиям для товара '"
+ СтрокаПоставщика.Номенклатура + "'.");
КонецЕсли;
Иначе
Сообщить("Цена в Заказе поставщика равна нулю для товара '" + СтрокаПоставщика.Номенклатура + "'. Сравнение невозможно.");
КонецЕсли;
Иначе
Сообщить("Валюты Заказа клиента и Заказа поставщика не совпадают. Сравнение цен пропущено.");
КонецЕсли;
КонецЦикла;
// После того как все необходимые изменения в Объекте ЗаказКлиента выполнены,
// Обязательно сохраните документ.
// РежимЗаписиДокумента.Проведение - если документ должен быть проведен.
// РежимЗаписиДокумента.Запись - если достаточно просто записать.
Попытка
ОбъектЗК.Записать(РежимЗаписиДокумента.Проведение);
Сообщить("Заказ клиента '" + ОбъектЗК.Номер + "' успешно обновлен и проведен.");
Исключение
Сообщить("Ошибка при записи Заказа клиента '" + ОбъектЗК.Номер + "': " + ОписаниеОшибки());
КонецПопытки;
Иначе
Сообщить("В Заказе клиента '" + ОбъектЗК.Номер + "' не найдена номенклатура '"
+ СтрокаПоставщика.Номенклатура + "' из Заказа поставщика.");
КонецЕсли;
В этом коде мы не только находим нужные строки, но и демонстрируем, как можно выполнять проверки и вносить изменения, а также обрабатывать ситуации с разными валютами или нулевыми ценами. Важно помнить, что после внесения изменений в объект документа (ОбъектЗК) его необходимо записать методом Записать(), чтобы изменения сохранились в информационной базе.
Если вы уверены, что в Заказе клиента может быть только одна строка с данной номенклатурой, вы можете использовать метод Найти(). Однако будьте осторожны, так как этот метод возвращает лишь первое совпадение.
// ...
// Поиск одной строки в Заказе клиента с использованием Найти()
СтрокаКлиента = ОбъектЗК.Товары.Найти(СтрокаПоставщика.Номенклатура, "Номенклатура");
Если СтрокаКлиента <> Неопределено Тогда
Сообщить("Найдена одна строка в Заказе клиента: " + СтрокаКлиента.Цена);
// ... дальнейшая логика, аналогичная циклу для НайтиСтроки ...
// После изменений не забудьте ОбъектЗК.Записать().
Иначе
Сообщить("В Заказе клиента не найдена номенклатура: " + СтрокаПоставщика.Номенклатура);
КонецЕсли;
// ...
Обратите внимание, что в исходной попытке автора Если СтрокаКлиента = ИзмененнаяЦена.Номенклатура Тогда была ошибка: СтрокаКлиента - это объект строки табличной части, а ИзмененнаяЦена.Номенклатура - это ссылка на номенклатуру. Сравнивать нужно было СтрокаКлиента.Номенклатура с ИзмененнаяЦена.Номенклатура. Корректное сравнение типов крайне важно.
Элементы.) возможен только на клиенте, а большинство бизнес-логики и работы с объектами данных (Объект.) выполняется на сервере. Поэтому используйте передачу идентификаторов или структурированных данных с клиента на сервер.
Попытка ... Исключение ... КонецПопытки, особенно при записи документов или получении объектов.
ПолучитьОбъект() автоматически блокирует объект для изменения.
Следуя этим рекомендациям и используя предложенные методы, вы сможете надежно реализовать логику синхронизации данных между "Заказом поставщику" и "Заказом клиента", избегая распространенных проблем и обеспечивая корректную работу системы.