Skip to main content
Glama
smile7up

Accounting MCP Server

by smile7up
models.py10.9 kB
""" 数据模型 - Transaction, Account, Category 核心数据结构 实现记账系统的核心数据模型,包括: - Transaction: 交易记录 - Account: 账户信息 - Category: 分类管理 - TransactionType: 交易类型枚举 """ from decimal import Decimal, ROUND_HALF_UP from datetime import datetime, date from enum import Enum from typing import Dict, Any, Optional, List from uuid import uuid4 import json class TransactionType(Enum): """交易类型枚举""" INCOME = "income" EXPENSE = "expense" @classmethod def from_amount(cls, amount: Decimal) -> "TransactionType": """根据金额判断交易类型""" if amount == 0: raise ValueError("金额不能为零") return cls.INCOME if amount > 0 else cls.EXPENSE class Transaction: """交易记录模型""" def __init__( self, amount: Decimal, category: str, description: str = "", date: Optional[date] = None, id: Optional[str] = None, timestamp: Optional[datetime] = None ): # 验证和设置金额 self.amount = self._validate_amount(amount) # 验证和设置分类 self.category = self._validate_category(category) # 设置描述 self.description = description or "" # 设置日期(默认为今天) self.date = date or datetime.now().date() self._validate_date(self.date) # 设置ID(如果未提供则生成) self.id = id or self._generate_id() # 设置时间戳(如果未提供则使用当前时间) self.timestamp = timestamp or datetime.now() # 根据金额确定交易类型 self.transaction_type = TransactionType.from_amount(self.amount) def _validate_amount(self, amount: Decimal) -> Decimal: """验证金额格式和精度""" if not isinstance(amount, Decimal): amount = Decimal(str(amount)) # 检查金额不能为零 if amount == 0: raise ValueError("金额不能为零") # 检查精度(最多2位小数) if amount.as_tuple().exponent < -2: raise ValueError("金额精度不能超过2位小数") # 四舍五入到2位小数 return amount.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP) def _validate_category(self, category: str) -> str: """验证分类""" if not category or not category.strip(): raise ValueError("分类不能为空") return category.strip() def _validate_date(self, transaction_date: date) -> None: """验证交易日期""" if transaction_date > date.today(): raise ValueError("交易日期不能是未来") def _generate_id(self) -> str: """生成唯一的交易ID""" return f"txn_{self.date.strftime('%Y%m%d')}_{str(uuid4())[:8]}" def to_dict(self) -> Dict[str, Any]: """转换为字典格式""" return { "id": self.id, "amount": float(self.amount), "category": self.category, "description": self.description, "date": self.date.isoformat(), "timestamp": self.timestamp.isoformat() + "Z" if self.timestamp.tzinfo is None else self.timestamp.isoformat(), "type": self.transaction_type.value } @classmethod def from_dict(cls, data: Dict[str, Any]) -> "Transaction": """从字典创建交易对象""" # 解析日期 transaction_date = datetime.fromisoformat(data["date"]).date() if isinstance(data["date"], str) else data["date"] # 解析时间戳 timestamp = None if "timestamp" in data and data["timestamp"]: timestamp_str = data["timestamp"].rstrip("Z") timestamp = datetime.fromisoformat(timestamp_str) return cls( id=data["id"], amount=Decimal(str(data["amount"])), category=data["category"], description=data.get("description", ""), date=transaction_date, timestamp=timestamp ) def __str__(self) -> str: """字符串表示""" type_symbol = "+" if self.transaction_type == TransactionType.INCOME else "-" return f"{type_symbol}¥{abs(self.amount)} [{self.category}] {self.description}" def __repr__(self) -> str: """调试字符串表示""" return f"Transaction(id={self.id}, amount={self.amount}, category={self.category})" class Account: """账户信息模型""" def __init__( self, balance: Decimal = Decimal('0.00'), total_transactions: int = 0, created_at: Optional[datetime] = None, last_updated: Optional[datetime] = None ): self.balance = Decimal(str(balance)) if balance else Decimal('0.00') self.total_transactions = total_transactions self.created_at = created_at or datetime.now() self.last_updated = last_updated or datetime.now() def add_transaction(self, transaction: Transaction) -> None: """添加交易到账户""" self.balance += transaction.amount self.total_transactions += 1 self.last_updated = datetime.now() def to_dict(self) -> Dict[str, Any]: """转换为字典格式""" return { "balance": float(self.balance), "total_transactions": self.total_transactions, "created_at": self.created_at.isoformat() + "Z" if self.created_at.tzinfo is None else self.created_at.isoformat(), "last_updated": self.last_updated.isoformat() + "Z" if self.last_updated.tzinfo is None else self.last_updated.isoformat() } @classmethod def from_dict(cls, data: Dict[str, Any]) -> "Account": """从字典创建账户对象""" # 解析时间戳 created_at = None last_updated = None if "created_at" in data and data["created_at"]: created_str = data["created_at"].rstrip("Z") created_at = datetime.fromisoformat(created_str) if "last_updated" in data and data["last_updated"]: updated_str = data["last_updated"].rstrip("Z") last_updated = datetime.fromisoformat(updated_str) return cls( balance=Decimal(str(data.get("balance", 0))), total_transactions=data.get("total_transactions", 0), created_at=created_at, last_updated=last_updated ) def __str__(self) -> str: """字符串表示""" return f"Account(余额: ¥{self.balance}, 交易数: {self.total_transactions})" class Category: """分类模型""" def __init__( self, id: str, name: str, description: str = "", category_type: TransactionType = TransactionType.EXPENSE, color: str = "#D3D3D3", icon: str = "📝" ): # 验证分类ID if not id or not id.strip(): raise ValueError("分类ID不能为空") self.id = id.strip() self.name = name or self.id self.description = description self.category_type = category_type self.color = color self.icon = icon def to_dict(self) -> Dict[str, Any]: """转换为字典格式""" return { "id": self.id, "name": self.name, "description": self.description, "type": self.category_type.value, "color": self.color, "icon": self.icon } @classmethod def from_dict(cls, data: Dict[str, Any]) -> "Category": """从字典创建分类对象""" category_type = TransactionType.EXPENSE if "type" in data: if data["type"] == "income": category_type = TransactionType.INCOME elif data["type"] == "expense": category_type = TransactionType.EXPENSE return cls( id=data["id"], name=data.get("name", data["id"]), description=data.get("description", ""), category_type=category_type, color=data.get("color", "#D3D3D3"), icon=data.get("icon", "📝") ) def __str__(self) -> str: """字符串表示""" return f"{self.icon} {self.name} ({self.category_type.value})" def __repr__(self) -> str: """调试字符串表示""" return f"Category(id={self.id}, name={self.name}, type={self.category_type.value})" # 辅助函数 def create_default_categories() -> List[Category]: """创建默认分类列表""" return [ Category( id="food", name="餐饮", description="餐费、外卖、聚餐等饮食相关支出", category_type=TransactionType.EXPENSE, color="#FF6B6B", icon="🍽️" ), Category( id="transport", name="交通", description="公交、地铁、打车、加油等交通费用", category_type=TransactionType.EXPENSE, color="#4ECDC4", icon="🚗" ), Category( id="entertainment", name="娱乐", description="电影、游戏、旅游等娱乐消费", category_type=TransactionType.EXPENSE, color="#45B7D1", icon="🎬" ), Category( id="shopping", name="购物", description="服装、日用品、电子产品等购物支出", category_type=TransactionType.EXPENSE, color="#96CEB4", icon="🛍️" ), Category( id="healthcare", name="医疗", description="医院、药品、体检等医疗健康支出", category_type=TransactionType.EXPENSE, color="#FFEAA7", icon="🏥" ), Category( id="education", name="教育", description="培训、课程、书籍等教育投资", category_type=TransactionType.EXPENSE, color="#DDA0DD", icon="📚" ), Category( id="income", name="收入", description="工资、奖金、投资收益等收入", category_type=TransactionType.INCOME, color="#90EE90", icon="💰" ), Category( id="other", name="其他", description="其他未分类的支出或收入", category_type=TransactionType.EXPENSE, color="#D3D3D3", icon="📝" ) ]

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/smile7up/accounting-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server