УПД и счета-фактуры в XML для ЭДО (ФНС 5.03)
Сборка и проверка XML УПД, счёта-фактуры и документа об отгрузке в формате ФНС 5.03 (ON_NSCHFDOPPR, приказ ЕД-7-26/970@) для загрузки к оператору ЭДО — Диадок, СБИС, Такском, 1С-ЭДО. Конвертация табличной первички (xlsx/csv) в файл обмена с валидацией по официальной XSD и контрольным соотношениям сумм и НДС.
Example tasks
УПД из Excel в XML для Диадока
Агент превратит таблицу отгрузки (xlsx/csv) в файл обмена ON_NSCHFDOPPR формата ФНС 5.03 и проверит его по официальной XSD до загрузки к оператору ЭДО.
Проверка готового файла перед отправкой
XML прогоняется по схеме ФНС и контрольным соотношениям: суммы позиций, НДС и итоги сходятся до копейки, каждое расхождение показывается списком.
Счёт-фактура и документ об отгрузке отдельно
Кроме УПД (СЧФДОП) агент собирает отдельные счёт-фактуру (СЧФ) и документ об отгрузке (ДОП) — для документооборота, где они идут раздельно.
FAQ
Какой формат файла на выходе?
XML по приказу ФНС № ЕД-7-26/970@ в редакции № ЕД-7-26/1032@, версия 5.03 — единственная, которую операторы ЭДО принимают с 01.04.2025. Форматы 5.01 и 5.02 недействительны.
С какими операторами ЭДО совместим файл?
Формат единый для всех операторов: Диадок, СБИС, Такском, ЭДО Лайт, 1С-ЭДО. Файл загружается в любой из них без доработок.
Что будет, если данные не проходят валидацию?
Файл не отдаётся. Агент показывает, какие поля не прошли XSD или контрольные соотношения сумм и НДС, и запрашивает недостающие данные.
УПД, счёт-фактура и ДОП в XML для ЭДО (формат ФНС 5.03)
Область: сборка и проверка файлов обмена
ON_NSCHFDOPPR— титул продавца УПД (СЧФДОП), счёта-фактуры (СЧФ) и документа об отгрузке (ДОП) для загрузки к любому оператору ЭДО (Диадок, СБИС, Такском, ЭДО Лайт, 1С-ЭДО). Не путать сcommerceml_ru(обмен 1С↔сайт),fns_xml_ru(2-НДФЛ/3-НДФЛ/ЕГРЮЛ) иaccounting_docs_ru(та же первичка, но в Excel).
Нормативная база: приказ ФНС от 19.12.2023 № ЕД-7-26/970@ в редакции приказа от 15.11.2024 № ЕД-7-26/1032@. Версия формата — только 5.03. Форматы 5.01 и 5.02 недействительны с 01.04.2025: оператор ЭДО отклоняет такой файл, а пользователь видит отказ уже после загрузки.
Главное правило
XML никогда не пишется руками и не сочиняется по памяти — структура собирается примитивами скилла, а затем обязательно прогоняется через validate_upd_xml, где оракул — встроенная официальная XSD ФНС (ON_NSCHFDOPPR_1_997_01_05_03_05.xsd, 195 КБ, вшита в upd_schema.py). Файл, не прошедший валидацию, клиенту не отдаётся.
Собранный файл не патчится — ни регуляркой, ни побайтово, ни правкой текста: ошибка XSD означает неверный doc, его и чинить, а потом пересобирать save_upd_xml. Патч мимо сборщика ломает ИдФайл, кодировку windows-1251 и повторяется при каждом следующем запуске.
from fns_upd import rows_to_positions, save_upd_xml, validate_upd_xml, reconcile_with_source, format_report
positions = rows_to_positions(df)
path = save_upd_xml(doc, output_dir="/home/user/output")
report = validate_upd_xml(path)
reconcile_with_source(report, positions=positions)
print(format_report(report))
if not report["valid"]:
...
Табличная часть передаётся целиком — со всеми денежными колонками, какие есть в исходнике. rows_to_positions сам находит суммы, НДС и итог по строке; они попадают в XML как напечатаны, а не пересчитываются. Отсюда и reconcile_with_source(report, positions=positions) без expected_total: ожидаемый итог берётся из колонки исходника, а сверка расчёта с самим собой ничего не доказывает.
Конвертация, а не пересборка
Исходный документ — единственный источник содержания. Задача звучит как «переделай в XML», но по сути это перенос: то, что напечатано в присланном файле, обязано оказаться в XML без изменений и без потерь.
- Табличная часть входит в документ только через
rows_to_positions. Разбирать таблицу самостоятельно — по координатам ячеек (df.iloc[7, 3]), regex'ом по тексту, «на глаз» из превью — запрещено: именно так теряются строки с объединёнными ячейками и переносами в наименовании. - Строка без наименования больше не пропускается молча:
rows_to_positionsпадает и называет отброшенные строки. Служебные строки (итог, разделитель, повтор шапки) пропускаются только явнымallow_partial=True— и это решение надо озвучить клиенту. - Реквизиты берутся из исходника дословно: наименования, ИНН/КПП, адреса, номер и дата документа, основание передачи. Переформулировать, сокращать, «причёсывать» — нельзя.
- Чего в исходнике нет — спросить. Заглушки, «типовые» значения и данные из прошлых документов не подставлять.
- Суммы не пересчитываются, если они напечатаны: колонки суммы без налога, НДС и стоимости с налогом переносятся как есть, расчёт остаётся только запасным вариантом для колонок, которых в таблице нет. Расхождение до копейки — округление исходника, и побеждает исходник; расхождение больше копейки — ошибка сборки с обоими числами, и чинится оно в исходнике или в разметке колонок, а не подгонкой.
- XSD и контрольные соотношения проверяют файл сам по себе: собранный из половины таблицы документ проходит их полностью. Единственное, что связывает результат с исходником, —
reconcile_with_source.
Ловушки формата — источник почти всех отказов операторов
| Реквизит | Неверно | Верно |
|---|---|---|
| Кодировка | UTF-8, тем более с BOM | windows-1251, без BOM |
| Даты | 2026-03-12 | 12.03.2026 (ДатаТип) |
| Время | 14:03:22 | 14.03.22 (ВремяТип, разделитель — точка) |
| Суммы | копейки целым числом (6550000) | рубли с двумя знаками (65500.00), разделитель — точка |
| Номер СФ | НомерСчФ / ДатаСчФ | НомерДок / ДатаДок |
| Организация | СвЮЛ | СвЮЛУч (для ИП — СвИП + ФИО) |
| Табличная часть | <СвТов> | <ТаблСчФакт> → <СведТов> |
| Сумма НДС | атрибут СумНал="0" | вложенный <СумНал><СумНал>360.00</СумНал></СумНал> |
| Освобождение | НалСт="0%" | НалСт="без НДС" + <СумНал><БезНДС>без НДС</БезНДС></СумНал> |
| Валюта | атрибут КодОКВ на СвСчФакт | отдельный <ДенИзм КодОКВ="643" НаимОКВ="Российский рубль"/> |
| Адрес | <АдрРФ><АдрТекст>… | АдрРФТип не имеет дочерних узлов; свободную строку класть в <АдрИнф КодСтр="643" НаимСтран="РОССИЯ" АдрТекст="…"/> |
| Передача без договора | <БезДокОснПер>БЕЗ ДОКУМЕНТА-ОСНОВАНИЯ</БезДокОснПер> | <БезДокОснПер>1</БезДокОснПер> — по XSD это перечисление из одного значения 1 |
0% ≠ без НДС. 0% — экспортная ставка со своим пакетом подтверждающих документов; без НДС — освобождение (УСН, ст. 145/149 НК). Подставлять одно вместо другого нельзя: у клиента разъедется декларация. Если в исходной таблице ставка не указана или написана словами — normalize_rate приводит её к перечню ФНС и отказывает, если значение неоднозначно. Спроси клиента, а не угадывай.
Обязательные блоки
Файл—ИдФайл,ВерсФорм="5.03",ВерсПрог(все три required).Документ—КНД="1115131",Функция,ДатаИнфПр,ВремИнфПр.СвСчФакт—НомерДок,ДатаДок,СвПрод,СвПокуп,ДенИзм.ТаблСчФакт— минимум однаСведТов(обязателенНалСти блокСумНал) плюсВсегоОплсСумНалВсего.СвПродПер— дляСЧФДОПиДОП:СодОпери либоОснПер(договор/спецификация), либоБезДокОснПер. УОснПеробязательны все три реквизита — наименование, номер и дата. Если в исходнике напечатано только «Основной договор» без номера и даты, дату не выдумывать: спросить клиента либо убратьоснованиеизпередача— тогда соберётсяБезДокОснПер, и это надо назвать в ответе.Подписант—СпосПодтПолномиФИО; блок обязателен, документ без него не проходит XSD.
ИдФайл строится по шаблону ON_NSCHFDOPPR_{ИНН+КПП продавца}_{ИНН+КПП покупателя}_{ГГГГММДД}_{UUID}_0_0_0_0_0_00 и обязан совпадать с именем файла — за это отвечает make_id_fajl, имя возвращает save_upd_xml. Переименовывать файл после сборки нельзя.
Примитивы
fns_upd — фасад, из него доступно всё:
save_upd_xml(doc, output_dir="/home/user/output")→ путь;build_upd_xml(doc)→(имя, байты).validate_upd_xml(path|bytes)→{"valid", "errors", "warnings", "summary", "reconciled"};format_report(report)— печатный вид.reconcile_with_source(report, expected_rows=None, expected_total=None, positions=None)— сверка готового XML с исходником; расхождение по числу позиций или итогу переводит отчёт вvalid=False. Ожидаемый итог берётся из колонки исходника; переданный вручнуюexpected_total, который с ней расходится, сам становится ошибкой. Без ожидаемых значений отказывает: сверять не с чем.rows_to_positions(rows, mapping=None, default_rate=None, allow_partial=False)— DataFrame /list[dict]/list[list]→ позиции; распознаёт русские заголовки, подставляет ОКЕИ по единице измерения. ВозвращаетPositionListсsource_rows,dropped,totals(уйдёт в XML) иsource_totals(напечатано в исходнике).line_amounts(line)— суммы одной позиции той же арифметикой, что и сборка;normalize_rate(value),guess_okei(unit),totals_of(positions),nds_amount(base, rate),money,fns_date,fns_time,make_id_fajl.- Имена колонок в
mappingи ключи позиций — одни и те же:наим,кол,ед,океи,цена,сумма_без_ндс,ставка,ндс,сумма_с_ндс,артикул,признак. Таблица, названная этими именами, разбирается безmapping.
Структура doc:
doc = {
"функция": "СЧФДОП",
"номер": "19389",
"дата": "12.03.2026",
"продавец": {"наим": 'ООО "ЭКСПЕРТНЫЕ РЕШЕНИЯ"', "инн": "5032349058", "кпп": "503201001",
"адрес": "143025, Московская обл, ..."},
"покупатель": {"наим": 'ООО "ПАКТЕХ"', "инн": "6151018385", "кпп": "616301001",
"адрес": "344000, Ростовская обл, ..."},
"позиции": [{"наим": "...", "кол": 1, "цена": 65500, "ставка": "20%", "ед": "шт"}],
"передача": {"содержание": "Товары переданы",
"основание": {"наим": "Договор", "номер": "12", "дата": "01.03.2026"}},
"подписант": {"фамилия": "Макаров", "имя": "Владимир", "отчество": "Олегович",
"должность": "Генеральный директор", "способ_подтв": "1"},
}
Контрагент с ИНН из 12 цифр автоматически собирается как ИП (нужны фамилия/имя), из 10 — как юрлицо. сумма_без_ндс в позиции имеет приоритет над цена × кол — так переносятся строки со скидкой; расхождение уходит в warnings, не в ошибку.
Что делать по шагам
- Прочитать исходник (
.xlsx/.csv/PDF) целиком и зафиксировать, с чем потом сверяться: число строк табличной части и напечатанный в документе итог. Показать клиенту, что распозналось: продавец, покупатель, число позиций, итог. - Перенести табличную часть через
rows_to_positions. Проверить реквизиты, которых нет в таблице и которые нельзя выдумать: ИНН/КПП обеих сторон, адреса, ФИО и должность подписанта, основание передачи. Отсутствующее — спросить, не подставлять заглушки. - Собрать (
save_upd_xml) и провалидировать (validate_upd_xml). Приvalid=False— чинить и повторять, не отдавая промежуточный файл. - Сверить с исходником:
reconcile_with_source(report, positions=positions).expected_rowsиexpected_totalпередаются руками, только когда итог напечатан вне табличной части и в позиции не попал. Пока сверка не пройдена, файл валиден только сам по себе — клиенту его не отдавать и «ВАЛИДЕН» не писать. - Отдать артефакт карточкой скачивания. XML не печатать в чат через
cat— это результат инструмента, клиент его не видит, а строк там сотни. - В ответе указать: версию формата (5.03), функцию, номер и дату документа, число позиций, итог, НДС, результат сверки с исходником — и напомнить, что подпись ставится уже в сервисе ЭДО сертификатом.
Ограничения
- Скилл собирает титул продавца. Ответный титул покупателя (
ON_NSCHFDOPPK) и приём входящих УПД сюда не входят. - Электронная подпись не формируется: файл загружается в ЭДО неподписанным, подпись накладывает оператор.
<СвОЭДОтпр>(идентификаторы участников ЭДО) не заполняется — оператор проставляет их при отправке.- ТОРГ-12 и акт выполненных работ — другие приказы и другие XSD, этим скиллом не покрываются.
Similar skills
Try this skill
Sign up and use the "УПД и счета-фактуры в XML для ЭДО (ФНС 5.03)" skill for free.