Files
Odoo-Modules/fusion_accounting/fusion_accounting_ai/CLAUDE.md
gsinghpal 9ebf89bde2 changes
2026-05-16 13:18:52 -04:00

17 KiB

fusion_accounting_ai — Cursor / Claude Context

Purpose

Conversational AI co-pilot for Odoo Accounting using Claude or GPT with native tool-calling. Embeds in any Odoo install via the data-adapter pattern (works on Community-only, Community + fusion native sub-modules, or Community + Enterprise).

Sub-module relationships

  • fusion_accounting_core: hard dep, provides security groups + Enterprise detection
  • fusion_accounting_bank_rec (Phase 1): adapter routes to it when present
  • fusion_accounting_reports (Phase 2): same
  • fusion_accounting_followup (Phase 5): same
  • Odoo Enterprise modules: detected at runtime, AI tools route through them via adapters

Data-adapter pattern (Phase 0 addition)

  • services/data_adapters/base.pyDataAdapter + AdapterMode
  • services/data_adapters/_registry.pyget_adapter(env, name) + register_adapter
  • One adapter file per domain: bank_rec.py, reports.py, followup.py, assets.py
  • Each adapter implements <method>_via_fusion, <method>_via_enterprise, <method>_via_community
  • Adapter _select_mode() picks fusion if model loaded, else enterprise if module installed, else community

Architecture

fusion_accounting_ai/
├── models/          7 files (5 new models + 2 inherits: account.move, res.config.settings)
├── services/
│   ├── agent.py         AI orchestrator (prompt assembly, tool dispatch loop)
│   ├── adapters/        Claude + OpenAI adapters with native tool-calling
│   ├── data_adapters/   Tri-mode domain routers (fusion / enterprise / community)
│   ├── tools/           93 tool functions across 11 domain files
│   ├── prompts/         System prompt builder + 12 domain-specific prompts
│   └── scoring.py       Confidence scoring + tier promotion logic
├── controllers/     10 JSON-RPC endpoints
├── wizards/         Rule creation wizard
├── static/src/      OWL dashboard + chat panel + approval cards
├── views/           List/form/search views, menus, settings
├── security/        ACLs + record rules (groups themselves live in fusion_accounting_core)
├── data/            88 tool definitions, 2 default rules, 2 crons, 1 sequence
├── tests/           API integration tests
└── report/          Audit report QWeb template

Key Design Decisions

AI Provider Integration

  • Uses fusion.api.service (from fusion_api module) for API key resolution with fallback to ir.config_parameter — NO hard dependency on fusion_api
  • Claude adapter: native tool_use blocks, extended thinking enabled (8K budget) for all Claude 4.x models
  • OpenAI adapter: Chat Completions API with o-series reasoning model support (developer role, max_completion_tokens, reasoning_effort)
  • API keys stored in ir.config_parameter with fusion_accounting. prefix
  • API key fields in Settings use password="True" widget — labels include "(Fusion AI)" suffix to avoid conflicts with other modules' key fields
  • Provider pinning: Sessions remember which provider was used. If the global provider changes mid-session, the session continues with its original provider to prevent cross-adapter message format contamination.

Tool Tiering

  • Tier 1 (Free): Read-only, execute immediately — 60+ tools
  • Tier 2 (Auto-approved): Low-risk writes, logged — ~10 tools
  • Tier 3 (Requires approval): Financial writes, user must approve — ~15 tools
  • Auto-promotion: Tier 3 → Tier 2 at 95% accuracy over 30+ decisions (atomic SQL counters on fusion.accounting.rule._record_decision)
  • Tool descriptions include tier labels (e.g., [Tier 3: Requires user approval]) so the AI knows which tools need approval
  • When a Tier 3 tool is encountered during the chat loop, the loop short-circuits: a final text response is forced so the AI can present approval cards to the user

Tier 3 Approval Flow

  • When a Tier 3 action is approved/rejected, the session's message_ids_json is updated to replace the pending_approval placeholder with the actual tool result — this prevents dangling tool_use blocks that would cause API errors on the next chat turn
  • After approval, scoring.check_promotions() is called to check if any rules should be promoted

