Files
Odoo-Modules/fusion_accounting_followup/CLAUDE.md
gsinghpal 3491069f48
Some checks failed
fusion_accounting CI / test (fusion_accounting_ai) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_core) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_migration) (push) Has been cancelled
docs(fusion_accounting_followup): CLAUDE.md, UPGRADE_NOTES.md, README.md
Made-with: Cursor
2026-04-19 21:41:41 -04:00

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 a fusion.followup.level
  • risk_scorer — 0-100 payment-risk score plus structured drivers
  • tone_selector — gentle / firm / legal based on level + risk
  • followup_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) — adds fusion_followup_status, fusion_followup_paused_until, fusion_followup_last_level_id, fusion_followup_risk_score, fusion_followup_risk_band
  • account.move.line (inherits) — adds fusion_followup_level_id and fusion_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; supports auto_resolve_level, override_level_id, and force flags

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 wrappers
  • views/followup_dashboard/* — top-level dashboard view
  • components/risk_badge, partner_card, aging_bucket_strip, ai_text_panel, followup_history_table — 5 components
  • scss/_variables.scss + followup.scss + dark_mode.scss
  • tours/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 levels
  • data/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_constraintsmodels.Constraint (every persisted model)
  • @api.depends('id') → not used (would raise NotImplementedError)
  • @route(type='json')type='jsonrpc' (all 6 endpoints in controllers/followup_controller.py)
  • numbercall removed from ir.cron (data/cron.xml)
  • res.groups.usersuser_ids and ir.ui.menu.groups_idgroup_ids (security + menu_views.xml)
  • SCSS: @import "variables" is forbidden in V19; rely on manifest asset concatenation order (_variables.scss first)
  • OWL t-on-click arrow handlers must use an explicit this. 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 (tagged local_llm, skips when no LLM), 5 OWL tour tests (tagged tour, 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_risk paid_late_count and avg_days_late are 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_followup do not sudo() 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.run record but does not raise.
  • followup_text_generator always returns a usable dict (templated fallback when LLM absent), so callers can't distinguish "AI said so" from "fallback fired"; the tone_used and absence of key_points are the only signals.
  • Sub-second SLA on controller.list_overdue for partner counts > 200 is not yet stress-tested.