kaikai_test/daily_report/report_service.py
2026-05-07 16:31:56 +08:00

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),
}