feat: add soft delete for import batches
This commit is contained in:
@@ -16,7 +16,13 @@ from .config import make_slug
|
||||
|
||||
|
||||
DEFAULT_BUSY_TIMEOUT_MS = 5000
|
||||
VALID_BATCH_FIELDS = {"source_files_json", "status", "note"}
|
||||
VALID_BATCH_FIELDS = {
|
||||
"source_files_json",
|
||||
"status",
|
||||
"note",
|
||||
"deleted_at",
|
||||
"deleted_reason",
|
||||
}
|
||||
VALID_ACCOUNT_FIELDS = {
|
||||
"account_name",
|
||||
"alias",
|
||||
@@ -61,6 +67,8 @@ class Batch:
|
||||
note: Optional[str]
|
||||
created_at: str
|
||||
updated_at: str
|
||||
deleted_at: Optional[str]
|
||||
deleted_reason: Optional[str]
|
||||
|
||||
@property
|
||||
def source_files(self) -> list[str]:
|
||||
@@ -157,7 +165,9 @@ CREATE TABLE IF NOT EXISTS batches (
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
note TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
updated_at TEXT NOT NULL,
|
||||
deleted_at TEXT,
|
||||
deleted_reason TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS accounts (
|
||||
@@ -313,8 +323,16 @@ def init_db(path=None, conn=None) -> None:
|
||||
with _connection(conn, path) as database:
|
||||
with database:
|
||||
database.executescript(SCHEMA_SQL)
|
||||
_ensure_batch_delete_columns(database)
|
||||
|
||||
|
||||
def _ensure_batch_delete_columns(database):
|
||||
columns = {row["name"] for row in database.execute("PRAGMA table_info(batches)").fetchall()}
|
||||
if "deleted_at" not in columns:
|
||||
database.execute("ALTER TABLE batches ADD COLUMN deleted_at TEXT")
|
||||
if "deleted_reason" not in columns:
|
||||
database.execute("ALTER TABLE batches ADD COLUMN deleted_reason TEXT")
|
||||
|
||||
def create_batch(file_paths: Iterable[str], note=None, path=None, conn=None) -> str:
|
||||
batch_id = datetime.now().strftime("%Y%m%d_%H%M%S_") + uuid.uuid4().hex[:8]
|
||||
files = [os.path.abspath(file_path) for file_path in file_paths]
|
||||
@@ -332,22 +350,26 @@ def create_batch(file_paths: Iterable[str], note=None, path=None, conn=None) ->
|
||||
return batch_id
|
||||
|
||||
|
||||
def get_batch(batch_id, path=None, conn=None):
|
||||
def get_batch(batch_id, path=None, conn=None, include_deleted=False):
|
||||
sql = "SELECT * FROM batches WHERE id = ?"
|
||||
params = [batch_id]
|
||||
if not include_deleted:
|
||||
sql += " AND deleted_at IS NULL"
|
||||
with _connection(conn, path) as database:
|
||||
return _fetch_one(
|
||||
database,
|
||||
"SELECT * FROM batches WHERE id = ?",
|
||||
(batch_id,),
|
||||
Batch,
|
||||
)
|
||||
return _fetch_one(database, sql, params, Batch)
|
||||
|
||||
|
||||
def list_batches(status=None, path=None, conn=None):
|
||||
sql = "SELECT * FROM batches"
|
||||
def list_batches(status=None, path=None, conn=None, include_deleted=False):
|
||||
clauses = []
|
||||
params = []
|
||||
if status is not None:
|
||||
sql += " WHERE status = ?"
|
||||
clauses.append("status = ?")
|
||||
params.append(status)
|
||||
if not include_deleted:
|
||||
clauses.append("deleted_at IS NULL")
|
||||
sql = "SELECT * FROM batches"
|
||||
if clauses:
|
||||
sql += " WHERE " + " AND ".join(clauses)
|
||||
sql += " ORDER BY created_at DESC, id DESC"
|
||||
with _connection(conn, path) as database:
|
||||
return _fetch_all(database, sql, params, Batch)
|
||||
@@ -497,35 +519,78 @@ def insert_tasks(batch_id, rows, path=None, conn=None) -> int:
|
||||
return len(values)
|
||||
|
||||
|
||||
def list_tasks(batch_id=None, stage=None, status=None, alias=None, path=None, conn=None):
|
||||
def list_tasks(
|
||||
batch_id=None,
|
||||
stage=None,
|
||||
status=None,
|
||||
alias=None,
|
||||
path=None,
|
||||
conn=None,
|
||||
include_deleted=False,
|
||||
):
|
||||
clauses = []
|
||||
params = []
|
||||
filters = {
|
||||
"batch_id": batch_id,
|
||||
"stage": stage,
|
||||
"status": status,
|
||||
"alias": alias,
|
||||
"t.batch_id": batch_id,
|
||||
"t.stage": stage,
|
||||
"t.status": status,
|
||||
"t.alias": alias,
|
||||
}
|
||||
for field, value in filters.items():
|
||||
if value is not None:
|
||||
clauses.append(f"{field} = ?")
|
||||
params.append(value)
|
||||
sql = "SELECT * FROM tasks"
|
||||
if not include_deleted:
|
||||
clauses.append("b.deleted_at IS NULL")
|
||||
sql = "SELECT t.* FROM tasks t JOIN batches b ON b.id = t.batch_id"
|
||||
if clauses:
|
||||
sql += " WHERE " + " AND ".join(clauses)
|
||||
sql += " ORDER BY id"
|
||||
sql += " ORDER BY t.id"
|
||||
with _connection(conn, path) as database:
|
||||
return _fetch_all(database, sql, params, Task)
|
||||
|
||||
|
||||
def get_task(task_id, path=None, conn=None):
|
||||
def delete_batch(batch_id, reason=None, path=None, conn=None) -> dict:
|
||||
"""Soft delete a batch so it disappears from normal UI and workflows."""
|
||||
|
||||
with _connection(conn, path) as database:
|
||||
return _fetch_one(
|
||||
database,
|
||||
"SELECT * FROM tasks WHERE id = ?",
|
||||
(int(task_id),),
|
||||
Task,
|
||||
)
|
||||
batch = get_batch(batch_id, conn=database)
|
||||
if batch is None:
|
||||
raise DbError(f"批次不存在或已删除: {batch_id}")
|
||||
tasks = list_tasks(batch_id=batch_id, conn=database)
|
||||
image_paths = []
|
||||
for task in tasks:
|
||||
for image_path in (task.old_cover_path, task.new_cover_path):
|
||||
if image_path and image_path not in image_paths:
|
||||
image_paths.append(image_path)
|
||||
committed_count = sum(1 for task in tasks if int(task.committed or 0) == 1)
|
||||
now = _now()
|
||||
with database:
|
||||
database.execute(
|
||||
"""
|
||||
UPDATE batches
|
||||
SET deleted_at = ?, deleted_reason = ?, updated_at = ?
|
||||
WHERE id = ? AND deleted_at IS NULL
|
||||
""",
|
||||
(now, str(reason or ""), now, batch_id),
|
||||
)
|
||||
return {
|
||||
"batch_id": batch_id,
|
||||
"deleted_at": now,
|
||||
"task_count": len(tasks),
|
||||
"committed_count": committed_count,
|
||||
"image_paths": image_paths,
|
||||
}
|
||||
|
||||
def get_task(task_id, path=None, conn=None, include_deleted=False):
|
||||
clauses = ["t.id = ?"]
|
||||
params = [int(task_id)]
|
||||
if not include_deleted:
|
||||
clauses.append("b.deleted_at IS NULL")
|
||||
sql = "SELECT t.* FROM tasks t JOIN batches b ON b.id = t.batch_id"
|
||||
sql += " WHERE " + " AND ".join(clauses)
|
||||
with _connection(conn, path) as database:
|
||||
return _fetch_one(database, sql, params, Task)
|
||||
|
||||
|
||||
def mark_running(task_id, phase, path=None, conn=None) -> None:
|
||||
|
||||
Reference in New Issue
Block a user