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