Как в 1С найти соответствующую номенклатуру в "Заказе клиента" при изменении цены в "Заказе поставщика"?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

При работе в 1С часто возникает задача, когда изменения в одном документе должны автоматически приводить к корректировкам в другом, связанном документе. Рассмотрим типичную ситуацию: вы изменяете цену номенклатуры в документе "Заказ поставщику" и хотите, чтобы система автоматически нашла эту же номенклатуру в "Заказе клиента", который был создан на основании этого заказа поставщику, и выполнила определенные действия, например, проверила или скорректировала его цену. Однако, в процессе реализации этой логики многие сталкиваются с ошибками, такими как "сравнение типов" или некорректное получение данных текущей строки табличной части.

Давайте подробно разберем, как правильно реализовать эту логику, избегая распространенных ошибок и используя эффективные методы платформы 1С.

Суть проблемы и распространенные ошибки

Основная проблема заключается в том, что при изменении строки табличной части документа "Заказ поставщику" необходимо:

  1. Правильно определить, какая именно строка была изменена, и получить ее данные.
  2. Обратиться к связанному документу "Заказ клиента", используя поле "Назначение" или аналогичный механизм связи.
  3. Найти в табличной части "Заказа клиента" строку с той же номенклатурой.
  4. Выполнить необходимые действия (например, сравнить или изменить цены).

Начальные попытки часто приводят к ошибкам по следующим причинам:

Давайте разберем по шагам правильное решение.

Шаг 1: Правильное получение данных текущей строки табличной части на сервере

Для того чтобы получить данные измененной строки табличной части "Заказа поставщику" и передать их для обработки на сервер, мы должны использовать идентификатор строки.

Мы будем работать в модуле формы документа "Заказ поставщику". Допустим, у нас есть обработчик события ПриИзменении для колонки "Цена" в табличной части "Товары".

  1. На клиенте получаем идентификатор текущей строки: В клиентской процедуре, которая обрабатывает событие изменения, мы можем получить уникальный идентификатор текущей строки через свойство Элементы.Товары.ТекущаяСтрока.
  2. Передаем идентификатор на сервер: Мы вызываем серверную процедуру, передавая ей этот идентификатор как параметр.
  3. На сервере находим строку по идентификатору: На сервере мы используем метод НайтиПоИдентификатору() коллекции табличной части объекта для получения самой строки табличной части. Этот метод позволяет найти нужную строку по ее уникальному идентификатору.

Посмотрим на пример кода:


// Это процедура-обработчик события ПриИзменении для колонки "Цена" в табличной части "Товары".
// Например, ТоварыЦенаПриИзменении
Процедура ТоварыЦенаПриИзменении(Элемент)
    // Прежде всего, убедимся, что есть выделенная строка.
    // Элементы.Товары.ТекущаяСтрока возвращает УНИКАЛЬНЫЙ ИДЕНТИФИКАТОР строки,
    // а не индекс или саму строку.
    Если Элементы.Товары.ТекущаяСтрока <> Неопределено Тогда
        // Вызываем серверную процедуру, передавая ей идентификатор текущей строки.
        // Это позволяет избежать ошибок доступа к клиентским данным на сервере.
        ОбработатьИзменениеЦеныНаСервере(Элементы.Товары.ТекущаяСтрока);
    КонецЕсли;
КонецПроцедуры

// Эта процедура выполняется на сервере.
// Она принимает ИдентификаторСтроки, который был получен на клиенте.
&НаСервере
Процедура ОбработатьИзменениеЦеныНаСервере(ИдентификаторТекущейСтроки)

    // Используем метод НайтиПоИдентификатору() для получения объекта строки табличной части
    // из коллекции Товары объекта документа (Объект - это ссылка на объект документа ЗаказПоставщику).
    СтрокаПоставщика = Объект.Товары.НайтиПоИдентификатору(ИдентификаторТекущейСтроки);

    // Важно всегда проверять, что строка была найдена.
    Если СтрокаПоставщика = Неопределено Тогда
        Сообщить("Ошибка: Не удалось найти строку Заказа поставщика по идентификатору.");
        Возврат;
    КонецЕсли;

    // Теперь СтрокаПоставщика - это полноценный объект строки табличной части,
    // и мы можем обращаться к его реквизитам.
    Сообщить("Номенклатура в Заказе поставщика: " + СтрокаПоставщика.Номенклатура);
    Сообщить("Цена в Заказе поставщика: " + СтрокаПоставщика.Цена);
    Сообщить("Назначение строки поставщика: " + СтрокаПоставщика.Назначение);

    // Переходим к следующему шагу: поиск Заказа клиента.
    // ...
