diff --git a/database.py b/database.py index badcd27..958d557 100644 --- a/database.py +++ b/database.py @@ -203,7 +203,22 @@ class DatabaseManager: recipients TEXT NOT NULL, -- JSON: список vk_id, либо строка "all" created_by INTEGER NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - active INTEGER NOT NULL DEFAULT 1 + active INTEGER NOT NULL DEFAULT 1, + paused INTEGER NOT NULL DEFAULT 0 + ) + """) + # Миграция: колонка paused для уже существующих БД (созданных до этой фичи) + try: + cursor.execute("ALTER TABLE reminders ADD COLUMN paused INTEGER NOT NULL DEFAULT 0") + except sqlite3.OperationalError: + pass + + # Table for per-admin settings (пока только часовой пояс для напоминалок) + cursor.execute(""" + CREATE TABLE IF NOT EXISTS admin_settings ( + user_id INTEGER PRIMARY KEY, + tz_offset INTEGER NOT NULL, -- смещение от UTC в часах, напр. 3 для Москвы + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """) @@ -647,19 +662,19 @@ class DatabaseManager: conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute( - "SELECT id, text, remind_at, next_at, recurrence, recipients, created_by " + "SELECT id, text, remind_at, next_at, recurrence, recipients, created_by, paused " "FROM reminders WHERE active = 1 ORDER BY next_at" ) return self._rows_with_parsed_recipients(cursor) def get_due_reminders(self, now_str: str) -> list: - """Активные напоминания, чьё время (next_at) уже наступило.""" + """Активные напоминания (не на паузе), чьё время (next_at) уже наступило.""" with self._get_connection() as conn: conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute( - "SELECT id, text, remind_at, next_at, recurrence, recipients, created_by " - "FROM reminders WHERE active = 1 AND next_at <= ?", (now_str,) + "SELECT id, text, remind_at, next_at, recurrence, recipients, created_by, paused " + "FROM reminders WHERE active = 1 AND paused = 0 AND next_at <= ?", (now_str,) ) return self._rows_with_parsed_recipients(cursor) @@ -682,6 +697,33 @@ class DatabaseManager: conn.commit() return cursor.rowcount > 0 + def set_reminder_paused(self, reminder_id: int, paused: bool): + with self._get_connection() as conn: + cursor = conn.cursor() + cursor.execute("UPDATE reminders SET paused = ? WHERE id = ?", + (1 if paused else 0, reminder_id)) + conn.commit() + + # --- Часовой пояс админов (для корректного показа/ввода времени напоминалок) --- + def get_admin_tz_offset(self, user_id: int) -> Optional[int]: + """Смещение от UTC в часах, которое задал себе админ, или None, если ещё не задавал.""" + with self._get_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT tz_offset FROM admin_settings WHERE user_id = ?", (user_id,)) + row = cursor.fetchone() + return row[0] if row else None + + def set_admin_tz_offset(self, user_id: int, tz_offset: int): + with self._get_connection() as conn: + cursor = conn.cursor() + cursor.execute( + "INSERT INTO admin_settings (user_id, tz_offset) VALUES (?, ?) " + "ON CONFLICT(user_id) DO UPDATE SET tz_offset = excluded.tz_offset, " + "updated_at = CURRENT_TIMESTAMP", + (user_id, tz_offset) + ) + conn.commit() + # --- Быстрые ответы (custom FAQ) --- def add_custom_faq(self, keyword: str, answer: str) -> int: with self._get_connection() as conn: diff --git a/tests/test_database.py b/tests/test_database.py index 4a7ce4c..fb6ac0b 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -83,3 +83,26 @@ def test_reminders_all_recipients_and_delete(tmp_db): assert r["recipients"] == "all" assert tmp_db.delete_reminder(rid) assert not tmp_db.delete_reminder(rid) # уже удалено, повторное удаление — False + + +def test_admin_tz_offset(tmp_db): + assert tmp_db.get_admin_tz_offset(999) is None + tmp_db.set_admin_tz_offset(999, 3) + assert tmp_db.get_admin_tz_offset(999) == 3 + tmp_db.set_admin_tz_offset(999, -5) # перезапись существующего значения + assert tmp_db.get_admin_tz_offset(999) == -5 + + +def test_reminder_pause_excludes_from_due_but_stays_visible(tmp_db): + rid = tmp_db.add_reminder("Продлить домен", "2020-01-01 09:00:00", "once", "all", created_by=1) + assert tmp_db.get_due_reminders("2020-01-01 09:00:00") # просрочено — должно найтись + + tmp_db.set_reminder_paused(rid, True) + assert tmp_db.get_due_reminders("2020-01-01 09:00:00") == [] # на паузе — не должно + row = next(r for r in tmp_db.get_active_reminders() if r["id"] == rid) + assert row["paused"] == 1 # но в общем списке всё ещё видно + + tmp_db.set_reminder_paused(rid, False) + assert tmp_db.get_due_reminders("2020-01-01 09:00:00") # сняли паузу — снова попадает в due + row = next(r for r in tmp_db.get_active_reminders() if r["id"] == rid) + assert row["paused"] == 0