Menu Location

  • Parent: accountant.menu_accounting (NOT account.menu_finance — that's Community Edition only)
  • Enterprise uses accountant.menu_accounting (ID 1663) as the visible menu root
  • account.menu_finance (ID 180) exists but has NO visible children in Enterprise — it's the Community root

Session Persistence

  • Chat sessions stored in fusion.accounting.session with message_ids_json (JSON text field)
  • On page load, chat panel calls /session/latest to restore the most recent active session
  • Empty assistant messages (tool-call-only responses with no text) are filtered out by the controller
  • "New Chat" button closes current session and creates a fresh one
  • Session name (e.g., FAS/2026/00001) shown in the chat header
  • Session ownership: Controllers verify the current user owns the session (managers can access any session)

Rich Text Chat Output

  • AI responses are rendered as rich HTML, not plain text
  • Markdown-to-HTML conversion happens client-side in chat_panel.js via mdToHtml() function
  • HTML is injected via innerHTML on onMounted + onPatched (NOT via OWL's markup() / t-out — those proved unreliable in Odoo 19)
  • The _renderRichMessages() method finds .fusion_rich_slot[data-idx] divs and sets their innerHTML
  • Supported: headers (# through #####), bold, italic, code, tables, bullet/numbered lists, horizontal rules, links
  • System prompt instructs AI to use markdown formatting and include Odoo record links like [INV/2026/00123](/odoo/accounting/123)

Interactive Tables (fusion-table)

  • AI can return fusion-table fenced code blocks instead of Markdown tables for actionable results
  • mdToHtml() detects these blocks, extracts JSON, and renders FusionInteractiveTable OWL components via mount()
  • Interactive mode: checkbox column + data columns + AI Recommendation column (colour-coded badge) + Your Input column (text field per row) + bottom bulk action bar
  • Read-only mode: styled table, no inputs/actions
  • Actions: Apply Recommendations, Flag Selected, Create Rules, Dismiss Selected, Submit All Notes to AI
  • Action button clicks format a [TABLE_ACTION] structured message and send it back through the chat endpoint
  • The AI decides per-response whether to use interactive or Markdown tables based on whether the data is actionable
  • Used for: find_missing_itc_bills, find_duplicate_bills, get_overdue_invoices, find_draft_entries, get_unreconciled_bank_lines, etc.
  • NOT used for: get_profit_loss, get_balance_sheet, get_trial_balance (informational, read-only)
  • All styles use Odoo CSS variables — dark/light mode handled automatically

Dashboard Layout

  • Health cards row at top (6 cards: Bank Recon, AR, AP, HST, Audit Score, Month-End)
  • Below: side-by-side layout — "Needs Attention" panel (flex-grow) + Chat panel (720px fixed width)
  • Chat panel is 720px (80% larger than original 400px design)
  • Dashboard endpoint returns needs_attention and recent_activity JSON arrays alongside health card metrics

HST Filing Workflow (4-Phase AI-Driven)

  • Phase 1: AI runs all HST reports (tax report, missing ITCs, compliance audit, HST balance)
  • Phase 2: AI sweeps ALL bank accounts for unreconciled expense payments
  • Phase 3: Per-line processing — check for existing bills, check history for coding patterns, ask about HST, create bills, register payments
  • Phase 4: Re-run reports to verify updated HST position
  • New tools added: search_partners (Tier 1), find_similar_bank_lines (Tier 1), get_bank_line_details (Tier 1), create_vendor_bill (Tier 3), register_bill_payment (Tier 3), create_expense_entry (Tier 3)
  • Two paths for recording expenses: (a) formal vendor bill + payment, or (b) direct GL entry in MISC journal with optional HST split
  • The create_expense_entry tool posts directly to the Miscellaneous Operations journal — debit expense + debit HST ITC (2006) + credit bank
  • Domain prompt (hst_management in domain_prompts.py) includes bank journal IDs and the full 4-phase workflow instructions

Odoo 19 Gotchas (Learned the Hard Way)

Search Views

  • NO string attribute on <search> element
  • NO string attribute on <group> element inside search views
  • Group-by filters MUST have domain="[]" attribute
  • Add <separator/> before <group> in search views

OWL Client Actions

  • Components registered as client actions receive props: action, actionId, updateActionState, className
  • Must use static props = ["*"] (accept any) — NOT static props = [] (accept none)

OWL Rich HTML Rendering

  • markup() from @odoo/owl + t-out is UNRELIABLE in Odoo 19 for rendering HTML in OWL components
  • Use onMounted + onPatched hooks to find DOM elements and set innerHTML directly
  • Pattern: render a placeholder <div class="slot" t-att-data-idx="index"/>, then in the hook find it and set .innerHTML
  • Always use BOTH onMounted AND onPatchedonPatched alone misses the first render

Cron Safe Eval

  • NO import statements (forbidden opcode IMPORT_NAME)
  • datetime module available as datetime (use datetime.datetime.now(), datetime.timedelta())
  • NO from datetime import X pattern

read_group Deprecated

  • read_group() is deprecated in Odoo 19 — use _read_group() instead
  • Still works but throws DeprecationWarning
  • Dashboard accounting_dashboard.py still uses read_group() — migrate to _read_group() when the new API is stable

Config Parameter Values

  • When changing a Selection field's options, the stored DB value in ir_config_parameter must match one of the new options or Settings page will crash with ValueError: Wrong value
  • Fix: UPDATE the value in DB after changing selection options:
    UPDATE ir_config_parameter SET value = 'new_value' WHERE key = 'fusion_accounting.field_name';
    

Field Label Conflicts

  • Odoo warns if two fields on the same model have the same string label
  • Our display_name_field conflicted with built-in display_name — renamed string to "Tool Label"
  • API key fields use "(Fusion AI)" suffix to avoid label conflicts with other modules
  • Tool model uses domain (not domain_name) and parameters_schema (not parameters) as field names

Group Assignment

  • implied_ids on groups only applies to NEWLY added users, not existing ones
  • After installing, manually add existing users to groups via SQL:
    INSERT INTO res_groups_users_rel (gid, uid)
    SELECT <group_id>, gu.uid FROM res_groups_users_rel gu
    JOIN ir_model_data imd ON imd.res_id = gu.gid AND imd.model = 'res.groups'
    WHERE imd.module = 'account' AND imd.name = 'group_account_manager'
    ON CONFLICT DO NOTHING;
    

TransientModel in Controllers

  • Use .new({...}) NOT .create({...}) for TransientModels in controller endpoints
  • .create() writes a DB row on every request; .new() is in-memory only
  • Dashboard controller uses .new() to compute health metrics without DB writes

Server Details

  • Server: odoo-westin (192.168.1.40, SSH via ssh odoo-westin)
  • Container: odoo-dev-app (Odoo), odoo-dev-db (PostgreSQL)
  • Database: westin-v19
  • Module path: /mnt/extra-addons/fusion_accounting_ai/
  • Python deps: anthropic (v0.88.0), openai (v2.30.0) — installed with --break-system-packages
  • URL: erp.westinhealthcare.ca

Deployment Commands

# Full deploy cycle (clean + copy + upgrade + restart)
ssh odoo-westin "docker exec -u 0 odoo-dev-app rm -rf /mnt/extra-addons/fusion_accounting_ai"
scp -r "K:\Github\Odoo-Modules\fusion_accounting_ai" odoo-westin:/tmp/fusion_accounting_ai
ssh odoo-westin "docker cp /tmp/fusion_accounting_ai odoo-dev-app:/mnt/extra-addons/fusion_accounting_ai && rm -rf /tmp/fusion_accounting_ai"
ssh odoo-westin "docker exec odoo-dev-app odoo -d westin-v19 -u fusion_accounting_ai --stop-after-init --http-port=8099 -c /etc/odoo/odoo.conf"
ssh odoo-westin "docker restart odoo-dev-app"

# Check logs
ssh odoo-westin "docker logs odoo-dev-app --tail 100"

# Quick DB queries
ssh odoo-westin "docker exec odoo-dev-db psql -U odoo -d westin-v19 -t -c \"<SQL>\""

# Check module state
ssh odoo-westin "docker exec odoo-dev-db psql -U odoo -d westin-v19 -t -c \"SELECT name, state, latest_version FROM ir_module_module WHERE name = 'fusion_accounting_ai';\""

Security Groups

(The three groups themselves are now defined in fusion_accounting_core. This module's security/ir.model.access.csv grants access on AI-specific models using those group XML-ids.)

XML ID (in fusion_accounting_core) Name Access in AI module
group_fusion_accounting_user User Dashboard, chat (read-only tools)
group_fusion_accounting_manager Manager + Approve/reject, Tier 2 tools, rules
group_fusion_accounting_admin Administrator + Config, all tools, rule admin

Auto-assigned (configured in _core): account.group_account_user → User, account.group_account_manager → Admin

Controller Endpoints

Route Auth Purpose
/fusion_accounting/session/create user Create new chat session
/fusion_accounting/session/close user (ownership check) Close active session
/fusion_accounting/session/latest user (own sessions only) Load most recent active session + messages
/fusion_accounting/session/history user (ownership check, managers see all) Load specific session messages
/fusion_accounting/chat user (ownership check) Send message, get AI response
/fusion_accounting/approve user + manager group check Approve single Tier 3 action
/fusion_accounting/reject user + manager group check Reject single Tier 3 action
/fusion_accounting/approve_all user + manager group check Batch approve multiple actions
/fusion_accounting/reject_all user + manager group check Batch reject multiple actions
/fusion_accounting/dashboard/data user Get dashboard health card metrics + needs_attention + recent_activity

Note: Approve/reject endpoints use auth='user' at the decorator level with an imperative has_group() check inside the handler (Odoo has no built-in auth='manager').

Models

Model Type Location Purpose
fusion.accounting.session Model models/ Chat sessions with message JSON storage
fusion.accounting.match.history Model models/ Every AI tool call + decision (approved/rejected/pending)
fusion.accounting.rule Model models/ Fusion Rules engine with versioning and auto-promotion
fusion.accounting.tool Model models/ Tool registry (82 tools seeded from XML)
fusion.accounting.dashboard TransientModel models/ Computed health metrics (use .new() not .create())
res.config.settings (inherit) TransientModel models/ Settings page (API keys, thresholds, toggles)
account.move (inherit) Model models/ Post-action audit hook
fusion.accounting.agent AbstractModel services/ AI orchestrator
fusion.accounting.adapter.claude AbstractModel services/ Claude tool-calling adapter
fusion.accounting.adapter.openai AbstractModel services/ OpenAI tool-calling adapter
fusion.accounting.scoring AbstractModel services/ Confidence scoring
fusion.accounting.rule.wizard TransientModel wizards/ Quick-create rule from chat suggestion

AI Models Available

Claude (default: claude-sonnet-4-6):

  • claude-opus-4-6, claude-sonnet-4-6, claude-haiku-4-5
  • claude-sonnet-4-5, claude-opus-4-5, claude-sonnet-4-0, claude-opus-4-0

OpenAI (default: gpt-5.4-mini):

  • gpt-5.4, gpt-5.4-mini, gpt-5.4-nano
  • o3, o4-mini
  • gpt-4o, gpt-4o-mini (legacy)

Theme / Styling Rules

  • NO hardcoded colours — use CSS variables (var(--o-border-color), var(--bs-body-color-rgb)) and Bootstrap utility classes
  • Must work in both light and dark mode
  • Box shadows: use rgba(var(--bs-body-color-rgb), 0.1) not rgba(0,0,0,0.1)
  • AI messages use var(--o-view-background-color) background + var(--o-border-color) border
  • Links use var(--o-action-color) for theme awareness

Known Issues / Future Work

  • read_group() deprecation warnings in accounting_dashboard.py — migrate to _read_group() when the new API format is stable
  • generate_t4, generate_roe are stubs pointing to fusion_payroll (by design — Phase 2)
  • get_payroll_schedule, verify_source_deductions, verify_payroll_deductions are stubs (Phase 2 — fusion_payroll integration)
  • answer_financial_question is a stub (returns message to use other tools instead)
  • Batch approval "Approve All" / "Reject All" buttons are in the chat panel but not yet in the match history list view
  • "Needs Attention" panel shows placeholder text in the dashboard — the data is computed and returned by the API but the frontend rendering needs to be connected
  • Consider switching OpenAI adapter from Chat Completions API to Responses API for better tool handling with newer models
  • o1 model does not support tool calling — no guard in place (o3/o4-mini do support it)
  • Multi-company record rule on fusion.accounting.session — added in Phase 0 split-out (see UPGRADE_NOTES.md)