- Enhanced validator with critical zero-price validation - Fixed currency GUIDs and money formatting in XML generator - Removed incompatible VidED field - Added comprehensive documentation - Updated test files
396 lines
18 KiB
Markdown
396 lines
18 KiB
Markdown
# Анализ генератора XML EnterpriseData 1.20
|
||
|
||
## Дата анализа: 6 ноября 2025 г.
|
||
|
||
## Проверенные проблемы и их статус
|
||
|
||
### ❌ КРИТИЧНО: 1. Жёстко прошитые GUID справочников
|
||
|
||
**Местоположение:**
|
||
- Строки 773-776 (валюта RUB)
|
||
- Строки 878, 1027 (страна Россия)
|
||
- Строки 990-994 (единица измерения "шт")
|
||
|
||
**Проблема:**
|
||
```python
|
||
# Валюта - жёстко прошитый GUID
|
||
if not currency_id:
|
||
currency_id = "72357d18-63a8-11e8-80dd-0050569f5448" # RUB
|
||
|
||
# Страна - жёстко прошитый GUID
|
||
etree.SubElement(country_elem, "Ссылка").text = "67700c1b-63a8-11e8-80dd-0050569f5448"
|
||
|
||
# Единица измерения - жёстко прошитый GUID
|
||
if not uom_id:
|
||
uom_id = "cdf130e2-63a8-11e8-80dd-0050569f5448" # шт (796)
|
||
```
|
||
|
||
**Почему это критично:**
|
||
- GUID справочников уникальны для каждой базы 1С
|
||
- В другой базе эти GUID не существуют
|
||
- Импорт завершится ошибкой "Объект не найден"
|
||
|
||
**Рекомендация:**
|
||
```python
|
||
# ПРАВИЛЬНО: Передавать только код и наименование, без Ссылки
|
||
# Либо позволить 1С создать объект автоматически
|
||
<Валюта>
|
||
<!-- Убрать <Ссылка> если объект не существует -->
|
||
<ДанныеКлассификатора>
|
||
<Код>643</Код>
|
||
<НаименованиеПолное>Российский рубль</НаименованиеПолное>
|
||
<КодЦифровой>643</КодЦифровой>
|
||
<КодСимвольный>RUB</КодСимвольный>
|
||
</ДанныеКлассификатора>
|
||
</Валюта>
|
||
```
|
||
|
||
**Решение:**
|
||
1. Либо получать GUID из конфигурации целевой базы
|
||
2. Либо использовать флаг "создавать отсутствующие объекты"
|
||
3. Либо убрать `<Ссылка>` и передавать только классификаторы
|
||
|
||
---
|
||
|
||
### ⚠️ КРИТИЧНО: 2. Структура валюты/банков не соответствует XSD
|
||
|
||
**Местоположение:** Строки 767-780
|
||
|
||
**Проблема:**
|
||
```python
|
||
currency_elem = etree.SubElement(doc, "Валюта")
|
||
etree.SubElement(currency_elem, "Ссылка").text = currency_id
|
||
currency_data = etree.SubElement(currency_elem, "ДанныеКлассификатора")
|
||
etree.SubElement(currency_data, "Код").text = currency.get("isoCode", "643")
|
||
```
|
||
|
||
**Почему это проблема:**
|
||
- Структура с вложенным `ДанныеКлассификатора` может не соответствовать XSD 1.20
|
||
- В типовых схемах ED часто используется плоская структура: `<Валюта><Ссылка/><Код/><Наименование/></Валюта>`
|
||
|
||
**Рекомендация:**
|
||
- **ОБЯЗАТЕЛЬНО** сверить структуру с XSD принимающей системы
|
||
- Возможно, нужна плоская структура без `ДанныеКлассификатора`
|
||
|
||
**TODO:**
|
||
- [ ] Получить актуальный XSD от клиента
|
||
- [ ] Проверить версию EnterpriseData в целевой 1С
|
||
- [ ] Адаптировать структуру под реальную схему
|
||
|
||
---
|
||
|
||
### ❌ КРИТИЧНО: 3. Неверные ссылки на банк/счёт
|
||
|
||
**Местоположение:** Строки 825-838
|
||
|
||
**Проблема:**
|
||
```python
|
||
bank_acc_elem = etree.SubElement(doc, "БанковскийСчетОрганизации")
|
||
acc_id = acc.get("id", "")
|
||
etree.SubElement(bank_acc_elem, "Ссылка").text = acc_id # ID счёта
|
||
|
||
# Банк
|
||
bank_elem = etree.SubElement(bank_acc_elem, "Банк")
|
||
etree.SubElement(bank_elem, "Ссылка").text = acc_id # ❌ ОШИБКА: используем тот же ID
|
||
```
|
||
|
||
**Почему это критично:**
|
||
- Для банка должна быть ссылка на объект справочника "Банки" (другой GUID)
|
||
- Для счёта - ссылка на объект справочника "БанковскиеСчета"
|
||
- Использование одного ID для обоих объектов неверно
|
||
|
||
**Рекомендация:**
|
||
```python
|
||
# ПРАВИЛЬНО: Разные ID для банка и счёта
|
||
bank_elem = etree.SubElement(bank_acc_elem, "Банк")
|
||
bank_id = acc.get("bank_id") or generate_bank_id_by_bic(acc.get("bic"))
|
||
etree.SubElement(bank_elem, "Ссылка").text = bank_id
|
||
|
||
# Или передавать без Ссылки, только классификатор
|
||
<Банк>
|
||
<ДанныеКлассификатораБанков>
|
||
<Наименование>Сбербанк</Наименование>
|
||
<БИК>044525225</БИК>
|
||
<КоррСчет>30101810400000000225</КоррСчет>
|
||
</ДанныеКлассификатораБанков>
|
||
</Банк>
|
||
```
|
||
|
||
**Решение:**
|
||
- Убрать `<Ссылка>` для банка, передавать только БИК и наименование
|
||
- 1С сама найдёт/создаст банк по БИК
|
||
|
||
---
|
||
|
||
### ⚠️ СРЕДНЕ: 4. Договор внутри "ДанныеВзаиморасчетов"
|
||
|
||
**Местоположение:** Строки 887-922
|
||
|
||
**Проблема:**
|
||
```python
|
||
contract_elem = etree.SubElement(settlement_elem, "Договор")
|
||
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 = "СПокупателем"
|
||
# Далее вложенные Организация, Контрагент...
|
||
```
|
||
|
||
**Почему это проблема:**
|
||
- Детальная структура договора (с Организацией/Контрагентом внутри) может не соответствовать XSD
|
||
- В ED обычно ссылка на договор - это просто `<Ссылка>` на существующий объект
|
||
- Полное описание договора должно быть отдельной сущностью в Message
|
||
|
||
**Рекомендация:**
|
||
```xml
|
||
<!-- Вариант 1: Только ссылка (если договор уже есть) -->
|
||
<Договор>
|
||
<Ссылка>uuid-договора</Ссылка>
|
||
</Договор>
|
||
|
||
<!-- Вариант 2: Полный объект в отдельном блоке Message -->
|
||
<Message>
|
||
<Body>
|
||
<Справочник.ДоговорыКонтрагентов>
|
||
<Ссылка>uuid</Ссылка>
|
||
<ВидДоговора>СПокупателем</ВидДоговора>
|
||
<!-- ... -->
|
||
</Справочник.ДоговорыКонтрагентов>
|
||
|
||
<Документ.РеализацияТоваровУслуг>
|
||
<Договор>
|
||
<Ссылка>uuid</Ссылка> <!-- Ссылка на объект выше -->
|
||
</Договор>
|
||
</Документ.РеализацияТоваровУслуг>
|
||
</Body>
|
||
</Message>
|
||
```
|
||
|
||
**TODO:**
|
||
- [ ] Уточнить у клиента: договоры уже есть в базе или нужно создавать?
|
||
- [ ] Если создавать - вынести договор в отдельный блок Message
|
||
- [ ] Если использовать существующие - передавать только Ссылка
|
||
|
||
---
|
||
|
||
### ❌ КРИТИЧНО: 5. Потеря копеек
|
||
|
||
**Местоположение:** Строки 1012-1014
|
||
|
||
**Проблема:**
|
||
```python
|
||
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)) # ❌ Отбрасываем копейки
|
||
```
|
||
|
||
**Почему это критично:**
|
||
- Округление вниз приводит к расхождению сумм
|
||
- НДС рассчитывается неверно
|
||
- Контрольные суммы не сходятся
|
||
- Невозможно сопоставить с УПД/счётом-фактурой
|
||
|
||
**Пример:**
|
||
```
|
||
Цена: 1234.56 руб → становится 1234 руб (потеря 56 коп)
|
||
Количество: 100 шт
|
||
Сумма должна быть: 123456.00 руб
|
||
Сумма получается: 123400 руб (потеря 56 руб!)
|
||
```
|
||
|
||
**Рекомендация:**
|
||
```python
|
||
# ПРАВИЛЬНО: Сохранять копейки
|
||
price = pos.get("price", 0) / 100
|
||
total = price * quantity
|
||
|
||
etree.SubElement(row_elem, "Сумма").text = f"{total:.2f}" # 2 знака после запятой
|
||
etree.SubElement(row_elem, "Цена").text = f"{price:.2f}" # 2 знака после запятой
|
||
```
|
||
|
||
**Решение:**
|
||
- Использовать `Decimal` для денежных расчётов
|
||
- Форматировать с 2 знаками: `f"{amount:.2f}"`
|
||
- НЕ использовать `int()` для денежных сумм
|
||
|
||
---
|
||
|
||
### ⚠️ СРЕДНЕ: 6. Лишние поля в строке товара
|
||
|
||
**Местоположение:** Строки 1036-1039
|
||
|
||
**Проблема:**
|
||
```python
|
||
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"
|
||
```
|
||
|
||
**Почему это проблема:**
|
||
- Эти поля есть не во всех версиях ED
|
||
- Не во всех прикладных решениях 1С используются эти поля
|
||
- Если их нет в XSD - валидация провалится
|
||
|
||
**Рекомендация:**
|
||
- Сделать эти поля опциональными (через настройку)
|
||
- Или убрать совсем - 1С подставит по умолчанию
|
||
- Или уточнить у клиента, нужны ли эти поля
|
||
|
||
**TODO:**
|
||
- [ ] Проверить XSD целевой системы
|
||
- [ ] Добавить флаг `include_accounting_fields` в конфиг
|
||
- [ ] По умолчанию отключить
|
||
|
||
---
|
||
|
||
### ⚠️ СРЕДНЕ: 7. ВидЭД внутри ED-документа
|
||
|
||
**Местоположение:** Строки 967-968
|
||
|
||
**Проблема:**
|
||
```python
|
||
# ВидЭД
|
||
etree.SubElement(doc, "ВидЭД").text = "ТОРГ12"
|
||
```
|
||
|
||
**Почему это проблема:**
|
||
- Поле `ВидЭД` со значением "ТОРГ12" не входит в типовую схему ED 1.20
|
||
- Это поле используется в УПД/ФормализованныхДокументах, но не в EnterpriseData
|
||
- При валидации по XSD произойдёт ошибка
|
||
|
||
**Рекомендация:**
|
||
- Убрать это поле из документа ED
|
||
- Если нужно указать тип документа - использовать корректное поле из XSD
|
||
- Возможно, это поле относится к другому формату (УПД)
|
||
|
||
**Решение:**
|
||
```python
|
||
# УБРАТЬ эту строку
|
||
# etree.SubElement(doc, "ВидЭД").text = "ТОРГ12"
|
||
```
|
||
|
||
---
|
||
|
||
### ⚠️ НИЗКО: 8. Резервные заглушки дат
|
||
|
||
**Местоположение:** Строки 648-657
|
||
|
||
**Проблема:**
|
||
```python
|
||
def _format_datetime(self, date_str: str) -> str:
|
||
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") # ❌ Подставляем текущую дату
|
||
```
|
||
|
||
**Почему это проблема:**
|
||
- Если дата не парсится - подставляется текущая
|
||
- Для первичных документов это искажение данных
|
||
- Риск налоговых проблем (неверная дата = неверный период)
|
||
|
||
**Рекомендация:**
|
||
```python
|
||
def _format_datetime(self, date_str: str) -> str:
|
||
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"Критическая ошибка: некорректная дата '{date_str}': {e}")
|
||
raise ValueError(f"Невозможно сформировать документ с некорректной датой: {date_str}")
|
||
```
|
||
|
||
**Решение:**
|
||
- Поднимать исключение при ошибке парсинга даты
|
||
- НЕ подставлять резервные значения для критичных полей
|
||
- Блокировать генерацию XML с некорректными данными
|
||
|
||
---
|
||
|
||
## Приоритеты исправления
|
||
|
||
### 🔴 СРОЧНО (блокируют импорт в 1С):
|
||
1. ✅ **Потеря копеек** - исправить форматирование денежных сумм
|
||
2. ⚠️ **GUID справочников** - убрать жёсткие UUID или получать из базы
|
||
3. ⚠️ **Банк/счёт** - исправить структуру ссылок
|
||
4. ✅ **ВидЭД** - убрать поле
|
||
|
||
### 🟡 ВАЖНО (могут вызвать проблемы):
|
||
5. ⚠️ **Структура валюты** - сверить с XSD клиента
|
||
6. ⚠️ **Договор** - упростить или вынести в отдельный блок
|
||
7. ⚠️ **Счета учёта** - сделать опциональными
|
||
|
||
### 🟢 ЖЕЛАТЕЛЬНО (улучшение качества):
|
||
8. ✅ **Заглушки дат** - поднимать исключения вместо fallback
|
||
|
||
---
|
||
|
||
## Рекомендуемый план действий
|
||
|
||
### Этап 1: Критичные исправления (сегодня)
|
||
- [ ] Исправить форматирование денежных сумм (добавить `.2f`)
|
||
- [ ] Убрать поле `ВидЭД`
|
||
- [ ] Заменить fallback даты на исключение
|
||
|
||
### Этап 2: Проверка со стороны клиента (завтра)
|
||
- [ ] Получить актуальный XSD EnterpriseData 1.20 от клиента
|
||
- [ ] Проверить структуру валюты/банков/договоров в реальной схеме
|
||
- [ ] Уточнить: нужны ли счета учёта в строках?
|
||
|
||
### Этап 3: Адаптация под реальную схему (2-3 дня)
|
||
- [ ] Убрать жёсткие GUID, использовать только классификаторы
|
||
- [ ] Исправить структуру банка (убрать Ссылку, оставить БИК)
|
||
- [ ] Упростить договор или вынести в отдельный блок
|
||
- [ ] Сделать счета учёта опциональными
|
||
|
||
### Этап 4: Тестирование (1 день)
|
||
- [ ] Тестовый импорт в реальную 1С клиента
|
||
- [ ] Проверка контрольных сумм
|
||
- [ ] Проверка НДС
|
||
- [ ] Сопоставление с УПД/счетами-фактурами
|
||
|
||
---
|
||
|
||
## Скептическая оценка анализа ИИ
|
||
|
||
### ✅ Что верно на 100%:
|
||
- Потеря копеек - **КРИТИЧНО**, будет ошибка в суммах
|
||
- GUID справочников - **КРИТИЧНО**, не будет работать в другой базе
|
||
- ВидЭД - **верно**, этого поля нет в ED
|
||
- Заглушки дат - **верно**, риск искажения данных
|
||
|
||
### ⚠️ Что нужно проверить:
|
||
- Структура валюты - **зависит от версии XSD** (нужен реальный XSD)
|
||
- Банк/счёт - **вероятно верно**, но нужна проверка по документации
|
||
- Договор - **может быть по-разному** в разных версиях 1С
|
||
- Счета учёта - **зависит от конфигурации** 1С клиента
|
||
|
||
### 📋 Что точно нужно сделать:
|
||
1. Получить от клиента XSD EnterpriseData 1.20 их версии
|
||
2. Протестировать импорт в реальной базе
|
||
3. Исправить копейки и ВидЭД (это точно ошибки)
|
||
4. Остальное - после проверки по реальной схеме
|
||
|
||
---
|
||
|
||
## Вывод
|
||
|
||
**Генератор работоспособен**, но имеет **критичные проблемы** для продакшена:
|
||
- ✅ Структура документа в целом корректна
|
||
- ❌ Денежные суммы теряют копейки
|
||
- ⚠️ Жёсткие GUID не будут работать в чужой базе
|
||
- ⚠️ Структура может не совпадать с XSD клиента
|
||
|
||
**Рекомендация:**
|
||
1. Срочно исправить копейки и убрать ВидЭД
|
||
2. Получить XSD от клиента
|
||
3. Протестировать в реальной базе
|
||
4. Доработать под реальную схему
|
||
|
||
**Риск:** Без тестирования в реальной 1С клиента есть **высокая вероятность** ошибок импорта.
|