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:
@@ -344,6 +344,9 @@ class DokoGenApp:
|
|||||||
ttk.Label(f_fin, text="Доходы за 5 лет (показатель 8):",
|
ttk.Label(f_fin, text="Доходы за 5 лет (показатель 8):",
|
||||||
font=('', 9, 'bold')).grid(row=len(fin_fields), column=0, columnspan=2,
|
font=('', 9, 'bold')).grid(row=len(fin_fields), column=0, columnspan=2,
|
||||||
sticky=tk.W, pady=(8, 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 = [
|
income_fields = [
|
||||||
("Доход за 5 лет назад (руб.):", "income_year_1"),
|
("Доход за 5 лет назад (руб.):", "income_year_1"),
|
||||||
("Доход за 4 года назад (руб.):", "income_year_2"),
|
("Доход за 4 года назад (руб.):", "income_year_2"),
|
||||||
@@ -3276,6 +3279,76 @@ class DokoGenApp:
|
|||||||
return True
|
return True
|
||||||
return False
|
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):
|
def _select_main_okved(self):
|
||||||
"""Выбор основного ОКВЭД из словаря (всегда один)."""
|
"""Выбор основного ОКВЭД из словаря (всегда один)."""
|
||||||
items = self._load_okved_dict()
|
items = self._load_okved_dict()
|
||||||
|
|||||||
@@ -251,6 +251,38 @@ def check_declension_morpher(text):
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
# DADATA — проверка организации по ИНН (нужен API-ключ)
|
# 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):
|
def check_company_dadata(inn, token):
|
||||||
"""Проверка организации по ИНН/ОГРН через DaData.
|
"""Проверка организации по ИНН/ОГРН через DaData.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user