КонецПроцедуры

Таким образом, мы корректно получили данные измененной строки "Заказа поставщику" в серверном контексте.

Шаг 2: Получение ссылки на связанный "Заказ клиента"

Как правило, между документами "Заказ поставщику" и "Заказ клиента" устанавливается связь через реквизит "Назначение" в табличной части "Заказа поставщику" или через реквизит "ДокументОснование" в самом "Заказе поставщику". В данном случае, судя по сообщению автора, связь хранится в реквизите Назначение каждой строки табличной части.

Проанализируем ситуацию:

  1. Прямая ссылка: Если реквизит СтрокаПоставщика.Назначение напрямую содержит ссылку на документ "Заказ клиента" (тип ДокументСсылка.ЗаказКлиента), то получение объекта будет простым.
  2. Косвенная ссылка: Иногда "Назначение" может ссылаться на другой объект (например, на "Партию", "Серию", или вспомогательный документ планирования), который уже содержит ссылку на "Заказ клиента". В таком случае потребуется дополнительный шаг для извлечения нужной ссылки. Пример автора ПолучитьЗначениеРеквизита(ИзмененнаяЦена.Назначение, "Заказ") намекает на такую косвенную связь, где Назначение является объектом, содержащим реквизит "Заказ". Однако для большинства типовых конфигураций (например, УТ, КА) Назначение чаще всего является прямой ссылкой на документ-основание. Мы рассмотрим более распространенный сценарий с прямой ссылкой, но имейте в виду, что вам может потребоваться адаптировать этот шаг под вашу специфическую конфигурацию.

Посмотрим на пример кода для получения объекта "Заказ клиента":


// Продолжение серверной процедуры ОбработатьИзменениеЦеныНаСервере
// ...
    // Предположим, что СтрокаПоставщика.Назначение содержит прямую ссылку на ЗаказКлиента.
    СсылкаЗК = СтрокаПоставщика.Назначение;

    // Проверяем тип полученной ссылки, чтобы избежать ошибок.
    Если ТипЗнч(СсылкаЗК) <> Тип("ДокументСсылка.ЗаказКлиента") Тогда
        Сообщить("Ошибка: Реквизит 'Назначение' не содержит ссылку на 'Заказ клиента'.");
        Возврат;
    КонецЕсли;

    // Получаем объект документа "Заказ клиента" для дальнейшей работы с ним.
    // Объект документа позволяет изменять его табличные части и реквизиты.
    ОбъектЗК = СсылкаЗК.ПолучитьОбъект();

    Если ОбъектЗК = Неопределено Тогда
        Сообщить("Ошибка: Не удалось получить объект Заказ клиента.");
        Возврат;
    КонецЕсли;

    Сообщить("Найден связанный Заказ клиента: " + ОбъектЗК.Номер + " от " + ОбъектЗК.Дата);

    // Теперь у нас есть объект ЗаказПоставщика (Объект) и объект ЗаказКлиента (ОбъектЗК).
    // Переходим к следующему шагу: поиск номенклатуры в Заказе клиента.
    // ...

Важно помнить, что если Назначение хранит ссылку на другой вид документа или справочника, вам нужно будет сначала получить этот объект, а затем из него извлечь ссылку на "Заказ клиента".

Шаг 3: Поиск номенклатуры в табличной части "Заказа клиента"

