"""
Sale order management tools.
Provides tools for listing and viewing sale orders.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from ..constants import SALE_ORDER_STATE_LABELS, OdooModel, SaleOrderState
from ..decorators import handle_odoo_errors
from ..exceptions import OdooValidationError
from ..formatters import MarkdownBuilder, format_money
from ..validators import validate_date
from .base import extract_name, format_state, get_odoo_client, normalize_pagination
if TYPE_CHECKING:
from mcp.server.fastmcp import FastMCP
def register_tools(mcp: "FastMCP") -> None:
"""Register sale order tools with the MCP server."""
@mcp.tool()
@handle_odoo_errors
def list_sale_orders(
state: str | None = None,
partner_id: int | None = None,
date_from: str | None = None,
date_to: str | None = None,
limit: int = 50,
offset: int = 0,
) -> str:
"""
List sale orders (quotations/orders).
Args:
state: State (draft=Quotation, sent=Sent, sale=Sales Order, done=Locked, cancel=Cancelled)
partner_id: Filter by customer
date_from: Start date (YYYY-MM-DD)
date_to: End date (YYYY-MM-DD)
limit: Maximum number (default: 50)
offset: Offset for pagination (default: 0)
Returns:
List of orders
"""
limit, offset = normalize_pagination(limit, offset)
client = get_odoo_client()
domain: list = []
if state:
valid_states = [s.value for s in SaleOrderState]
if state not in valid_states:
raise OdooValidationError(
f"Invalid state: {state}. Valid: {', '.join(valid_states)}",
field="state",
)
domain.append(("state", "=", state))
if partner_id:
domain.append(("partner_id", "=", partner_id))
if date_from:
domain.append(("date_order", ">=", validate_date(date_from, "date_from")))
if date_to:
domain.append(("date_order", "<=", validate_date(date_to, "date_to")))
orders = client.search_read(
OdooModel.SALE_ORDER,
domain,
[
"name",
"partner_id",
"date_order",
"amount_total",
"state",
"currency_id",
"user_id",
],
limit=limit,
offset=offset,
order="date_order desc, name desc",
)
if not orders:
return "No sale orders found."
builder = MarkdownBuilder("Sale Orders")
rows = []
for order in orders:
currency = extract_name(order.get("currency_id"), "EUR")
date_str = str(order.get("date_order", "-"))[:10]
rows.append([
order["id"],
order["name"],
extract_name(order.get("partner_id"), "-"),
date_str,
format_money(order.get("amount_total", 0), currency),
format_state(order.get("state", ""), SALE_ORDER_STATE_LABELS),
extract_name(order.get("user_id"), "-"),
])
builder.add_table(
["ID", "Number", "Customer", "Date", "Total", "Status", "Salesperson"],
rows,
alignments=["right", "left", "left", "center", "right", "center", "left"],
)
builder.add_pagination(len(orders), limit, offset)
return builder.build()
@mcp.tool()
@handle_odoo_errors
def get_sale_order(order_id: int) -> str:
"""
Get details of a sale order with its lines.
Args:
order_id: Order ID
Returns:
Complete order details
"""
client = get_odoo_client()
order = client.get_record(
OdooModel.SALE_ORDER,
order_id,
[
"name",
"partner_id",
"date_order",
"validity_date",
"amount_untaxed",
"amount_tax",
"amount_total",
"state",
"currency_id",
"user_id",
"client_order_ref",
"note",
"order_line",
],
)
currency = extract_name(order.get("currency_id"), "EUR")
date_str = str(order.get("date_order", "-"))[:10]
builder = MarkdownBuilder(f"Sale Order: {order['name']}")
# Header info
builder.add_heading("General Information", 2)
builder.add_key_value({
"ID": order["id"],
"Number": order["name"],
"Customer": extract_name(order.get("partner_id")),
"Date": date_str,
"Validity": order.get("validity_date") or "-",
"Customer Reference": order.get("client_order_ref") or "-",
"Salesperson": extract_name(order.get("user_id")),
"Status": format_state(order.get("state", ""), SALE_ORDER_STATE_LABELS),
})
# Order lines
line_ids = order.get("order_line", [])
if line_ids:
lines = client.read(
OdooModel.SALE_ORDER_LINE,
line_ids,
["name", "product_id", "product_uom_qty", "price_unit", "price_subtotal"],
)
builder.add_heading("Order Lines", 2)
rows = []
for line in lines:
rows.append([
extract_name(line.get("product_id"), line.get("name", "-")[:40]),
f"{line.get('product_uom_qty', 0):.2f}",
format_money(line.get("price_unit", 0), currency),
format_money(line.get("price_subtotal", 0), currency),
])
builder.add_table(
["Product", "Qty", "Unit Price", "Subtotal"],
rows,
alignments=["left", "right", "right", "right"],
)
# Totals
builder.add_heading("Totals", 2)
builder.add_key_value({
"Subtotal": format_money(order.get("amount_untaxed", 0), currency),
"Tax": format_money(order.get("amount_tax", 0), currency),
"Total": format_money(order.get("amount_total", 0), currency),
})
if order.get("note"):
builder.add_heading("Notes", 2)
builder.add_text(order["note"])
return builder.build()