feat: логи, фикс табов, справка с инструкциями
1) Логи: все действия пишутся в dokogen.log (импорт, генерация, обращения к Морфер/DaData/ИИ с ответами), кнопка «Экспорт логов» на вкладке «Лог» 2) Табы: при пересборке параграфа табуляция сохраняется как <w:tab/> (раньше Word «съедал» табы между «_____» и датой) 3) Справка: инструкция пользователя (циклы, программа), инструкция администратора (словари, API-ключи, лимиты), справочник переменных, пустой опросник Excel — всё скачивается в Word/Excel
This commit is contained in:
+106
-5
@@ -49,6 +49,13 @@ class DokoGenApp:
|
||||
# None = import_settings.json рядом с программой (создаётся автоматически)
|
||||
self.import_settings_path: Optional[str] = None
|
||||
|
||||
# Файловый лог: рядом с exe (frozen) или рядом с модулем
|
||||
if getattr(sys, 'frozen', False):
|
||||
_base = os.path.dirname(sys.executable)
|
||||
else:
|
||||
_base = os.path.dirname(os.path.abspath(__file__))
|
||||
self._log_path = os.path.join(_base, 'dokogen.log')
|
||||
|
||||
# Переменные
|
||||
self.doc_number_var = tk.StringVar(value="")
|
||||
self.doc_date_var = tk.StringVar(value="")
|
||||
@@ -103,7 +110,10 @@ class DokoGenApp:
|
||||
help_menu = tk.Menu(menubar, tearoff=0)
|
||||
menubar.add_cascade(label="Справка", menu=help_menu)
|
||||
help_menu.add_command(label="О программе", command=self.show_about)
|
||||
help_menu.add_command(label="Переменные шаблонов", command=self.show_variables)
|
||||
help_menu.add_command(label="Инструкция пользователя (Word)…", command=self.download_user_manual)
|
||||
help_menu.add_command(label="Инструкция администратора (Word)…", command=self.download_admin_manual)
|
||||
help_menu.add_command(label="Переменные шаблонов (Word)…", command=self.download_variables_doc)
|
||||
help_menu.add_command(label="Пустой опросник (Excel)…", command=self.download_survey)
|
||||
|
||||
# ==================== ИНТЕРФЕЙС ====================
|
||||
def _build_ui(self):
|
||||
@@ -364,6 +374,7 @@ class DokoGenApp:
|
||||
if not name and not short:
|
||||
messagebox.showwarning("Проверка", "Сначала введите полное или краткое название организации")
|
||||
return
|
||||
self.log(f"🌐 Запрос к Морфер 3.0: «{name or short}»")
|
||||
self._verify_log("Запрос к Морфер 3.0…")
|
||||
|
||||
def _run():
|
||||
@@ -395,6 +406,7 @@ class DokoGenApp:
|
||||
if not settings.get('ai_api_key'):
|
||||
messagebox.showwarning("Настройки", "Не задан API-ключ ИИ.\nМеню «Файл → Настройки»")
|
||||
return
|
||||
self.log(f"🤖 Запрос к ИИ ({settings.get('ai_model') or 'deepseek-chat'}): склонение «{name or short}»")
|
||||
self._verify_log("Запрос к ИИ…")
|
||||
|
||||
def _run():
|
||||
@@ -420,6 +432,7 @@ class DokoGenApp:
|
||||
def _verify_declension_done(self, results, err):
|
||||
if err:
|
||||
self._verify_log(f"❌ {err}")
|
||||
self.log(f"❌ Проверка склонений: {err}")
|
||||
return
|
||||
# Собираем единый результат для применения
|
||||
combined = {
|
||||
@@ -446,6 +459,13 @@ class DokoGenApp:
|
||||
lines.append("\nНажмите «✓ Применить», чтобы подставить склонения")
|
||||
self._verify_result = combined
|
||||
self._verify_log('\n'.join(lines))
|
||||
# Общий лог с ответом сервиса
|
||||
sample = ''
|
||||
for key, res in results:
|
||||
sample = res['data'].get('П', '')
|
||||
if sample:
|
||||
break
|
||||
self.log(f"{combined['source']}: склонение проверено → «{sample}»")
|
||||
|
||||
def _check_company_web(self):
|
||||
inn_entry = self._company_entries.get('inn')
|
||||
@@ -457,6 +477,7 @@ class DokoGenApp:
|
||||
if not settings.get('dadata_token'):
|
||||
messagebox.showwarning("Настройки", "Не задан API-ключ DaData.\nМеню «Файл → Настройки»")
|
||||
return
|
||||
self.log(f"🌐 Запрос к DaData: ИНН/ОГРН {inn}")
|
||||
self._verify_log("Запрос к DaData…")
|
||||
|
||||
def _run():
|
||||
@@ -478,6 +499,7 @@ class DokoGenApp:
|
||||
'ogrn_date': c.ogrn_date,
|
||||
'chief_position': c.chief_position, 'chief_fio': c.chief_fio,
|
||||
}
|
||||
self.log(f"🤖 Запрос к ИИ ({settings.get('ai_model') or 'deepseek-chat'}): реквизиты (ИНН {company_data.get('inn') or '—'})")
|
||||
self._verify_log("Запрос к ИИ…")
|
||||
|
||||
def _run():
|
||||
@@ -490,6 +512,7 @@ class DokoGenApp:
|
||||
def _verify_company_done(self, result, err):
|
||||
if err:
|
||||
self._verify_log(f"❌ {err}")
|
||||
self.log(f"❌ Проверка реквизитов: {err}")
|
||||
return
|
||||
self._verify_result = result
|
||||
lines = [f"Источник: {result['source']} ({result['source_url']})"]
|
||||
@@ -527,6 +550,13 @@ class DokoGenApp:
|
||||
lines.append(f" {label}: {v}")
|
||||
lines.append("\nНажмите «✓ Применить», чтобы подставить данные")
|
||||
self._verify_log('\n'.join(lines))
|
||||
# Общий лог с ответом сервиса
|
||||
summary = ''
|
||||
if 'full_name' in d:
|
||||
summary = f"{d.get('full_name', '')[:60]} (ИНН {d.get('inn', '')})"
|
||||
else:
|
||||
summary = f"оценка {d.get('rating', '')}, замечаний: {len(d.get('notes') or [])}"
|
||||
self.log(f"{result['source']}: проверка реквизитов → {summary}")
|
||||
|
||||
def _verify_apply(self):
|
||||
result = self._verify_result
|
||||
@@ -1000,6 +1030,13 @@ class DokoGenApp:
|
||||
def _build_log_tab(self):
|
||||
tab = ttk.Frame(self.notebook)
|
||||
self.notebook.add(tab, text="Лог")
|
||||
|
||||
top = ttk.Frame(tab)
|
||||
top.pack(fill=tk.X, padx=5, pady=3)
|
||||
ttk.Button(top, text="💾 Экспорт логов…", command=self._export_logs).pack(side=tk.LEFT)
|
||||
ttk.Label(top, text="Журнал пишется в dokogen.log рядом с программой",
|
||||
foreground='gray').pack(side=tk.LEFT, padx=8)
|
||||
|
||||
self.log_text = scrolledtext.ScrolledText(tab, height=10)
|
||||
self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
self.log_text.insert(tk.END, "Лог готов\n")
|
||||
@@ -2057,8 +2094,32 @@ class DokoGenApp:
|
||||
self.log_text.configure(state='disabled')
|
||||
except Exception:
|
||||
pass
|
||||
# Дублируем в файловый журнал
|
||||
try:
|
||||
with open(self._log_path, 'a', encoding='utf-8') as f:
|
||||
f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {message}\n")
|
||||
except Exception:
|
||||
pass
|
||||
self.status_var.set(message)
|
||||
|
||||
def _export_logs(self):
|
||||
"""Экспорт лога в файл."""
|
||||
filename = filedialog.asksaveasfilename(
|
||||
title="Экспорт логов",
|
||||
defaultextension=".log",
|
||||
initialfile=f"dokogen_log_{datetime.now().strftime('%Y%m%d_%H%M')}.log",
|
||||
filetypes=[("Лог-файл", "*.log"), ("Текстовый файл", "*.txt"), ("Все файлы", "*.*")]
|
||||
)
|
||||
if not filename:
|
||||
return
|
||||
try:
|
||||
content = self.log_text.get('1.0', tk.END)
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
self.log(f"💾 Лог экспортирован: {filename}")
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось экспортировать лог: {e}")
|
||||
|
||||
def show_about(self):
|
||||
messagebox.showinfo("О программе",
|
||||
"ДоКоГеНеРаТоР — 152-ФЗ\n"
|
||||
@@ -2066,10 +2127,50 @@ class DokoGenApp:
|
||||
"Генератор документов из шаблонов")
|
||||
|
||||
def show_variables(self):
|
||||
text = "Переменные шаблонов:\n\n"
|
||||
for var, group, desc, _ in VARIABLE_DEFS:
|
||||
text += f"{var} — {desc} ({'общ.' if group == 'A' else '152-ФЗ'})\n"
|
||||
messagebox.showinfo("Переменные шаблонов", text)
|
||||
"""Показ переменных шаблонов — теперь через скачивание Word-документа."""
|
||||
self.download_variables_doc()
|
||||
|
||||
# ---------- СПРАВКА: СКАЧИВАНИЕ ИНСТРУКЦИЙ / ОПРОСНИКА ----------
|
||||
def _save_resource(self, source_path, default_name, title):
|
||||
"""Копирует ресурс (docx/xlsx) в выбранное пользователем место."""
|
||||
if not source_path or not os.path.exists(source_path):
|
||||
messagebox.showerror("Справка", f"Файл не найден:\n{source_path}")
|
||||
return
|
||||
filename = filedialog.asksaveasfilename(
|
||||
title=title,
|
||||
defaultextension=os.path.splitext(default_name)[1],
|
||||
initialfile=default_name,
|
||||
filetypes=[("Документ", "*.docx"), ("Все файлы", "*.*")]
|
||||
)
|
||||
if not filename:
|
||||
return
|
||||
try:
|
||||
import shutil
|
||||
shutil.copy(source_path, filename)
|
||||
self.log(f"💾 Сохранено: {filename}")
|
||||
messagebox.showinfo("Справка", f"Файл сохранён:\n{filename}")
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось сохранить: {e}")
|
||||
|
||||
def download_user_manual(self):
|
||||
from .help_docs import ensure_docs, user_manual_path
|
||||
ensure_docs()
|
||||
self._save_resource(user_manual_path(), "Инструкция_пользователя.docx", "Сохранить инструкцию пользователя")
|
||||
|
||||
def download_admin_manual(self):
|
||||
from .help_docs import ensure_docs, admin_manual_path
|
||||
ensure_docs()
|
||||
self._save_resource(admin_manual_path(), "Инструкция_администратора.docx", "Сохранить инструкцию администратора")
|
||||
|
||||
def download_variables_doc(self):
|
||||
from .help_docs import ensure_docs, variables_reference_path
|
||||
ensure_docs()
|
||||
self._save_resource(variables_reference_path(), "Переменные_шаблонов.docx", "Сохранить справочник переменных")
|
||||
|
||||
def download_survey(self):
|
||||
from .help_docs import ensure_docs, survey_path
|
||||
ensure_docs()
|
||||
self._save_resource(survey_path(), "Опросный_лист_152_ФЗ.xlsx", "Сохранить пустой опросник")
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
Reference in New Issue
Block a user