feat: загрузка доходов организации из DaData (показатель 8)

- verify.py: get_company_finance_dadata(inn, token) — findById/party,
  возвращает finance (последний отчётный год) + finance_history (по годам)
- ui.py: кнопка «🌐 Загрузить доходы из DaData (по ИНН)» в блоке
  «Доходы за 5 лет (показатель 8)» — по ИНН организации подтягивает
  доходы за последние 5 лет и заполняет income_year_1..5
- если в тарифе DaData нет финансовой отчётности — понятное сообщение,
  доходы заполняются вручную
This commit is contained in:
2026-08-04 23:20:04 +04:00
parent 7742cb1e10
commit c1ad496319
2 changed files with 105 additions and 0 deletions
+73
View File
@@ -344,6 +344,9 @@ class DokoGenApp:
ttk.Label(f_fin, text="Доходы за 5 лет (показатель 8):",
font=('', 9, 'bold')).grid(row=len(fin_fields), column=0, columnspan=2,
sticky=tk.W, pady=(8, 2))
ttk.Button(f_fin, text="🌐 Загрузить доходы из DaData (по ИНН)",
command=self._load_incomes_from_dadata).grid(
row=len(fin_fields), column=2, padx=5, sticky=tk.W)
income_fields = [
("Доход за 5 лет назад (руб.):", "income_year_1"),
("Доход за 4 года назад (руб.):", "income_year_2"),
@@ -3276,6 +3279,76 @@ class DokoGenApp:
return True
return False
def _load_incomes_from_dadata(self):
"""🌐 Загрузить доходы организации за 5 лет из DaData (finance_history / finance)."""
from .api_settings import load_api_settings
settings = load_api_settings()
if not settings.get('dadata_token'):
messagebox.showwarning("DaData", "Не задан API-ключ DaData.\nМеню «Файл → Настройки»")
return
inn = self._company_entries.get('inn', None)
inn_val = (inn.get().strip() if inn else '') or getattr(self.model, 'inn', '') or ''
if not inn_val:
messagebox.showinfo("DaData", "Сначала укажите ИНН организации (вкладка «Организация»).")
return
self.log(f"🌐 Запрос доходов из DaData (ИНН {inn_val})…")
def _run():
try:
from .verify import get_company_finance_dadata
res, err = get_company_finance_dadata(inn_val, settings['dadata_token'])
self.root.after(0, lambda: self._load_incomes_done(res, err))
except Exception as e:
self.root.after(0, lambda: self._load_incomes_done(None, str(e)))
threading.Thread(target=_run, daemon=True).start()
def _load_incomes_done(self, res, err):
if err:
messagebox.showerror("DaData", f"Не удалось получить доходы:\n{err}")
self.log(f"❌ Доходы DaData: {err}")
return
finance = res.get('finance') or {}
history = res.get('history') or []
# Доход за последний год из finance (income/revenue)
last_income = finance.get('income') or finance.get('revenue') or None
last_year = finance.get('year') or None
# history: список {year, income, revenue, ...} — сортируем по году
rows = []
for h in history:
y = h.get('year')
inc = h.get('income') or h.get('revenue') or None
if y and inc:
rows.append((int(y), float(inc)))
if last_year and last_income:
rows.append((int(last_year), float(last_income)))
rows = sorted(set(rows), key=lambda x: x[0])
if not rows:
messagebox.showinfo(
"DaData",
f"Финансовые показатели для ИНН {res.get('inn')} в ответе DaData отсутствуют "
f"(нужен тариф DaData с финансовой отчётностью).\n"
f"Заполните доходы за 5 лет вручную.")
return
# Последние 5 лет: income_year_5 (самый свежий) ... income_year_1 (самый старый)
last5 = rows[-5:]
mapping = []
for i, (y, inc) in enumerate(last5):
# y — календарный год; income_year_1 — самый старый из пяти
mapping.append((f'income_year_{i+1}', y, inc))
for key, y, inc in mapping:
entry = self._company_entries.get(key)
if entry:
entry.delete(0, tk.END)
entry.insert(0, f"{inc:,.0f}".replace(',', ' '))
self._collect_company()
years = ', '.join(f"{y}: {inc:,.0f} руб." for _, y, inc in mapping)
self.log(f"🌐 Доходы из DaData загружены: {years}")
messagebox.showinfo("DaData", f"Доходы загружены из DaData:\n{years}")
def _select_main_okved(self):
"""Выбор основного ОКВЭД из словаря (всегда один)."""
items = self._load_okved_dict()
+32
View File
@@ -251,6 +251,38 @@ def check_declension_morpher(text):
# ============================================================
# DADATA — проверка организации по ИНН (нужен API-ключ)
# ============================================================
def get_company_finance_dadata(inn, token):
"""Доходы организации по годам через DaData (findById/party → finance/finance_history).
Возвращает (dict, ошибка): dict вида
{'finance': {...}, 'history': [{'year': 2023, 'income': 123456, ...}, ...]}
finance показатели за последний отчётный год;
history история по годам (если доступна в тарифе DaData).
"""
if not token:
return None, "Не задан API-ключ DaData (Файл → Настройки → API)"
inn = (inn or '').replace(' ', '')
if not inn or not inn.isdigit():
return None, "Введите ИНН для загрузки доходов"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Token {token}',
}
js, err = _http_json(DADATA_URL, data={'query': inn}, headers=headers)
if err:
return None, err
suggestions = (js or {}).get('suggestions') or []
if not suggestions:
return None, f"DaData: организация с ИНН {inn} не найдена"
d = suggestions[0].get('data') or {}
return {
'finance': d.get('finance') or {},
'history': d.get('finance_history') or [],
'inn': d.get('inn', inn),
'name': (d.get('name') or {}).get('short_with_opf') or suggestions[0].get('value', ''),
}, None
def check_company_dadata(inn, token):
"""Проверка организации по ИНН/ОГРН через DaData.