feat: 完成T-204旧字段回写
实现 Excel 旧标题与旧封面路径回写原文件,按 source_file_abs/source_sheet/source_row 定位行,支持输出列自动追加、xlsm 保留 VBA、文件占用中文错误提示与 export_copy 另存副本。 Tab① 增加回写旧数据按钮与 WriteBackWorker,后台调用 excel.write_back,锁文件时提示关闭后重试;补充 Excel 与 GUI 单测覆盖回写、锁文件、另存副本和 worker 调用。 同步 harness 文档:T-204 标记完成,新增 T-204b 记录采集完成自动回写缺口,扩展 T-205 覆盖 Chrome 未启动/未登录引导保护,并更新 current-state、routes、api、architecture、requirements 与 progress。
This commit is contained in:
+126
-1
@@ -1,10 +1,11 @@
|
||||
import os
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from _helpers import TempDirMixin
|
||||
|
||||
try:
|
||||
from openpyxl import Workbook
|
||||
from openpyxl import Workbook, load_workbook
|
||||
except ModuleNotFoundError:
|
||||
raise unittest.SkipTest("openpyxl 未安装")
|
||||
|
||||
@@ -23,6 +24,32 @@ class ExcelImportTests(TempDirMixin, unittest.TestCase):
|
||||
workbook.save(path)
|
||||
workbook.close()
|
||||
|
||||
def row_values_by_header(self, path, row_number=2):
|
||||
workbook = load_workbook(path, data_only=True)
|
||||
try:
|
||||
sheet = workbook.active
|
||||
headers = {
|
||||
sheet.cell(row=1, column=column).value: column
|
||||
for column in range(1, sheet.max_column + 1)
|
||||
}
|
||||
return {
|
||||
header: sheet.cell(row=row_number, column=column).value
|
||||
for header, column in headers.items()
|
||||
}
|
||||
finally:
|
||||
workbook.close()
|
||||
|
||||
def headers(self, path):
|
||||
workbook = load_workbook(path, data_only=True)
|
||||
try:
|
||||
sheet = workbook.active
|
||||
return [
|
||||
sheet.cell(row=1, column=column).value
|
||||
for column in range(1, sheet.max_column + 1)
|
||||
]
|
||||
finally:
|
||||
workbook.close()
|
||||
|
||||
def test_import_tasks_parses_rows_and_writes_batch_tasks(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
db_path = os.path.join(temp_dir, "cmshopee.db")
|
||||
@@ -162,6 +189,104 @@ class ExcelImportTests(TempDirMixin, unittest.TestCase):
|
||||
excel.match_summary(rows, accounts),
|
||||
)
|
||||
|
||||
def test_write_back_writes_old_fields_to_original_excel(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
db_path = os.path.join(temp_dir, "cmshopee.db")
|
||||
excel_path = os.path.join(temp_dir, "input.xlsx")
|
||||
self.save_workbook(
|
||||
excel_path,
|
||||
[
|
||||
(
|
||||
"待处理任务",
|
||||
[
|
||||
["账号名", "别名", "商品id"],
|
||||
["主店", "alias-a", "51100639510"],
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
result = excel.import_tasks([excel_path], path=db_path)
|
||||
task = db.list_tasks(batch_id=result["batch_id"], path=db_path)[0]
|
||||
db.set_collected(
|
||||
task.id,
|
||||
"原始商品标题",
|
||||
r"D:\images\51100639510_old.jpg",
|
||||
path=db_path,
|
||||
)
|
||||
|
||||
summary = excel.write_back(result["batch_id"], path=db_path)
|
||||
|
||||
self.assertEqual(True, summary["ok"])
|
||||
self.assertEqual(1, summary["files"])
|
||||
self.assertEqual(1, summary["rows"])
|
||||
self.assertEqual([os.path.abspath(excel_path)], summary["written_files"])
|
||||
values = self.row_values_by_header(excel_path)
|
||||
self.assertEqual("原始商品标题", values["旧标题"])
|
||||
self.assertEqual(r"D:\images\51100639510_old.jpg", values["旧封面图片路径"])
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_write_back_locked_file_raises_clear_error(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
db_path = os.path.join(temp_dir, "cmshopee.db")
|
||||
excel_path = os.path.join(temp_dir, "input.xlsx")
|
||||
self.save_workbook(
|
||||
excel_path,
|
||||
[
|
||||
(
|
||||
"待处理任务",
|
||||
[
|
||||
["账号名", "别名", "商品id"],
|
||||
["主店", "alias-a", "51100639510"],
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
result = excel.import_tasks([excel_path], path=db_path)
|
||||
task = db.list_tasks(batch_id=result["batch_id"], path=db_path)[0]
|
||||
db.set_collected(task.id, "原始商品标题", "old.jpg", path=db_path)
|
||||
|
||||
with mock.patch("app.excel.load_workbook", side_effect=PermissionError("locked")):
|
||||
with self.assertRaisesRegex(excel.ExcelError, "Excel 文件被占用"):
|
||||
excel.write_back(result["batch_id"], path=db_path)
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_export_copy_writes_copy_without_touching_original(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
db_path = os.path.join(temp_dir, "cmshopee.db")
|
||||
excel_path = os.path.join(temp_dir, "input.xlsx")
|
||||
out_dir = os.path.join(temp_dir, "out")
|
||||
self.save_workbook(
|
||||
excel_path,
|
||||
[
|
||||
(
|
||||
"待处理任务",
|
||||
[
|
||||
["账号名", "别名", "商品id"],
|
||||
["主店", "alias-a", "51100639510"],
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
result = excel.import_tasks([excel_path], path=db_path)
|
||||
task = db.list_tasks(batch_id=result["batch_id"], path=db_path)[0]
|
||||
db.set_collected(task.id, "原始商品标题", "old.jpg", path=db_path)
|
||||
|
||||
summary = excel.export_copy(result["batch_id"], out_dir, path=db_path)
|
||||
|
||||
self.assertEqual(1, summary["files"])
|
||||
self.assertEqual(1, summary["rows"])
|
||||
copy_path = summary["written_files"][0]
|
||||
self.assertTrue(os.path.exists(copy_path))
|
||||
self.assertIn("input_cmshopee回写", os.path.basename(copy_path))
|
||||
self.assertNotIn("旧标题", self.headers(excel_path))
|
||||
values = self.row_values_by_header(copy_path)
|
||||
self.assertEqual("原始商品标题", values["旧标题"])
|
||||
self.assertEqual("old.jpg", values["旧封面图片路径"])
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -24,6 +24,7 @@ from app.gui import (
|
||||
MainWindow,
|
||||
TAB_STYLE,
|
||||
TAB_TITLES,
|
||||
WriteBackWorker,
|
||||
)
|
||||
|
||||
|
||||
@@ -57,6 +58,10 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
self.assertIn("padding: 8px 18px", window.tabs.styleSheet())
|
||||
self.assertIn("margin-right: 8px", window.tabs.styleSheet())
|
||||
self.assertIsInstance(window.tabs.widget(0), CollectTab)
|
||||
self.assertEqual(
|
||||
"回写旧数据到 Excel",
|
||||
window.tabs.widget(0).write_back_button.text(),
|
||||
)
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
@@ -382,6 +387,20 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_write_back_worker_calls_excel_write_back(self):
|
||||
with mock.patch(
|
||||
"app.gui.excel.write_back",
|
||||
return_value={"ok": True, "batch_id": "batch-1", "files": 1, "rows": 2},
|
||||
) as write_back:
|
||||
summary = WriteBackWorker("batch-1", db_path="db.sqlite").execute()
|
||||
|
||||
self.assertEqual({"ok": True, "batch_id": "batch-1", "files": 1, "rows": 2}, summary)
|
||||
write_back.assert_called_once_with(
|
||||
"batch-1",
|
||||
excel_path=None,
|
||||
path="db.sqlite",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user