101 lines
3.6 KiB
Python
101 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
import csv
|
|
import io
|
|
from datetime import datetime, timezone
|
|
from typing import Any, Callable
|
|
|
|
from .db import Database
|
|
|
|
|
|
Clock = Callable[[], datetime]
|
|
|
|
|
|
def _required_text(value: Any, field_name: str) -> str:
|
|
text = str(value or "").strip()
|
|
if not text:
|
|
raise ValueError(f"{field_name} is required")
|
|
return text
|
|
|
|
|
|
def _optional_text(value: Any) -> str:
|
|
return str(value or "").strip()
|
|
|
|
|
|
class ReportService:
|
|
def __init__(self, database: Database, clock: Clock | None = None):
|
|
self.database = database
|
|
self.clock = clock or (lambda: datetime.now(timezone.utc))
|
|
|
|
def upsert_report(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
feishu_user_id = _required_text(data.get("feishu_user_id"), "feishu_user_id")
|
|
report_date = _required_text(data.get("report_date"), "report_date")
|
|
today_done = _required_text(data.get("today_done"), "today_done")
|
|
tomorrow_plan = _required_text(data.get("tomorrow_plan"), "tomorrow_plan")
|
|
employee = self.database.find_employee(feishu_user_id)
|
|
if not employee or employee.get("active") != 1:
|
|
raise ValueError("employee is not active")
|
|
|
|
now = self.clock().isoformat()
|
|
self.database.upsert_report(
|
|
{
|
|
"feishu_user_id": feishu_user_id,
|
|
"employee_name": employee["name"],
|
|
"report_date": report_date,
|
|
"today_done": today_done,
|
|
"tomorrow_plan": tomorrow_plan,
|
|
"blockers": _optional_text(data.get("blockers")),
|
|
"help_needed": _optional_text(data.get("help_needed")),
|
|
"submitted_at": now,
|
|
"updated_at": now,
|
|
}
|
|
)
|
|
return next(
|
|
report
|
|
for report in self.list_reports_for_date(report_date)["reports"]
|
|
if report["feishu_user_id"] == feishu_user_id
|
|
)
|
|
|
|
def list_reports_for_date(self, report_date: str) -> dict[str, Any]:
|
|
date = _required_text(report_date, "report_date")
|
|
employees = self.database.list_active_employees()
|
|
reports = self.database.list_reports_for_date(date)
|
|
submitted_ids = {report["feishu_user_id"] for report in reports}
|
|
missing = [employee for employee in employees if employee["feishu_user_id"] not in submitted_ids]
|
|
return {
|
|
"date": date,
|
|
"expectedCount": len(employees),
|
|
"submittedCount": len(reports),
|
|
"missingCount": len(missing),
|
|
"reports": reports,
|
|
"missing": missing,
|
|
}
|
|
|
|
def export_reports_csv(self, report_date: str) -> str:
|
|
result = self.list_reports_for_date(report_date)
|
|
output = io.StringIO()
|
|
fieldnames = [
|
|
"employee_name",
|
|
"report_date",
|
|
"today_done",
|
|
"tomorrow_plan",
|
|
"blockers",
|
|
"help_needed",
|
|
"submitted_at",
|
|
]
|
|
writer = csv.DictWriter(output, fieldnames=fieldnames, extrasaction="ignore")
|
|
writer.writeheader()
|
|
writer.writerows(result["reports"])
|
|
return output.getvalue()
|
|
|
|
def list_employee_history(self, feishu_user_id: str, limit: int = 10) -> dict[str, Any]:
|
|
user_id = _required_text(feishu_user_id, "feishu_user_id")
|
|
employee = self.database.find_employee(user_id)
|
|
if not employee or employee.get("active") != 1:
|
|
raise ValueError("employee is not active")
|
|
|
|
return {
|
|
"employee": employee,
|
|
"reports": self.database.list_reports_for_employee(user_id, limit),
|
|
}
|