fix: корректная работа inline и reply кнопок после генерации документов, улучшена обработка состояний ConversationHandler, исправлен переход при повторной генерации
This commit is contained in:
parent
8e56415e68
commit
ad94c29bbd
27
CHANGELOG.md
27
CHANGELOG.md
@ -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 МойСклад
|
||||
|
||||
---
|
||||
|
||||
# Обновление бота - Поиск заказов
|
||||
|
||||
## ✅ Исправлено
|
||||
|
||||
11
README.md
11
README.md
@ -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
355
bot.py
@ -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=[
|
||||
|
||||
@ -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
195
docs/XML_1C_FORMAT.md
Normal 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) - Начальная версия с поддержкой документа реализации
|
||||
@ -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
176
test_1c_realization.xml
Normal 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
61
test_upd.xml
Normal 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">
|
||||
<ФИО Фамилия="Беляев" Имя="Андрей" Отчество="Юрьевич"/>
|
||||
</Подписант>
|
||||
</Документ>
|
||||
</Файл>
|
||||
Loading…
x
Reference in New Issue
Block a user