feat: массовая генерация документов в один XML (EnterpriseData 1.20)

This commit is contained in:
admin i 2025-11-06 13:47:52 +03:00
parent 5283bf2b85
commit 107c5a82c3
2 changed files with 794 additions and 2 deletions

208
bot.py
View File

@ -55,8 +55,9 @@ logger = logging.getLogger(__name__)
VALIDATE_ORDER,
SELECT_FORMAT,
SELECT_TEMPLATE,
DOWNLOAD_DOCUMENT
) = range(10)
DOWNLOAD_DOCUMENT,
BATCH_INPUT
) = range(11)
# Менеджер пользователей
user_manager = UserManager()
@ -87,6 +88,7 @@ def get_main_keyboard():
"""Возвращает основную клавиатуру"""
keyboard = [
["🔍 Поиск отгрузки", "📋 Список отгрузок"],
["📦 Массовая генерация"],
["❓ Помощь"]
]
return ReplyKeyboardMarkup(keyboard, resize_keyboard=True)
@ -1294,6 +1296,199 @@ async def generate_package(update: Update, context: ContextTypes.DEFAULT_TYPE):
return MAIN_MENU
async def batch_generation_prompt(
update: Update,
context: ContextTypes.DEFAULT_TYPE
):
"""Запрос номеров заказов для массовой генерации"""
await update.message.reply_text(
"📦 <b>Массовая генерация документов</b>\n\n"
"Введите номера отгрузок через запятую <b>без пробелов</b>.\n\n"
"Примеры:\n"
" <code>4926,4927,4928</code>\n"
" <code>00001,00002,00003</code>\n\n"
"⚠️ Все отгрузки будут объединены в один XML файл "
"(EnterpriseData 1.20).",
parse_mode='HTML',
reply_markup=ReplyKeyboardMarkup(
[["⬅️ Назад"]],
resize_keyboard=True
)
)
return BATCH_INPUT
async def batch_generation_process(
update: Update,
context: ContextTypes.DEFAULT_TYPE
):
"""Обработка массовой генерации"""
user_id = update.effective_user.id
text = update.message.text.strip()
# Проверка на отмену
if text == "⬅️ Назад":
await update.message.reply_text(
"Массовая генерация отменена.",
reply_markup=get_main_keyboard()
)
return MAIN_MENU
# Парсинг номеров
order_numbers = [num.strip() for num in text.split(',') if num.strip()]
if not order_numbers:
await update.message.reply_text(
"❌ Не указано ни одного номера отгрузки.\n"
"Попробуйте ещё раз:",
parse_mode='HTML'
)
return BATCH_INPUT
if len(order_numbers) > 50:
await update.message.reply_text(
"❌ Слишком много отгрузок (максимум 50).\n"
"Попробуйте ещё раз:",
parse_mode='HTML'
)
return BATCH_INPUT
# Уведомление о начале обработки
progress_msg = await update.message.reply_text(
f"⏳ Обработка {len(order_numbers)} отгрузок...\n"
f"Это может занять некоторое время."
)
try:
moysklad_client = get_moysklad_client(user_id)
# Сбор данных по всем отгрузкам
orders_data = []
not_found = []
errors = []
for idx, order_num in enumerate(order_numbers, 1):
try:
# Обновляем прогресс
await progress_msg.edit_text(
f"⏳ Обработка отгрузки {idx}/{len(order_numbers)}: {order_num}"
)
# Поиск отгрузки
search_results = moysklad_client.search_orders(
order_num,
limit=5
)
rows = search_results.get('rows', [])
if not rows:
not_found.append(order_num)
continue
# Берём первый результат
order_id = rows[0]['id']
# Получаем полные данные
order_data = moysklad_client.get_full_order_data(order_id)
orders_data.append(order_data)
except Exception as e:
logger.error(f"Ошибка обработки {order_num}: {e}")
errors.append(f"{order_num}: {str(e)[:50]}")
# Проверка результатов
if not orders_data:
error_text = "❌ <b>Не удалось обработать ни одной отгрузки</b>\n\n"
if not_found:
error_text += f"Не найдено: {', '.join(not_found)}\n"
if errors:
error_text += f"\nОшибки:\n" + "\n".join(errors[:5])
await progress_msg.edit_text(
error_text,
parse_mode='HTML'
)
await update.message.reply_text(
"Попробуйте ещё раз или вернитесь в главное меню:",
reply_markup=get_main_keyboard()
)
return MAIN_MENU
# Генерация XML
await progress_msg.edit_text(
f"✅ Найдено отгрузок: {len(orders_data)}\n"
f"⏳ Генерация XML..."
)
from xml_generator import XML1CGenerator
xml_gen = XML1CGenerator()
# Создаём временную директорию
output_dir = Path("temp_documents")
output_dir.mkdir(exist_ok=True)
# Генерируем пакетный XML
xml_filename = f"batch_{len(orders_data)}_orders.xml"
xml_path = str(output_dir / xml_filename)
xml_gen.generate_batch_realization(orders_data, xml_path)
# Формируем отчёт
order_names = [
od['order'].get('name', 'N/A')
for od in orders_data
]
caption = (
f"✅ <b>Пакетный XML создан!</b>\n\n"
f"📦 Документов: {len(orders_data)}\n"
f"📋 Отгрузки: {', '.join(order_names)}\n\n"
)
if not_found:
caption += f"⚠️ Не найдено: {', '.join(not_found)}\n"
if errors:
caption += f"⚠️ Ошибок: {len(errors)}\n"
caption += "\n📄 Формат: 1С EnterpriseData 1.20"
# Отправляем файл
await update.message.reply_document(
document=open(xml_path, 'rb'),
caption=caption,
parse_mode='HTML'
)
# Удаляем временный файл
os.remove(xml_path)
# Показываем главное меню
await update.message.reply_text(
"✅ Готово! Используйте кнопки для продолжения:",
reply_markup=get_main_keyboard()
)
await progress_msg.delete()
return MAIN_MENU
except Exception as e:
logger.error(f"Ошибка массовой генерации: {e}", exc_info=True)
await progress_msg.edit_text(
f"❌ <b>Ошибка массовой генерации</b>\n\n"
f"{str(e)}",
parse_mode='HTML'
)
await update.message.reply_text(
"Попробуйте ещё раз или вернитесь в главное меню:",
reply_markup=get_main_keyboard()
)
return MAIN_MENU
async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Обработчик текстовых сообщений"""
text = update.message.text
@ -1304,6 +1499,9 @@ async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
elif text == "📋 Список отгрузок":
return await show_orders_list(update, context)
elif text == "📦 Массовая генерация":
return await batch_generation_prompt(update, context)
elif text == "❓ Помощь":
return await help_command(update, context)
@ -1381,6 +1579,12 @@ def main():
CallbackQueryHandler(handle_callback),
MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text),
],
BATCH_INPUT: [
MessageHandler(
filters.TEXT & ~filters.COMMAND,
batch_generation_process
),
],
},
fallbacks=[
CommandHandler("start", start),

View File

@ -1115,4 +1115,592 @@ class XML1CGenerator:
logger.info(f"Документ реализации 1С сохранен: {output_path}")
return xml_string
def generate_batch_realization(
self,
orders_data: list,
output_path: Optional[str] = None
) -> str:
"""
Генерация множественных документов реализации в один XML
Args:
orders_data: Список полных данных заказов из МойСклад
output_path: Путь для сохранения файла
Returns:
XML в виде строки
"""
# Корневой элемент 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})
logger.info(f"Генерация пакета из {len(orders_data)} документов")
# Генерируем каждый документ внутри одного Body
for idx, order_data in enumerate(orders_data, 1):
logger.info(
f"Обработка документа {idx}/{len(orders_data)}: "
f"{order_data['order'].get('name', 'N/A')}"
)
# Генерируем содержимое документа (без root/header/body)
doc_xml = self._generate_single_document_content(order_data)
# Добавляем документ в Body
body.append(doc_xml)
# Конвертация в строку
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} "
f"({len(orders_data)} документов)"
)
return xml_string
def _generate_single_document_content(
self,
order_data: Dict
) -> etree.Element:
"""
Генерация содержимого одного документа (без обёртки Message)
Args:
order_data: Полные данные заказа
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", [])
# Документ.РеализацияТоваровУслуг
doc = etree.Element("Документ.РеализацияТоваровУслуг")
# КлючевыеСвойства
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", "").strip()
etree.SubElement(key_props, "Дата").text = order_date
etree.SubElement(key_props, "Номер").text = order_number
# Организация
org_elem = etree.SubElement(key_props, "Организация")
org_href = (
organization.get("meta", {}).get("href", "")
if organization.get("meta") else ""
)
org_id = (
org_href.split("/")[-1].split("?")[0]
if org_href else 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_href = (
currency.get("meta", {}).get("href", "")
if currency.get("meta") else ""
)
currency_id = (
currency_href.split("/")[-1].split("?")[0]
if currency_href else ""
)
if not currency_id:
currency_id = "72357d18-63a8-11e8-80dd-0050569f5448"
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
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", "")
else:
etree.SubElement(
org_ref, "НаименованиеСокращенное"
).text = organization.get("name", "")
etree.SubElement(
org_ref, "НаименованиеПолное"
).text = organization.get("legalTitle", "")
etree.SubElement(org_ref, "ИНН").text = organization.get("inn", "")
if company_type == "entrepreneur":
etree.SubElement(
org_ref, "ЮридическоеФизическоеЛицо"
).text = "ФизическоеЛицо"
else:
etree.SubElement(
org_ref, "ЮридическоеФизическоеЛицо"
).text = "ЮридическоеЛицо"
# Контрагент
cp_elem = etree.SubElement(doc, "Контрагент")
cp_href = (
counterparty.get("meta", {}).get("href", "")
if counterparty.get("meta") else ""
)
cp_id = (
cp_href.split("/")[-1].split("?")[0]
if cp_href else 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 = "ФизическоеЛицо"
else:
etree.SubElement(
cp_elem, "ЮридическоеФизическоеЛицо"
).text = "ЮридическоеЛицо"
country_elem = etree.SubElement(cp_elem, "СтранаРегистрации")
etree.SubElement(
country_elem, "Ссылка"
).text = "67700c1b-63a8-11e8-80dd-0050569f5448"
country_data = etree.SubElement(
country_elem, "ДанныеКлассификатора"
)
etree.SubElement(country_data, "Код").text = "643"
etree.SubElement(country_data, "Наименование").text = "РОССИЯ"
if cp_type == "entrepreneur":
etree.SubElement(
cp_elem, "ИндивидуальныйПредприниматель"
).text = "true"
# ДанныеВзаиморасчетов
settlement_elem = etree.SubElement(doc, "ДанныеВзаиморасчетов")
# Договор
contract_elem = etree.SubElement(settlement_elem, "Договор")
import uuid
currency_code = currency.get("isoCode", "643")
is_ue = "false"
seed = f"contract-{cp_id}-{org_id}-CUR-{currency_code}-UE-{is_ue}"
contract_uuid = str(uuid.uuid5(uuid.NAMESPACE_DNS, seed))
etree.SubElement(contract_elem, "Ссылка").text = contract_uuid
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", "")
else:
etree.SubElement(
contract_org, "НаименованиеСокращенное"
).text = organization.get("name", "")
etree.SubElement(
contract_org, "НаименованиеПолное"
).text = organization.get("legalTitle", "")
etree.SubElement(
contract_org, "ИНН"
).text = organization.get("inn", "")
if company_type == "entrepreneur":
etree.SubElement(
contract_org, "ЮридическоеФизическоеЛицо"
).text = "ФизическоеЛицо"
else:
etree.SubElement(
contract_org, "ЮридическоеФизическоеЛицо"
).text = "ЮридическоеЛицо"
# Контрагент в договоре
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 = "ФизическоеЛицо"
else:
etree.SubElement(
contract_cp, "ЮридическоеФизическоеЛицо"
).text = "ЮридическоеЛицо"
cp_country = etree.SubElement(contract_cp, "СтранаРегистрации")
etree.SubElement(
cp_country, "Ссылка"
).text = "67700c1b-63a8-11e8-80dd-0050569f5448"
cp_country_data = etree.SubElement(
cp_country, "ДанныеКлассификатора"
)
etree.SubElement(cp_country_data, "Код").text = "643"
etree.SubElement(cp_country_data, "Наименование").text = "РОССИЯ"
if cp_type == "entrepreneur":
etree.SubElement(
contract_cp, "ИндивидуальныйПредприниматель"
).text = "true"
# Валюта взаиморасчетов в договоре
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 = "ПродажаОблагаетсяНДС"
# Товары
goods_elem = etree.SubElement(doc, "Товары")
for pos in positions:
row_elem = etree.SubElement(goods_elem, "Строка")
assortment = pos.get("assortment", {})
product_href = (
assortment.get("meta", {}).get("href", "")
if assortment.get("meta") else ""
)
product_id = (
product_href.split("/")[-1].split("?")[0]
if product_href 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_href = (
uom.get("meta", {}).get("href", "")
if uom and uom.get("meta") else ""
)
uom_id = (
uom_href.split("/")[-1].split("?")[0]
if uom_href else ""
)
if not uom_id:
uom_id = "cdf130e2-63a8-11e8-80dd-0050569f5448"
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 = f"{total:.2f}"
etree.SubElement(row_elem, "Цена").text = f"{price:.2f}"
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 = "67700c1b-63a8-11e8-80dd-0050569f5448"
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 = str(uuid.uuid4())
etree.SubElement(
row_elem, "ПрослеживаемыйТовар"
).text = "false"
# СпособПогашенияЗадолженности
etree.SubElement(
doc, "СпособПогашенияЗадолженности"
).text = "Автоматически"
# КомплектПечатныхДокументовПодписанПолучен
etree.SubElement(
doc, "КомплектПечатныхДокументовПодписанПолучен"
).text = "false"
# ЭтоУПД
etree.SubElement(doc, "ЭтоУПД").text = "false"
return doc