feat: 完成T-403结果回写与汇总
新增excel.write_back_results,按原Excel文件、工作表和行号回写新标题、新封面图片路径与更新状态。 ③更新完成后自动用WriteBackWorker(mode=results)回写结果,并弹窗汇总成功、失败、略过数量;文件被占用时提示关闭Excel后手动重试。 补充Excel结果回写、GUI自动回写/汇总/锁文件提示和模块契约测试,同步任务看板、API、流程和当前状态文档。
This commit is contained in:
+59
-1
@@ -32,7 +32,10 @@ OUTPUT_HEADERS = {
|
||||
"update_status": "更新状态",
|
||||
}
|
||||
OLD_WRITE_BACK_FIELDS = ("old_title", "old_cover_path")
|
||||
RESULT_WRITE_BACK_FIELDS = ("new_title", "new_cover_path", "update_status")
|
||||
WRITEABLE_STAGES = {"collected", "generated", "applied"}
|
||||
RESULT_WRITEABLE_STAGES = {"generated", "applied"}
|
||||
RESULT_WRITEABLE_STATUSES = {"failed", "skipped", "cancelled"}
|
||||
LOCK_WINERRORS = {5, 32, 33}
|
||||
LOCK_ERRNOS = {13, 16}
|
||||
|
||||
@@ -310,7 +313,21 @@ def _filter_tasks_by_excel_path(tasks, excel_path):
|
||||
return filtered
|
||||
|
||||
|
||||
def _is_result_fields(fields) -> bool:
|
||||
return any(field in RESULT_WRITE_BACK_FIELDS for field in fields)
|
||||
|
||||
|
||||
def _has_write_back_data(task, fields) -> bool:
|
||||
if _is_result_fields(fields):
|
||||
if getattr(task, "stage", None) in RESULT_WRITEABLE_STAGES:
|
||||
return True
|
||||
if getattr(task, "status", None) in RESULT_WRITEABLE_STATUSES:
|
||||
return True
|
||||
return any(
|
||||
getattr(task, field, None) not in (None, "")
|
||||
for field in ("new_title", "new_cover_path")
|
||||
if field in fields
|
||||
)
|
||||
if getattr(task, "stage", None) in WRITEABLE_STAGES:
|
||||
return True
|
||||
return any(getattr(task, field, None) not in (None, "") for field in fields)
|
||||
@@ -367,6 +384,32 @@ def _ensure_output_columns(sheet, fields):
|
||||
return columns
|
||||
|
||||
|
||||
def _update_status(task) -> str:
|
||||
error = getattr(task, "last_error", None)
|
||||
status = getattr(task, "status", None)
|
||||
stage = getattr(task, "stage", None)
|
||||
committed = bool(getattr(task, "committed", 0))
|
||||
if stage == "applied" and committed:
|
||||
return "成功"
|
||||
if status == "failed":
|
||||
return f"失败:{error}" if error else "失败"
|
||||
if status == "skipped":
|
||||
return f"略过:{error}" if error else "略过"
|
||||
if status == "running":
|
||||
return "处理中"
|
||||
if status == "cancelled":
|
||||
return f"已取消:{error}" if error else "已取消"
|
||||
if stage == "generated":
|
||||
return "待更新"
|
||||
return ""
|
||||
|
||||
|
||||
def _field_value(task, field):
|
||||
if field == "update_status":
|
||||
return _update_status(task)
|
||||
return getattr(task, field, None) or ""
|
||||
|
||||
|
||||
def _write_tasks_to_workbook(workbook, tasks, fields) -> int:
|
||||
rows = 0
|
||||
missing_sheets = []
|
||||
@@ -387,7 +430,7 @@ def _write_tasks_to_workbook(workbook, tasks, fields) -> int:
|
||||
sheet.cell(
|
||||
row=int(task.source_row),
|
||||
column=columns[field],
|
||||
).value = getattr(task, field, None) or ""
|
||||
).value = _field_value(task, field)
|
||||
rows += 1
|
||||
|
||||
if missing_sheets:
|
||||
@@ -450,6 +493,21 @@ def write_back(batch_id, excel_path=None, path=None) -> dict:
|
||||
)
|
||||
|
||||
|
||||
def write_back_results(batch_id, excel_path=None, path=None) -> dict:
|
||||
"""Write generated title/cover and update status back to the original Excel file."""
|
||||
|
||||
_require_openpyxl()
|
||||
fields = RESULT_WRITE_BACK_FIELDS
|
||||
tasks = _filter_tasks_by_excel_path(_batch_tasks(batch_id, path=path), excel_path)
|
||||
groups = _group_by_source_file(tasks, fields)
|
||||
return _write_result(
|
||||
batch_id,
|
||||
groups,
|
||||
lambda source_path: source_path,
|
||||
fields,
|
||||
)
|
||||
|
||||
|
||||
def _is_excel_output_path(path_value) -> bool:
|
||||
return _excel_suffix(path_value) in {".xlsx", ".xlsm"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user