mirror of
https://github.com/goldenfishs/MRobot.git
synced 2026-02-04 18:00:19 +08:00
好了
This commit is contained in:
parent
77b9eb978d
commit
d9a02a8670
268
app/category_management_dialog.py
Normal file
268
app/category_management_dialog.py
Normal file
@ -0,0 +1,268 @@
|
||||
"""
|
||||
分类管理对话框
|
||||
提供新增、重命名、删除分类的功能
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QListWidget, QListWidgetItem)
|
||||
from PyQt5.QtCore import Qt
|
||||
from qfluentwidgets import (BodyLabel, PushButton, PrimaryPushButton, LineEdit,
|
||||
InfoBar, InfoBarPosition)
|
||||
from .tools.finance_manager import FinanceManager
|
||||
|
||||
|
||||
class CategoryManagementDialog(QDialog):
|
||||
"""分类管理对话框"""
|
||||
|
||||
def __init__(self, parent=None, finance_manager: Optional[FinanceManager] = None, account_id: Optional[str] = None):
|
||||
super().__init__(parent)
|
||||
self.finance_manager = finance_manager
|
||||
self.account_id = account_id
|
||||
self.setWindowTitle("分类管理")
|
||||
self.setGeometry(100, 100, 500, 400)
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
"""初始化UI"""
|
||||
main_layout = QVBoxLayout()
|
||||
|
||||
# 标签
|
||||
title_label = BodyLabel("选择分类进行管理:")
|
||||
main_layout.addWidget(title_label)
|
||||
|
||||
# 分类列表
|
||||
self.category_list = QListWidget()
|
||||
self.category_list.itemSelectionChanged.connect(self.on_category_selected)
|
||||
main_layout.addWidget(self.category_list)
|
||||
|
||||
# 加载分类
|
||||
self.load_categories()
|
||||
|
||||
# 按钮区域
|
||||
btn_layout = QHBoxLayout()
|
||||
btn_layout.addStretch()
|
||||
|
||||
# 新增按钮
|
||||
add_btn = PrimaryPushButton("新增")
|
||||
add_btn.clicked.connect(self.on_add_category)
|
||||
btn_layout.addWidget(add_btn)
|
||||
|
||||
# 重命名按钮
|
||||
self.rename_btn = PushButton("重命名")
|
||||
self.rename_btn.clicked.connect(self.on_rename_category)
|
||||
self.rename_btn.setEnabled(False)
|
||||
btn_layout.addWidget(self.rename_btn)
|
||||
|
||||
# 删除按钮
|
||||
self.delete_btn = PushButton("删除")
|
||||
self.delete_btn.clicked.connect(self.on_delete_category)
|
||||
self.delete_btn.setEnabled(False)
|
||||
btn_layout.addWidget(self.delete_btn)
|
||||
|
||||
# 关闭按钮
|
||||
close_btn = PushButton("关闭")
|
||||
close_btn.clicked.connect(self.accept)
|
||||
btn_layout.addWidget(close_btn)
|
||||
|
||||
main_layout.addLayout(btn_layout)
|
||||
self.setLayout(main_layout)
|
||||
|
||||
def load_categories(self):
|
||||
"""加载分类列表"""
|
||||
if not self.finance_manager or not self.account_id:
|
||||
return
|
||||
|
||||
self.category_list.clear()
|
||||
categories = self.finance_manager.get_categories(self.account_id)
|
||||
for category in categories:
|
||||
item = QListWidgetItem(category)
|
||||
self.category_list.addItem(item)
|
||||
|
||||
def on_category_selected(self):
|
||||
"""分类被选择"""
|
||||
has_selection = self.category_list.currentItem() is not None
|
||||
self.rename_btn.setEnabled(has_selection)
|
||||
self.delete_btn.setEnabled(has_selection)
|
||||
|
||||
def on_add_category(self):
|
||||
"""新增分类"""
|
||||
# 弹出输入对话框
|
||||
from PyQt5.QtWidgets import QDialog as QStdDialog
|
||||
from PyQt5.QtWidgets import QLabel
|
||||
|
||||
dialog = QStdDialog(self)
|
||||
dialog.setWindowTitle("新增分类")
|
||||
dialog.setGeometry(150, 150, 400, 150)
|
||||
|
||||
layout = QVBoxLayout(dialog)
|
||||
layout.addWidget(BodyLabel("分类名称:"))
|
||||
|
||||
input_edit = LineEdit()
|
||||
input_edit.setPlaceholderText("例如:食品、交通、娱乐等")
|
||||
layout.addWidget(input_edit)
|
||||
|
||||
# 按钮
|
||||
btn_layout = QHBoxLayout()
|
||||
btn_layout.addStretch()
|
||||
|
||||
def on_create():
|
||||
category_name = input_edit.text().strip()
|
||||
if not category_name:
|
||||
InfoBar.warning(
|
||||
title="提示",
|
||||
content="分类名称不能为空",
|
||||
isClosable=True,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=2000,
|
||||
parent=self
|
||||
)
|
||||
return
|
||||
|
||||
if self.finance_manager.add_category(self.account_id, category_name):
|
||||
InfoBar.success(
|
||||
title="成功",
|
||||
content=f"分类 '{category_name}' 创建成功",
|
||||
isClosable=True,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=2000,
|
||||
parent=self
|
||||
)
|
||||
self.load_categories()
|
||||
dialog.accept()
|
||||
else:
|
||||
InfoBar.warning(
|
||||
title="提示",
|
||||
content="分类已存在",
|
||||
isClosable=True,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=2000,
|
||||
parent=self
|
||||
)
|
||||
|
||||
cancel_btn = PushButton("取消")
|
||||
cancel_btn.clicked.connect(dialog.reject)
|
||||
btn_layout.addWidget(cancel_btn)
|
||||
|
||||
create_btn = PrimaryPushButton("创建")
|
||||
create_btn.clicked.connect(on_create)
|
||||
btn_layout.addWidget(create_btn)
|
||||
|
||||
layout.addLayout(btn_layout)
|
||||
dialog.exec()
|
||||
|
||||
def on_rename_category(self):
|
||||
"""重命名分类"""
|
||||
current_item = self.category_list.currentItem()
|
||||
if not current_item:
|
||||
return
|
||||
|
||||
old_name = current_item.text()
|
||||
|
||||
# 弹出输入对话框
|
||||
from PyQt5.QtWidgets import QDialog as QStdDialog
|
||||
|
||||
dialog = QStdDialog(self)
|
||||
dialog.setWindowTitle("重命名分类")
|
||||
dialog.setGeometry(150, 150, 400, 150)
|
||||
|
||||
layout = QVBoxLayout(dialog)
|
||||
layout.addWidget(BodyLabel(f"原分类名: {old_name}"))
|
||||
layout.addWidget(BodyLabel("新分类名:"))
|
||||
|
||||
input_edit = LineEdit()
|
||||
input_edit.setText(old_name)
|
||||
input_edit.selectAll()
|
||||
layout.addWidget(input_edit)
|
||||
|
||||
# 按钮
|
||||
btn_layout = QHBoxLayout()
|
||||
btn_layout.addStretch()
|
||||
|
||||
def on_rename():
|
||||
new_name = input_edit.text().strip()
|
||||
if not new_name:
|
||||
InfoBar.warning(
|
||||
title="提示",
|
||||
content="分类名称不能为空",
|
||||
isClosable=True,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=2000,
|
||||
parent=self
|
||||
)
|
||||
return
|
||||
|
||||
if new_name == old_name:
|
||||
dialog.accept()
|
||||
return
|
||||
|
||||
if self.finance_manager.rename_category(self.account_id, old_name, new_name):
|
||||
InfoBar.success(
|
||||
title="成功",
|
||||
content=f"分类已重命名为 '{new_name}'",
|
||||
isClosable=True,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=2000,
|
||||
parent=self
|
||||
)
|
||||
self.load_categories()
|
||||
dialog.accept()
|
||||
else:
|
||||
InfoBar.warning(
|
||||
title="提示",
|
||||
content="重命名失败,可能分类已存在",
|
||||
isClosable=True,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=2000,
|
||||
parent=self
|
||||
)
|
||||
|
||||
cancel_btn = PushButton("取消")
|
||||
cancel_btn.clicked.connect(dialog.reject)
|
||||
btn_layout.addWidget(cancel_btn)
|
||||
|
||||
rename_btn = PrimaryPushButton("重命名")
|
||||
rename_btn.clicked.connect(on_rename)
|
||||
btn_layout.addWidget(rename_btn)
|
||||
|
||||
layout.addLayout(btn_layout)
|
||||
dialog.exec()
|
||||
|
||||
def on_delete_category(self):
|
||||
"""删除分类"""
|
||||
current_item = self.category_list.currentItem()
|
||||
if not current_item:
|
||||
return
|
||||
|
||||
category_name = current_item.text()
|
||||
|
||||
# 确认删除
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
"确认删除",
|
||||
f"确定要删除分类 '{category_name}' 吗?\n\n使用该分类的交易记录分类将被清空。",
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No
|
||||
)
|
||||
|
||||
if reply == QMessageBox.Yes:
|
||||
if self.finance_manager.delete_category(self.account_id, category_name):
|
||||
InfoBar.success(
|
||||
title="成功",
|
||||
content=f"分类 '{category_name}' 已删除",
|
||||
isClosable=True,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=2000,
|
||||
parent=self
|
||||
)
|
||||
self.load_categories()
|
||||
else:
|
||||
InfoBar.warning(
|
||||
title="错误",
|
||||
content="删除分类失败",
|
||||
isClosable=True,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=2000,
|
||||
parent=self
|
||||
)
|
||||
@ -21,6 +21,7 @@ import json
|
||||
import os
|
||||
|
||||
from .tools.finance_manager import FinanceManager, TransactionType, Transaction, Account
|
||||
from .category_management_dialog import CategoryManagementDialog
|
||||
|
||||
|
||||
class CreateTransactionDialog(QDialog):
|
||||
@ -502,11 +503,11 @@ class FinanceInterface(QWidget):
|
||||
title_layout = QHBoxLayout()
|
||||
title_layout.addWidget(SubtitleLabel("交易记录"))
|
||||
|
||||
# 新建分类按钮
|
||||
new_category_btn = PushButton("新建分类")
|
||||
new_category_btn.setFixedWidth(90)
|
||||
new_category_btn.clicked.connect(self.on_create_category_clicked)
|
||||
title_layout.addWidget(new_category_btn)
|
||||
# 管理分类按钮
|
||||
manage_category_btn = PushButton("管理分类")
|
||||
manage_category_btn.setFixedWidth(90)
|
||||
manage_category_btn.clicked.connect(self.on_manage_category_clicked)
|
||||
title_layout.addWidget(manage_category_btn)
|
||||
|
||||
title_layout.addStretch()
|
||||
|
||||
@ -1224,8 +1225,8 @@ class FinanceInterface(QWidget):
|
||||
if trans_id:
|
||||
self.view_record(trans_id)
|
||||
|
||||
def on_create_category_clicked(self):
|
||||
"""新建分类按钮点击"""
|
||||
def on_manage_category_clicked(self):
|
||||
"""分类管理按钮点击"""
|
||||
account_id = self.get_current_account_id()
|
||||
if not account_id:
|
||||
InfoBar.warning(
|
||||
@ -1238,71 +1239,27 @@ class FinanceInterface(QWidget):
|
||||
)
|
||||
return
|
||||
|
||||
# 创建自定义对话框
|
||||
from PyQt5.QtWidgets import QDialog as QStdDialog, QLabel
|
||||
# 打开分类管理对话框
|
||||
dialog = CategoryManagementDialog(self, self.finance_manager, account_id)
|
||||
if dialog.exec():
|
||||
# 刷新查询页面的分类下拉框
|
||||
self.refresh_query_category_dropdown()
|
||||
|
||||
category_dialog = QStdDialog(self)
|
||||
category_dialog.setWindowTitle("新建分类")
|
||||
category_dialog.setGeometry(100, 100, 400, 150)
|
||||
def refresh_query_category_dropdown(self):
|
||||
"""刷新查询页面的分类下拉框"""
|
||||
account_id = self.get_current_account_id()
|
||||
if not account_id or not hasattr(self, 'query_category'):
|
||||
return
|
||||
|
||||
layout = QVBoxLayout(category_dialog)
|
||||
layout.addWidget(BodyLabel("分类名称:"))
|
||||
current_text = self.query_category.currentText()
|
||||
self.query_category.clear()
|
||||
self.query_category.addItem("全部")
|
||||
|
||||
input_edit = LineEdit()
|
||||
input_edit.setPlaceholderText("例如:食品、交通、娱乐等")
|
||||
layout.addWidget(input_edit)
|
||||
categories = self.finance_manager.get_categories(account_id)
|
||||
for category in categories:
|
||||
self.query_category.addItem(category)
|
||||
|
||||
# 按钮
|
||||
btn_layout = QHBoxLayout()
|
||||
btn_layout.addStretch()
|
||||
|
||||
def on_create():
|
||||
category_name = input_edit.text().strip()
|
||||
if not category_name:
|
||||
InfoBar.warning(
|
||||
title="提示",
|
||||
content="分类名称不能为空",
|
||||
isClosable=True,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=2000,
|
||||
parent=self
|
||||
)
|
||||
return
|
||||
|
||||
if self.finance_manager.add_category(account_id, category_name):
|
||||
InfoBar.success(
|
||||
title="成功",
|
||||
content=f"分类 '{category_name}' 创建成功",
|
||||
isClosable=True,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=2000,
|
||||
parent=self
|
||||
)
|
||||
|
||||
# 更新查询页面的分类下拉框
|
||||
if hasattr(self, 'query_category'):
|
||||
# 检查是否已经存在
|
||||
if self.query_category.findText(category_name) < 0:
|
||||
self.query_category.addItem(category_name)
|
||||
|
||||
category_dialog.accept()
|
||||
else:
|
||||
InfoBar.warning(
|
||||
title="提示",
|
||||
content="分类已存在",
|
||||
isClosable=True,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=2000,
|
||||
parent=self
|
||||
)
|
||||
|
||||
cancel_btn = PushButton("取消")
|
||||
cancel_btn.clicked.connect(category_dialog.reject)
|
||||
btn_layout.addWidget(cancel_btn)
|
||||
|
||||
create_btn = PrimaryPushButton("创建")
|
||||
create_btn.clicked.connect(on_create)
|
||||
btn_layout.addWidget(create_btn)
|
||||
|
||||
layout.addLayout(btn_layout)
|
||||
category_dialog.exec()
|
||||
# 恢复之前的选择
|
||||
index = self.query_category.findText(current_text)
|
||||
if index >= 0:
|
||||
self.query_category.setCurrentIndex(index)
|
||||
|
||||
@ -576,6 +576,45 @@ class FinanceManager:
|
||||
return True
|
||||
return False
|
||||
|
||||
def rename_category(self, account_id: str, old_name: str, new_name: str) -> bool:
|
||||
"""重命名交易分类"""
|
||||
account = self.accounts.get(account_id)
|
||||
if not account:
|
||||
return False
|
||||
|
||||
if old_name not in account.categories:
|
||||
return False
|
||||
|
||||
if new_name in account.categories:
|
||||
return False # 新分类名已存在
|
||||
|
||||
# 重命名分类
|
||||
idx = account.categories.index(old_name)
|
||||
account.categories[idx] = new_name
|
||||
|
||||
# 更新所有使用旧分类的交易
|
||||
account_dir = self._get_account_dir(account_id)
|
||||
for trans_dir in account_dir.iterdir():
|
||||
if not trans_dir.is_dir() or trans_dir.name in ['invoice', 'payment', 'purchase']:
|
||||
continue
|
||||
|
||||
data_file = trans_dir / 'data.json'
|
||||
if data_file.exists():
|
||||
try:
|
||||
with open(data_file, 'r', encoding='utf-8') as f:
|
||||
transaction_data = json.load(f)
|
||||
|
||||
if transaction_data.get('category') == old_name:
|
||||
transaction_data['category'] = new_name
|
||||
with open(data_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(transaction_data, f, ensure_ascii=False, indent=2)
|
||||
except Exception as e:
|
||||
print(f"更新交易分类出错: {e}")
|
||||
|
||||
account.updated_at = datetime.now().isoformat()
|
||||
self._save_account_metadata(account)
|
||||
return True
|
||||
|
||||
def delete_category(self, account_id: str, category: str) -> bool:
|
||||
"""删除交易分类"""
|
||||
account = self.accounts.get(account_id)
|
||||
@ -584,6 +623,26 @@ class FinanceManager:
|
||||
|
||||
if category in account.categories:
|
||||
account.categories.remove(category)
|
||||
|
||||
# 清除所有使用此分类的交易的分类字段
|
||||
account_dir = self._get_account_dir(account_id)
|
||||
for trans_dir in account_dir.iterdir():
|
||||
if not trans_dir.is_dir() or trans_dir.name in ['invoice', 'payment', 'purchase']:
|
||||
continue
|
||||
|
||||
data_file = trans_dir / 'data.json'
|
||||
if data_file.exists():
|
||||
try:
|
||||
with open(data_file, 'r', encoding='utf-8') as f:
|
||||
transaction_data = json.load(f)
|
||||
|
||||
if transaction_data.get('category') == category:
|
||||
transaction_data['category'] = ""
|
||||
with open(data_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(transaction_data, f, ensure_ascii=False, indent=2)
|
||||
except Exception as e:
|
||||
print(f"清除交易分类出错: {e}")
|
||||
|
||||
account.updated_at = datetime.now().isoformat()
|
||||
self._save_account_metadata(account)
|
||||
return True
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
{
|
||||
"id": "28ae4af8-67d9-431a-b2fe-312f587020b3",
|
||||
"date": "2025-01-01",
|
||||
"amount": 5000,
|
||||
"amount": 5000.0,
|
||||
"trader": "工作",
|
||||
"notes": "1月工资",
|
||||
"invoice_path": null,
|
||||
"payment_path": null,
|
||||
"purchase_path": null,
|
||||
"category": "工资",
|
||||
"category": "NUC",
|
||||
"created_at": "2025-11-25T20:44:13.766681",
|
||||
"updated_at": "2025-11-25T20:44:13.766682"
|
||||
"updated_at": "2025-11-25T20:54:10.845738"
|
||||
}
|
||||
@ -1,13 +1,13 @@
|
||||
{
|
||||
"id": "5cf3dc01-8a1c-4418-86b6-1728f1ae47d1",
|
||||
"date": "2025-01-10",
|
||||
"amount": -200,
|
||||
"amount": -200.0,
|
||||
"trader": "超市",
|
||||
"notes": "食材",
|
||||
"invoice_path": null,
|
||||
"invoice_path": "accounts/b87b752a-7fb6-40b6-a0cf-287df1d64e56/5cf3dc01-8a1c-4418-86b6-1728f1ae47d1/invoice/截屏2025-11-25 02.51.14.png",
|
||||
"payment_path": null,
|
||||
"purchase_path": null,
|
||||
"category": "食品",
|
||||
"category": "NUC",
|
||||
"created_at": "2025-11-25T20:44:13.766985",
|
||||
"updated_at": "2025-11-25T20:44:13.766986"
|
||||
"updated_at": "2025-11-25T20:56:17.767227"
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
@ -1,13 +1,13 @@
|
||||
{
|
||||
"id": "86232779-66fb-4e8e-b31e-477aef8e4ecf",
|
||||
"date": "2025-01-05",
|
||||
"amount": -1500,
|
||||
"amount": -1500.0,
|
||||
"trader": "房东",
|
||||
"notes": "房租",
|
||||
"invoice_path": null,
|
||||
"payment_path": null,
|
||||
"purchase_path": null,
|
||||
"category": "房租",
|
||||
"category": "NUC",
|
||||
"created_at": "2025-11-25T20:44:13.766850",
|
||||
"updated_at": "2025-11-25T20:44:13.766853"
|
||||
"updated_at": "2025-11-25T20:54:13.895024"
|
||||
}
|
||||
@ -3,11 +3,8 @@
|
||||
"name": "admin",
|
||||
"description": "默认管理账户",
|
||||
"categories": [
|
||||
"工资",
|
||||
"房租",
|
||||
"食品",
|
||||
"NUC"
|
||||
],
|
||||
"created_at": "2025-11-25T20:44:13.766008",
|
||||
"updated_at": "2025-11-25T20:44:37.459188"
|
||||
"updated_at": "2025-11-25T20:53:48.533051"
|
||||
}
|
||||
206
test_category_management.py
Normal file
206
test_category_management.py
Normal file
@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
分类管理功能测试脚本
|
||||
测试新增、重命名、删除分类的功能
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
# 添加app目录到路径
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from app.tools.finance_manager import FinanceManager, Transaction
|
||||
|
||||
|
||||
def test_rename_category():
|
||||
"""测试重命名分类"""
|
||||
print("=== 测试重命名分类 ===")
|
||||
|
||||
# 创建测试用的FinanceManager
|
||||
test_data_root = Path("/tmp/test_finance_data_rename")
|
||||
fm = FinanceManager(str(test_data_root))
|
||||
|
||||
# 获取admin账户或创建一个测试账户
|
||||
account_ids = list(fm.accounts.keys())
|
||||
if not account_ids:
|
||||
account_id = fm.create_account("test_account", "测试账户")
|
||||
else:
|
||||
account_id = account_ids[0]
|
||||
|
||||
print(f"使用账户: {account_id}")
|
||||
|
||||
# 添加分类
|
||||
print("添加分类: 食品、交通、娱乐")
|
||||
assert fm.add_category(account_id, "食品"), "添加分类'食品'失败"
|
||||
assert fm.add_category(account_id, "交通"), "添加分类'交通'失败"
|
||||
assert fm.add_category(account_id, "娱乐"), "添加分类'娱乐'失败"
|
||||
|
||||
categories = fm.get_categories(account_id)
|
||||
print(f"当前分类: {categories}")
|
||||
assert len(categories) == 3, f"期望3个分类,实际{len(categories)}个"
|
||||
|
||||
# 添加一个使用"食品"分类的交易
|
||||
trans = Transaction(
|
||||
date="2024-01-01",
|
||||
amount=100.0,
|
||||
trader="超市",
|
||||
notes="购买食品",
|
||||
category="食品"
|
||||
)
|
||||
fm.add_transaction(account_id, trans)
|
||||
print(f"添加交易,分类为'食品': {trans.id}")
|
||||
|
||||
# 重命名分类
|
||||
print("重命名分类: 食品 -> 饮食")
|
||||
assert fm.rename_category(account_id, "食品", "饮食"), "重命名分类失败"
|
||||
|
||||
categories = fm.get_categories(account_id)
|
||||
print(f"重命名后分类: {categories}")
|
||||
assert "饮食" in categories, "重命名后分类中没有'饮食'"
|
||||
assert "食品" not in categories, "重命名后分类中仍有'食品'"
|
||||
|
||||
# 验证交易的分类也被更新了
|
||||
# 需要重新加载账户数据
|
||||
fm.load_all_accounts()
|
||||
account = fm.accounts[account_id]
|
||||
for t in account.transactions:
|
||||
if t.id == trans.id:
|
||||
print(f"交易分类已更新为: {t.category}")
|
||||
assert t.category == "饮食", f"交易分类应该是'饮食',实际是'{t.category}'"
|
||||
break
|
||||
|
||||
# 测试重命名失败的情况:新分类名已存在
|
||||
print("测试重命名失败情况: 新分类名'交通'已存在")
|
||||
assert not fm.rename_category(account_id, "饮食", "交通"), "应该重命名失败"
|
||||
|
||||
print("✓ 重命名分类测试通过\n")
|
||||
|
||||
|
||||
def test_delete_category():
|
||||
"""测试删除分类"""
|
||||
print("=== 测试删除分类 ===")
|
||||
|
||||
test_data_root = Path("/tmp/test_finance_data_delete")
|
||||
fm = FinanceManager(str(test_data_root))
|
||||
|
||||
account_ids = list(fm.accounts.keys())
|
||||
if not account_ids:
|
||||
account_id = fm.create_account("test_account", "测试账户")
|
||||
else:
|
||||
account_id = account_ids[0]
|
||||
|
||||
print(f"使用账户: {account_id}")
|
||||
|
||||
# 添加分类
|
||||
print("添加分类: 食品、交通、娱乐")
|
||||
fm.add_category(account_id, "食品")
|
||||
fm.add_category(account_id, "交通")
|
||||
fm.add_category(account_id, "娱乐")
|
||||
|
||||
# 添加使用"食品"分类的交易
|
||||
trans1 = Transaction(
|
||||
date="2024-01-01",
|
||||
amount=100.0,
|
||||
trader="超市",
|
||||
notes="购买食品",
|
||||
category="食品"
|
||||
)
|
||||
trans2 = Transaction(
|
||||
date="2024-01-02",
|
||||
amount=50.0,
|
||||
trader="出租车",
|
||||
notes="交通费用",
|
||||
category="交通"
|
||||
)
|
||||
|
||||
fm.add_transaction(account_id, trans1)
|
||||
fm.add_transaction(account_id, trans2)
|
||||
|
||||
print(f"添加交易1(分类'食品'): {trans1.id}")
|
||||
print(f"添加交易2(分类'交通'): {trans2.id}")
|
||||
|
||||
# 删除"食品"分类
|
||||
print("删除分类: 食品")
|
||||
assert fm.delete_category(account_id, "食品"), "删除分类失败"
|
||||
|
||||
categories = fm.get_categories(account_id)
|
||||
print(f"删除后分类: {categories}")
|
||||
assert "食品" not in categories, "删除后分类中仍有'食品'"
|
||||
|
||||
# 验证使用"食品"分类的交易分类被清空了
|
||||
fm.load_all_accounts()
|
||||
account = fm.accounts[account_id]
|
||||
for t in account.transactions:
|
||||
if t.id == trans1.id:
|
||||
print(f"使用已删除分类的交易,其分类现在为: '{t.category}'")
|
||||
assert t.category == "", f"交易分类应该被清空,实际是'{t.category}'"
|
||||
elif t.id == trans2.id:
|
||||
print(f"使用'交通'分类的交易,其分类仍为: '{t.category}'")
|
||||
assert t.category == "交通", f"交易分类应该保持'交通',实际是'{t.category}'"
|
||||
|
||||
print("✓ 删除分类测试通过\n")
|
||||
|
||||
|
||||
def test_add_category():
|
||||
"""测试添加分类"""
|
||||
print("=== 测试添加分类 ===")
|
||||
|
||||
test_data_root = Path("/tmp/test_finance_data_add")
|
||||
fm = FinanceManager(str(test_data_root))
|
||||
|
||||
account_ids = list(fm.accounts.keys())
|
||||
if not account_ids:
|
||||
account_id = fm.create_account("test_account", "测试账户")
|
||||
else:
|
||||
account_id = account_ids[0]
|
||||
|
||||
print(f"使用账户: {account_id}")
|
||||
|
||||
# 初始应该没有分类
|
||||
categories = fm.get_categories(account_id)
|
||||
print(f"初始分类: {categories}")
|
||||
|
||||
# 添加分类
|
||||
print("添加分类: 食品")
|
||||
assert fm.add_category(account_id, "食品"), "添加分类失败"
|
||||
|
||||
categories = fm.get_categories(account_id)
|
||||
print(f"添加后分类: {categories}")
|
||||
assert "食品" in categories, "分类中没有'食品'"
|
||||
|
||||
# 测试添加重复分类
|
||||
print("测试添加重复分类")
|
||||
assert not fm.add_category(account_id, "食品"), "应该返回False"
|
||||
|
||||
categories = fm.get_categories(account_id)
|
||||
assert len(categories) == 1, f"应该只有1个分类,实际{len(categories)}个"
|
||||
|
||||
print("✓ 添加分类测试通过\n")
|
||||
|
||||
|
||||
def main():
|
||||
"""运行所有测试"""
|
||||
print("开始测试分类管理功能...\n")
|
||||
|
||||
try:
|
||||
test_add_category()
|
||||
test_rename_category()
|
||||
test_delete_category()
|
||||
|
||||
print("=" * 50)
|
||||
print("✓ 所有测试通过!")
|
||||
print("=" * 50)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n✗ 测试失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue
Block a user