Нам часто приходится сталкиваться с задачами автоматизации рутинных операций в 1С. Одна из таких задач – массовое создание документов, например, "Счетов покупателю", на основе данных, поступающих из внешних источников. Рассмотрим, как мы можем реализовать обработку, которая будет загружать информацию из файла и формировать новые счета для различных контрагентов в 1С:Бухгалтерии Предприятия 8.3. Мы проанализируем типовые подходы, разберем конкретные примеры кода и уточним важные нюансы, такие как добавление нескольких позиций в счет и правильный поиск объектов в базе.
Приступая к созданию обработки, которая загружает данные из внешнего файла, важно правильно интерпретировать структуру этого файла и корректно переносить информацию в документ 1С. В нашем случае, пользователь столкнулся с проблемой, когда при попытке загрузки нескольких позиций номенклатуры в документ "Счет покупателю" добавлялась только одна строка. Давайте проанализируем предоставленный код и структуру XML-файла, чтобы выяснить причину.
Исходная проблема:
В представленном фрагменте кода, который предназначен для чтения XML-файла, мы видим, что значения атрибутов "литров" и "Цена" из элементов Количество собираются в объект СписокЗначений. Затем, при создании новой строки табличной части Товары документа, используются только первый и второй элементы этого списка (СписокЗначений[0].Значение и СписокЗначений[1].Значение). Это означает, что даже если в XML-файле для одного клиента предусмотрено несколько строк состава с количеством и ценой, в документ 1С будет добавлена только одна строка, использующая первые найденные значения.
// Фрагмент кода из сообщения 12, демонстрирующий проблему
// ...
Для Каждого Элемент1 Из Клиент.ДочерниеУзлы Цикл
Если Элемент1.ИмяУзла = "СтрокаСостава" Тогда
СтрокаСостава = Элемент1;
Для Каждому Элемент2 Из СтрокаСостава.ДочерниеУзлы Цикл
Если Элемент2.ИмяУзла = "Количество" Тогда
Количество = Элемент2;
литров = Количество.Атрибуты.ПолучитьИменованныйЭлемент("литров");
// ...
Если литров <> Неопределено Тогда
СписокЗначений.Добавить(литров.Значение); // Здесь значения собираются в СписокЗначений
// ...
Если Цена <> Неопределено Тогда
СписокЗначений.Добавить(Цена.Значение); // и здесь
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
// ...
НоваяСтрока=НовыйДокумент.Товары.Добавить();
// Здесь используются только первые два значения из всего списка
НоваяСтрока.Количество = СписокЗначений[0].Значение;
НоваяСтрока.Цена = СписокЗначений[1].Значение;
// ...
Структура XML-файла:
Предоставленный XML-файл имеет следующую структуру, которая показывает, что внутри элемента СтрокаСостава может быть несколько элементов Количество, каждый из которых представляет отдельную позицию товара с атрибутами "литров" и "Цена".
<?xml version="1.0" encoding="UTF-8"?>
<СписокКлиентов>
<Клиент Название= "УБРИР КБ ПАО">
<СтрокаСостава>
<Количество литров="10" Цена ="23" />
<Количество литров="20" Цена ="24" />
</СтрокаСостава>
</Клиент>
<Клиент Название= "ГКУ ЯМАЛСПАС">
<СтрокаСостава>
<Количество литров="30" Цена ="23" />
<Количество литров="40" Цена ="24" />
</СтрокаСостава>
</Клиент>
</СписокКлиентов>
Решение проблемы:
Для корректного добавления каждой позиции в табличную часть документа "Счет на оплату покупателю" нам необходимо создавать новую строку в табличной части Товары внутри цикла, который обходит элементы Количество. Это позволит нам для каждой найденной пары "количество-цена" сформировать отдельную позицию в счете.
Рассмотрим исправленный фрагмент кода:
Процедура Команда2НаСервере()
ОсновнаяОрганизация = БухгалтерскийУчетПереопределяемый.ПолучитьЗначениеПоУмолчанию("ОсновнаяОрганизация");
АртикулТекст ="AFA100L"; // Уточнить способ поиска номенклатуры, возможно, из файла или настроек
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл("C:\Users\Mishin\Documents\new 1.xml");
Построитель = Новый ПостроительDOM;
Документ = Построитель.Прочитать(ЧтениеXML);
Для Каждого Элемент0 Из Документ.ЭлементДокумента.ДочерниеУзлы Цикл
Если Элемент0.ИмяУзла = "Клиент" Тогда
КлиентЭлемент = Элемент0;
НазваниеКонтрагента = КлиентЭлемент.Атрибуты.ПолучитьИменованныйЭлемент("Название");
Если НазваниеКонтрагента <> Неопределено Тогда
Сообщить("== " + НазваниеКонтрагента.Значение + " ==");
КонецЕсли;
// Создаем новый документ "Счет на оплату покупателю" для каждого клиента
НовыйДокумент = Документы.СчетНаОплатуПокупателю.СоздатьДокумент();
НовыйДокумент.Дата = ТекущаяДата();
НовыйДокумент.Организация = ОсновнаяОрганизация;
// Используем более надежный поиск контрагента, например, по ИНН/КПП, если данные доступны
НовыйДокумент.Контрагент = Справочники.Контрагенты.НайтиПоНаименованию(НазваниеКонтрагента.Значение); // Пока используем наименование из примера
// Заполняем основные реквизиты документа
НовыйДокумент.ТипЦен = Справочники.ТипыЦенНоменклатуры.НайтиПоКоду("000000001"); // Или другой способ определения типа цен
НовыйДокумент.ОрганизацияПолучатель = НовыйДокумент.Организация;
НовыйДокумент.УчитыватьНДС = Истина;
НовыйДокумент.СуммаВключаетНДС = Истина;
НовыйДокумент.Комментарий = "Счёт создан при импорте.";
Для Каждого Элемент1 Из КлиентЭлемент.ДочерниеУзлы Цикл
Если Элемент1.ИмяУзла = "СтрокаСостава" Тогда
СтрокаСоставаЭлемент = Элемент1;
// Обходим все элементы "Количество" внутри "СтрокаСостава"
Для Каждого Элемент2 Из СтрокаСоставаЭлемент.ДочерниеУзлы Цикл
Если Элемент2.ИмяУзла = "Количество" Тогда
КоличествоУзел = Элемент2;
ЛитровАтрибут = КоличествоУзел.Атрибуты.ПолучитьИменованныйЭлемент("литров");
ЦенаАтрибут = КоличествоУзел.Атрибуты.ПолучитьИменованныйЭлемент("Цена");
Если ЛитровАтрибут <> Неопределено И ЦенаАтрибут <> Неопределено Тогда
Сообщить(" Добавляем позицию: литров=" + ЛитровАтрибут.Значение + ", Цена=" + ЦенаАтрибут.Значение);
НоваяСтрока = НовыйДокумент.Товары.Добавить();
НоваяСтрока.Номенклатура = НайтиНоменклатуруПоАртикулу(АртикулТекст); // Функция для поиска номенклатуры
НоваяСтрока.Количество = Число(ЛитровАтрибут.Значение);
НоваяСтрока.Цена = Число(ЦенаАтрибут.Значение);
НоваяСтрока.Сумма = НоваяСтрока.Цена * НоваяСтрока.Количество;
НоваяСтрока.СтавкаНДС = Перечисления.СтавкиНДС.НДС20; // Используем перечисление для ставки НДС
// Здесь могут быть другие поля табличной части, например, ЕдиницаИзмерения, СуммаНДС, ПроцентРучнойСкидки и т.д.
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецЦикла;
// После заполнения всех позиций, записываем документ
Если НовыйДокумент.Товары.Количество() > 0 Тогда // Проверяем, что в счете есть позиции
НовыйДокумент.Записать();
Сообщить("Счет для клиента " + НазваниеКонтрагента.Значение + " успешно создан.");
Иначе
Сообщить("Предупреждение: для клиента " + НазваниеКонтрагента.Значение + " не найдено позиций для добавления в счет.");
КонецЕсли;
КонецЕсли;
КонецЦикла;
ЧтениеXML.Закрыть();
КонецПроцедуры
// Пример вспомогательной функции для поиска номенклатуры по артикулу
Функция НайтиНоменклатуруПоАртикулу(Артикул)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Номенклатура.Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Артикул = &Артикул";
Запрос.УстановитьПараметр("Артикул", Артикул);
Результат = Запрос.Выполнить().Выбрать();
Если Результат.Следующий() Тогда
Возврат Результат.Ссылка;
Иначе
Возврат Справочники.Номенклатура.ПустаяСсылка(); // или выдать ошибку
КонецЕсли;
КонецФункции
Обратите внимание, что мы переместили создание и запись документа внутрь цикла по клиентам, а заполнение табличной части — внутрь цикла по элементам Количество. Также мы используем Число() для преобразования строковых значений из XML в числовой формат и Перечисления.СтавкиНДС.НДС20 для корректного указания ставки НДС.
Функция НайтиНоменклатуруПоАртикулу является примером того, как можно найти номенклатуру по артикулу. В реальных условиях вам может потребоваться более сложная логика поиска или использование дополнительных свойств номенклатуры.
В исходном коде для поиска контрагента использовалось наименование: Справочники.Контрагенты.НайтиПоНаименованию(Название.Значение). Этот метод может быть не всегда надежным, поскольку наименования могут изменяться, содержать ошибки или не быть уникальными (например, "ООО Ромашка" и "ООО «Ромашка»").
Мы рекомендуем использовать более надежные идентификаторы для поиска контрагентов, такие как ИНН и КПП. Комбинация ИНН и КПП является уникальной для юридического лица или его обособленного подразделения. Для физических лиц достаточно ИНН.
Рассмотрим, как мы можем модифицировать поиск контрагента:
Пример кода для поиска контрагента по ИНН и КПП:
// Предполагаем, что из XML мы получили значения ИНН и КПП
// СтрокаИНН = "7707083893";
// СтрокаКПП = "770701001";
Функция НайтиКонтрагентаПоИНН_КПП(ИНН, КПП)
КонтрагентСсылка = Неопределено;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Контрагенты.Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.ИНН = &ИНН
| И Контрагенты.КПП = &КПП";
Запрос.УстановитьПараметр("ИНН", ИНН);
Запрос.УстановитьПараметр("КПП", КПП);
РезультатЗапроса = Запрос.Выполнить().Выбрать();
Если РезультатЗапроса.Следующий() Тогда
КонтрагентСсылка = РезультатЗапроса.Ссылка;
Иначе
// Если контрагент не найден по ИНН и КПП, можно попробовать найти только по ИНН
// для случаев, когда КПП может отсутствовать или быть неверным,
// или если это индивидуальный предприниматель без КПП.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Контрагенты.Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.ИНН = &ИНН";
Запрос.УстановитьПараметр("ИНН", ИНН);
РезультатЗапроса = Запрос.Выполнить().Выбрать();
Если РезультатЗапроса.Следующий() Тогда
КонтрагентСсылка = РезультатЗапроса.Ссылка;
КонецЕсли;
КонецЕсли;
Возврат КонтрагентСсылка;
КонецФункции
// Пример использования
// Контрагент = НайтиКонтрагентаПоИНН_КПП(СтрокаИНН, СтрокаКПП);
// Если Контрагент = Неопределено Или Контрагент.Пустая() Тогда
// Сообщить("Контрагент с ИНН " + СтрокаИНН + " и КПП " + СтрокаКПП + " не найден. Проверьте данные.");
// Иначе
// НовыйДокумент.Контрагент = Контрагент;
// КонецЕсли;
Важно помнить, что если контрагент не найден, нам нужно либо создать нового, либо сообщить об ошибке и пропустить создание счета для этого клиента. Решение зависит от требований к обработке.
Один из важных реквизитов документа "Счет на оплату покупателю" – это банковский счет, на который будут перечисляться средства. В 1С этот реквизит называется БанковскийСчет и должен содержать ссылку на элемент справочника "БанковскиеСчета". Мы рассмотрим, как правильно установить его.
Как правило, банковский счет, который указывается в счете, является расчетным счетом организации, от имени которой выставляется счет. Если у организации есть основной банковский счет, его можно получить напрямую из объекта организации.
Пример кода для установки банковского счета:
// ... (после создания документа и определения ОсновнаяОрганизация)
// Вариант 1: Использование основного банковского счета организации
// Предполагаем, что у вашей организации настроен основной банковский счет
Если ОсновнаяОрганизация.ОсновнойБанковскийСчет.Пустая() Тогда
Сообщить("Для организации " + ОсновнаяОрганизация.Наименование + " не указан основной банковский счет.", СтатусСообщения.Важное);
// Возможно, следует прервать создание счета или выбрать счет другим способом
Иначе
НовыйДокумент.БанковскийСчет = ОсновнаяОрганизация.ОсновнойБанковскийСчет;
КонецЕсли;
// Вариант 2: Поиск банковского счета по наименованию или другим реквизитам
// Если у вас есть конкретное наименование счета, или вы хотите использовать другой счет
// НовыйДокумент.БанковскийСчет = Справочники.БанковскиеСчета.НайтиПоНаименованию("Название расчетного счета");
// Если НайтиПоНаименованию() вернул Неопределено или ПустаяСсылка,
// необходимо предусмотреть логику обработки такой ситуации.
// Если требуется указать банковский счет контрагента (например, при оплате от нашего имени)
// НовыйДокумент.БанковскийСчетКонтрагента = Контрагент.ОсновнойБанковскийСчет; // Или поиск по другим данным
Мы настоятельно рекомендуем использовать основной банковский счет организации, поскольку это наиболее распространенный сценарий для выставления счетов.
Метод Заполнить() является мощным инструментом в 1С для автоматического заполнения реквизитов нового документа. Он значительно упрощает создание документов, особенно когда они формируются на основании других объектов или имеют типовые значения.
Когда вы создаете новый документ, многие его реквизиты могут быть заполнены стандартными значениями или данными из связанных справочников. Метод Заполнить() позволяет сделать это автоматически, основываясь на настройках конфигурации или переданных параметрах.
Применение метода:
Если мы создаем документ "Счет на оплату покупателю" "с нуля", без документа-основания (например, без "Заказа покупателя"), то в метод Заполнить() можно передать Неопределено в качестве параметра. Это укажет системе на необходимость заполнения реквизитов на основе настроек по умолчанию для данного вида документа.
Рассмотрим, как его применять:
// ... (после создания документа)
НовыйДокумент = Документы.СчетНаОплатуПокупателю.СоздатьДокумент();
// Вызываем метод Заполнить() для автоматического заполнения стандартных реквизитов
// Это поможет установить значения по умолчанию для таких полей, как Валюта, Курс,
// Ставка НДС по умолчанию и прочее, в зависимости от конфигурации.
НовыйДокумент.Заполнить(Неопределено);
// Далее мы переопределяем те реквизиты, которые загружаем из файла или задаем явно
НовыйДокумент.Дата = ТекущаяДата();
НовыйДокумент.Организация = ОсновнаяОрганизация;
// ... (остальное заполнение документа, включая табличные части)
Использование НовыйДокумент.Заполнить(Неопределено) в начале заполнения документа позволяет убедиться, что все стандартные реквизиты будут инициализированы корректными значениями по умолчанию, а затем мы можем переопределить те из них, которые поступают из нашего источника данных или имеют специфическое значение.
Помимо написания собственного кода для парсинга XML, в 1С:Предприятии 8.3 существуют типовые механизмы, которые мы можем использовать для загрузки данных. Их применение часто позволяет сократить время на разработку и повысить надежность решения, особенно если формат исходного файла не слишком сложен или является стандартным.
Это универсальный инструмент, доступный во многих конфигурациях 1С. Он позволяет загружать данные из файлов различных форматов, таких как Excel (.xls, .xlsx), OpenDocument Spreadsheet (.ods) или текстовые файлы с разделителями (CSV). Для его использования необходимо:
Этот механизм удобен, когда источник данных может предоставить информацию в табличном виде, и не требуется сложная логика трансформации данных.
Данный механизм предназначен в основном для обмена данными между различными информационными базами 1С, но может быть адаптирован для импорта данных из внешних систем. Если ваша внешняя система может формировать XML-файлы в формате, соответствующем правилам обмена 1С (например, CommerceML или собственный формат обмена), то этот механизм может быть очень мощным. Он позволяет:
Однако, для его использования требуется более глубокое понимание структуры обмена данными в 1С и, возможно, адаптация формата исходного XML-файла.
Выбор подходящего метода зависит от множества факторов, таких как сложность структуры исходного файла, частота загрузок, требуемая гибкость и уровень подготовки разработчика. Для простых задач загрузки данных из таблиц "Загрузка данных из табличного документа" часто является предпочтительным выбором.
Реализация обработки для создания документов "Счет покупателю" из файла требует внимательного подхода к парсингу данных, корректному поиску справочников и правильному заполнению реквизитов документа. Мы рассмотрели, как исправить распространенную ошибку с добавлением одной позиции вместо нескольких, предложили надежные методы поиска контрагентов, показали, как установить банковский счет, и напомнили о полезности метода Заполнить(). Также мы обсудили альтернативные типовые механизмы загрузки данных, которые могут быть более подходящими в некоторых сценариях. Применяя эти рекомендации, мы сможем создать эффективную и надежную обработку для автоматизации формирования счетов.