diff --git a/daily_report/db.py b/daily_report/db.py index 18c2c4d..07f3c33 100644 --- a/daily_report/db.py +++ b/daily_report/db.py @@ -120,6 +120,18 @@ class Database: ).fetchall() return [dict(row) for row in rows] + def list_report_required_employees(self) -> list[dict[str, Any]]: + with self._lock: + rows = self.connection.execute( + """ + SELECT feishu_user_id, name, department, manager, role, active + FROM employees + WHERE active = 1 AND role != 'admin' + 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( diff --git a/daily_report/report_service.py b/daily_report/report_service.py index a95501d..16f9df4 100644 --- a/daily_report/report_service.py +++ b/daily_report/report_service.py @@ -61,14 +61,19 @@ class ReportService: ) return next( report - for report in self.list_reports_for_date(report_date)["reports"] + for report in self.database.list_reports_for_employee(feishu_user_id, 1) 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) + employees = self.database.list_report_required_employees() + required_ids = {employee["feishu_user_id"] for employee in employees} + reports = [ + report + for report in self.database.list_reports_for_date(date) + if report["feishu_user_id"] in required_ids + ] 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 { diff --git a/tests/test_report_service.py b/tests/test_report_service.py index 3c11523..e9354ae 100644 --- a/tests/test_report_service.py +++ b/tests/test_report_service.py @@ -120,6 +120,32 @@ class ReportServiceTest(unittest.TestCase): finally: db.close() + def test_admins_are_not_required_to_submit_reports(self) -> None: + db = make_database( + [ + {"feishu_user_id": "u_admin", "name": "Admin", "active": True, "role": "admin"}, + {"feishu_user_id": "u_staff", "name": "Staff", "active": True, "role": "staff"}, + ] + ) + service = ReportService(db, clock=lambda: datetime(2026, 5, 7, 10, tzinfo=timezone.utc)) + try: + service.upsert_report( + { + "feishu_user_id": "u_admin", + "report_date": "2026-05-07", + "today_done": "1. 查看汇总", + "tomorrow_plan": "1. 继续跟进", + } + ) + result = service.list_reports_for_date("2026-05-07") + self.assertEqual(result["expectedCount"], 1) + self.assertEqual(result["submittedCount"], 0) + self.assertEqual(result["missingCount"], 1) + self.assertEqual(result["missing"][0]["name"], "Staff") + self.assertEqual(result["reports"], []) + finally: + db.close() + if __name__ == "__main__": unittest.main() diff --git a/tests/test_scheduled.py b/tests/test_scheduled.py index 60933e2..be7c5d7 100644 --- a/tests/test_scheduled.py +++ b/tests/test_scheduled.py @@ -95,6 +95,44 @@ class ScheduledTest(unittest.TestCase): self.assertEqual(result["mode"], "bot_private") self.assertEqual([item[1] for item in sent], ["ou_2", "ou_1"]) + def test_send_reminder_skips_admins(self) -> None: + with tempfile.TemporaryDirectory(prefix="daily-report-scheduled-") as temp: + root = Path(temp) + seed_path = root / "employees.json" + seed_path.write_text( + json.dumps( + [ + {"feishu_user_id": "ou_admin", "name": "Admin", "active": True, "role": "admin"}, + {"feishu_user_id": "ou_staff", "name": "Staff", "active": True, "role": "staff"}, + ], + ensure_ascii=False, + ), + encoding="utf-8", + ) + fake_file = root / "daily_report" / "config.py" + fake_file.parent.mkdir() + fake_file.write_text("", encoding="utf-8") + env = { + "DATABASE_PATH": "test.sqlite", + "EMPLOYEE_SEED_PATH": "employees.json", + "BASE_URL": "http://localhost:8787", + "FEISHU_APP_ID": "cli_test", + "FEISHU_APP_SECRET": "secret", + } + sent = [] + + with patch("daily_report.config.__file__", str(fake_file)), patch.dict("os.environ", env, clear=True), patch( + "daily_report.scheduled.get_tenant_access_token", lambda app_id, app_secret: "tenant-token" + ), patch( + "daily_report.scheduled.send_bot_interactive_message", + lambda token, receive_id, payload: sent.append(receive_id) or {"ok": True}, + ), patch("daily_report.scheduled.date") as fake_date: + fake_date.today.return_value = date(2026, 5, 7) + result = scheduled.send_reminder() + + self.assertEqual(result["mode"], "bot_private") + self.assertEqual(sent, ["ou_staff"]) + def test_send_summary_private_messages_admins_only(self) -> None: with tempfile.TemporaryDirectory(prefix="daily-report-scheduled-") as temp: root = Path(temp) diff --git a/tests/test_web.py b/tests/test_web.py index 82e89cd..9c0e012 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -137,15 +137,15 @@ class WebTest(unittest.TestCase): db.upsert_employee( { - "feishu_user_id": "u_1", - "name": "Lin", + "feishu_user_id": "u_admin", + "name": "Admin", "department": "Design", "manager": "", "active": True, "role": "admin", } ) - status, body = get_with_cookie(f"{base_url}/api/reports?date=2026-05-07", admin_cookie()) + status, body = get_with_cookie(f"{base_url}/api/reports?date=2026-05-07", admin_cookie("u_admin", "Admin")) summary = json.loads(body) self.assertEqual(status, 200) self.assertEqual(summary["submittedCount"], 1)