fix: корректная работа inline и reply кнопок после генерации документов, улучшена обработка состояний ConversationHandler, исправлен переход при повторной генерации

This commit is contained in:
admin i 2025-11-05 15:09:59 +03:00
parent 8e56415e68
commit ad94c29bbd
8 changed files with 1254 additions and 59 deletions

View File

@ -1,3 +1,30 @@
# Changelog
## [2025-11-05] - Формат 1С EnterpriseData 1.20
### Added
- 🆕 **Формат 1С EnterpriseData 1.20** - новый генератор XML для прямой загрузки документов реализации в конфигурации 1С
- 📄 Класс `XML1CGenerator` в `modules/xml_generator.py` для генерации документов в формате обмена 1С
- 📖 Документация по формату 1С: `docs/XML_1C_FORMAT.md`
- ✨ Поддержка UUID, ISO 8601 дат, сумм в рублях (без копеек)
- 🏢 Корректная обработка ИП и юридических лиц в формате 1С
- 💼 Экспорт банковских реквизитов организации
- 📦 Детализация товарных позиций с кодами ОКЕИ
- 🔄 Полная структура документа "Реализация товаров и услуг" для 1С
### Changed
- 📝 Обновлен README с информацией о новом формате
- 🔄 Сохранены оба формата: ВерсФорм 5.03 (для ФНС) и 1С EnterpriseData 1.20 (для 1С)
### Technical Details
- Namespace: `http://v8.1c.ru/edi/edi_stnd/EnterpriseData/1.20`
- Формат даты: ISO 8601 (YYYY-MM-DDTHH:MM:SS)
- Денежные суммы: рубли (целое число)
- UUID для всех ссылок на объекты
- Полная интеграция с API МойСклад
---
# Обновление бота - Поиск заказов
## ✅ Исправлено

View File

@ -1,6 +1,6 @@
# Бот для бухгалтеров
Telegram-бот для автоматизации работы бухгалтеров: получение данных из МойСклад, валидация и генерация документов (УПД, ТН) в форматах XML (ВерсФорм 5.03) и Excel.
Telegram-бот для автоматизации работы бухгалтеров: получение данных из МойСклад, валидация и генерация документов (УПД, ТН) в форматах XML (ВерсФорм 5.03, 1С EnterpriseData 1.20) и Excel.
## 🔐 Многопользовательская система
@ -17,7 +17,7 @@ Telegram-бот для автоматизации работы бухгалте
- 📦 **Интеграция с МойСклад** - автоматическое получение данных о заказах через API
- ✅ **Валидация данных** - проверка обязательных и желательных полей (ИНН, КПП, адреса, товары)
- 📄 **Генерация XML** - создание документов в формате ВерсФорм 5.03 (УПД/ТН)
- 📄 **Генерация XML** - создание документов в форматах ВерсФорм 5.03 (УПД/ТН) и 1С EnterpriseData 1.20 (Реализация)
- 📊 **Генерация Excel** - выгрузка в форматы УПД и ТОРГ-12
- 🤖 **Telegram интерфейс** - удобное управление через бота
@ -275,12 +275,17 @@ BUH_bot/
## 📄 Форматы документов
### XML (ВерсФорм 5.03)
### XML (ВерсФорм 5.03) - для ФНС
- **УПД** - Универсальный передаточный документ (Счет-фактура + ТОРГ-12)
- **ТН** - Товарная накладная (ТОРГ-12)
Соответствие приказу ФНС России для электронного документооборота.
### XML (1С EnterpriseData 1.20) - для загрузки в 1С
- **Документ реализации товаров и услуг** - прямая загрузка в конфигурации 1С
📖 Подробная документация: [docs/XML_1C_FORMAT.md](docs/XML_1C_FORMAT.md)
### Excel
- **УПД** - Печатная форма с полными реквизитами
- **ТОРГ-12** - Товарная накладная

355
bot.py
View File

