feat: exclude admins from report reminders
This commit is contained in:
parent
b538e2f8cb
commit
9a00bcf5d2
@ -120,6 +120,18 @@ class Database:
|
|||||||
).fetchall()
|
).fetchall()
|
||||||
return [dict(row) for row in rows]
|
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:
|
def find_employee(self, feishu_user_id: str) -> dict[str, Any] | None:
|
||||||
with self._lock:
|
with self._lock:
|
||||||
row = self.connection.execute(
|
row = self.connection.execute(
|
||||||
|
|||||||
@ -61,14 +61,19 @@ class ReportService:
|
|||||||
)
|
)
|
||||||
return next(
|
return next(
|
||||||
report
|
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
|
if report["feishu_user_id"] == feishu_user_id
|
||||||
)
|
)
|
||||||
|
|
||||||
def list_reports_for_date(self, report_date: str) -> dict[str, Any]:
|
def list_reports_for_date(self, report_date: str) -> dict[str, Any]:
|
||||||
date = _required_text(report_date, "report_date")
|
date = _required_text(report_date, "report_date")
|
||||||
employees = self.database.list_active_employees()
|
employees = self.database.list_report_required_employees()
|
||||||
reports = self.database.list_reports_for_date(date)
|
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}
|
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]
|
missing = [employee for employee in employees if employee["feishu_user_id"] not in submitted_ids]
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -120,6 +120,32 @@ class ReportServiceTest(unittest.TestCase):
|
|||||||
finally:
|
finally:
|
||||||
db.close()
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@ -95,6 +95,44 @@ class ScheduledTest(unittest.TestCase):
|
|||||||
self.assertEqual(result["mode"], "bot_private")
|
self.assertEqual(result["mode"], "bot_private")
|
||||||
self.assertEqual([item[1] for item in sent], ["ou_2", "ou_1"])
|
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:
|
def test_send_summary_private_messages_admins_only(self) -> None:
|
||||||
with tempfile.TemporaryDirectory(prefix="daily-report-scheduled-") as temp:
|
with tempfile.TemporaryDirectory(prefix="daily-report-scheduled-") as temp:
|
||||||
root = Path(temp)
|
root = Path(temp)
|
||||||
|
|||||||
@ -137,15 +137,15 @@ class WebTest(unittest.TestCase):
|
|||||||
|
|
||||||
db.upsert_employee(
|
db.upsert_employee(
|
||||||
{
|
{
|
||||||
"feishu_user_id": "u_1",
|
"feishu_user_id": "u_admin",
|
||||||
"name": "Lin",
|
"name": "Admin",
|
||||||
"department": "Design",
|
"department": "Design",
|
||||||
"manager": "",
|
"manager": "",
|
||||||
"active": True,
|
"active": True,
|
||||||
"role": "admin",
|
"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)
|
summary = json.loads(body)
|
||||||
self.assertEqual(status, 200)
|
self.assertEqual(status, 200)
|
||||||
self.assertEqual(summary["submittedCount"], 1)
|
self.assertEqual(summary["submittedCount"], 1)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user