fix: корректный договор по валюте и адреса контрагентов
This commit is contained in:
parent
a16ffca725
commit
5283bf2b85
22
CHANGELOG.md
22
CHANGELOG.md
@ -1,5 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
## [2025-11-06] - Исправление: GUID договора и выбор счетов 62.xx в 1С
|
||||
|
||||
### Fixed
|
||||
- 🐛 **Критическое исправление**: GUID договора теперь формируется с учётом валюты и признака у.е.
|
||||
- 📊 **Выбор счетов в 1С**: Рублёвые документы теперь корректно попадают на счета 62.01/62.02 вместо 62.21/62.22
|
||||
- 🔍 Добавлено диагностическое логирование для выявления проблем с валютой и счетами
|
||||
|
||||
### Technical Details
|
||||
- **Проблема**: Генератор создавал один GUID договора для всех валют → при наличии старого валютного договора рублёвые документы попадали на валютные счета 62.21/62.22
|
||||
- **Решение**: GUID договора теперь включает валюту и признак у.е.: `contract-{cp_id}-{org_id}-CUR-{currency_code}-UE-{is_ue}`
|
||||
- **Результат**:
|
||||
- RUB + не у.е. → отдельный договор → счета **62.01** (задолженность) / **62.02** (авансы)
|
||||
- Иностранная валюта → отдельный договор → счета **62.21** / **62.22**
|
||||
- Условные единицы → отдельный договор → счета **62.31** / **62.32**
|
||||
|
||||
### Documentation
|
||||
- 📖 Создана подробная документация: `docs/CONTRACT_UUID_FIX.md`
|
||||
- ✅ Инструкции по проверке и исправлению существующих данных
|
||||
- 🔗 Ссылки на типовую документацию 1С
|
||||
|
||||
---
|
||||
|
||||
## [2025-11-05] - Формат 1С EnterpriseData 1.20
|
||||
|
||||
### Added
|
||||
|
||||
146
docs/CONTRACT_UUID_FIX.md
Normal file
146
docs/CONTRACT_UUID_FIX.md
Normal file
@ -0,0 +1,146 @@
|
||||
# Исправление: GUID договора и выбор счетов 62 в 1С
|
||||
|
||||
## Проблема
|
||||
|
||||
### Симптомы
|
||||
При импорте рублёвого документа (валюта RUB, код 643) в 1С:Бухгалтерия 8, система подставляла счета **62.21 (Расчёты в валюте)** / **62.22 (Авансы в валюте)** вместо ожидаемых **62.01 (Задолженность)** / **62.02 (Авансы)**.
|
||||
|
||||
### Причина
|
||||
В генераторе ED-XML использовался детерминированный GUID договора, созданный **только на основе пары (контрагент, организация)**:
|
||||
|
||||
```python
|
||||
# БЫЛО (неправильно):
|
||||
contract_uuid = uuid.uuid5(uuid.NAMESPACE_DNS, f"contract-{cp_id}-{org_id}")
|
||||
```
|
||||
|
||||
Это означало «один договор на пару контрагент-организация» **независимо от валюты расчётов**.
|
||||
|
||||
### Механизм в 1С
|
||||
В типовой конфигурации «1С:Бухгалтерия 8» выбор счетов учёта расчётов с покупателями (62.xx) определяется регистром **«Счета расчетов с контрагентами»** на основании:
|
||||
|
||||
1. **Договора** (точнее - его валюты взаиморасчётов)
|
||||
2. **Вида расчётов** (задолженность/аванс)
|
||||
|
||||
Логика подстановки счетов:
|
||||
- **RUB + не в у.е.** → **62.01** (задолженность) / **62.02** (авансы)
|
||||
- **Иностранная валюта** → **62.21** (валютные расчёты) / **62.22** (авансы в валюте)
|
||||
- **Условные единицы** → **62.31** (расчёты в у.е.) / **62.32** (авансы в у.е.)
|
||||
|
||||
### Сценарий проблемы
|
||||
1. Ранее в базу был импортирован документ **в валюте** (например, USD)
|
||||
2. Для него создался договор с GUID `abc123...` (на основе пары контрагент-организация)
|
||||
3. Этот договор записался в регистр с валютой USD → счета 62.21/62.22
|
||||
4. Позже импортируется документ **в рублях** (RUB)
|
||||
5. Генератор создаёт **тот же GUID** `abc123...` (та же пара контрагент-организация!)
|
||||
6. 1С находит **существующий договор** с валютой USD
|
||||
7. Регистр подставляет счета **62.21/62.22** (как для валютного договора)
|
||||
8. ❌ Документ рублёвый, но счета валютные!
|
||||
|
||||
## Решение
|
||||
|
||||
### 1. Исправление генератора GUID договора
|
||||
|
||||
Включаем в seed для `uuid5` **валюту и признак условных единиц**:
|
||||
|
||||
```python
|
||||
# СТАЛО (правильно):
|
||||
currency_code = currency.get("isoCode", "643")
|
||||
is_ue = "false" # В текущей реализации всегда false
|
||||
seed = f"contract-{cp_id}-{org_id}-CUR-{currency_code}-UE-{is_ue}"
|
||||
contract_uuid = str(uuid.uuid5(uuid.NAMESPACE_DNS, seed))
|
||||
```
|
||||
|
||||
Теперь для каждой комбинации (контрагент, организация, **валюта**, **признак у.е.**) создаётся **отдельный договор** с уникальным GUID.
|
||||
|
||||
### 2. Добавление диагностического логирования
|
||||
|
||||
В конце метода `generate_realization()` добавлена проверка:
|
||||
|
||||
```python
|
||||
if currency_code == "643" or currency_code == "RUB":
|
||||
logger.info(
|
||||
"✓ Документ в рублях (RUB, код 643), не в у.е. "
|
||||
"→ ожидаются счета 62.01 / 62.02. "
|
||||
"Если 1С подставляет 62.21/62.22 или 62.31/62.32, проверьте: "
|
||||
"1) Регистр 'Счета расчетов с контрагентами'. "
|
||||
"2) В карточке договора: Валюта = RUB, У.Е. = нет."
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
f"⚠ Документ в иностранной валюте ({currency_code}) "
|
||||
"→ ожидаются счета 62.21/62.22."
|
||||
)
|
||||
```
|
||||
|
||||
## Проверка после исправления
|
||||
|
||||
### 1. Проверьте регистр «Счета расчетов с контрагентами»
|
||||
|
||||
В 1С:
|
||||
1. Откройте **«Контрагенты»** → список контрагентов
|
||||
2. Найдите проблемного контрагента
|
||||
3. Перейдите по гиперссылке **«Счета расчетов с контрагентами»**
|
||||
4. Проверьте, нет ли индивидуальных правил с 62.21/62.22
|
||||
5. Если есть - удалите или исправьте на 62.01/62.02
|
||||
|
||||
### 2. Проверьте карточку договора
|
||||
|
||||
После импорта нового документа (с обновлённым генератором):
|
||||
1. Откройте **«Договоры контрагентов»**
|
||||
2. Найдите договор (по имени контрагента + дата создания)
|
||||
3. Проверьте:
|
||||
- **Валюта взаиморасчётов** = `RUB` (Российский рубль)
|
||||
- **Расчёты в условных единицах** = `нет` (галочка снята)
|
||||
|
||||
### 3. Проверьте подстановку счетов
|
||||
|
||||
Создайте новый документ реализации:
|
||||
1. Импортируйте тестовый заказ в рублях
|
||||
2. Откройте созданный документ в 1С
|
||||
3. Перейдите на вкладку **«Счета учёта»**
|
||||
4. Проверьте:
|
||||
- Счёт учёта расчётов с покупателем = **62.01**
|
||||
- Счёт учёта авансов = **62.02** (если есть)
|
||||
|
||||
## Дополнительные замечания
|
||||
|
||||
### Миграция существующих данных
|
||||
|
||||
Если в базе уже есть документы с неправильными счетами:
|
||||
|
||||
1. **Вариант 1: Пересоздать договоры**
|
||||
- Удалите старые договоры (или переименуйте)
|
||||
- Импортируйте документы заново
|
||||
- Новый генератор создаст правильные договоры
|
||||
|
||||
2. **Вариант 2: Исправить вручную**
|
||||
- Откройте карточку существующего договора
|
||||
- Измените **«Валюта взаиморасчётов»** на RUB
|
||||
- Убедитесь, что **«Расчёты в условных единицах»** = нет
|
||||
- Проведите документы заново
|
||||
|
||||
### Поддержка условных единиц (будущее)
|
||||
|
||||
Если в будущем потребуется поддержка расчётов в у.е.:
|
||||
1. Добавьте в API МойСклад передачу признака у.е.
|
||||
2. Измените строку в генераторе:
|
||||
```python
|
||||
is_ue = "true" if order.get("useConventionalUnits") else "false"
|
||||
```
|
||||
3. В ED-XML добавьте:
|
||||
```xml
|
||||
<РасчетыВУсловныхЕдиницах>true</РасчетыВУсловныхЕдиницах>
|
||||
```
|
||||
|
||||
## Ссылки
|
||||
|
||||
1. [Расчеты с контрагентами в 1С:Бухгалтерии 8](https://buh.ru/articles/raschety-s-kontragentami-v-1s-bukhgalterii-8.html)
|
||||
2. [Взаиморасчёты в валюте, оплата в рублях (1С:ERP)](https://sitec-it.ru/blog/1c-erp/vzaimoraschety-v-valyute-oplata-v-rublyakh-oformlenie-realizatsii-v-1s-erp/)
|
||||
3. [Формат обмена EnterpriseData 1.20](https://buh.ru/news/samoe-novoe-v-1s-bukhgalterii-8-format-obmena-enterprisedata-1-20-s-drugimi-programmami.html)
|
||||
|
||||
## История изменений
|
||||
|
||||
### 2025-11-06
|
||||
- **Исправление**: Добавлена валюта и признак у.е. в seed для GUID договора
|
||||
- **Улучшение**: Добавлено диагностическое логирование для RUB/валюта
|
||||
- **Документация**: Создан файл CONTRACT_UUID_FIX.md
|
||||
@ -441,6 +441,12 @@ class MoySkladClient:
|
||||
elif "meta" in consignee_data:
|
||||
consignee_id = consignee_data["meta"]["href"].split("/")[-1]
|
||||
result["consignee"] = self.get_counterparty(consignee_id)
|
||||
|
||||
# Добавляем адрес доставки из заказа, если он указан
|
||||
if result.get("consignee") and "shipmentAddress" in order:
|
||||
shipment_addr = order["shipmentAddress"]
|
||||
if shipment_addr:
|
||||
result["consignee"]["deliveryAddress"] = shipment_addr
|
||||
|
||||
# Позиции отгрузки
|
||||
result["positions"] = self.get_order_positions(order_id)
|
||||
|
||||
@ -269,12 +269,23 @@ class OrderValidator:
|
||||
|
||||
# ИНН
|
||||
inn = agent.get("inn")
|
||||
company_type = agent.get("companyType", "legal")
|
||||
|
||||
if not inn:
|
||||
results.append(ValidationResult(
|
||||
level=ValidationLevel.CRITICAL,
|
||||
field="counterparty.inn",
|
||||
message="Не указан ИНН контрагента"
|
||||
))
|
||||
# Для физических лиц (не ИП) ИНН не обязателен
|
||||
if company_type == "individual":
|
||||
results.append(ValidationResult(
|
||||
level=ValidationLevel.WARNING,
|
||||
field="counterparty.inn",
|
||||
message="Не указан ИНН контрагента (физическое лицо)"
|
||||
))
|
||||
else:
|
||||
# Для юр. лиц и ИП ИНН обязателен
|
||||
results.append(ValidationResult(
|
||||
level=ValidationLevel.CRITICAL,
|
||||
field="counterparty.inn",
|
||||
message="Не указан ИНН контрагента"
|
||||
))
|
||||
elif not self.validate_inn(inn):
|
||||
results.append(ValidationResult(
|
||||
level=ValidationLevel.CRITICAL,
|
||||
@ -304,7 +315,7 @@ class OrderValidator:
|
||||
address = agent.get("legalAddress") or agent.get("actualAddress")
|
||||
if not address:
|
||||
results.append(ValidationResult(
|
||||
level=ValidationLevel.CRITICAL,
|
||||
level=ValidationLevel.WARNING,
|
||||
field="counterparty.address",
|
||||
message="Не указан адрес контрагента"
|
||||
))
|
||||
@ -488,6 +499,29 @@ class OrderValidator:
|
||||
field="consignee.name",
|
||||
message="Не указано наименование грузополучателя"
|
||||
))
|
||||
|
||||
# КРИТИЧНО: адрес грузополучателя обязателен для УПД
|
||||
consignee_address = (
|
||||
consignee.get("deliveryAddress") or
|
||||
consignee.get("actualAddress") or
|
||||
consignee.get("legalAddress")
|
||||
)
|
||||
if not consignee_address:
|
||||
# Фолбэк: используем адрес отгрузки из документа, если он есть
|
||||
shipment_addr = (
|
||||
(order.get("shipmentAddressFull") or {}).get("addInfo")
|
||||
or order.get("shipmentAddress")
|
||||
)
|
||||
if shipment_addr:
|
||||
consignee_address = shipment_addr # используется для логики
|
||||
else:
|
||||
results.append(ValidationResult(
|
||||
level=ValidationLevel.CRITICAL,
|
||||
field="consignee.address",
|
||||
message=(
|
||||
"Не указан адрес грузополучателя (обязательно для УПД)"
|
||||
)
|
||||
))
|
||||
|
||||
# Валидация позиций
|
||||
positions = order_data.get("positions", [])
|
||||
|
||||
@ -202,8 +202,12 @@ class XMLGenerator:
|
||||
name = counterparty.get("legalTitle", "")
|
||||
etree.SubElement(shipper_info, "НаимОрг", Наим=name)
|
||||
|
||||
# Адрес
|
||||
address = counterparty.get("legalAddress", "")
|
||||
# Адрес (приоритет: deliveryAddress -> actualAddress -> legalAddress)
|
||||
address = (
|
||||
counterparty.get("deliveryAddress") or
|
||||
counterparty.get("actualAddress") or
|
||||
counterparty.get("legalAddress", "")
|
||||
)
|
||||
if address:
|
||||
addr_element = etree.SubElement(shipper, "Адрес")
|
||||
etree.SubElement(
|
||||
@ -292,8 +296,8 @@ class XMLGenerator:
|
||||
name = counterparty.get("legalTitle", "")
|
||||
etree.SubElement(buyer_info, "НаимОрг", Наим=name)
|
||||
|
||||
# Адрес
|
||||
address = counterparty.get("legalAddress", "")
|
||||
# Адрес (для покупателя используем юридический адрес)
|
||||
address = counterparty.get("legalAddress") or counterparty.get("actualAddress", "")
|
||||
if address:
|
||||
addr_element = etree.SubElement(buyer, "Адрес")
|
||||
etree.SubElement(
|
||||
@ -898,8 +902,22 @@ class XML1CGenerator:
|
||||
# Договор
|
||||
contract_elem = etree.SubElement(settlement_elem, "Договор")
|
||||
# Генерируем детерминированный UUID для договора на основе контрагента
|
||||
# ВАЖНО: включаем валюту и признак у.е. в seed для uuid5
|
||||
# Это обеспечивает создание отдельных договоров для разных валют:
|
||||
# - RUB/не-у.е. → 62.01/62.02 (задолженность/авансы)
|
||||
# - валюта → 62.21/62.22 (валютные расчёты)
|
||||
# - у.е. → 62.31/62.32 (расчёты в у.е.)
|
||||
import uuid
|
||||
contract_uuid = str(uuid.uuid5(uuid.NAMESPACE_DNS, f"contract-{cp_id}-{org_id}"))
|
||||
currency_code = currency.get("isoCode", "643")
|
||||
is_ue = "false" # В текущей реализации всегда false
|
||||
seed = f"contract-{cp_id}-{org_id}-CUR-{currency_code}-UE-{is_ue}"
|
||||
contract_uuid = str(uuid.uuid5(uuid.NAMESPACE_DNS, seed))
|
||||
|
||||
logger.info(
|
||||
f"Создан договор с учётом валюты: "
|
||||
f"контрагент={cp_id}, организация={org_id}, "
|
||||
f"валюта={currency_code}, у.е.={is_ue}, UUID={contract_uuid}"
|
||||
)
|
||||
etree.SubElement(contract_elem, "Ссылка").text = contract_uuid
|
||||
etree.SubElement(contract_elem, "ВидДоговора").text = "СПокупателем"
|
||||
|
||||
@ -1068,6 +1086,28 @@ class XML1CGenerator:
|
||||
encoding=self.encoding
|
||||
).decode(self.encoding)
|
||||
|
||||
# ДИАГНОСТИКА: Проверяем соответствие валюты документа и договора
|
||||
# Если валюта RUB и не у.е., 1С должна использовать счета 62.01/62.02
|
||||
# Если валюта иностранная → 62.21/62.22
|
||||
# Если у.е. → 62.31/62.32
|
||||
if currency_code == "643" or currency_code == "RUB":
|
||||
logger.info(
|
||||
"✓ Документ в рублях (RUB, код 643), не в у.е. "
|
||||
"→ ожидаются счета 62.01 (задолженность) / 62.02 (авансы). "
|
||||
"Если 1С подставляет 62.21/62.22 или 62.31/62.32, проверьте: "
|
||||
"1) Регистр 'Счета расчетов с контрагентами' - нет ли "
|
||||
"индивидуальных правил на контрагента/договор. "
|
||||
"2) В карточке договора: Валюта взаиморасчётов = RUB, "
|
||||
"Расчёты в условных единицах = нет."
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
f"⚠ Документ в иностранной валюте ({currency_code}) "
|
||||
"→ ожидаются счета 62.21/62.22. "
|
||||
"Убедитесь, что договор создан с валютой взаиморасчётов "
|
||||
f"= {currency_code}."
|
||||
)
|
||||
|
||||
# Сохранение в файл
|
||||
if output_path:
|
||||
with open(output_path, "w", encoding=self.encoding) as f:
|
||||
|
||||
89
test_order_4926.xml
Normal file
89
test_order_4926.xml
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<Файл ВерсПрог="BUH_BOT_1.0" ВерсФорм="5.03">
|
||||
<Документ>
|
||||
<СвСчФакт>
|
||||
<НомерДата Номер="4926" Дата="06.11.2025"/>
|
||||
<СвПрод ОКПО="0142109827">
|
||||
<ИдСв>
|
||||
<СвИП ИННФЛ="263605160270" СвГосРегИП="304263530000073">
|
||||
<ФИО Фамилия="Беляев" Имя="Андрей" Отчество="Юрьевич"/>
|
||||
</СвИП>
|
||||
</ИдСв>
|
||||
<Адрес>
|
||||
<АдрТекст Текст="355000, Россия, Ставропольский край, г Ставрополь"/>
|
||||
</Адрес>
|
||||
<БанкРекв НомерСчета="40802810860270001979">
|
||||
<СвБанк НаимБанк="СТАВРОПОЛЬСКОЕ ОТДЕЛЕНИЕ N5230 ПАО СБЕРБАНК" БИК="040702615" КорСчет="30101810907020000615"/>
|
||||
</БанкРекв>
|
||||
<Контакт>
|
||||
<Тлф>8-800-234-33-68</Тлф>
|
||||
<ЭлПочта>robotail@bk.ru</ЭлПочта>
|
||||
</Контакт>
|
||||
</СвПрод>
|
||||
<ГрузОт>
|
||||
<ОнЖе>он же</ОнЖе>
|
||||
</ГрузОт>
|
||||
<ГрузПолуч>
|
||||
<ИдСв СвЮЛУч="" КПП="">
|
||||
<НаимОрг Наим="Котов Антон Максимович"/>
|
||||
</ИдСв>
|
||||
<Контакт>
|
||||
<Тлф>+79218378869</Тлф>
|
||||
</Контакт>
|
||||
</ГрузПолуч>
|
||||
<ДокПодтвОтгрНом РеквНаимДок="УПД" РеквНомерДок="4926" РеквДатаДок="06.11.2025"/>
|
||||
<СвПокуп>
|
||||
<ИдСв СвЮЛУч="" КПП="">
|
||||
<НаимОрг Наим="Котов Антон Максимович"/>
|
||||
</ИдСв>
|
||||
<Контакт>
|
||||
<Тлф>+79218378869</Тлф>
|
||||
<ЭлПочта>77alpha@mail.ru</ЭлПочта>
|
||||
</Контакт>
|
||||
</СвПокуп>
|
||||
<ДенИзм КодОКВ="643" НаимОКВ="Российский рубль"/>
|
||||
</СвСчФакт>
|
||||
<ТаблСчФакт>
|
||||
<СведТов НомСтр="1">
|
||||
<НаимТов Наим="Камера для силомера Rocky Boxer"/>
|
||||
<ОКЕИ_Тов НаимЕдИзм="шт"/>
|
||||
<КолТов Количество="3.0"/>
|
||||
<ЦенаТов Цена="390.00"/>
|
||||
<СтТовБезНДС СтоимБезНДС="1114.29"/>
|
||||
<НалСт СтавНДС="5" СумНДС="55.71"/>
|
||||
<СтТовУчНал СтоимУчНДС="1170.00"/>
|
||||
</СведТов>
|
||||
<СведТов НомСтр="2">
|
||||
<НаимТов Наим="Кожаный чехол для груши (Черный)"/>
|
||||
<ОКЕИ_Тов НаимЕдИзм="шт"/>
|
||||
<КолТов Количество="2.0"/>
|
||||
<ЦенаТов Цена="7810.00"/>
|
||||
<СтТовБезНДС СтоимБезНДС="14876.19"/>
|
||||
<НалСт СтавНДС="5" СумНДС="743.81"/>
|
||||
<СтТовУчНал СтоимУчНДС="15620.00"/>
|
||||
</СведТов>
|
||||
<СведТов НомСтр="3">
|
||||
<НаимТов Наим="Полиуретановый конус с усиленной оксидированной шпилькой"/>
|
||||
<ОКЕИ_Тов НаимЕдИзм="шт"/>
|
||||
<КолТов Количество="2.0"/>
|
||||
<ЦенаТов Цена="2680.00"/>
|
||||
<СтТовБезНДС СтоимБезНДС="5104.76"/>
|
||||
<НалСт СтавНДС="5" СумНДС="255.24"/>
|
||||
<СтТовУчНал СтоимУчНДС="5360.00"/>
|
||||
</СведТов>
|
||||
<ВсегоОпл>
|
||||
<СтТовБезНДСВсего СтоимБезНДС="21095.24"/>
|
||||
<СумНДСВсего СумНДС="1054.76"/>
|
||||
<СтТовУчНалВсего СтоимУчНДС="22150.00"/>
|
||||
</ВсегоОпл>
|
||||
</ТаблСчФакт>
|
||||
<СвПродПер>
|
||||
<СвПер СодОпер="Товары переданы" ДатаПер="06.11.2025">
|
||||
<БезДокОснПер>1</БезДокОснПер>
|
||||
</СвПер>
|
||||
</СвПродПер>
|
||||
<Подписант СпосПодтПолном="6">
|
||||
<ФИО Фамилия="Беляев" Имя="Андрей" Отчество="Юрьевич"/>
|
||||
</Подписант>
|
||||
</Документ>
|
||||
</Файл>
|
||||
Loading…
x
Reference in New Issue
Block a user