При переносе данных из устаревших конфигураций (например, «Торговля и Склад 7.7») в «1С:Управление торговлей 10.3» пользователи часто сталкиваются с технической проблемой: справочники контрагентов и их договоров успешно перенесены, но ни один договор не назначен в качестве основного. В интерфейсе программы такие договоры не выделяются жирным шрифтом, а при создании новых документов (например, «Реализация товаров и услуг» или «Поступление товаров и услуг») поле «Договор» остается пустым, требуя от пользователя ручного выбора.
Если в базе данных содержатся тысячи контрагентов, открывать карточку каждого из них и вручную нажимать кнопку «Установить основным» физически невозможно. В этой статье мы подробно разберем, почему стандартная групповая обработка не подходит для решения этой задачи, и рассмотрим три эффективных способа автоматизации данного процесса с примерами готового и оптимизированного программного кода.
В меню программы «Сервис — Универсальные обработки — Групповая обработка справочников и документов» действительно можно выбрать справочник Контрагенты и попытаться изменить реквизит ОсновнойДоговорКонтрагента. Однако стандартный инструмент позволяет установить только одно конкретное значение для всех выбранных записей.
Поскольку справочник ДоговорыКонтрагентов подчинен справочнику Контрагенты (каждый договор жестко привязан к конкретному владельцу), попытка назначить один общий договор всем контрагентам приведет к логической ошибке и нарушению целостности данных. Программа либо заблокирует это действие из-за нарушения подчиненности, либо запишет чужой договор всем контрагентам, что полностью разрушит аналитический учет по взаиморасчетам. Нам необходимо, чтобы у каждого контрагента основным стал именно его собственный договор.
В сообщениях на форумах часто предлагают простые скрипты для обхода контрагентов. Проанализируем типичный пример неоптимального кода, который часто пишут начинающие разработчики:
Для Каждого ВыборкаСтрока Из ТабличнаяЧасть Цикл
КонтрагентОбъект = ВыборкаСтрока.Контрагент.ПолучитьОбъект();
ТекстЗапроса =
"ВЫБРАТЬ ПЕРВЫЕ 1
| ДоговорыКонтрагентов.Ссылка
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
|ГДЕ
| ДоговорыКонтрагентов.Владелец = &ВладелецКонтрагент";
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ВладелецКонтрагент", ВыборкаСтрока.Контрагент);
Запрос.Текст = ТекстЗапроса;
ВыборкаЗапрос = Запрос.Выполнить().Выбрать();
Пока ВыборкаЗапрос.Следующий() Цикл
КонтрагентОбъект.ОсновнойДоговорКонтрагента = ВыборкаЗапрос.Ссылка;
КонецЦикла;
КонтрагентОбъект.Записать();
КонецЦикла;
В чем заключается критическая ошибка данного кода? Здесь запрос к базе данных выполняется внутри цикла Для Каждого. Если у вас в базе 10 000 контрагентов, программа сделает 10 000 отдельных запросов к СУБД. Это приведет к колоссальной нагрузке на сервер, зависанию сеанса 1С и долгому ожиданию завершения операции.
Рассмотрим правильный, профессиональный и оптимизированный вариант решения. Мы сделаем всего один запрос ко всей базе данных, получим список контрагентов и их первых договоров, а затем в транзакции быстро запишем изменения:
// 1. Создаем один запрос ко всей базе данных без циклов
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДоговорыКонтрагентов.Владелец КАК Контрагент,
| МИНИМУМ(ДоговорыКонтрагентов.Ссылка) КАК Договор
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
|ГДЕ
| ДоговорыКонтрагентов.Владелец.ОсновнойДоговорКонтрагента = ЗНАЧЕНИЕ(Справочник.ДоговорыКонтрагентов.ПустаяСсылка)
| И НЕ ДоговорыКонтрагентов.ПометкаУдаления
|ГРУППИРОВАТЬ ПО
| ДоговорыКонтрагентов.Владелец";
Выборка = Запрос.Выполнить().Выбрать();
// Начало транзакции для ускорения записи и обеспечения целостности
НачатьТранзакцию();
Попытка
// 2. В цикле выполняем только запись измененных объектов
Пока Выборка.Следующий() Цикл
КонтрагентОбъект = Выборка.Контрагент.ПолучитьОбъект();
Если КонтрагентОбъект <> Неопределено Тогда
КонтрагентОбъект.ОсновнойДоговорКонтрагента = Выборка.Договор;
// Свойство ОбменДанными.Загрузка = Истина позволяет обойти тяжелые
// встроенные проверки при записи (например, проверку заполнения ИНН/КПП),
// что ускоряет процесс в десятки раз
КонтрагентОбъект.ОбменДанными.Загрузка = Истина;
КонтрагентОбъект.Записать();
КонецЕсли;
КонецЦикла;
ЗафиксироватьТранзакцию();
Сообщить("Обработка успешно завершена! Все договоры установлены как основные.");
Исключение
ОтменитьТранзакцию();
Сообщить("Произошла ошибка при записи данных: " + ОписаниеОшибки(), СтатусСообщения.Важное);
КонецПопытки;
В этом коде функция МИНИМУМ(ДоговорыКонтрагентов.Ссылка) гарантирует, что если у контрагента создано несколько договоров, программа выберет первый созданный договор (с наименьшим внутренним идентификатором) и назначит его в качестве основного.
Если вы прямо сейчас занимаетесь переносом данных и используете стандартную обработку ЗагрузкаДанныхИзТабличногоДокумента.epf для импорта договоров из Excel или MXL, вы можете решить проблему превентивно. Нет необходимости писать внешние файлы обработок — достаточно использовать встроенные события загрузчика.
Разберем по шагам, как настроить автоматическое назначение договора основным непосредственно при его загрузке:
ДоговорыКонтрагентов.
// Проверяем заполненность владельца (контрагента) загружаемого договора
Если ЗначениеЗаполнено(Объект.Владелец) Тогда
КонтрагентОбъект = Объект.Владелец.ПолучитьОбъект();
Если КонтрагентОбъект <> Неопределено Тогда
// Назначаем только что созданный договор основным для его владельца
КонтрагентОбъект.ОсновнойДоговорКонтрагента = Объект.Ссылка;
// Отключаем лишние подписки на события для ускорения работы
КонтрагентОбъект.ОбменДанными.Загрузка = Истина;
Попытка
КонтрагентОбъект.Записать();
Исключение
// Записываем информацию об ошибке в журнал регистрации, чтобы загрузка не прерывалась
ЗаписьЖурналаРегистрации("ЗагрузкаДоговоров", УровеньЖурналаРегистрации.Предупреждение, , ,
"Не удалось обновить контрагента: " + КонтрагентОбъект.Наименование + ". Ошибка: " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
КонецЕсли;
Данный подход экономит время, так как реквизиты контрагентов заполняются одновременно с созданием самих договоров, исключая необходимость повторного сканирования базы данных.
Если договоры уже загружены, но вы не хотите писать программный код, можно воспользоваться альтернативным методом повторного импорта через ту же обработку ЗагрузкаДанныхИзТабличногоДокумента.epf.
Для этого выполните следующие действия:
Контрагенты.Наименование (или ИНН, установив для него флаг «Искать по этому полю») и ОсновнойДоговорКонтрагента. Напротив остальных полей снимите флаги активности.ОсновнойДоговорКонтрагента настройте поиск договора по наименованию с учетом владельца.Важное предупреждение: перед выполнением любых массовых групповых операций с базой данных 1С обязательно создавайте резервную копию вашей информационной базы (файл 1Cv8.1CD или выгрузку .dt)!
Если вы планируете в обозримом будущем совершить переход с устаревшей платформы «1С:Предприятие 8.2» и конфигурации «Управление торговлей 10.3» на современные решения (такие как «Управление торговлей 11», «Комплексная автоматизация 2» или «ERP Управление предприятием»), вам необходимо учитывать критическое изменение в архитектуре метаданных 1С:
ОсновнойДоговорКонтрагента в справочнике Контрагенты полностью удален.ОсновныеДоговорыКонтрагента. Это решение обусловлено тем, что в современных базах учет часто ведется в разрезе нескольких собственных организаций. У одного и того же контрагента для разных наших организаций основные договоры могут различаться.Контрагент.ОсновнойДоговорКонтрагента в новой базе выдадут критическую ошибку выполнения: «Поле объекта не обнаружено».Для программной установки основного договора в современных версиях программ 1С на управляемых формах используется создание записи в регистре сведений либо вызов стандартных процедур из Библиотеки стандартных подсистем (БСП):
// Пример установки основного договора для современных конфигураций на платформе 1С 8.3
МенеджерЗаписи = РегистрыСведений.ОсновныеДоговорыКонтрагентов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Организация = СсылкаНаНашуОрганизацию;
МенеджерЗаписи.Контрагент = СсылкаНаКонтрагента;
МенеджерЗаписи.Договор = СсылкаНаДоговор;
МенеджерЗаписи.Записать(Истина); // Запись с замещением старого значения
Понимание этих принципов позволит вам не только эффективно решить текущую задачу в «УТ 10.3», но и подготовить вашу информационную систему к беспрепятственному переходу на актуальные версии платформы «1С:Предприятие».