Skip to main content
Glama
hachecito

Odoo MCP Improved

by hachecito
tools_accounting.py15.2 kB
""" Implementación de herramientas (tools) para contabilidad en MCP-Odoo """ from typing import Dict, List, Any, Optional from datetime import datetime, timedelta from mcp.server.fastmcp import FastMCP, Context from .models import ( JournalEntryFilter, JournalEntryCreate, FinancialRatioInput ) def register_accounting_tools(mcp: FastMCP) -> None: """Registra herramientas relacionadas con contabilidad""" @mcp.tool(description="Busca asientos contables con filtros") def search_journal_entries( ctx: Context, filters: JournalEntryFilter ) -> Dict[str, Any]: """ Busca asientos contables según los filtros especificados Args: filters: Filtros para la búsqueda de asientos Returns: Diccionario con resultados de la búsqueda """ odoo = ctx.request_context.lifespan_context.odoo try: # Construir dominio de búsqueda domain = [] if filters.date_from: try: datetime.strptime(filters.date_from, "%Y-%m-%d") domain.append(("date", ">=", filters.date_from)) except ValueError: return {"success": False, "error": f"Formato de fecha inválido: {filters.date_from}. Use YYYY-MM-DD."} if filters.date_to: try: datetime.strptime(filters.date_to, "%Y-%m-%d") domain.append(("date", "<=", filters.date_to)) except ValueError: return {"success": False, "error": f"Formato de fecha inválido: {filters.date_to}. Use YYYY-MM-DD."} if filters.journal_id: domain.append(("journal_id", "=", filters.journal_id)) if filters.state: domain.append(("state", "=", filters.state)) # Campos a recuperar fields = [ "name", "ref", "date", "journal_id", "state", "amount_total", "amount_total_signed", "line_ids" ] # Ejecutar búsqueda entries = odoo.search_read( "account.move", domain, fields=fields, limit=filters.limit, offset=filters.offset ) # Obtener el conteo total sin límite para paginación total_count = odoo.execute_method("account.move", "search_count", domain) # Para cada asiento, obtener información resumida de las líneas for entry in entries: if entry.get("line_ids"): line_ids = entry["line_ids"] lines = odoo.search_read( "account.move.line", [("id", "in", line_ids)], fields=["name", "account_id", "partner_id", "debit", "credit", "balance"] ) entry["lines"] = lines # Eliminar la lista de IDs para reducir tamaño entry.pop("line_ids", None) return { "success": True, "result": { "count": len(entries), "total_count": total_count, "entries": entries } } except Exception as e: return {"success": False, "error": str(e)} @mcp.tool(description="Crea un nuevo asiento contable") def create_journal_entry( ctx: Context, entry: JournalEntryCreate ) -> Dict[str, Any]: """ Crea un nuevo asiento contable Args: entry: Datos del asiento a crear Returns: Respuesta con el resultado de la operación """ odoo = ctx.request_context.lifespan_context.odoo try: # Verificar que el debe y el haber cuadran total_debit = sum(line.debit for line in entry.lines) total_credit = sum(line.credit for line in entry.lines) if round(total_debit, 2) != round(total_credit, 2): return { "success": False, "error": f"El asiento no está cuadrado. Debe: {total_debit}, Haber: {total_credit}" } # Preparar valores para el asiento move_vals = { "journal_id": entry.journal_id, "line_ids": [] } if entry.ref: move_vals["ref"] = entry.ref if entry.date: try: datetime.strptime(entry.date, "%Y-%m-%d") move_vals["date"] = entry.date except ValueError: return {"success": False, "error": f"Formato de fecha inválido: {entry.date}. Use YYYY-MM-DD."} # Preparar líneas del asiento for line in entry.lines: line_vals = [ 0, 0, { "account_id": line.account_id, "name": line.name or "/", "debit": line.debit, "credit": line.credit } ] if line.partner_id: line_vals[2]["partner_id"] = line.partner_id move_vals["line_ids"].append(line_vals) # Crear asiento move_id = odoo.execute_method("account.move", "create", move_vals) # Obtener información del asiento creado move_info = odoo.execute_method("account.move", "read", [move_id], ["name", "state"])[0] return { "success": True, "result": { "move_id": move_id, "name": move_info["name"], "state": move_info["state"] } } except Exception as e: return {"success": False, "error": str(e)} @mcp.tool(description="Calcula ratios financieros clave") def analyze_financial_ratios( ctx: Context, params: FinancialRatioInput ) -> Dict[str, Any]: """ Calcula ratios financieros clave para un período específico Args: params: Parámetros para el análisis Returns: Diccionario con los ratios calculados """ odoo = ctx.request_context.lifespan_context.odoo try: # Validar fechas try: datetime.strptime(params.date_from, "%Y-%m-%d") datetime.strptime(params.date_to, "%Y-%m-%d") except ValueError: return {"success": False, "error": "Formato de fecha inválido. Use YYYY-MM-DD."} # Verificar qué ratios se solicitan requested_ratios = params.ratios # Inicializar resultado ratios = {} # Obtener datos del balance general # Activos assets_domain = [ ("account_id.user_type_id.internal_group", "=", "asset"), ("date", ">=", params.date_from), ("date", "<=", params.date_to), ("parent_state", "=", "posted") ] assets_data = odoo.search_read( "account.move.line", assets_domain, fields=["account_id", "balance"] ) total_assets = sum(line["balance"] for line in assets_data) # Activos corrientes current_assets_domain = [ ("account_id.user_type_id.internal_group", "=", "asset"), ("account_id.user_type_id.type", "=", "liquidity"), ("date", ">=", params.date_from), ("date", "<=", params.date_to), ("parent_state", "=", "posted") ] current_assets_data = odoo.search_read( "account.move.line", current_assets_domain, fields=["account_id", "balance"] ) current_assets = sum(line["balance"] for line in current_assets_data) # Pasivos liabilities_domain = [ ("account_id.user_type_id.internal_group", "=", "liability"), ("date", ">=", params.date_from), ("date", "<=", params.date_to), ("parent_state", "=", "posted") ] liabilities_data = odoo.search_read( "account.move.line", liabilities_domain, fields=["account_id", "balance"] ) total_liabilities = sum(line["balance"] for line in liabilities_data) # Pasivos corrientes current_liabilities_domain = [ ("account_id.user_type_id.internal_group", "=", "liability"), ("account_id.user_type_id.type", "=", "payable"), ("date", ">=", params.date_from), ("date", "<=", params.date_to), ("parent_state", "=", "posted") ] current_liabilities_data = odoo.search_read( "account.move.line", current_liabilities_domain, fields=["account_id", "balance"] ) current_liabilities = sum(line["balance"] for line in current_liabilities_data) # Patrimonio equity_domain = [ ("account_id.user_type_id.internal_group", "=", "equity"), ("date", ">=", params.date_from), ("date", "<=", params.date_to), ("parent_state", "=", "posted") ] equity_data = odoo.search_read( "account.move.line", equity_domain, fields=["account_id", "balance"] ) total_equity = sum(line["balance"] for line in equity_data) # Ingresos income_domain = [ ("account_id.user_type_id.internal_group", "=", "income"), ("date", ">=", params.date_from), ("date", "<=", params.date_to), ("parent_state", "=", "posted") ] income_data = odoo.search_read( "account.move.line", income_domain, fields=["account_id", "balance"] ) total_income = sum(line["balance"] for line in income_data) # Gastos expense_domain = [ ("account_id.user_type_id.internal_group", "=", "expense"), ("date", ">=", params.date_from), ("date", "<=", params.date_to), ("parent_state", "=", "posted") ] expense_data = odoo.search_read( "account.move.line", expense_domain, fields=["account_id", "balance"] ) total_expenses = sum(line["balance"] for line in expense_data) # Calcular beneficio neto net_income = total_income - total_expenses # Calcular ratios solicitados if "liquidity" in requested_ratios: # Ratio de liquidez corriente current_ratio = 0 if current_liabilities != 0: current_ratio = current_assets / abs(current_liabilities) ratios["liquidity"] = { "current_ratio": current_ratio, "current_assets": current_assets, "current_liabilities": abs(current_liabilities) } if "profitability" in requested_ratios: # Rentabilidad sobre activos (ROA) roa = 0 if total_assets != 0: roa = (net_income / total_assets) * 100 # Rentabilidad sobre patrimonio (ROE) roe = 0 if total_equity != 0: roe = (net_income / total_equity) * 100 # Margen de beneficio neto profit_margin = 0 if total_income != 0: profit_margin = (net_income / total_income) * 100 ratios["profitability"] = { "return_on_assets": roa, "return_on_equity": roe, "net_profit_margin": profit_margin, "net_income": net_income, "total_income": total_income } if "debt" in requested_ratios: # Ratio de endeudamiento debt_ratio = 0 if total_assets != 0: debt_ratio = (abs(total_liabilities) / total_assets) * 100 # Ratio de apalancamiento leverage_ratio = 0 if total_equity != 0: leverage_ratio = (abs(total_liabilities) / total_equity) ratios["debt"] = { "debt_ratio": debt_ratio, "leverage_ratio": leverage_ratio, "total_liabilities": abs(total_liabilities), "total_equity": total_equity } if "efficiency" in requested_ratios: # Rotación de activos asset_turnover = 0 if total_assets != 0: asset_turnover = total_income / total_assets ratios["efficiency"] = { "asset_turnover": asset_turnover } # Preparar resultado result = { "period": { "from": params.date_from, "to": params.date_to }, "summary": { "total_assets": total_assets, "total_liabilities": abs(total_liabilities), "total_equity": total_equity, "total_income": total_income, "total_expenses": abs(total_expenses), "net_income": net_income }, "ratios": ratios } return {"success": True, "result": result} except Exception as e: return {"success": False, "error": str(e)}

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/hachecito/odoo-mcp-improved'

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