Task 20 of Phase 0: document the sub-module split. - fusion_accounting_core: foundation doc covering security groups, shared-field schema preservation, and the Enterprise-detection helper. - fusion_accounting_ai: preserves the original module's AI-specific design decisions, Odoo 19 gotchas, deployment commands, controllers, models, theme rules, and known issues. Adds a new Data-adapter pattern section documenting tri-mode routing (fusion / enterprise / community). - fusion_accounting_migration: doc for the Enterprise uninstall safety guard and the wizard shell that future feature sub-modules will extend. - fusion_accounting (meta): rewritten CLAUDE.md as a pure overview pointing at sub-modules, plus a new README.md covering one-click install/uninstall. Each sub-module now has CLAUDE.md (Cursor/Claude context), UPGRADE_NOTES.md (version-by-version deltas / reference sources), and README.md (user-facing install/usage docs). 11 files total. Made-with: Cursor
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 detectionfusion_accounting_bank_rec(Phase 1): adapter routes to it when presentfusion_accounting_reports(Phase 2): samefusion_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.py—DataAdapter+AdapterModeservices/data_adapters/_registry.py—get_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 toir.config_parameter— NO hard dependency on fusion_api - Claude adapter: native
tool_useblocks, extended thinking enabled (8K budget) for all Claude 4.x models - OpenAI adapter: Chat Completions API with o-series reasoning model support (
developerrole,max_completion_tokens,reasoning_effort) - API keys stored in
ir.config_parameterwithfusion_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_jsonis updated to replace thepending_approvalplaceholder with the actual tool result — this prevents danglingtool_useblocks 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(NOTaccount.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.sessionwithmessage_ids_json(JSON text field) - On page load, chat panel calls
/session/latestto 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.jsviamdToHtml()function - HTML is injected via
innerHTMLononMounted+onPatched(NOT via OWL'smarkup()/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-tablefenced code blocks instead of Markdown tables for actionable results mdToHtml()detects these blocks, extracts JSON, and rendersFusionInteractiveTableOWL components viamount()- 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_attentionandrecent_activityJSON 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_entrytool posts directly to the Miscellaneous Operations journal — debit expense + debit HST ITC (2006) + credit bank - Domain prompt (
hst_managementin domain_prompts.py) includes bank journal IDs and the full 4-phase workflow instructions
Odoo 19 Gotchas (Learned the Hard Way)
Search Views
- NO
stringattribute on<search>element - NO
stringattribute 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) — NOTstatic props = [](accept none)
OWL Rich HTML Rendering
markup()from@odoo/owl+t-outis UNRELIABLE in Odoo 19 for rendering HTML in OWL components- Use
onMounted+onPatchedhooks to find DOM elements and setinnerHTMLdirectly - Pattern: render a placeholder
<div class="slot" t-att-data-idx="index"/>, then in the hook find it and set.innerHTML - Always use BOTH
onMountedANDonPatched—onPatchedalone misses the first render
Cron Safe Eval
- NO
importstatements (forbidden opcodeIMPORT_NAME) datetimemodule available asdatetime(usedatetime.datetime.now(),datetime.timedelta())- NO
from datetime import Xpattern
read_group Deprecated
read_group()is deprecated in Odoo 19 — use_read_group()instead- Still works but throws DeprecationWarning
- Dashboard
accounting_dashboard.pystill usesread_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_parametermust match one of the new options or Settings page will crash withValueError: 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
stringlabel - Our
display_name_fieldconflicted with built-indisplay_name— renamed string to "Tool Label" - API key fields use "(Fusion AI)" suffix to avoid label conflicts with other modules
- Tool model uses
domain(notdomain_name) andparameters_schema(notparameters) as field names
Group Assignment
implied_idson 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)notrgba(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 inaccounting_dashboard.py— migrate to_read_group()when the new API format is stablegenerate_t4,generate_roeare stubs pointing to fusion_payroll (by design — Phase 2)get_payroll_schedule,verify_source_deductions,verify_payroll_deductionsare stubs (Phase 2 — fusion_payroll integration)answer_financial_questionis 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
o1model 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)