После того как мы получили объект "Заказ клиента", нам нужно найти в его табличной части "Товары" строки, соответствующие номенклатуре из нашего "Заказа поставщику". Для этого мы можем использовать методы НайтиСтроки() или Найти().

  1. Метод НайтиСтроки(): Этот метод является наиболее гибким и рекомендуемым, так как он возвращает массив всех строк, соответствующих заданным критериям отбора. Это особенно полезно, если одна и та же номенклатура может встречаться в "Заказе клиента" несколько раз (например, с разными характеристиками, сериями или просто в разных строках). Метод принимает структуру, где ключ – это имя реквизита табличной части, а значение – искомое значение.
  2. Метод Найти(): Этот метод возвращает только первую найденную строку, соответствующую критерию отбора, или Неопределено, если строка не найдена. Он полезен, если вы уверены, что номенклатура может встретиться в табличной части только один раз, или если вас устраивает обработка только первого совпадения.

Мы будем использовать метод НайтиСтроки() как более универсальный.


// Продолжение серверной процедуры ОбработатьИзменениеЦеныНаСервере
// ...
    // Создаем структуру для отбора. Ключ - имя реквизита табличной части,
    // Значение - искомое значение номенклатуры.
    СтруктураОтбора = Новый Структура("Номенклатура", СтрокаПоставщика.Номенклатура);

    // Используем метод НайтиСтроки() для поиска всех строк в табличной части "Товары" Заказа клиента,
    // у которых реквизит "Номенклатура" совпадает с номенклатурой из Заказа поставщика.
    МассивНайденныхСтрокКлиента = ОбъектЗК.Товары.НайтиСтроки(СтруктураОтбора);

    Если МассивНайденныхСтрокКлиента.Количество() > 0 Тогда
        Сообщить("В Заказе клиента найдено " + МассивНайденныхСтрокКлиента.Количество()
                  + " строк с номенклатурой '" + СтрокаПоставщика.Номенклатура + "'.");

        Для Каждого СтрокаКлиента Из МассивНайденныхСтрокКлиента Цикл
            // Здесь СтрокаКлиента - это объект строки табличной части Заказа клиента,
            // который соответствует номенклатуре из Заказа поставщика.
            Сообщить("  Обрабатываем строку Заказа клиента:");
            Сообщить("    Текущая цена клиента: " + СтрокаКлиента.Цена);
            Сообщить("    Текущее количество клиента: " + СтрокаКлиента.Количество);

            // Пример дальнейших действий: сравнение цен и потенциальное обновление.
            // Убедимся, что валюты совпадают, чтобы сравнение цен было корректным.
            Если ОбъектЗК.Валюта = Объект.Валюта Тогда
                // Проверяем, что цена поставщика не равна нулю, чтобы избежать деления на ноль.
                Если СтрокаПоставщика.Цена <> 0 Тогда
                    Расчет = СтрокаКлиента.Цена / СтрокаПоставщика.Цена;

                    // Например, проверяем, что цена клиента не ниже цены поставщика более чем на 5%.
                    Если Расчет < 0.95 Тогда
                        Сообщить("ВНИМАНИЕ! Цена в Заказе клиента (" + СтрокаКлиента.Цена
                                  + ") для товара '" + СтрокаПоставщика.Номенклатура
                                  + "' МЕНЬШЕ цены Заказа поставщика (" + СтрокаПоставщика.Цена
                                  + ") более чем на 5%.");
                        // Здесь вы можете реализовать логику корректировки цены в Заказе клиента.
                        // Например, установить новую цену:
                        // СтрокаКлиента.Цена = СтрокаПоставщика.Цена * 1.1; // Наценка 10%
                        // СтрокаКлиента.Сумма = СтрокаКлиента.Цена * СтрокаКлиента.Количество;
                        // Сообщить("Цена клиента скорректирована на " + СтрокаКлиента.Цена);
                    Иначе
                        Сообщить("Цена в Заказе клиента соответствует требованиям для товара '"
                                  + СтрокаПоставщика.Номенклатура + "'.");
                    КонецЕсли;
                Иначе
                    Сообщить("Цена в Заказе поставщика равна нулю для товара '" + СтрокаПоставщика.Номенклатура + "'. Сравнение невозможно.");
                КонецЕсли;
            Иначе
                Сообщить("Валюты Заказа клиента и Заказа поставщика не совпадают. Сравнение цен пропущено.");
            КонецЕсли;

        КонецЦикла;

        // После того как все необходимые изменения в Объекте ЗаказКлиента выполнены,
        // Обязательно сохраните документ.
        // РежимЗаписиДокумента.Проведение - если документ должен быть проведен.
        // РежимЗаписиДокумента.Запись - если достаточно просто записать.
        Попытка
            ОбъектЗК.Записать(РежимЗаписиДокумента.Проведение);
            Сообщить("Заказ клиента '" + ОбъектЗК.Номер + "' успешно обновлен и проведен.");
        Исключение
            Сообщить("Ошибка при записи Заказа клиента '" + ОбъектЗК.Номер + "': " + ОписаниеОшибки());
        КонецПопытки;

    Иначе
        Сообщить("В Заказе клиента '" + ОбъектЗК.Номер + "' не найдена номенклатура '"
                  + СтрокаПоставщика.Номенклатура + "' из Заказа поставщика.");
    КонецЕсли;

