163 lines
6.3 KiB
Python
163 lines
6.3 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import sqlite3
|
|
import threading
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
class Database:
|
|
def __init__(self, database_path: Path, employee_seed_path: Path):
|
|
database_path.parent.mkdir(parents=True, exist_ok=True)
|
|
self.connection = sqlite3.connect(database_path, check_same_thread=False)
|
|
self.connection.row_factory = sqlite3.Row
|
|
self._lock = threading.RLock()
|
|
self._migrate()
|
|
self.load_employees(employee_seed_path)
|
|
|
|
def close(self) -> None:
|
|
with self._lock:
|
|
self.connection.close()
|
|
|
|
def _migrate(self) -> None:
|
|
with self._lock:
|
|
self.connection.executescript(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS employees (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
feishu_user_id TEXT NOT NULL UNIQUE,
|
|
name TEXT NOT NULL,
|
|
department TEXT NOT NULL DEFAULT '',
|
|
manager TEXT NOT NULL DEFAULT '',
|
|
active INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS daily_reports (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
feishu_user_id TEXT NOT NULL,
|
|
employee_name TEXT NOT NULL,
|
|
report_date TEXT NOT NULL,
|
|
today_done TEXT NOT NULL,
|
|
tomorrow_plan TEXT NOT NULL,
|
|
blockers TEXT NOT NULL DEFAULT '',
|
|
help_needed TEXT NOT NULL DEFAULT '',
|
|
submitted_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
UNIQUE(feishu_user_id, report_date)
|
|
);
|
|
"""
|
|
)
|
|
self.connection.commit()
|
|
|
|
def load_employees(self, employee_seed_path: Path) -> None:
|
|
if not employee_seed_path.exists():
|
|
return
|
|
|
|
employees = json.loads(employee_seed_path.read_text(encoding="utf-8"))
|
|
self.upsert_employees(employees)
|
|
|
|
def upsert_employees(self, employees: list[dict[str, Any]]) -> None:
|
|
with self._lock, self.connection:
|
|
for employee in employees:
|
|
self.connection.execute(
|
|
"""
|
|
INSERT INTO employees (feishu_user_id, name, department, manager, active, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
ON CONFLICT(feishu_user_id) DO UPDATE SET
|
|
name = excluded.name,
|
|
department = excluded.department,
|
|
manager = excluded.manager,
|
|
active = excluded.active,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
""",
|
|
(
|
|
employee["feishu_user_id"],
|
|
employee["name"],
|
|
employee.get("department", ""),
|
|
employee.get("manager", ""),
|
|
0 if employee.get("active") is False else 1,
|
|
),
|
|
)
|
|
|
|
def upsert_employee(self, employee: dict[str, Any]) -> None:
|
|
self.upsert_employees([employee])
|
|
|
|
def list_active_employees(self) -> list[dict[str, Any]]:
|
|
with self._lock:
|
|
rows = self.connection.execute(
|
|
"""
|
|
SELECT feishu_user_id, name, department, manager, active
|
|
FROM employees
|
|
WHERE active = 1
|
|
ORDER BY department, name
|
|
"""
|
|
).fetchall()
|
|
return [dict(row) for row in rows]
|
|
|
|
def find_employee(self, feishu_user_id: str) -> dict[str, Any] | None:
|
|
with self._lock:
|
|
row = self.connection.execute(
|
|
"""
|
|
SELECT feishu_user_id, name, department, manager, active
|
|
FROM employees
|
|
WHERE feishu_user_id = ?
|
|
""",
|
|
(feishu_user_id,),
|
|
).fetchone()
|
|
return dict(row) if row else None
|
|
|
|
def upsert_report(self, report: dict[str, Any]) -> None:
|
|
with self._lock, self.connection:
|
|
self.connection.execute(
|
|
"""
|
|
INSERT INTO daily_reports (
|
|
feishu_user_id, employee_name, report_date, today_done, tomorrow_plan,
|
|
blockers, help_needed, submitted_at, updated_at
|
|
)
|
|
VALUES (
|
|
:feishu_user_id, :employee_name, :report_date, :today_done, :tomorrow_plan,
|
|
:blockers, :help_needed, :submitted_at, :updated_at
|
|
)
|
|
ON CONFLICT(feishu_user_id, report_date) DO UPDATE SET
|
|
employee_name = excluded.employee_name,
|
|
today_done = excluded.today_done,
|
|
tomorrow_plan = excluded.tomorrow_plan,
|
|
blockers = excluded.blockers,
|
|
help_needed = excluded.help_needed,
|
|
updated_at = excluded.updated_at
|
|
""",
|
|
report,
|
|
)
|
|
|
|
def list_reports_for_date(self, report_date: str) -> list[dict[str, Any]]:
|
|
with self._lock:
|
|
rows = self.connection.execute(
|
|
"""
|
|
SELECT id, feishu_user_id, employee_name, report_date, today_done,
|
|
tomorrow_plan, blockers, help_needed, submitted_at, updated_at
|
|
FROM daily_reports
|
|
WHERE report_date = ?
|
|
ORDER BY employee_name
|
|
""",
|
|
(report_date,),
|
|
).fetchall()
|
|
return [dict(row) for row in rows]
|
|
|
|
def list_reports_for_employee(self, feishu_user_id: str, limit: int = 10) -> list[dict[str, Any]]:
|
|
with self._lock:
|
|
rows = self.connection.execute(
|
|
"""
|
|
SELECT id, feishu_user_id, employee_name, report_date, today_done,
|
|
tomorrow_plan, blockers, help_needed, submitted_at, updated_at
|
|
FROM daily_reports
|
|
WHERE feishu_user_id = ?
|
|
ORDER BY report_date DESC, updated_at DESC
|
|
LIMIT ?
|
|
""",
|
|
(feishu_user_id, limit),
|
|
).fetchall()
|
|
return [dict(row) for row in rows]
|