157 lines
6.1 KiB
Python
157 lines
6.1 KiB
Python
"""
|
|
===============================================================================
|
|
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} |