При разработке механизмов автоматического создания обеспечивающих или связанных документов разработчики нередко сталкиваются с ситуацией: новый документ создается кодом, записывается, но в базе данных не сохраняется, а попытка обратиться к нему возвращает битую ссылку вида <Объект не найден> (GUID). Разберем по шагам причины такого поведения платформы 1С:Предприятие и рассмотрим архитектурно верные способы реализации подобной задачи.
Типичный сценарий: при проведении или записи документа РеализацияТоваровУслуг требуется автоматически создать документ ПеремещениеТоваров, если на складе отгрузки не хватает остатка. Разработчик помещает создание и запись перемещения в процедуру ПередЗаписью модуля объекта реализации. Однако при попытке провести реализацию возникает ошибка нехватки остатка, а созданный документ перемещения бесследно исчезает.
Выясним причину такого поведения:
ПередЗаписью, ПриЗаписи и ОбработкаПроведения модуля объекта) выполняется строго внутри неделимой системной транзакции.Отказ = Истина. Это приводит к полному откату всей транзакции. Все данные, созданные, измененные и записанные внутри этого блока (в том числе новый документ перемещения), физически удаляются из таблиц СУБД.Записать() платформа генерирует для нового объекта уникальный идентификатор (GUID). Если транзакция откатилась, запись из базы стирается, но ссылка с этим идентификатором в памяти (или в открытой экранной форме) остается. Попытка прочитать такой объект и выдает <Объект не найден>.ПолучитьФорму().Открыть()) в модуле объекта категорически недопустимы. Во время ожидания действий пользователя открытая транзакция держит исключительные блокировки таблиц базы данных, что блокирует параллельную работу пользователей и приводит к взаимоблокировкам (deadlocks).Посмотрим на пример избыточного кода, который часто пишут начинающие разработчики:
НовыйДокумент = Документы.ПеремещениеТоваров.СоздатьДокумент();
НовыйДокумент.Дата = ЭтотОбъект.Дата - 1;
НовыйДокумент.Записать(); // Лишняя предварительная запись
// Затем ссылка передается в процедуру, где снова берется объект:
ДокументПеремещения = СсылкаНаДокументПеремещения.ПолучитьОбъект();
Метод СоздатьДокумент() сразу возвращает готовый экземпляр ДокументОбъект. Нет никакой необходимости записывать пустой документ ради ссылки, а затем повторно считывать его через ПолучитьОбъект(). Заполнять реквизиты и табличные части следует сразу в созданном объекте перед его единственной записью.
Если же новому перемещению требуется передать ссылку на документ-основание, который сам еще не записан в базу, используем метод УстановитьСсылкуНового():
СсылкаНаРеализацию = Документы.РеализацияТоваровУслуг.ПолучитьСсылку();
ЭтотОбъект.УстановитьСсылкуНового(СсылкаНаРеализацию);
ДокументПеремещения.ДокументОснование = СсылкаНаРеализацию;
Проанализируем самое простое и надежное решение для интерактивной работы пользователя. Перемещение должно быть создано и зафиксировано в базе данных до того, как начнется транзакция проведения реализации.
Перенесем обработку в событие ПередЗаписью модуля формы документа:
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
// Проверяем, что документ проводится интерактивно
Если ПараметрыЗаписи.РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
ТаблицаНехватки = СформироватьТаблицуНехваткиНаСервере();
Если ТаблицаНехватки.Количество() > 0 Тогда
// Создаем и проводим перемещение в отдельной транзакции
СсылкаНаПеремещение = СоздатьИПровестиПеремещениеНаСервере(ТаблицаНехватки);
Если Не ЗначениеЗаполнено(СсылкаНаПеремещение) Тогда
Сообщить("Не удалось автоматически переместить товар. Проведение отменено.");
Отказ = Истина;
Возврат;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
На сервере формируем и сразу проводим документ ПеремещениеТоваров:
&НаСервере
Функция СоздатьИПровестиПеремещениеНаСервере(ТаблицаПеремещений)
ДокументПеремещения = Документы.ПеремещениеТоваров.СоздатьДокумент();
ДокументПеремещения.Дата = Объект.Дата - 1;
ДокументПеремещения.Организация = Объект.Организация;
ДокументПеремещения.СкладПолучатель = Объект.Склад;
ДокументПеремещения.СкладОтправитель = ПолучитьСкладОтправитель();
ДокументПеремещения.ОтражатьВУправленческомУчете = Истина;
ДокументПеремещения.Ответственный = ПараметрыСеанса.ТекущийПользователь;
Для Каждого Стр Из ТаблицаПеремещений Цикл
НоваяСтрока = ДокументПеремещения.Товары.Добавить();
НоваяСтрока.Номенклатура = Стр.Товар;
НоваяСтрока.Количество = Стр.КоличествоДляПеремещения;
НоваяСтрока.ЕдиницаИзмерения = Стр.Товар.ЕдиницаХраненияОстатков;
НоваяСтрока.Коэффициент = 1;
КонецЦикла;
Попытка
ДокументПеремещения.Записать(РежимЗаписиДокумента.Проведение);
Возврат ДокументПеремещения.Ссылка;
Исключение
Сообщить(ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
КонецФункции
При таком подходе перемещение фиксируется в СУБД в своей собственной транзакции. Затем стартует проведение реализации, остаток на складе уже присутствует, и реализация проводится без ошибок.
Если бизнес-процесс требует контроля со стороны менеджера, автоматическое создание документов в событиях записи лучше заменить на явное действие пользователя:
ПеремещениеТоваров и открывает его пользователю для проверки либо сразу проводит.Рассмотрим типовой методологический подход конфигураций 1С (УТ, КА, ERP):
Заказ покупателя (или Заказ клиента).Перемещение товаров со статусом к обеспечению или отгрузке.Реализация товаров и услуг.Такой подход исключает возникновение отрицательных остатков, блокировок транзакций и гарантирует прозрачность складского учета.