feat: массовая генерация документов в один XML (EnterpriseData 1.20)
This commit is contained in:
parent
5283bf2b85
commit
107c5a82c3
208
bot.py
208
bot.py
@ -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),
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user