6.3 KiB
fusion_accounting_followup — Cursor / Claude Context
Purpose
AI-augmented customer follow-ups (dunning) — a Fusion-native replacement
for (and coexisting with) Odoo Enterprise's account_followup module.
Ships in Phase 4 of the fusion_accounting roadmap.
Architecture
Hybrid: the engine (fusion.followup.engine, AbstractModel) is the
SINGLE write surface for the follow-up lifecycle. Everything else
(controllers, OWL components, AI tools, wizards, cron) routes through
the engine's 7-method public API:
get_overdue_for_partner(partner)compute_followup_level(partner)send_followup_email(partner, level=None, force=False)escalate_to_next_level(partner)pause_followup(partner, until_date=None)reset_followup(partner)snapshot_followup_history(partner, limit=50)
Pure-Python services live in services/:
overdue_aging— 6 buckets (current, 1-30, 31-60, 61-90, 91-120, 120+)level_resolver— match aging to afusion.followup.levelrisk_scorer— 0-100 payment-risk score plus structured driverstone_selector— gentle / firm / legal based on level + riskfollowup_text_generator+followup_text_prompt— LLM-generated follow-up text with a templated fallback that keeps the feature usable offline
Persisted models in models/:
fusion.followup.level— level definition (delay_days, tone, mail_template_id, requires_manual_review, sequence)fusion.followup.run— per-partner audit record (state, level, amount, ai-generated flag, error captured)fusion.followup.text.cache— LLM cost-saving cache keyed on (partner, level, tone, prompt fingerprint)fusion.followup.engine— AbstractModel (the API)fusion.followup.cron— cron handlers (daily scan, weekly risk refresh)res.partner(inherits) — addsfusion_followup_status,fusion_followup_paused_until,fusion_followup_last_level_id,fusion_followup_risk_score,fusion_followup_risk_bandaccount.move.line(inherits) — addsfusion_followup_level_idandfusion_followup_last_run_date
Wizards (TransientModel) in wizards/:
fusion.batch.followup.wizard— bulk-send across all overdue customers, a manual selection, or a level-filtered subset; supportsauto_resolve_level,override_level_id, andforceflags
Controllers: controllers/followup_controller.py exposes 6 JSON-RPC
endpoints under /fusion/followup/* (list_overdue, get_partner,
compute_level, send, escalate, pause, reset, history,
generate_text). All calls route through the engine.
OWL frontend: static/src/
services/followup_service.js— central reactive state + RPC wrappersviews/followup_dashboard/*— top-level dashboard viewcomponents/risk_badge,partner_card,aging_bucket_strip,ai_text_panel,followup_history_table— 5 componentsscss/_variables.scss+followup.scss+dark_mode.scsstours/followup_tours.js— 5 OWL tour smoke tests
Default data:
data/followup_levels_data.xml— 3 default levels (Reminder @ 7d gentle, Warning @ 30d firm, Legal Notice @ 60d legal)data/mail_templates_data.xml— 3 mail templates wired to the levelsdata/cron.xml— daily scan + weekly risk refresh
Coexistence
When account_followup is installed the Customer Follow-ups menu hides
via fusion_accounting_core.group_fusion_show_when_enterprise_absent.
The engine + AI tools always remain available for the chat / API. The
migration step in fusion.migration.wizard backfills
fusion.followup.level records from existing
account_followup.followup.line rows (idempotent — skips rows already
linked via the legacy_followup_line_id column).
V19 Conventions Applied
_sql_constraints→models.Constraint(every persisted model)@api.depends('id')→ not used (would raiseNotImplementedError)@route(type='json')→type='jsonrpc'(all 6 endpoints incontrollers/followup_controller.py)numbercallremoved fromir.cron(data/cron.xml)res.groups.users→user_idsandir.ui.menu.groups_id→group_ids(security + menu_views.xml)- SCSS:
@import "variables"is forbidden in V19; rely on manifest asset concatenation order (_variables.scssfirst) - OWL
t-on-clickarrow handlers must use an explicitthis.reference
Performance baseline (Task 21)
| Operation | P95 | Budget |
|---|---|---|
engine.compute_followup_level |
0ms | 50ms |
engine.get_overdue_for_partner |
1ms | 100ms |
engine.send_followup_email (no due) |
0ms | 200ms |
controller.list_overdue (20 ptrs) |
100ms | 500ms |
(Engine ops measured against partners with no overdue lines — these are
floor measurements; load-driven scaling is verified in
test_performance_benchmarks.py.) All Phase 4 perf metrics are within
1x of budget; no optimization needed at ship.
Test counts (Phase 4 ship)
- 106 logical tests in
fusion_accounting_followup - 0 failures, 0 errors
- Coverage includes: 4 engine + 1 controller benchmark (tagged
benchmark), 1 local LLM smoke (taggedlocal_llm, skips when no LLM), 5 OWL tour tests (taggedtour, skip without websocket-client), Hypothesis property tests on the engine, integration tests on the public API, controller round-trip tests, cron tests, batch wizard tests, coexistence tests, migration round-trip test.
Known concerns / Phase 4.5 backlog
risk_scorer._compute_riskpaid_late_countandavg_days_lateare placeholders; full reconciliation traversal deferred for performance.- Migration tone heuristic could misclassify Enterprise levels with non-standard sequence numbers (numeric sequence outside 1/10/100 buckets).
pause_followup/reset_followupdo notsudo()the partner write — could fail for non-admin users without partner-write rights.- Email send is best-effort — failure is captured on the
fusion.followup.runrecord but does not raise. followup_text_generatoralways returns a usable dict (templated fallback when LLM absent), so callers can't distinguish "AI said so" from "fallback fired"; thetone_usedand absence ofkey_pointsare the only signals.- Sub-second SLA on
controller.list_overduefor partner counts > 200 is not yet stress-tested.