Files
odoo-ai/agent_service/tools/project_tools.py
ActiveBlue Build fe47f950e4 feat(agents): add 7 specialist agents with tools and system prompts
Agents (all following 6-step contract: _plan/_gather/_reason/_act/_report):
- AccountingAgent: trial balance, chart of accounts, tax summary (HIPAA-locked)
- CrmAgent: pipeline summary, lead/opportunity management, won/lost analysis
- SalesAgent: sales orders, quotations, revenue by rep, expired quote detection
- ProjectAgent: task tracking, blocked/overdue detection, timesheet logging
- ElearningAgent: course completion, low-engagement flagging, next-course suggestion
- ExpensesAgent: expense sheets, pending approvals, policy violations (HIPAA-locked)
- EmployeesAgent: headcount, contracts, leaves, attendance, expired contract sweep (HIPAA-locked)

Tools (one file per domain):
- accounting_tools.py, crm_tools.py, sales_tools.py, project_tools.py
- elearning_tools.py, expenses_tools.py, employees_tools.py

System prompts: each agent has a domain-specific system.txt with rules and output format

All agents implement handle_peer_request() and sweep() for proactive monitoring
HIPAA-locked agents (accounting, expenses, employees) enforced via LLMRouter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 18:04:32 -04:00

84 lines
3.6 KiB
Python

from __future__ import annotations
import logging
from ..tools.odoo_client import OdooClient
logger = logging.getLogger(__name__)
class ProjectTools:
def __init__(self, odoo: OdooClient):
self._o = odoo
async def get_projects(self, active: bool = True, limit: int = 50) -> list:
domain = [('active', '=', active)]
fields = ['name', 'partner_id', 'user_id', 'date_start', 'date',
'task_count', 'description', 'last_update_status']
return await self._o.search_read('project.project', domain, fields, limit=limit)
async def get_tasks(self, project_id: int = None, stage_id: int = None,
user_id: int = None, limit: int = 100) -> list:
domain = [('active', '=', True)]
if project_id:
domain.append(('project_id', '=', project_id))
if stage_id:
domain.append(('stage_id', '=', stage_id))
if user_id:
domain.append(('user_ids', 'in', [user_id]))
fields = ['name', 'project_id', 'stage_id', 'user_ids', 'date_deadline',
'priority', 'kanban_state', 'description', 'tag_ids']
return await self._o.search_read('project.task', domain, fields, limit=limit)
async def get_project_summary(self, project_id: int) -> dict:
tasks = await self._o.search_read(
'project.task', [('project_id', '=', project_id), ('active', '=', True)],
['stage_id', 'kanban_state', 'date_deadline', 'user_ids'],
limit=500,
)
total = len(tasks)
blocked = [t for t in tasks if t.get('kanban_state') == 'blocked']
overdue = [t for t in tasks if t.get('date_deadline') and t['date_deadline'] < str(__import__('datetime').date.today())]
return {
'project_id': project_id,
'total_tasks': total,
'blocked_tasks': len(blocked),
'overdue_tasks': len(overdue),
}
async def update_task_stage(self, task_id: int, stage_id: int) -> bool:
result = await self._o.write('project.task', [task_id], {'stage_id': stage_id})
return result.success
async def assign_task(self, task_id: int, user_id: int) -> bool:
result = await self._o.write('project.task', [task_id], {'user_ids': [(4, user_id)]})
return result.success
async def create_task(self, project_id: int, name: str, description: str = '',
user_id: int = None, date_deadline: str = None) -> int:
vals = {'project_id': project_id, 'name': name}
if description:
vals['description'] = description
if user_id:
vals['user_ids'] = [(4, user_id)]
if date_deadline:
vals['date_deadline'] = date_deadline
record_id = await self._o.call('project.task', 'create', [vals])
logger.info('Created task %s in project %s', record_id, project_id)
return record_id
async def log_timesheet(self, task_id: int, employee_id: int, hours: float,
description: str = '', date: str = None) -> int:
import datetime
vals = {
'task_id': task_id,
'employee_id': employee_id,
'unit_amount': hours,
'name': description or 'AI-logged timesheet',
'date': date or str(datetime.date.today()),
}
record_id = await self._o.call('account.analytic.line', 'create', [vals])
return record_id
async def post_chatter_note(self, model: str, record_id: int, note: str) -> bool:
await self._o.call(model, 'message_post', [[record_id]], {'body': note, 'message_type': 'comment'})
return True