feat(web): стабилизация UI, Gemini-скроллинг, роутеры контекста/снапшотов и актуализация роадмапа
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
===============================================================================
|
||||
FILE: modules/web_api/routers/remote_workers.py
|
||||
ROLE: REST API реестра удаленщиков (CRUD, редактирование сроков, автоочистка).
|
||||
===============================================================================
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
import pandas as pd
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from routers.auth import get_current_user
|
||||
from config import BASE_DIR, DATA_DIR
|
||||
|
||||
router = APIRouter(prefix="/api/v1/remote-workers", tags=["RemoteWorkers"])
|
||||
CSV_PATH = os.path.join(DATA_DIR, "static_reason_workers.csv")
|
||||
|
||||
|
||||
def _load_workers() -> List[Dict[str, Any]]:
|
||||
"""Читает CSV, гарантирует структуру колонок и удаляет просроченные записи."""
|
||||
if not os.path.exists(CSV_PATH):
|
||||
return []
|
||||
try:
|
||||
df = pd.read_csv(CSV_PATH, dtype=str, on_bad_lines='skip').fillna("")
|
||||
for col in ["fio", "department", "reason", "date_from", "date_to"]:
|
||||
if col not in df.columns:
|
||||
df[col] = ""
|
||||
|
||||
today_date = datetime.now().date()
|
||||
valid_workers = []
|
||||
has_expired = False
|
||||
|
||||
for row in df.to_dict(orient="records"):
|
||||
fio = str(row.get("fio", "")).strip()
|
||||
if not fio:
|
||||
continue
|
||||
|
||||
d_to_str = str(row.get("date_to", "")).strip()
|
||||
if d_to_str and d_to_str.lower() not in ["nan", "none", ""]:
|
||||
try:
|
||||
d_to = datetime.strptime(d_to_str.replace('_', '.'), "%d.%m.%Y").date()
|
||||
# Если срок завершился вчера или ранее — запись удаляется из файла
|
||||
if today_date > d_to:
|
||||
has_expired = True
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
valid_workers.append(row)
|
||||
|
||||
# Синхронная перезапись файла при обнаружении истекших сроков
|
||||
if has_expired:
|
||||
_save_workers(valid_workers)
|
||||
|
||||
return valid_workers
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _save_workers(workers: List[Dict[str, Any]]):
|
||||
"""Сохраняет актуальный список в CSV с сохранением колонок."""
|
||||
df = pd.DataFrame(workers)
|
||||
for col in ["fio", "department", "reason", "date_from", "date_to"]:
|
||||
if col not in df.columns:
|
||||
df[col] = ""
|
||||
os.makedirs(os.path.dirname(CSV_PATH), exist_ok=True)
|
||||
df.to_csv(CSV_PATH, index=False, encoding="utf-8")
|
||||
|
||||
|
||||
class RemoteWorkerItem(BaseModel):
|
||||
fio: str
|
||||
department: Optional[str] = "Все"
|
||||
reason: Optional[str] = "Удаленная работа"
|
||||
date_from: Optional[str] = ""
|
||||
date_to: Optional[str] = ""
|
||||
|
||||
|
||||
class UpdateDatesRequest(BaseModel):
|
||||
fio: str
|
||||
date_from: Optional[str] = ""
|
||||
date_to: Optional[str] = ""
|
||||
|
||||
|
||||
@router.get("")
|
||||
def api_get_remote_workers(current_user = Depends(get_current_user)):
|
||||
return {"workers": _load_workers()}
|
||||
|
||||
|
||||
@router.post("")
|
||||
def api_add_remote_worker(item: RemoteWorkerItem, current_user = Depends(get_current_user)):
|
||||
workers = _load_workers()
|
||||
fio_clean = item.fio.strip()
|
||||
if not fio_clean:
|
||||
raise HTTPException(status_code=400, detail="ФИО не может быть пустым")
|
||||
|
||||
# Если начало не указано — берем сегодня
|
||||
today_str = datetime.now().strftime("%d.%m.%Y")
|
||||
date_from = item.date_from.strip() if item.date_from and item.date_from.strip() else today_str
|
||||
date_to = item.date_to.strip() if item.date_to else ""
|
||||
|
||||
# Проверка на совпадение ФИО (обновление существующей записи)
|
||||
for w in workers:
|
||||
if str(w.get("fio", "")).strip().lower() == fio_clean.lower():
|
||||
w["department"] = item.department.strip() if item.department else (w.get("department") or "Все")
|
||||
w["reason"] = item.reason.strip() if item.reason else (w.get("reason") or "Удаленная работа")
|
||||
w["date_from"] = date_from
|
||||
w["date_to"] = date_to
|
||||
_save_workers(workers)
|
||||
return {"status": "success", "message": "Срок удаленки обновлен", "workers": workers}
|
||||
|
||||
workers.append({
|
||||
"fio": fio_clean,
|
||||
"department": item.department.strip() if item.department else "Все",
|
||||
"reason": item.reason.strip() if item.reason else "Удаленная работа",
|
||||
"date_from": date_from,
|
||||
"date_to": date_to
|
||||
})
|
||||
_save_workers(workers)
|
||||
return {"status": "success", "workers": workers}
|
||||
|
||||
|
||||
@router.put("")
|
||||
def api_update_worker_dates(req: UpdateDatesRequest, current_user = Depends(get_current_user)):
|
||||
"""Редактирование срока удаленки (продление или сокращение)."""
|
||||
workers = _load_workers()
|
||||
target_fio = req.fio.strip().lower()
|
||||
found = False
|
||||
|
||||
for w in workers:
|
||||
if str(w.get("fio", "")).strip().lower() == target_fio:
|
||||
w["date_from"] = req.date_from.strip() if req.date_from is not None else w.get("date_from", "")
|
||||
w["date_to"] = req.date_to.strip() if req.date_to is not None else w.get("date_to", "")
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
raise HTTPException(status_code=404, detail="Сотрудник не найден в списке")
|
||||
|
||||
_save_workers(workers)
|
||||
return {"status": "success", "message": "Сроки успешно изменены", "workers": workers}
|
||||
|
||||
|
||||
@router.delete("")
|
||||
def api_delete_remote_worker(fio: str, current_user = Depends(get_current_user)):
|
||||
workers = _load_workers()
|
||||
fio_clean = fio.strip().lower()
|
||||
initial_len = len(workers)
|
||||
workers = [w for w in workers if str(w.get("fio", "")).strip().lower() != fio_clean]
|
||||
|
||||
if len(workers) == initial_len:
|
||||
raise HTTPException(status_code=404, detail="Сотрудник не найден")
|
||||
|
||||
_save_workers(workers)
|
||||
return {"status": "success", "workers": workers}
|
||||
Reference in New Issue
Block a user