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