fix(mcp): status active + search_products guard + item.product + cleanup auto-name
This commit is contained in:
@@ -361,19 +361,19 @@ async def get_shopping_lists() -> str:
|
||||
|
||||
@mcp.tool()
|
||||
async def get_active_shopping_list() -> str:
|
||||
"""Retourne la première liste en statut 'draft' avec tous ses articles triés."""
|
||||
"""Retourne la première liste en statut 'draft' ou 'active' avec tous ses articles triés."""
|
||||
async with AsyncSessionLocal() as session:
|
||||
stmt = (
|
||||
select(ShoppingList)
|
||||
.where(ShoppingList.status == "draft")
|
||||
.where(ShoppingList.status.in_(["draft", "active"]))
|
||||
.options(selectinload(ShoppingList.items).selectinload(ListItem.product))
|
||||
.order_by(ShoppingList.created_at.desc())
|
||||
.order_by(ShoppingList.status.asc(), ShoppingList.created_at.desc())
|
||||
.limit(1)
|
||||
)
|
||||
result = await session.execute(stmt)
|
||||
lst = result.scalar_one_or_none()
|
||||
if not lst:
|
||||
return _dumps({"error": "Aucune liste active (statut draft)"})
|
||||
return _dumps({"error": "Aucune liste active (statut draft ou active)"})
|
||||
sorted_items = sorted(lst.items, key=lambda i: (i.sort_order or 999, str(i.id)))
|
||||
return _dumps({
|
||||
"id": lst.id,
|
||||
@@ -394,6 +394,8 @@ async def get_active_shopping_list() -> str:
|
||||
@mcp.tool()
|
||||
async def search_products(q: str) -> str:
|
||||
"""Recherche dans le catalogue produits par nom, description ou catégorie."""
|
||||
if not q or not q.strip():
|
||||
return _dumps({"error": "Paramètre q requis"})
|
||||
async with AsyncSessionLocal() as session:
|
||||
stmt = (
|
||||
select(Product)
|
||||
@@ -490,19 +492,18 @@ async def check_shopping_item(list_id: str, item_id: str) -> str:
|
||||
return _dumps({"error": f"Article introuvable : {item_id} dans liste {list_id}"})
|
||||
was_checked = item.is_checked
|
||||
item.is_checked = True
|
||||
if not was_checked and item.product_id:
|
||||
product = await session.get(Product, item.product_id)
|
||||
if product:
|
||||
today = date_type.today()
|
||||
if product.last_purchased_at and product.last_purchased_at < today:
|
||||
days = (today - product.last_purchased_at).days
|
||||
if product.avg_interval_days is None:
|
||||
product.avg_interval_days = Decimal(str(days))
|
||||
else:
|
||||
product.avg_interval_days = Decimal(str(
|
||||
round(float(product.avg_interval_days) * 0.7 + days * 0.3, 1)
|
||||
))
|
||||
product.last_purchased_at = today
|
||||
product.frequency_score += 1
|
||||
if not was_checked and item.product:
|
||||
product = item.product
|
||||
today = date_type.today()
|
||||
if product.last_purchased_at and product.last_purchased_at < today:
|
||||
days = (today - product.last_purchased_at).days
|
||||
if product.avg_interval_days is None:
|
||||
product.avg_interval_days = Decimal(str(days))
|
||||
else:
|
||||
product.avg_interval_days = Decimal(str(
|
||||
round(float(product.avg_interval_days) * 0.7 + days * 0.3, 1)
|
||||
))
|
||||
product.last_purchased_at = today
|
||||
product.frequency_score += 1
|
||||
await session.commit()
|
||||
return _dumps({"id": item.id, "is_checked": item.is_checked})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
import uuid
|
||||
import pytest
|
||||
from sqlalchemy import delete
|
||||
import app.api.mcp_server as mcp_server_module
|
||||
@@ -155,7 +156,14 @@ async def test_create_shopping_list_outil():
|
||||
async def test_create_shopping_list_nom_auto():
|
||||
result = await create_shopping_list()
|
||||
data = json.loads(result)
|
||||
# Le nom auto est au format "S{semaine} {année}" (ex: "S22 2026")
|
||||
assert data["name"].startswith("S")
|
||||
# Cleanup manuel — la liste auto n'a pas le préfixe TEST_MCP_
|
||||
async with mcp_server_module.AsyncSessionLocal() as session:
|
||||
await session.execute(
|
||||
delete(ShoppingList).where(ShoppingList.id == uuid.UUID(data["id"]))
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def test_add_shopping_item_outil():
|
||||
|
||||
Reference in New Issue
Block a user