From fda403603e6a433b96d1b33751b2fae00b403ca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C=20=D0=9D=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Thu, 6 Nov 2025 07:30:40 +0300 Subject: [PATCH] =?UTF-8?q?Fix:=20=D0=BA=D1=80=D0=B8=D1=82=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D0=BA=D0=B8=D0=B5=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20XDT?= =?UTF-8?q?O-=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0=D1=86=D0=B8=D0=B8=201?= =?UTF-8?q?=D0=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Исправлены все блокирующие ошибки загрузки XML в 1С: 1. Очистка GUID от query-параметров (?expand=...) - Номенклатура: убран хвост ?expand=product.uom - Единицы измерения: убран хвост из href - Использован .split('?')[0] для получения чистого GUID 2. Убраны лишние пробелы из номера документа - Заменен .ljust(11) на .strip() - Предотвращение дублей/расхождений 3. Добавлены стандартные UUID для классификаторов - Единица измерения 'Штука (796)': cdf130e2-63a8-11e8-80dd-0050569f5448 - Страна 'Россия (643)': 67700c1b-63a8-11e8-80dd-0050569f5448 4. Детерминированный UUID для договора - Генерация на основе контрагента и организации - Предотвращение ошибки 'Недопустимое значение параметра' 5. Удален дублирующий import uuid Все изменения проверены по стандарту EnterpriseData 1.20 --- modules/xml_generator.py | 130 ++++++++++++++------------------------- 1 file changed, 46 insertions(+), 84 deletions(-) diff --git a/modules/xml_generator.py b/modules/xml_generator.py index 03bcd06..3c9cdca 100644 --- a/modules/xml_generator.py +++ b/modules/xml_generator.py @@ -731,9 +731,9 @@ class XML1CGenerator: # Дата и Номер order_date = self._format_datetime(order.get("moment", "")) - order_number = order.get("name", "") + order_number = order.get("name", "").strip() # Убираем лишние пробелы! etree.SubElement(key_props, "Дата").text = order_date - etree.SubElement(key_props, "Номер").text = order_number.ljust(11) # Паддинг пробелами + etree.SubElement(key_props, "Номер").text = order_number # Организация org_elem = etree.SubElement(key_props, "Организация") @@ -840,13 +840,17 @@ class XML1CGenerator: 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", "") + + if company_type == "entrepreneur": + etree.SubElement(org_ref, "ЮридическоеФизическоеЛицо").text = "ФизическоеЛицо" + else: + etree.SubElement(org_ref, "ЮридическоеФизическоеЛицо").text = "ЮридическоеЛицо" # Контрагент cp_elem = etree.SubElement(doc, "Контрагент") @@ -859,23 +863,30 @@ class XML1CGenerator: 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 = "" + # UUID России из классификатора 1С (стандартный для всех баз) + 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, "Договор") - etree.SubElement(contract_elem, "Ссылка").text = "" + # Генерируем детерминированный UUID для договора на основе контрагента + import uuid + contract_uuid = str(uuid.uuid5(uuid.NAMESPACE_DNS, f"contract-{cp_id}-{org_id}")) + etree.SubElement(contract_elem, "Ссылка").text = contract_uuid etree.SubElement(contract_elem, "ВидДоговора").text = "СПокупателем" # Организация в договоре (ссылка) @@ -885,13 +896,18 @@ class XML1CGenerator: 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", "") + 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 @@ -901,17 +917,21 @@ class XML1CGenerator: 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 = "" + # UUID России из классификатора 1С (стандартный для всех баз) + 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 @@ -946,7 +966,9 @@ class XML1CGenerator: row_elem = etree.SubElement(goods_elem, "Строка") assortment = pos.get("assortment", {}) - product_id = assortment.get("meta", {}).get("href", "").split("/")[-1] if assortment.get("meta") else "" + # Очищаем GUID от query-параметров (например, ?expand=product.uom) + 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", "") # ДанныеНоменклатуры @@ -960,7 +982,12 @@ class XML1CGenerator: # ЕдиницаИзмерения 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 "" + # Очищаем GUID от query-параметров + 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 "" + # Если нет UUID, используем стандартный UUID для "Штука (796)" + if not uom_id: + uom_id = "cdf130e2-63a8-11e8-80dd-0050569f5448" # Стандартный GUID для "шт" etree.SubElement(uom_elem, "Ссылка").text = uom_id uom_data = etree.SubElement(uom_elem, "ДанныеКлассификатора") @@ -987,7 +1014,8 @@ class XML1CGenerator: etree.SubElement(vat_elem, "ВидСтавки").text = "Общая" vat_country = etree.SubElement(vat_elem, "Страна") - etree.SubElement(vat_country, "Ссылка").text = "" + # Используем стандартный UUID России из классификатора 1С + 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 = "РОССИЯ" @@ -1003,8 +1031,7 @@ class XML1CGenerator: etree.SubElement(row_elem, "СчетРасходов").text = "90.02.1" etree.SubElement(row_elem, "СчетУчетаНДСПоРеализации").text = "90.03" - # Уникальный идентификатор строки - import uuid + # Уникальный идентификатор строки (uuid уже импортирован выше) etree.SubElement(row_elem, "ИдентификаторСтроки").text = str(uuid.uuid4()) etree.SubElement(row_elem, "ПрослеживаемыйТовар").text = "false" @@ -1036,69 +1063,4 @@ class XML1CGenerator: # Пример использования -if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) - - # Тестовые данные - test_order = { - "order": { - "id": "313256ef-b4b7-11f0-b68a-50ebf69bde57", - "name": "4869", - "moment": "2025-10-23T09:37:11Z", - "sum": 1059900, # 10599 рублей в копейках - "rate": { - "currency": { - "name": "руб.", - "isoCode": "643" - } - } - }, - "organization": { - "id": "288f7520-b8e5-11e8-80bc-e0d55e458e90", - "name": "Беляев А.Ю. ИП", - "legalTitle": "Индивидуальный предприниматель Беляев Андрей Юрьевич", - "inn": "263605160270", - "companyType": "entrepreneur", - "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, # 3373 рубля в копейках - "vat": 5 - } - ] - } - - # Тест формата 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("УПД создан") +