from __future__ import annotations from collections.abc import Callable from typing import Annotated, Any from pydantic import Field SafeResult = Callable[[Callable[[], dict[str, Any]]], dict[str, Any]] SafeRawResult = Callable[[bool, Callable[[], dict[str, Any]]], dict[str, Any]] def register_project_tools(server: Any, service: Any, safe_result: SafeResult, safe_raw_result: SafeRawResult) -> None: @server.tool() def list_projects( customer_id: Annotated[Any | None, Field(description="Optional customer ID.")] = None, team: Annotated[str | None, Field(description="Optional CRM team name.")] = None, stage: Annotated[str | None, Field(description="Optional project stage label or configured value.")] = None, limit: Annotated[int, Field(description="Maximum projects to return.")] = 20, offset: Annotated[int, Field(description="Zero-based result offset.")] = 0, include_raw: Annotated[bool, Field(description="Include original NocoBase project rows.")] = False, ) -> dict[str, Any]: """List CRM projects linked to customers, teams, and project stages.""" return safe_raw_result( include_raw, lambda: service.list_projects( customer_id=customer_id, team=team, stage=stage, limit=limit, offset=offset, include_raw=include_raw, ), ) @server.tool() def get_project( project_id: Annotated[Any, Field(description="NocoBase CRM project ID.")], include_contacts: Annotated[bool, Field(description="Include related contacts.")] = True, include_followups: Annotated[bool, Field(description="Include related follow-ups.")] = True, include_updates: Annotated[bool, Field(description="Include project update records.")] = True, ) -> dict[str, Any]: """Get a CRM project with its existing NocoBase relations.""" return safe_result( lambda: service.get_project( project_id, include_contacts=include_contacts, include_followups=include_followups, include_updates=include_updates, ) ) @server.tool() def get_project_timeline( project_id: Annotated[Any, Field(description="NocoBase CRM project ID.")], date_from: Annotated[str | None, Field(description="Optional lower event date bound.")] = None, date_to: Annotated[str | None, Field(description="Optional upper event date bound.")] = None, ) -> dict[str, Any]: """Return a chronological CRM project timeline from follow-ups and project updates.""" return safe_result(lambda: service.get_project_timeline(project_id, date_from=date_from, date_to=date_to)) @server.tool() def list_stale_customers( team: Annotated[str | None, Field(description="Optional CRM team name.")] = None, owner: Annotated[str | None, Field(description="Optional customer owner.")] = None, inactive_days: Annotated[int, Field(description="Days without a valid follow-up.")] = 30, as_of: Annotated[str | None, Field(description="Reference date in YYYY-MM-DD format.")] = None, limit: Annotated[int, Field(description="Maximum customers to return.")] = 20, offset: Annotated[int, Field(description="Zero-based result offset.")] = 0, ) -> dict[str, Any]: """List customers whose last valid follow-up is older than the configured threshold.""" return safe_result( lambda: service.list_stale_customers( team=team, owner=owner, inactive_days=inactive_days, as_of=as_of, limit=limit, offset=offset, ) )