@ -507,6 +507,8 @@ async def handle_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
"Выберите действие:",
reply_markup=get_main_keyboard()
)
# Очищаем данные пользователя
context.user_data.clear()
return MAIN_MENU
elif data.startswith("page:"):
@ -541,13 +543,41 @@ async def handle_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
context.user_data['format'] = format_type
if format_type == "xml":
# XML - сразу генерируем
return await generate_xml(update, context)
# XML - показываем выбор типа XML
return await show_xml_format_choice(update, context)
elif format_type == "package":
# Комплект документов - генерируем сразу
return await generate_package(update, context)
else:
# Excel - сбрасываем флаг и показываем шаблоны
context.user_data['show_all_templates'] = False
return await show_templates(update, context)
elif data.startswith("xml_format:"):
# Выбран тип XML
xml_format = data.split(":")[1]
context.user_data['xml_format'] = xml_format
return await generate_xml(update, context)
elif data == "generate_another":
# Генерировать другой файл - возвращаемся к выбору формата
order_id = context.user_data.get('selected_order_id')
logger.info(f"generate_another clicked, order_id: {order_id}")
if order_id:
# Убираем кнопки с предыдущего сообщения
await query.edit_message_reply_markup(reply_markup=None)
# Вызываем validate_order напрямую с query
# validate_order сам покажет данные валидации
return await validate_order(update, context)
else:
await query.answer("❌ Ошибка: отгрузка не найдена")
await query.message.reply_text(
"Выберите действие:",
reply_markup=get_main_keyboard()
)
context.user_data.clear()
return MAIN_MENU
elif data.startswith("template:"):
# Выбран шаблон
template_id = data.split(":")[1]
@ -560,7 +590,9 @@ async def handle_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
context.user_data['show_all_templates'] = True
return await show_templates(update, context)
return SELECT_ORDER
# Если callback не распознан - логируем и возвращаем текущее состояние
logger.warning(f"Неизвестный callback: {data}")
return SELECT_FORMAT
async def validate_order(update: Update, context: ContextTypes.DEFAULT_TYPE):
@ -680,15 +712,30 @@ async def validate_order(update: Update, context: ContextTypes.DEFAULT_TYPE):
"📊 Excel",
callback_data="format:excel"
)],
[InlineKeyboardButton(
"📦 Комплект (XML 1С + Excel)",
callback_data="format:package"
)],
[InlineKeyboardButton("❌ Отмена", callback_data="cancel")]
]
if update.callback_query:
await update.callback_query.edit_message_text(
validation_text,
parse_mode='HTML',
reply_markup=InlineKeyboardMarkup(keyboard)
)
# Проверяем, есть ли у сообщения reply_markup
# Если кнопки уже убраны (generate_another), отправляем новое
try:
await update.callback_query.edit_message_text(
validation_text,
parse_mode='HTML',
reply_markup=InlineKeyboardMarkup(keyboard)
)
except Exception as e:
# Если редактирование не удалось, отправляем новое сообщение
logger.warning(f"Не удалось отредактировать сообщение: {e}")
await update.callback_query.message.reply_text(
validation_text,
parse_mode='HTML',
reply_markup=InlineKeyboardMarkup(keyboard)
)
else:
await update.message.reply_text(
validation_text,
@ -717,13 +764,53 @@ async def validate_order(update: Update, context: ContextTypes.DEFAULT_TYPE):
return MAIN_MENU
async def show_xml_format_choice(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Показывает выбор формата XML"""
query = update.callback_query
text = (
"📄 <b>Выберите формат XML:</b>\n\n"
"🏛 <b>ВерсФорм 5.03 (УПД/ТН)</b>\n"
"Для ФНС и электронного документооборота\n"
"Стандартный формат для налоговой\n\n"
"💼 <b>1С EnterpriseData 1.20</b>\n"
"Для загрузки в программы 1С\n"
"Формат обмена с конфигурациями 1С"
)
keyboard = [
[InlineKeyboardButton(
"🏛 ВерсФорм 5.03 (УПД)",
callback_data="xml_format:versform"
)],
[InlineKeyboardButton(
"💼 1С EnterpriseData 1.20",
callback_data="xml_format:1c"
)],
[InlineKeyboardButton("❌ Отмена", callback_data="cancel")]
]
await query.edit_message_text(
text,
parse_mode='HTML',
reply_markup=InlineKeyboardMarkup(keyboard)
)
return SELECT_FORMAT
async def generate_xml(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Генерация XML документа"""
user_id = update.effective_user.id
query = update.callback_query
# Получаем выбранный формат XML
xml_format = context.user_data.get('xml_format', 'versform')
format_name = "ВерсФорм 5.03 (УПД)" if xml_format == 'versform' else "1С EnterpriseData 1.20"
await query.edit_message_text(
"🔄 Генерация XML документа...",
f"🔄 Генерация XML документа ({format_name})...",
reply_markup=None
)
@ -739,8 +826,16 @@ async def generate_xml(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Получаем полные данные отгрузки
order_data = moysklad_client.get_full_order_data(order_id)
# Создаём генератор XML
xml_gen = XMLGenerator()
# Создаём генератор XML в зависимости от выбранного формата
if xml_format == '1c':
from xml_generator import XML1CGenerator
xml_gen = XML1CGenerator()
generate_method = xml_gen.generate_realization
caption_format = "1С EnterpriseData 1.20"
else:
xml_gen = XMLGenerator()
generate_method = xml_gen.generate_upd
caption_format = "УПД ВерсФорм 5.03"
# Создаём временную директорию
output_dir = Path("temp_documents")
@ -749,27 +844,56 @@ async def generate_xml(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Генерируем XML файл
filename = f"{order_data.get('name', order_id)}.xml"
output_path = str(output_dir / filename)
xml_gen.generate_upd(order_data, output_path)
generate_method(order_data, output_path)
# Отправляем файл
# Формируем информацию об отгрузке
order = order_data.get('order', {})
order_name = order.get('name', 'N/A')
order_moment = order.get('moment', '')[:10]
order_sum = order.get('sum', 0) / 100
counterparty = order_data.get('counterparty', {})
counterparty_name = counterparty.get('name', 'N/A')
caption = (
f"✅ <b>XML документ создан!</b>\n\n"
f"📄 Формат: {caption_format}\n"
f"📋 Отгрузка: №{order_name} от {order_moment}\n"
f"👤 Контрагент: {counterparty_name}\n"
f"💰 Сумма: {order_sum:,.2f} ₽"
)
# Кнопка для генерации другого файла
keyboard = [
[InlineKeyboardButton(
"📝 Сгенерировать другой файл",
callback_data="generate_another"
)],
[InlineKeyboardButton(
"🏠 Главное меню",
callback_data="cancel"
)]
]
# Отправляем файл с inline кнопками
await query.message.reply_document(
document=open(output_path, 'rb'),
caption="✅ XML документ (УПД 5.03) успешно создан!",
reply_markup=get_main_keyboard()
caption=caption,
parse_mode='HTML',
reply_markup=InlineKeyboardMarkup(keyboard)
)
# Удаляем временный файл
os.remove(output_path)
# Очищаем данные пользователя
context.user_data.clear()
await query.message.reply_text(
"Выберите действие:",
# Показываем главное меню (ReplyKeyboard)
await context.bot.send_message(
chat_id=update.effective_chat.id,
text="Используйте кнопки ниже для продолжения работы:",
reply_markup=get_main_keyboard()
)
return MAIN_MENU
# НЕ очищаем user_data - нужны данные для "Сгенерировать другой"
return SELECT_FORMAT
except Exception as e:
logger.error(f"Ошибка генерации XML: {e}")
@ -971,25 +1095,54 @@ async def generate_excel(update: Update, context: ContextTypes.DEFAULT_TYPE):
output_path=str(output_dir / f"{order_id}.xls")
)
# Получаем информацию об отгрузке
order_data = context.user_data.get('order_data', {})
order = order_data.get('order', {})
order_name = order.get('name', 'N/A')
order_moment = order.get('moment', '')[:10]
order_sum = order.get('sum', 0) / 100
counterparty = order_data.get('counterparty', {})
counterparty_name = counterparty.get('name', 'N/A')
caption = (
f"✅ <b>Excel документ создан!</b>\n\n"
f"📋 Отгрузка: №{order_name} от {order_moment}\n"
f"👤 Контрагент: {counterparty_name}\n"
f"💰 Сумма: {order_sum:,.2f} ₽"
)
# Кнопка для генерации другого файла
keyboard = [
[InlineKeyboardButton(
"📝 Сгенерировать другой файл",
callback_data="generate_another"
)],
[InlineKeyboardButton(
"🏠 Главное меню",
callback_data="cancel"
)]
]
# Отправляем файл
await query.message.reply_document(
document=open(output_path, 'rb'),
caption="✅ Документ успешно создан!",
reply_markup=get_main_keyboard()
caption=caption,
parse_mode='HTML',
reply_markup=InlineKeyboardMarkup(keyboard)
)
# Удаляем временный файл
os.remove(output_path)
# Очищаем данные пользователя
context.user_data.clear()
await query.message.reply_text(
"Выберите действие:",
# Показываем главное меню (ReplyKeyboard)
await context.bot.send_message(
chat_id=update.effective_chat.id,
text="Используйте кнопки ниже для продолжения работы:",
reply_markup=get_main_keyboard()
)
return MAIN_MENU
# НЕ очищаем user_data
return SELECT_FORMAT
except Exception as e:
logger.error(f"Ошибка генерации документа: {e}")
@ -1004,6 +1157,143 @@ async def generate_excel(update: Update, context: ContextTypes.DEFAULT_TYPE):
return MAIN_MENU
async def generate_package(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Генерация комплекта документов (XML 1С + рекомендованный Excel)"""
user_id = update.effective_user.id
query = update.callback_query
await query.edit_message_text(
"🔄 Генерация комплекта документов...\n"
"Это может занять несколько секунд",
reply_markup=None
)
try:
order_id = context.user_data.get('selected_order_id')
if not order_id:
raise ValueError("ID отгрузки не найден")
# Получаем клиента для пользователя
moysklad_client = get_moysklad_client(user_id)
# Получаем полные данные отгрузки
order_data = context.user_data.get('order_data')
if not order_data:
order_data = moysklad_client.get_full_order_data(order_id)
context.user_data['order_data'] = order_data
order = order_data.get('order', {})
counterparty = order_data.get('counterparty', {})
# Создаём временную директорию
output_dir = Path("temp_documents")
output_dir.mkdir(exist_ok=True)
# 1. Генерируем XML 1С
from xml_generator import XML1CGenerator
xml_gen = XML1CGenerator()
xml_filename = f"{order.get('name', order_id)}_1c.xml"
xml_path = str(output_dir / xml_filename)
xml_gen.generate_realization(order_data, xml_path)
# 2. Определяем рекомендованный Excel шаблон
inn = counterparty.get('inn', '')
kpp = counterparty.get('kpp', '')
name = counterparty.get('name', '').upper()
# Определяем тип клиента
is_individual = True
if len(inn) == 10:
is_individual = False
elif len(inn) == 12:
if kpp or 'ИП ' in name or name.startswith('ИП'):
is_individual = False
# ID рекомендованных шаблонов
TN_DEFAULT_ID = "92743bbb-c8c8-42e2-8686-350eace81edd"
UPD_DEFAULT_ID = "be6d441a-5982-48fe-ac21-46754e051f8c"
template_id = TN_DEFAULT_ID if is_individual else UPD_DEFAULT_ID
# Генерируем Excel
excel_path = moysklad_client.print_document(
entity_type="demand",
entity_id=order_id,
template_id=template_id,
template_type="customtemplate",
extension="xls",
output_path=str(output_dir / f"{order_id}.xls")
)
# Формируем информацию об отгрузке
order_name = order.get('name', 'N/A')
order_moment = order.get('moment', '')[:10]
order_sum = order.get('sum', 0) / 100
counterparty_name = counterparty.get('name', 'N/A')
caption = (
f"✅ <b>Комплект документов создан!</b>\n\n"
f"📦 Включено:\n"
f" • XML 1С EnterpriseData 1.20\n"
f" • Excel (рекомендованный шаблон)\n\n"
f"📋 Отгрузка: №{order_name} от {order_moment}\n"
f"👤 Контрагент: {counterparty_name}\n"
f"💰 Сумма: {order_sum:,.2f} ₽"
)
# Кнопка для генерации другого файла
keyboard = [
[InlineKeyboardButton(
"📝 Сгенерировать другой файл",
callback_data="generate_another"
)],
[InlineKeyboardButton(
"🏠 Главное меню",
callback_data="cancel"
)]
]
# Отправляем XML файл
await query.message.reply_document(
document=open(xml_path, 'rb'),
caption="📄 XML 1С EnterpriseData 1.20"
)
# Отправляем Excel файл с полной информацией
await query.message.reply_document(
document=open(excel_path, 'rb'),
caption=caption,
parse_mode='HTML',
reply_markup=InlineKeyboardMarkup(keyboard)
)
# Удаляем временные файлы
os.remove(xml_path)
os.remove(excel_path)
# Показываем главное меню (ReplyKeyboard)
await context.bot.send_message(
chat_id=update.effective_chat.id,
text="Используйте кнопки ниже для продолжения работы:",
reply_markup=get_main_keyboard()
)
# НЕ очищаем user_data
return SELECT_FORMAT
except Exception as e:
logger.error(f"Ошибка генерации комплекта: {e}")
await query.message.reply_text(
f"❌ Ошибка генерации комплекта: {str(e)}\n\n"
f"Выберите действие:",
reply_markup=get_main_keyboard()
)
context.user_data.clear()
return MAIN_MENU
async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Обработчик текстовых сообщений"""
text = update.message.text
@ -1036,8 +1326,7 @@ def main():
settings.telegram_bot_token
).build()
# Обработчики
application.add_handler(CommandHandler("start", start))
# Обработчики команд (не входящих в ConversationHandler)
application.add_handler(CommandHandler("help", help_command))
application.add_handler(CommandHandler("logout", logout_command))
application.add_handler(
@ -1047,6 +1336,7 @@ def main():
# Обработчик состояний
conv_handler = ConversationHandler(
entry_points=[
CommandHandler("start", start),
MessageHandler(
filters.Regex("^🔍 Поиск отгрузки$"),
search_order_prompt
@ -1081,12 +1371,15 @@ def main():
],
SELECT_ORDER: [
CallbackQueryHandler(handle_callback),
MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text),
],
SELECT_FORMAT: [
CallbackQueryHandler(handle_callback),
MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text),
],
SELECT_TEMPLATE: [
CallbackQueryHandler(handle_callback),
MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text),
],
},
fallbacks=[

View File

@ -6,7 +6,7 @@ version: '3.8'
services:
# Telegram бот
buh_bot:
image: ${DOCKER_REGISTRY:-ghcr.io}/${DOCKER_USERNAME:-username}/buh_bot:${BOT_VERSION:-latest}
image: igornadein/buh_bot:latest
container_name: buh_bot_prod
restart: always
@ -20,9 +20,9 @@ services:
volumes:
# Персистентные данные на хосте
- /var/lib/buh_bot/data:/app/data
- /var/log/buh_bot:/app/logs
- /var/tmp/buh_bot:/app/temp_documents
- ~/BUH_bot/data:/app/data
- ~/BUH_bot/logs:/app/logs
- ~/BUH_bot/temp_documents:/app/temp_documents
networks:
- buh_network

195
docs/XML_1C_FORMAT.md Normal file
View File

@ -0,0 +1,195 @@
# Генератор XML в формате 1С EnterpriseData 1.20
## Описание
Создан новый класс `XML1CGenerator` для генерации XML документов в формате обмена данными 1С (EnterpriseData 1.20). Этот формат используется для загрузки документов реализации в конфигурации 1С.
## Отличия от формата ВерсФорм 5.03
| Параметр | Формат 1С | ВерсФорм 5.03 (УПД/ТН) |
|----------|-----------|------------------------|
| **Назначение** | Обмен данными между 1С | УПД/ТОРГ-12 для ФНС |
| **Корневой элемент** | `<Message>` | `<Файл>` |
| **Namespace** | http://v8.1c.ru/edi/edi_stnd/EnterpriseData/1.20 | Нет |
| **Документ** | `<Документ.РеализацияТоваровУслуг>` | `<СвСчФакт>` |
| **Формат даты** | ISO 8601 (2025-10-23T09:37:11) | ДД.ММ.ГГГГ |
| **Суммы** | Рубли (целое число) | Рубли с копейками |
## Использование
### Базовый пример
```python
from modules.xml_generator import XML1CGenerator
# Создание генератора
generator = XML1CGenerator()
# Подготовка данных заказа из МойСклад
order_data = {
"order": {
"id": "313256ef-b4b7-11f0-b68a-50ebf69bde57",
"name": "4869",
"moment": "2025-10-23T09:37:11Z",
"sum": 1059900, # В копейках!
"rate": {
"currency": {
"name": "руб.",
"isoCode": "643"
}
}
},
"organization": {
"id": "288f7520-b8e5-11e8-80bc-e0d55e458e90",
"name": "Беляев А.Ю. ИП",
"legalTitle": "Индивидуальный предприниматель Беляев Андрей Юрьевич",
"inn": "263605160270",
"companyType": "entrepreneur", # или "legal"
"legalLastName": "Беляев",
"legalFirstName": "Андрей",
"legalMiddleName": "Юрьевич"
},
"counterparty": {
"id": "f37958af-d807-11ef-b676-50ebf69bde57",
"name": "АСЕЕВА АННА КУЗЬМИНИЧНА ИП",
"legalTitle": "ИНДИВИДУАЛЬНЫЙ ПРЕДПРИНИМАТЕЛЬ АСЕЕВА АННА КУЗЬМИНИЧНА",
"inn": "383700097634",
"companyType": "entrepreneur"
},
"org_accounts": [
{
"id": "bdd39811-27ee-11ea-80e7-e0d55e458e90",
"accountNumber": "40802810860270001979",
"bankName": "СТАВРОПОЛЬСКОЕ ОТДЕЛЕНИЕ N5230 ПАО СБЕРБАНК",
"bic": "040702615",
"correspondentAccount": "30101810907020000615"
}
],
"positions": [
{
"assortment": {
"name": "Жевательная резинка RUSGUM Арбузное трио 24 мм 7*150 штук",
"code": "00-00000682",
"uom": {"name": "шт"}
},
"quantity": 1,
"price": 337300, # В копейках!
"vat": 5
}
]
}
# Генерация XML
xml_string = generator.generate_realization(order_data, "realization_1c.xml")
print("XML создан успешно!")
```
## Структура XML документа
### 1. Message Header
```xml
<msg:Header>
<msg:Format>http://v8.1c.ru/edi/edi_stnd/EnterpriseData/1.20</msg:Format>
<msg:CreationDate>2025-11-05T10:39:56</msg:CreationDate>
<msg:AvailableVersion>1.20</msg:AvailableVersion>
</msg:Header>
```
### 2. Body с документом реализации
```xml
<Body>
<Документ.РеализацияТоваровУслуг>
<КлючевыеСвойства>...</КлючевыеСвойства>
<ВидОперации>РеализацияКлиенту</ВидОперации>
<Товары>...</Товары>
...
</Документ.РеализацияТоваровУслуг>
</Body>
```
## Обязательные поля
### Организация (продавец)
- `id` - UUID организации
- `name` - Краткое наименование
- `legalTitle` - Полное наименование
- `inn` - ИНН
- `companyType` - Тип: "entrepreneur" или "legal"
Для ИП дополнительно:
- `legalLastName` - Фамилия
- `legalFirstName` - Имя
- `legalMiddleName` - Отчество (опционально)
### Контрагент (покупатель)
- `id` - UUID контрагента
- `name` - Наименование
- `legalTitle` - Полное наименование
- `inn` - ИНН
- `companyType` - Тип
### Заказ
- `id` - UUID заказа
- `name` - Номер документа
- `moment` - Дата в ISO формате
- `sum` - Сумма в копейках
### Позиции
- `assortment.name` - Наименование товара
- `assortment.code` - Код товара (опционально)
- `assortment.uom.name` - Единица измерения
- `quantity` - Количество
- `price` - Цена в копейках
- `vat` - Ставка НДС (5, 10, 20)
## Интеграция с ботом
В `bot.py` добавьте выбор формата:
```python
from modules.xml_generator import XMLGenerator, XML1CGenerator
# При генерации документа
def generate_document(order_data, format_type):
if format_type == "1c":
generator = XML1CGenerator()
xml = generator.generate_realization(order_data, "doc.xml")
elif format_type == "upd":
generator = XMLGenerator()
xml = generator.generate_upd(order_data, "doc.xml")
elif format_type == "torg12":
generator = XMLGenerator()
xml = generator.generate_tn(order_data, "doc.xml")
return xml
```
## Особенности формата
1. **Суммы в рублях** - Все денежные значения в целых рублях (без копеек)
2. **UUID везде** - Все ссылки используют UUID формат
3. **Дата ISO 8601** - Формат: `YYYY-MM-DDTHH:MM:SS`
4. **Номер с паддингом** - Номер документа дополняется пробелами до 11 символов
5. **Тип лица** - "ФизическоеЛицо" для ИП, "ЮридическоеЛицо" для ООО
6. **НДС извлекается** - Сумма НДС вычисляется из общей суммы (включающей НДС)
## Проверка результата
Сгенерированный XML можно загрузить в 1С через:
- Обработку "Универсальный обмен данными в формате XML"
- Загрузку из файла в документе "Реализация товаров и услуг"
## Поддержка
При возникновении проблем проверьте:
1. Все обязательные поля заполнены
2. Суммы передаются в копейках (умножить на 100)
3. Дата в ISO формате с временем
4. ИНН корректный (10 или 12 цифр)
5. UUID в формате: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
## Версии
- **1.0** (2025-11-05) - Начальная версия с поддержкой документа реализации

View File

@ -622,6 +622,419 @@ class XMLGenerator:
return xml_string
class XML1CGenerator:
"""Генератор XML документов в формате 1С EnterpriseData 1.20"""
FORMAT_VERSION = "1.20"
FORMAT_URI = "http://v8.1c.ru/edi/edi_stnd/EnterpriseData/1.20"
def __init__(self):
self.encoding = "UTF-8"
# Регистрация namespace
self.nsmap = {
None: self.FORMAT_URI,
'msg': 'http://www.1c.ru/SSL/Exchange/Message',
'xs': 'http://www.w3.org/2001/XMLSchema',
'xsi': 'http://www.w3.org/2001/XMLSchema-instance'
}
def _format_datetime(self, date_str: str) -> str:
"""
Форматирование даты в ISO формат для 1С
Args:
date_str: Дата в ISO формате
Returns:
Дата в формате ISO 8601 (YYYY-MM-DDTHH:MM:SS)
"""
try:
dt = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
return dt.strftime("%Y-%m-%dT%H:%M:%S")
except Exception as e:
logger.error(f"Ошибка форматирования даты: {e}")
return datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
def _format_money_1c(self, amount: int) -> str:
"""
Форматирование денежной суммы для 1С (копейки -> рубли без копеек)
Args:
amount: Сумма в копейках
Returns:
Сумма в рублях (целое число)
"""
return str(int(amount / 100))
def _format_money_with_decimals(self, amount: float) -> str:
"""
Форматирование денежной суммы с копейками
Args:
amount: Сумма в рублях
Returns:
Сумма с 2 знаками после запятой
"""
return f"{amount:.2f}"
def generate_realization(
self,
order_data: Dict,
output_path: Optional[str] = None
) -> str:
"""
Генерация документа Реализация товаров и услуг в формате 1С
Args:
order_data: Полные данные заказа из МойСклад
output_path: Путь для сохранения файла
Returns:
XML в виде строки
"""
order = order_data["order"]
organization = order_data["organization"]
counterparty = order_data["counterparty"]
positions = order_data["positions"]
org_accounts = order_data.get("org_accounts", [])
# Корневой элемент Message
root = etree.Element(
"Message",
nsmap={
'msg': self.nsmap['msg'],
'xs': self.nsmap['xs'],
'xsi': self.nsmap['xsi']
}
)
# Header
header = etree.SubElement(root, "{%s}Header" % self.nsmap['msg'])
etree.SubElement(header, "{%s}Format" % self.nsmap['msg']).text = self.FORMAT_URI
etree.SubElement(header, "{%s}CreationDate" % self.nsmap['msg']).text = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
etree.SubElement(header, "{%s}AvailableVersion" % self.nsmap['msg']).text = self.FORMAT_VERSION
# Body
body = etree.SubElement(root, "Body", nsmap={None: self.FORMAT_URI})
# Документ.РеализацияТоваровУслуг
doc = etree.SubElement(body, "Документ.РеализацияТоваровУслуг")
# КлючевыеСвойства
key_props = etree.SubElement(doc, "КлючевыеСвойства")
# Ссылка (UUID документа)
order_id = order.get("id", "")
etree.SubElement(key_props, "Ссылка").text = order_id
# Дата и Номер
order_date = self._format_datetime(order.get("moment", ""))
order_number = order.get("name", "")
etree.SubElement(key_props, "Дата").text = order_date
etree.SubElement(key_props, "Номер").text = order_number.ljust(11) # Паддинг пробелами
# Организация
org_elem = etree.SubElement(key_props, "Организация")
org_id = organization.get("id", "")
etree.SubElement(org_elem, "Ссылка").text = org_id
etree.SubElement(org_elem, "Наименование").text = organization.get("name", "")
# Полные наименования для ИП
company_type = organization.get("companyType", "")
if company_type == "entrepreneur":
legal_name = organization.get("legalTitle", "")
etree.SubElement(org_elem, "НаименованиеСокращенное").text = legal_name
etree.SubElement(org_elem, "НаименованиеПолное").text = legal_name
else:
etree.SubElement(org_elem, "НаименованиеСокращенное").text = organization.get("name", "")
etree.SubElement(org_elem, "НаименованиеПолное").text = organization.get("legalTitle", "")
etree.SubElement(org_elem, "ИНН").text = organization.get("inn", "")
# Тип лица
if company_type == "entrepreneur":
etree.SubElement(org_elem, "ЮридическоеФизическоеЛицо").text = "ФизическоеЛицо"
else:
etree.SubElement(org_elem, "ЮридическоеФизическоеЛицо").text = "ЮридическоеЛицо"
# ВидОперации
etree.SubElement(doc, "ВидОперации").text = "РеализацияКлиенту"
# Валюта
rate = order.get("rate", {})
currency = rate.get("currency", {}) if rate else {}
currency_elem = etree.SubElement(doc, "Валюта")
currency_id = currency.get("meta", {}).get("href", "").split("/")[-1] if currency.get("meta") else ""
etree.SubElement(currency_elem, "Ссылка").text = currency_id
currency_data = etree.SubElement(currency_elem, "ДанныеКлассификатора")
etree.SubElement(currency_data, "Код").text = currency.get("isoCode", "643")
etree.SubElement(currency_data, "Наименование").text = currency.get("name", "руб.")
# Сумма (в рублях, без копеек)
order_sum = order.get("sum", 0)
etree.SubElement(doc, "Сумма").text = self._format_money_1c(order_sum)
# СуммаВключаетНДС
etree.SubElement(doc, "СуммаВключаетНДС").text = "true"
# Склад (если есть)
store = order.get("store", {})
if store:
store_elem = etree.SubElement(doc, "Склад")
store_id = store.get("meta", {}).get("href", "").split("/")[-1] if store.get("meta") else ""
etree.SubElement(store_elem, "Ссылка").text = store_id
etree.SubElement(store_elem, "Наименование").text = store.get("name", "Основной склад")
etree.SubElement(store_elem, "ТипСклада").text = "Оптовый"
# Руководитель (для ИП - сам ИП)
if company_type == "entrepreneur":
chief_elem = etree.SubElement(doc, "Руководитель")
etree.SubElement(chief_elem, "Ссылка").text = org_id
# ФИО
fio_parts = []
last_name = organization.get("legalLastName", "")
first_name = organization.get("legalFirstName", "")
middle_name = organization.get("legalMiddleName", "")
if last_name:
fio_parts.append(last_name)
if first_name:
fio_parts.append(first_name)
if middle_name:
fio_parts.append(middle_name)
full_fio = " ".join(fio_parts)
etree.SubElement(chief_elem, "ФИО").text = full_fio
etree.SubElement(chief_elem, "Фамилия").text = last_name
etree.SubElement(chief_elem, "Имя").text = first_name
etree.SubElement(chief_elem, "Отчество").text = middle_name
etree.SubElement(chief_elem, "ИНН").text = organization.get("inn", "")
# БанковскийСчетОрганизации
if org_accounts and len(org_accounts) > 0:
acc = org_accounts[0]
bank_acc_elem = etree.SubElement(doc, "БанковскийСчетОрганизации")
acc_id = acc.get("id", "")
etree.SubElement(bank_acc_elem, "Ссылка").text = acc_id
etree.SubElement(bank_acc_elem, "НомерСчета").text = acc.get("accountNumber", "")
# Банк
bank_elem = etree.SubElement(bank_acc_elem, "Банк")
etree.SubElement(bank_elem, "Ссылка").text = acc_id # Используем тот же ID
bank_data = etree.SubElement(bank_elem, "ДанныеКлассификатораБанков")
etree.SubElement(bank_data, "Наименование").text = acc.get("bankName", "")
etree.SubElement(bank_data, "БИК").text = acc.get("bic", "")
etree.SubElement(bank_data, "КоррСчет").text = acc.get("correspondentAccount", "")
# Владелец
owner_elem = etree.SubElement(bank_acc_elem, "Владелец")
org_ref = etree.SubElement(owner_elem, "ОрганизацииСсылка")
etree.SubElement(org_ref, "Ссылка").text = org_id
etree.SubElement(org_ref, "Наименование").text = organization.get("name", "")
if company_type == "entrepreneur":
etree.SubElement(org_ref, "НаименованиеСокращенное").text = organization.get("legalTitle", "")
etree.SubElement(org_ref, "НаименованиеПолное").text = organization.get("legalTitle", "")
etree.SubElement(org_ref, "ЮридическоеФизическоеЛицо").text = "ФизическоеЛицо"
else:
etree.SubElement(org_ref, "НаименованиеСокращенное").text = organization.get("name", "")
etree.SubElement(org_ref, "НаименованиеПолное").text = organization.get("legalTitle", "")
etree.SubElement(org_ref, "ЮридическоеФизическоеЛицо").text = "ЮридическоеЛицо"
etree.SubElement(org_ref, "ИНН").text = organization.get("inn", "")
# Контрагент
cp_elem = etree.SubElement(doc, "Контрагент")
cp_id = counterparty.get("id", "")
etree.SubElement(cp_elem, "Ссылка").text = cp_id
etree.SubElement(cp_elem, "Наименование").text = counterparty.get("name", "")
etree.SubElement(cp_elem, "НаименованиеПолное").text = counterparty.get("legalTitle", counterparty.get("name", ""))
etree.SubElement(cp_elem, "ИНН").text = counterparty.get("inn", "")
cp_type = counterparty.get("companyType", "legal")
if cp_type == "entrepreneur":
etree.SubElement(cp_elem, "ЮридическоеФизическоеЛицо").text = "ФизическоеЛицо"
etree.SubElement(cp_elem, "ИндивидуальныйПредприниматель").text = "true"
else:
etree.SubElement(cp_elem, "ЮридическоеФизическоеЛицо").text = "ЮридическоеЛицо"
# Страна регистрации
country_elem = etree.SubElement(cp_elem, "СтранаРегистрации")
etree.SubElement(country_elem, "Ссылка").text = ""
country_data = etree.SubElement(country_elem, "ДанныеКлассификатора")
etree.SubElement(country_data, "Код").text = "643"
etree.SubElement(country_data, "Наименование").text = "РОССИЯ"
# ДанныеВзаиморасчетов
settlement_elem = etree.SubElement(doc, "ДанныеВзаиморасчетов")
# Договор
contract_elem = etree.SubElement(settlement_elem, "Договор")
etree.SubElement(contract_elem, "Ссылка").text = ""
etree.SubElement(contract_elem, "ВидДоговора").text = "СПокупателем"
# Организация в договоре (ссылка)
contract_org = etree.SubElement(contract_elem, "Организация")
etree.SubElement(contract_org, "Ссылка").text = org_id
etree.SubElement(contract_org, "Наименование").text = organization.get("name", "")
if company_type == "entrepreneur":
etree.SubElement(contract_org, "НаименованиеСокращенное").text = organization.get("legalTitle", "")
etree.SubElement(contract_org, "НаименованиеПолное").text = organization.get("legalTitle", "")
etree.SubElement(contract_org, "ЮридическоеФизическоеЛицо").text = "ФизическоеЛицо"
else:
etree.SubElement(contract_org, "НаименованиеСокращенное").text = organization.get("name", "")
etree.SubElement(contract_org, "НаименованиеПолное").text = organization.get("legalTitle", "")
etree.SubElement(contract_org, "ЮридическоеФизическоеЛицо").text = "ЮридическоеЛицо"
etree.SubElement(contract_org, "ИНН").text = organization.get("inn", "")
# Контрагент в договоре
contract_cp = etree.SubElement(contract_elem, "Контрагент")
etree.SubElement(contract_cp, "Ссылка").text = cp_id
etree.SubElement(contract_cp, "Наименование").text = counterparty.get("name", "")
etree.SubElement(contract_cp, "НаименованиеПолное").text = counterparty.get("legalTitle", "")
etree.SubElement(contract_cp, "ИНН").text = counterparty.get("inn", "")
if cp_type == "entrepreneur":
etree.SubElement(contract_cp, "ЮридическоеФизическоеЛицо").text = "ФизическоеЛицо"
etree.SubElement(contract_cp, "ИндивидуальныйПредприниматель").text = "true"
else:
etree.SubElement(contract_cp, "ЮридическоеФизическоеЛицо").text = "ЮридическоеЛицо"
# Страна в контрагенте договора
cp_country = etree.SubElement(contract_cp, "СтранаРегистрации")
etree.SubElement(cp_country, "Ссылка").text = ""
cp_country_data = etree.SubElement(cp_country, "ДанныеКлассификатора")
etree.SubElement(cp_country_data, "Код").text = "643"
etree.SubElement(cp_country_data, "Наименование").text = "РОССИЯ"
# Валюта взаиморасчетов в договоре
contract_currency = etree.SubElement(contract_elem, "ВалютаВзаиморасчетов")
etree.SubElement(contract_currency, "Ссылка").text = currency_id
contract_curr_data = etree.SubElement(contract_currency, "ДанныеКлассификатора")
etree.SubElement(contract_curr_data, "Код").text = currency.get("isoCode", "643")
etree.SubElement(contract_curr_data, "Наименование").text = currency.get("name", "руб.")
etree.SubElement(contract_elem, "РасчетыВУсловныхЕдиницах").text = "false"
etree.SubElement(contract_elem, "Наименование").text = "Основной договор"
# Валюта взаиморасчетов
settlement_currency = etree.SubElement(settlement_elem, "ВалютаВзаиморасчетов")
etree.SubElement(settlement_currency, "Ссылка").text = currency_id
settlement_curr_data = etree.SubElement(settlement_currency, "ДанныеКлассификатора")
etree.SubElement(settlement_curr_data, "Код").text = currency.get("isoCode", "643")
etree.SubElement(settlement_curr_data, "Наименование").text = currency.get("name", "руб.")
etree.SubElement(settlement_elem, "КурсВзаиморасчетов").text = "1"
etree.SubElement(settlement_elem, "КратностьВзаиморасчетов").text = "1"
etree.SubElement(settlement_elem, "РасчетыВУсловныхЕдиницах").text = "false"
# Налогообложение
etree.SubElement(doc, "Налогообложение").text = "ПродажаОблагаетсяНДС"
# ВидЭД
etree.SubElement(doc, "ВидЭД").text = "ТОРГ12"
# Товары
goods_elem = etree.SubElement(doc, "Товары")
for pos in positions:
row_elem = etree.SubElement(goods_elem, "Строка")
assortment = pos.get("assortment", {})
product_id = assortment.get("meta", {}).get("href", "").split("/")[-1] if assortment.get("meta") else ""
product_name = assortment.get("name", "")
# ДанныеНоменклатуры
nomenclature_data = etree.SubElement(row_elem, "ДанныеНоменклатуры")
nomenclature = etree.SubElement(nomenclature_data, "Номенклатура")
etree.SubElement(nomenclature, "Ссылка").text = product_id
etree.SubElement(nomenclature, "НаименованиеПолное").text = product_name
etree.SubElement(nomenclature, "КодВПрограмме").text = assortment.get("code", "")
etree.SubElement(nomenclature, "Наименование").text = product_name
# ЕдиницаИзмерения
uom = assortment.get("uom", {})
uom_elem = etree.SubElement(row_elem, "ЕдиницаИзмерения")
uom_id = uom.get("meta", {}).get("href", "").split("/")[-1] if uom and uom.get("meta") else ""
etree.SubElement(uom_elem, "Ссылка").text = uom_id
uom_data = etree.SubElement(uom_elem, "ДанныеКлассификатора")
etree.SubElement(uom_data, "Код").text = "796" # Код ОКЕИ для "шт"
etree.SubElement(uom_data, "Наименование").text = uom.get("name", "шт") if uom else "шт"
# Количество
quantity = pos.get("quantity", 0)
etree.SubElement(row_elem, "Количество").text = str(quantity)
# Цена и суммы (в рублях)
price = pos.get("price", 0) / 100 # Конвертируем из копеек
total = price * quantity
etree.SubElement(row_elem, "Сумма").text = str(int(total))
etree.SubElement(row_elem, "Цена").text = str(int(price))
# СтавкаНДС
vat_rate = pos.get("vat", 20)
vat_elem = etree.SubElement(row_elem, "СтавкаНДС")
etree.SubElement(vat_elem, "Ставка").text = str(vat_rate)
etree.SubElement(vat_elem, "РасчетнаяСтавка").text = "false"
etree.SubElement(vat_elem, "НеОблагается").text = "false"
etree.SubElement(vat_elem, "ВидСтавки").text = "Общая"
vat_country = etree.SubElement(vat_elem, "Страна")
etree.SubElement(vat_country, "Ссылка").text = ""
vat_country_data = etree.SubElement(vat_country, "ДанныеКлассификатора")
etree.SubElement(vat_country_data, "Код").text = "643"
etree.SubElement(vat_country_data, "Наименование").text = "РОССИЯ"
# СуммаНДС (извлекаем из суммы)
vat_amount = total / (1 + vat_rate / 100) * (vat_rate / 100) if vat_rate else 0
etree.SubElement(row_elem, "СуммаНДС").text = self._format_money_with_decimals(vat_amount)
# Дополнительные поля
etree.SubElement(row_elem, "ТипЗапасов").text = "СобственныеТовары"
etree.SubElement(row_elem, "СчетУчета").text = "43"
etree.SubElement(row_elem, "СчетДоходов").text = "90.01.1"
etree.SubElement(row_elem, "СчетРасходов").text = "90.02.1"
etree.SubElement(row_elem, "СчетУчетаНДСПоРеализации").text = "90.03"
# Уникальный идентификатор строки
import uuid
etree.SubElement(row_elem, "ИдентификаторСтроки").text = str(uuid.uuid4())
etree.SubElement(row_elem, "ПрослеживаемыйТовар").text = "false"
# СпособПогашенияЗадолженности
etree.SubElement(doc, "СпособПогашенияЗадолженности").text = "Автоматически"
# КомплектПечатныхДокументовПодписанПолучен
etree.SubElement(doc, "КомплектПечатныхДокументовПодписанПолучен").text = "false"
# ЭтоУПД
etree.SubElement(doc, "ЭтоУПД").text = "false"
# Конвертация в строку
xml_string = etree.tostring(
root,
pretty_print=True,
xml_declaration=True,
encoding=self.encoding
).decode(self.encoding)
# Сохранение в файл
if output_path:
with open(output_path, "w", encoding=self.encoding) as f:
f.write(xml_string)
logger.info(f"Документ реализации 1С сохранен: {output_path}")
return xml_string
# Пример использования
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
@ -629,38 +1042,63 @@ if __name__ == "__main__":
# Тестовые данные
test_order = {
"order": {
"name": "00001",
"moment": "2024-01-15T10:00:00Z",
"sum": 120000
"id": "313256ef-b4b7-11f0-b68a-50ebf69bde57",
"name": "4869",
"moment": "2025-10-23T09:37:11Z",
"sum": 1059900, # 10599 рублей в копейках
"rate": {
"currency": {
"name": "руб.",
"isoCode": "643"
}
}
},
"organization": {
"name": "ООО Тестовая организация",
"inn": "7707083893",
"kpp": "770701001",
"legalAddress": "123456, г. Москва, ул. Тестовая, д. 1"
"id": "288f7520-b8e5-11e8-80bc-e0d55e458e90",
"name": "Беляев А.Ю. ИП",
"legalTitle": "Индивидуальный предприниматель Беляев Андрей Юрьевич",
"inn": "263605160270",
"companyType": "entrepreneur",
"legalLastName": "Беляев",
"legalFirstName": "Андрей",
"legalMiddleName": "Юрьевич"
},
"counterparty": {
"name": "ООО Покупатель",
"inn": "7707083894",
"kpp": "770701002",
"legalAddress": "654321, г. Москва, ул. Другая, д. 2"
"id": "f37958af-d807-11ef-b676-50ebf69bde57",
"name": "АСЕЕВА АННА КУЗЬМИНИЧНА ИП",
"legalTitle": "ИНДИВИДУАЛЬНЫЙ ПРЕДПРИНИМАТЕЛЬ АСЕЕВА АННА КУЗЬМИНИЧНА",
"inn": "383700097634",
"companyType": "entrepreneur"
},
"org_accounts": [
{
"id": "bdd39811-27ee-11ea-80e7-e0d55e458e90",
"accountNumber": "40802810860270001979",
"bankName": "СТАВРОПОЛЬСКОЕ ОТДЕЛЕНИЕ N5230 ПАО СБЕРБАНК",
"bic": "040702615",
"correspondentAccount": "30101810907020000615"
}
],
"positions": [
{
"assortment": {"name": "Товар 1", "uom": {"name": "шт"}},
"quantity": 10,
"price": 10000,
"vat": 20
"assortment": {
"name": "Жевательная резинка RUSGUM Арбузное трио 24 мм 7*150 штук",
"code": "00-00000682",
"uom": {"name": "шт"}
},
"quantity": 1,
"price": 337300, # 3373 рубля в копейках
"vat": 5
}
]
}
generator = XMLGenerator()
# Тест формата 1С
generator_1c = XML1CGenerator()
xml_1c = generator_1c.generate_realization(test_order, "test_1c_realization.xml")
print("Документ реализации 1С создан")
# Генерация УПД
# Тест старого формата УПД
generator = XMLGenerator()
upd_xml = generator.generate_upd(test_order, "test_upd.xml")
print("УПД создан")
# Генерация ТН
tn_xml = generator.generate_tn(test_order, "test_tn.xml")
print("ТН создана")

176
test_1c_realization.xml Normal file
View File

@ -0,0 +1,176 @@
<?xml version='1.0' encoding='UTF-8'?>
<Message xmlns:msg="http://www.1c.ru/SSL/Exchange/Message" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<msg:Header>
<msg:Format>http://v8.1c.ru/edi/edi_stnd/EnterpriseData/1.20</msg:Format>
<msg:CreationDate>2025-11-05T14:08:06</msg:CreationDate>
<msg:AvailableVersion>1.20</msg:AvailableVersion>
</msg:Header>
<Body xmlns="http://v8.1c.ru/edi/edi_stnd/EnterpriseData/1.20">
<Документ.РеализацияТоваровУслуг>
<КлючевыеСвойства>
<Ссылка>313256ef-b4b7-11f0-b68a-50ebf69bde57</Ссылка>
<Дата>2025-10-23T09:37:11</Дата>
<Номер>4869 </Номер>
<Организация>
<Ссылка>288f7520-b8e5-11e8-80bc-e0d55e458e90</Ссылка>
<Наименование>Беляев А.Ю. ИП</Наименование>
<НаименованиеСокращенное>Индивидуальный предприниматель Беляев Андрей Юрьевич</НаименованиеСокращенное>
<НаименованиеПолное>Индивидуальный предприниматель Беляев Андрей Юрьевич</НаименованиеПолное>
<ИНН>263605160270</ИНН>
<ЮридическоеФизическоеЛицо>ФизическоеЛицо</ЮридическоеФизическоеЛицо>
</Организация>
</КлючевыеСвойства>
<ВидОперации>РеализацияКлиенту</ВидОперации>
<Валюта>
<Ссылка></Ссылка>
<ДанныеКлассификатора>
<Код>643</Код>
<Наименование>руб.</Наименование>
</ДанныеКлассификатора>
</Валюта>
<Сумма>10599</Сумма>
<СуммаВключаетНДС>true</СуммаВключаетНДС>
<Руководитель>
<Ссылка>288f7520-b8e5-11e8-80bc-e0d55e458e90</Ссылка>
<ФИО>Беляев Андрей Юрьевич</ФИО>
<Фамилия>Беляев</Фамилия>
<Имя>Андрей</Имя>
<Отчество>Юрьевич</Отчество>
<ИНН>263605160270</ИНН>
</Руководитель>
<БанковскийСчетОрганизации>
<Ссылка>bdd39811-27ee-11ea-80e7-e0d55e458e90</Ссылка>
<НомерСчета>40802810860270001979</НомерСчета>
<Банк>
<Ссылка>bdd39811-27ee-11ea-80e7-e0d55e458e90</Ссылка>
<ДанныеКлассификатораБанков>
<Наименование>СТАВРОПОЛЬСКОЕ ОТДЕЛЕНИЕ N5230 ПАО СБЕРБАНК</Наименование>
<БИК>040702615</БИК>
<КоррСчет>30101810907020000615</КоррСчет>
</ДанныеКлассификатораБанков>
</Банк>
<Владелец>
<ОрганизацииСсылка>
<Ссылка>288f7520-b8e5-11e8-80bc-e0d55e458e90</Ссылка>
<Наименование>Беляев А.Ю. ИП</Наименование>
<НаименованиеСокращенное>Индивидуальный предприниматель Беляев Андрей Юрьевич</НаименованиеСокращенное>
<НаименованиеПолное>Индивидуальный предприниматель Беляев Андрей Юрьевич</НаименованиеПолное>
<ЮридическоеФизическоеЛицо>ФизическоеЛицо</ЮридическоеФизическоеЛицо>
<ИНН>263605160270</ИНН>
</ОрганизацииСсылка>
</Владелец>
</БанковскийСчетОрганизации>
<Контрагент>
<Ссылка>f37958af-d807-11ef-b676-50ebf69bde57</Ссылка>
<Наименование>АСЕЕВА АННА КУЗЬМИНИЧНА ИП</Наименование>
<НаименованиеПолное>ИНДИВИДУАЛЬНЫЙ ПРЕДПРИНИМАТЕЛЬ АСЕЕВА АННА КУЗЬМИНИЧНА</НаименованиеПолное>
<ИНН>383700097634</ИНН>
<ЮридическоеФизическоеЛицо>ФизическоеЛицо</ЮридическоеФизическоеЛицо>
<ИндивидуальныйПредприниматель>true</ИндивидуальныйПредприниматель>
<СтранаРегистрации>
<Ссылка></Ссылка>
<ДанныеКлассификатора>
<Код>643</Код>
<Наименование>РОССИЯ</Наименование>
</ДанныеКлассификатора>
</СтранаРегистрации>
</Контрагент>
<ДанныеВзаиморасчетов>
<Договор>
<Ссылка></Ссылка>
<ВидДоговора>СПокупателем</ВидДоговора>
<Организация>
<Ссылка>288f7520-b8e5-11e8-80bc-e0d55e458e90</Ссылка>
<Наименование>Беляев А.Ю. ИП</Наименование>
<НаименованиеСокращенное>Индивидуальный предприниматель Беляев Андрей Юрьевич</НаименованиеСокращенное>
<НаименованиеПолное>Индивидуальный предприниматель Беляев Андрей Юрьевич</НаименованиеПолное>
<ЮридическоеФизическоеЛицо>ФизическоеЛицо</ЮридическоеФизическоеЛицо>
<ИНН>263605160270</ИНН>
</Организация>
<Контрагент>
<Ссылка>f37958af-d807-11ef-b676-50ebf69bde57</Ссылка>
<Наименование>АСЕЕВА АННА КУЗЬМИНИЧНА ИП</Наименование>
<НаименованиеПолное>ИНДИВИДУАЛЬНЫЙ ПРЕДПРИНИМАТЕЛЬ АСЕЕВА АННА КУЗЬМИНИЧНА</НаименованиеПолное>
<ИНН>383700097634</ИНН>
<ЮридическоеФизическоеЛицо>ФизическоеЛицо</ЮридическоеФизическоеЛицо>
<ИндивидуальныйПредприниматель>true</ИндивидуальныйПредприниматель>
<СтранаРегистрации>
<Ссылка></Ссылка>
<ДанныеКлассификатора>
<Код>643</Код>
<Наименование>РОССИЯ</Наименование>
</ДанныеКлассификатора>
</СтранаРегистрации>
</Контрагент>
<ВалютаВзаиморасчетов>
<Ссылка></Ссылка>
<ДанныеКлассификатора>
<Код>643</Код>
<Наименование>руб.</Наименование>
</ДанныеКлассификатора>
</ВалютаВзаиморасчетов>
<РасчетыВУсловныхЕдиницах>false</РасчетыВУсловныхЕдиницах>
<Наименование>Основной договор</Наименование>
</Договор>
<ВалютаВзаиморасчетов>
<Ссылка></Ссылка>
<ДанныеКлассификатора>
<Код>643</Код>
<Наименование>руб.</Наименование>
</ДанныеКлассификатора>
</ВалютаВзаиморасчетов>
<КурсВзаиморасчетов>1</КурсВзаиморасчетов>
<КратностьВзаиморасчетов>1</КратностьВзаиморасчетов>
<РасчетыВУсловныхЕдиницах>false</РасчетыВУсловныхЕдиницах>
</ДанныеВзаиморасчетов>
<Налогообложение>ПродажаОблагаетсяНДС</Налогообложение>
<ВидЭД>ТОРГ12</ВидЭД>
<Товары>
<Строка>
<ДанныеНоменклатуры>
<Номенклатура>
<Ссылка></Ссылка>
<НаименованиеПолное>Жевательная резинка RUSGUM Арбузное трио 24 мм 7*150 штук</НаименованиеПолное>
<КодВПрограмме>00-00000682</КодВПрограмме>
<Наименование>Жевательная резинка RUSGUM Арбузное трио 24 мм 7*150 штук</Наименование>
</Номенклатура>
</ДанныеНоменклатуры>
<ЕдиницаИзмерения>
<Ссылка></Ссылка>
<ДанныеКлассификатора>
<Код>796</Код>
<Наименование>шт</Наименование>
</ДанныеКлассификатора>
</ЕдиницаИзмерения>
<Количество>1</Количество>
<Сумма>3373</Сумма>
<Цена>3373</Цена>
<СтавкаНДС>
<Ставка>5</Ставка>
<РасчетнаяСтавка>false</РасчетнаяСтавка>
<НеОблагается>false</НеОблагается>
<ВидСтавки>Общая</ВидСтавки>
<Страна>
<Ссылка></Ссылка>
<ДанныеКлассификатора>
<Код>643</Код>
<Наименование>РОССИЯ</Наименование>
</ДанныеКлассификатора>
</Страна>
</СтавкаНДС>
<СуммаНДС>160.62</СуммаНДС>
<ТипЗапасов>СобственныеТовары</ТипЗапасов>
<СчетУчета>43</СчетУчета>
<СчетДоходов>90.01.1</СчетДоходов>
<СчетРасходов>90.02.1</СчетРасходов>
<СчетУчетаНДСПоРеализации>90.03</СчетУчетаНДСПоРеализации>
<ИдентификаторСтроки>09ef6d80-f310-4c63-a242-a23db0ee7a55</ИдентификаторСтроки>
<ПрослеживаемыйТовар>false</ПрослеживаемыйТовар>
</Строка>
</Товары>
<СпособПогашенияЗадолженности>Автоматически</СпособПогашенияЗадолженности>
<КомплектПечатныхДокументовПодписанПолучен>false</КомплектПечатныхДокументовПодписанПолучен>
<ЭтоУПД>false</ЭтоУПД>
</Документ.РеализацияТоваровУслуг>
</Body>
</Message>

61
test_upd.xml Normal file
View File

@ -0,0 +1,61 @@
<?xml version='1.0' encoding='UTF-8'?>
<Файл ВерсПрог="BUH_BOT_1.0" ВерсФорм="5.03">
<Документ>
<СвСчФакт>
<НомерДата Номер="4869" Дата="23.10.2025"/>
<СвПрод>
<ИдСв>
<СвИП ИННФЛ="263605160270" СвГосРегИП="">
<ФИО Фамилия="Беляев" Имя="Андрей" Отчество="Юрьевич"/>
</СвИП>
</ИдСв>
<БанкРекв НомерСчета="40802810860270001979">
<СвБанк НаимБанк="СТАВРОПОЛЬСКОЕ ОТДЕЛЕНИЕ N5230 ПАО СБЕРБАНК" БИК="040702615" КорСчет="30101810907020000615"/>
</БанкРекв>
</СвПрод>
<ГрузОт>
<ОнЖе>он же</ОнЖе>
</ГрузОт>
<ГрузПолуч>
<ИдСв>
<СвИП ИННФЛ="383700097634" СвГосРегИП="">
<ФИО Фамилия="" Имя=""/>
</СвИП>
</ИдСв>
</ГрузПолуч>
<ДокПодтвОтгрНом РеквНаимДок="УПД" РеквНомерДок="4869" РеквДатаДок="23.10.2025"/>
<СвПокуп>
<ИдСв>
<СвИП ИННФЛ="383700097634" СвГосРегИП="">
<ФИО Фамилия="" Имя=""/>
</СвИП>
</ИдСв>
</СвПокуп>
<ДенИзм КодОКВ="643" НаимОКВ="Российский рубль"/>
</СвСчФакт>
<ТаблСчФакт>
<СведТов НомСтр="1">
<НаимТов Наим="Жевательная резинка RUSGUM Арбузное трио 24 мм 7*150 штук"/>
<ОКЕИ_Тов НаимЕдИзм="шт"/>
<КолТов Количество="1"/>
<ЦенаТов Цена="3373.00"/>
<СтТовБезНДС СтоимБезНДС="3212.38"/>
<НалСт СтавНДС="5" СумНДС="160.62"/>
<СтТовУчНал СтоимУчНДС="3373.00"/>
</СведТов>
<ВсегоОпл>
<СтТовБезНДСВсего СтоимБезНДС="3212.38"/>
<СумНДСВсего СумНДС="160.62"/>
<СтТовУчНалВсего СтоимУчНДС="3373.00"/>
</ВсегоОпл>
</ТаблСчФакт>
<СвПродПер>
<СвПер СодОпер="Товары переданы" ДатаПер="23.10.2025">
<БезДокОснПер>1</БезДокОснПер>
</СвПер>
</СвПродПер>
<Подписант СпосПодтПолном="6">
<ФИО Фамилия="Беляев" Имя="Андрей" Отчество="Юрьевич"/>
</Подписант>
</Документ>
</Файл>