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>
87 lines
4.0 KiB
Python
87 lines
4.0 KiB
Python
from __future__ import annotations
|
|
import logging
|
|
from datetime import date, timedelta
|
|
from ..tools.odoo_client import OdooClient
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AccountingTools:
|
|
def __init__(self, odoo: OdooClient):
|
|
self._o = odoo
|
|
|
|
async def get_journal_entries(self, journal_id: int = None, date_from: str = None,
|
|
date_to: str = None, state: str = 'posted', limit: int = 50) -> list:
|
|
domain = [('move_type', '=', 'entry'), ('state', '=', state)]
|
|
if journal_id:
|
|
domain.append(('journal_id', '=', journal_id))
|
|
if date_from:
|
|
domain.append(('date', '>=', date_from))
|
|
if date_to:
|
|
domain.append(('date', '<=', date_to))
|
|
fields = ['name', 'date', 'journal_id', 'ref', 'state', 'amount_total', 'line_ids']
|
|
return await self._o.search_read('account.move', domain, fields, limit=limit)
|
|
|
|
async def get_chart_of_accounts(self, account_type: str = None, limit: int = 100) -> list:
|
|
domain = [('deprecated', '=', False)]
|
|
if account_type:
|
|
domain.append(('account_type', '=', account_type))
|
|
fields = ['code', 'name', 'account_type', 'balance', 'currency_id']
|
|
return await self._o.search_read('account.account', domain, fields, limit=limit)
|
|
|
|
async def get_account_balance(self, account_id: int) -> dict:
|
|
records = await self._o.search_read(
|
|
'account.account', [('id', '=', account_id)],
|
|
['code', 'name', 'balance', 'account_type'], limit=1,
|
|
)
|
|
return records[0] if records else {}
|
|
|
|
async def get_trial_balance(self, date_from: str = None, date_to: str = None) -> list:
|
|
today = date.today()
|
|
df = date_from or today.replace(day=1).isoformat()
|
|
dt = date_to or today.isoformat()
|
|
domain = [
|
|
('move_id.state', '=', 'posted'),
|
|
('date', '>=', df),
|
|
('date', '<=', dt),
|
|
]
|
|
fields = ['account_id', 'debit', 'credit', 'balance']
|
|
lines = await self._o.search_read('account.move.line', domain, fields, limit=500)
|
|
by_account: dict = {}
|
|
for line in lines:
|
|
aid = line['account_id'][0] if isinstance(line['account_id'], list) else line['account_id']
|
|
aname = line['account_id'][1] if isinstance(line['account_id'], list) else str(aid)
|
|
if aid not in by_account:
|
|
by_account[aid] = {'account_id': aid, 'account_name': aname, 'debit': 0.0, 'credit': 0.0}
|
|
by_account[aid]['debit'] += line.get('debit', 0.0)
|
|
by_account[aid]['credit'] += line.get('credit', 0.0)
|
|
result = list(by_account.values())
|
|
for r in result:
|
|
r['balance'] = r['debit'] - r['credit']
|
|
return result
|
|
|
|
async def get_tax_summary(self, date_from: str = None, date_to: str = None) -> dict:
|
|
today = date.today()
|
|
df = date_from or today.replace(day=1).isoformat()
|
|
dt = date_to or today.isoformat()
|
|
domain = [
|
|
('move_id.state', '=', 'posted'),
|
|
('date', '>=', df),
|
|
('date', '<=', dt),
|
|
('tax_ids', '!=', False),
|
|
]
|
|
fields = ['tax_ids', 'debit', 'credit', 'balance']
|
|
lines = await self._o.search_read('account.move.line', domain, fields, limit=500)
|
|
total_tax = sum(abs(line.get('balance', 0)) for line in lines)
|
|
return {'period_from': df, 'period_to': dt, 'total_tax_lines': len(lines), 'total_tax_amount': total_tax}
|
|
|
|
async def flag_for_review(self, model: str, record_id: int, reason: str, severity: str = 'medium') -> bool:
|
|
note = f'[AI FLAG - {severity.upper()}] {reason}'
|
|
await self._o.call(model, 'message_post', [[record_id]], {'body': note, 'message_type': 'comment'})
|
|
logger.info('Flagged %s:%s (%s) for review', model, record_id, severity)
|
|
return True
|
|
|
|
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
|