В этом коде мы не только находим нужные строки, но и демонстрируем, как можно выполнять проверки и вносить изменения, а также обрабатывать ситуации с разными валютами или нулевыми ценами. Важно помнить, что после внесения изменений в объект документа (ОбъектЗК) его необходимо записать методом Записать(), чтобы изменения сохранились в информационной базе.

Альтернативный подход: Метод Найти()

Если вы уверены, что в Заказе клиента может быть только одна строка с данной номенклатурой, вы можете использовать метод Найти(). Однако будьте осторожны, так как этот метод возвращает лишь первое совпадение.


// ...
// Поиск одной строки в Заказе клиента с использованием Найти()
СтрокаКлиента = ОбъектЗК.Товары.Найти(СтрокаПоставщика.Номенклатура, "Номенклатура");

Если СтрокаКлиента <> Неопределено Тогда
    Сообщить("Найдена одна строка в Заказе клиента: " + СтрокаКлиента.Цена);
    // ... дальнейшая логика, аналогичная циклу для НайтиСтроки ...
    // После изменений не забудьте ОбъектЗК.Записать().
Иначе
    Сообщить("В Заказе клиента не найдена номенклатура: " + СтрокаПоставщика.Номенклатура);
КонецЕсли;
// ...

Обратите внимание, что в исходной попытке автора Если СтрокаКлиента = ИзмененнаяЦена.Номенклатура Тогда была ошибка: СтрокаКлиента - это объект строки табличной части, а ИзмененнаяЦена.Номенклатура - это ссылка на номенклатуру. Сравнивать нужно было СтрокаКлиента.Номенклатура с ИзмененнаяЦена.Номенклатура. Корректное сравнение типов крайне важно.

Дополнительные рекомендации и важные моменты

  1. Клиент-серверное взаимодействие: Всегда помните о том, что доступ к данным формы (Элементы.) возможен только на клиенте, а большинство бизнес-логики и работы с объектами данных (Объект.) выполняется на сервере. Поэтому используйте передачу идентификаторов или структурированных данных с клиента на сервер.
  2. Обработка ошибок и исключений: В реальных условиях всегда добавляйте обработку ошибок с помощью блоков Попытка ... Исключение ... КонецПопытки, особенно при записи документов или получении объектов.
  3. Производительность: При работе с большими табличными частями и частыми изменениями, рассмотрите возможность оптимизации. Например, если изменения касаются многих строк, возможно, имеет смысл собрать все изменения на клиенте и передать их на сервер одним вызовом, чтобы избежать множественных серверных обращений.
  4. Блокировка данных: При изменении связанного документа, особенно если он может быть открыт другими пользователями, учитывайте механизмы блокировки данных 1С, чтобы избежать коллизий. Метод ПолучитьОбъект() автоматически блокирует объект для изменения.
  5. Тестирование: Всегда тщательно тестируйте разработанную логику на разных сценариях: одна строка, несколько строк, номенклатура найдена/не найдена, разные валюты, нулевые цены, существующие и новые документы.

Следуя этим рекомендациям и используя предложенные методы, вы сможете надежно реализовать логику синхронизации данных между "Заказом поставщику" и "Заказом клиента", избегая распространенных проблем и обеспечивая корректную работу системы.

← На главную