This commit is contained in:
2026-04-09 16:43:59 +05:00
parent 0b7a5bc454
commit 3a690ecf39
7 changed files with 379 additions and 6 deletions

View File

@@ -1,7 +1,8 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
from pydantic import BaseModel
from typing import Optional
import uuid
import io
from bd import make_engine
@@ -102,3 +103,106 @@ def delete_organization(org_id: str):
session.delete(org)
session.commit()
return {}
def _extract_names_from_docx(content: bytes) -> list[str]:
"""Извлекает уникальные непустые строки из docx.
Для таблиц берётся только ПЕРВЫЙ столбец каждой строки.
Параграфы вне таблиц используются как запасной источник (если таблиц нет).
"""
from docx import Document as DocxDocument
doc = DocxDocument(io.BytesIO(content))
names: list[str] = []
seen: set[str] = set()
def _add(text: str):
t = text.strip()
if t and t not in seen:
seen.add(t)
names.append(t)
# Если есть таблицы — берём только первый столбец
if doc.tables:
for table in doc.tables:
for row in table.rows:
if row.cells:
_add(row.cells[0].text)
else:
# Нет таблиц — берём параграфы
for para in doc.paragraphs:
_add(para.text)
return names
@router.post("/import/docx/preview", dependencies=[Depends(require_admin_key)])
async def preview_import_from_docx(file: UploadFile = File(...)):
"""Предпросмотр: возвращает список строк, которые будут импортированы, БЕЗ записи в БД.
Используйте перед /import/docx чтобы убедиться, что извлекаются нужные данные.
"""
if not file.filename or not file.filename.lower().endswith(".docx"):
raise HTTPException(status_code=400, detail="Ожидается файл .docx")
try:
from docx import Document as DocxDocument # noqa: F401
except ImportError:
raise HTTPException(status_code=500, detail="python-docx не установлен на сервере")
content = await file.read()
try:
names = _extract_names_from_docx(content)
except Exception:
raise HTTPException(status_code=400, detail="Не удалось открыть файл")
return {"total": len(names), "names": names}
@router.post("/import/docx", dependencies=[Depends(require_admin_key)])
async def import_organizations_from_docx(file: UploadFile = File(...)):
"""Массовый импорт организаций из .docx файла.
Каждая непустая строка (параграф или ячейка таблицы) становится отдельной организацией.
Уже существующие названия пропускаются без ошибки.
Перед использованием проверьте результат через /import/docx/preview.
"""
if not file.filename or not file.filename.lower().endswith(".docx"):
raise HTTPException(status_code=400, detail="Ожидается файл .docx")
try:
from docx import Document as DocxDocument # noqa: F401
except ImportError:
raise HTTPException(status_code=500, detail="python-docx не установлен на сервере")
content = await file.read()
try:
names = _extract_names_from_docx(content)
except Exception:
raise HTTPException(status_code=400, detail="Не удалось открыть файл. Убедитесь, что это корректный .docx")
if not names:
raise HTTPException(status_code=400, detail="В файле не найдено ни одной строки")
Session = get_session()
added: list[str] = []
skipped: list[str] = []
with Session() as session:
for name in names:
existing = session.query(Organization).filter(
Organization.name_organization == name
).first()
if existing:
skipped.append(name)
continue
org = Organization(name_organization=name)
session.add(org)
added.append(name)
session.commit()
return {
"added": len(added),
"skipped": len(skipped),
"added_names": added,
"skipped_names": skipped,
}