feat(etl): stable pipeline, exception registry in SQLite, multi-pass aggregation and db_cli
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
"""
|
||||
===============================================================================
|
||||
FILE: services/tasks/repository.py
|
||||
===============================================================================
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import List, Dict, Any, Optional
|
||||
from core.connection import get_connection
|
||||
|
||||
|
||||
def normalize_task_id(task_id_input: str) -> str:
|
||||
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}"
|
||||
|
||||
|
||||
def repo_get_tasks(user_id: int, status: Optional[str] = None) -> List[Dict[str, Any]]:
|
||||
with get_connection(row_factory=True) as conn:
|
||||
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()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
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]:
|
||||
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"
|
||||
|
||||
with get_connection() as conn:
|
||||
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}"
|
||||
|
||||
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()
|
||||
return {"status": "success", "task_id": new_task_id, "id": max_id + 1}
|
||||
|
||||
|
||||
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]:
|
||||
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:
|
||||
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 = ?
|
||||
"""
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(sql, params)
|
||||
rows_affected = cursor.rowcount
|
||||
conn.commit()
|
||||
|
||||
if rows_affected == 0:
|
||||
return {"error": f"Задача {task_id} не найдена или принадлежит другому пользователю"}
|
||||
|
||||
return {"status": "success", "message": f"Задача #{task_id} успешно обновлена"}
|
||||
|
||||
|
||||
def repo_delete_task(user_id: int, task_id: str) -> Dict[str, Any]:
|
||||
clean_num = re.sub(r'\D', '', str(task_id))
|
||||
formatted_id = normalize_task_id(task_id)
|
||||
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
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()
|
||||
|
||||
if deleted == 0:
|
||||
return {"error": f"Задача {task_id} не найдена"}
|
||||
return {"status": "success", "message": f"Задача #{task_id} удалена"}
|
||||
Reference in New Issue
Block a user