""" =============================================================================== FILE: services/tasks/repository.py PROJECT: SCUD Orion AI (Unified Architecture) MODULE: services / tasks ROLE: Низкоуровневые операции к таблице tasks в SQLite (CRUD). AI-CONTEXT-ANCHORS: - ANCHOR[TASK_REPO_GET]: Выборка задач с фильтрацией по статусу и пользователю. - ANCHOR[TASK_REPO_ADD]: Вставка новой задачи со сквозным ID. - ANCHOR[TASK_REPO_UPDATE]: Обновление реквизитов и статуса задачи. - ANCHOR[TASK_REPO_DELETE]: Удаление задачи по числовому или строковому ID. =============================================================================== """ import re from typing import List, Dict, Any, Optional from core.connection import get_connection def normalize_task_id(task_id_input: str) -> str: """Нормализует идентификатор задачи к формату TASK-XX.""" if not task_id_input: return "" clean_id = str(task_id_input).strip().upper().replace("TASK-", "").replace("TASK", "").replace("#", "") if clean_id.isdigit(): num = int(clean_id) return f"TASK-{num:02d}" if num < 100 else f"TASK-{num:03d}" return f"TASK-{clean_id}" # ANCHOR[TASK_REPO_GET] def repo_get_tasks(user_id: int, status: Optional[str] = None) -> List[Dict[str, Any]]: """Получает список задач пользователя с опциональной фильтрацией по статусу.""" conn = get_connection(row_factory=True) cursor = conn.cursor() if status and status.upper() != "ALL": target_status = status.upper() if target_status in ["PROGRESS", "В РАБОТЕ"]: target_status = "IN_PROGRESS" elif target_status in ["DONE", "ГОТОВО"]: target_status = "COMPLETED" elif target_status in ["PLANNED", "ПЛАНЫ"]: target_status = "BACKLOG" cursor.execute(""" SELECT id, task_id, module, title, priority, status, due_date, created_at FROM tasks WHERE user_id = ? AND (status = ? OR (status = 'BACKLOG' AND ? = 'PLANNED')) ORDER BY id DESC """, (user_id, target_status, target_status)) else: cursor.execute(""" SELECT id, task_id, module, title, priority, status, due_date, created_at FROM tasks WHERE user_id = ? ORDER BY id DESC """, (user_id,)) rows = cursor.fetchall() conn.close() return [dict(r) for r in rows] # ANCHOR[TASK_REPO_ADD] def repo_add_task( user_id: int, module: str, title: str, priority: str = "MEDIUM", due_date: Optional[str] = None, status: str = "BACKLOG" ) -> Dict[str, Any]: """Добавляет новую задачу в SQLite с автогенерацией порядкового TASK-ID.""" conn = get_connection() cursor = conn.cursor() cursor.execute("SELECT MAX(id) FROM tasks") max_id = cursor.fetchone()[0] or 0 new_task_id = f"TASK-{(max_id + 1):02d}" target_status = status.upper() if status else "BACKLOG" if target_status in ["PROGRESS", "В РАБОТЕ"]: target_status = "IN_PROGRESS" elif target_status in ["DONE", "ГОТОВО"]: target_status = "COMPLETED" elif target_status in ["PLANNED", "ПЛАНЫ", "BACKLOG"]: target_status = "BACKLOG" cursor.execute(""" INSERT INTO tasks (task_id, module, title, priority, status, due_date, user_id) VALUES (?, ?, ?, ?, ?, ?, ?) """, (new_task_id, module or "general", title.strip(), priority.upper(), target_status, due_date, user_id)) conn.commit() conn.close() return {"status": "success", "task_id": new_task_id, "id": max_id + 1} # ANCHOR[TASK_REPO_UPDATE] def repo_update_task( user_id: int, task_id: str, title: Optional[str] = None, priority: Optional[str] = None, status: Optional[str] = None, due_date: Optional[str] = None ) -> Dict[str, Any]: """Комплексное обновление атрибутов задачи.""" conn = get_connection() cursor = conn.cursor() clean_num = re.sub(r'\D', '', str(task_id)) formatted_id = normalize_task_id(task_id) updates = [] params = [] if title is not None and title.strip(): updates.append("title = ?") params.append(title.strip()) if priority is not None and priority.strip(): updates.append("priority = ?") params.append(priority.strip().upper()) if status is not None and status.strip(): target_status = status.strip().upper() if target_status in ["PROGRESS", "В РАБОТЕ"]: target_status = "IN_PROGRESS" elif target_status in ["DONE", "ГОТОВО"]: target_status = "COMPLETED" elif target_status in ["PLANNED", "ПЛАНЫ"]: target_status = "BACKLOG" updates.append("status = ?") params.append(target_status) if due_date is not None: updates.append("due_date = ?") params.append(due_date.strip() if due_date.strip() else None) if not updates: conn.close() return {"status": "success", "message": "Нет данных для обновления"} params.extend([clean_num, formatted_id, f"%{task_id.strip()}", user_id]) sql = f""" UPDATE tasks SET {', '.join(updates)} WHERE (id = ? OR UPPER(task_id) = ? OR task_id LIKE ?) AND user_id = ? """ cursor.execute(sql, params) rows_affected = cursor.rowcount conn.commit() conn.close() if rows_affected == 0: return {"error": f"Задача {task_id} не найдена или принадлежит другому пользователю"} return {"status": "success", "message": f"Задача #{task_id} успешно обновлена"} # ANCHOR[TASK_REPO_DELETE] def repo_delete_task(user_id: int, task_id: str) -> Dict[str, Any]: """Удаляет задачу по номеру ID.""" conn = get_connection() cursor = conn.cursor() clean_num = re.sub(r'\D', '', str(task_id)) formatted_id = normalize_task_id(task_id) cursor.execute(""" DELETE FROM tasks WHERE (id = ? OR UPPER(task_id) = ? OR task_id LIKE ?) AND user_id = ? """, (clean_num, formatted_id, f"%{task_id.strip()}", user_id)) deleted = cursor.rowcount conn.commit() conn.close() if deleted == 0: return {"error": f"Задача {task_id} не найдена"} return {"status": "success", "message": f"Задача #{task_id} удалена"}