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>
84 lines
3.6 KiB
Python
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
|