feat(fusion_accounting_followup): Phase 4 skeleton + plan

35-task plan to replace Enterprise account_followup module:
- Multi-level dunning (gentle reminder -> firm warning -> legal)
- AI augmentation: contextual follow-up text generation + payment risk scoring + tone selection
- HYBRID engine: shared primitives + persisted level/run/cache models
- Per-partner state: current level, paused-until, history
- Coexists with Enterprise (group_fusion_show_when_enterprise_absent)
- Same V19 conventions + test pyramid + perf-budget discipline as Phases 1-3

Made-with: Cursor
This commit is contained in:
gsinghpal
2026-04-19 20:31:07 -04:00
parent b4558a223c
commit ea2f44287f
11 changed files with 186 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
# Phase 4 — Fusion Accounting Follow-up Implementation Plan
**Module:** `fusion_accounting_followup`
**Branch:** `fusion_accounting/phase-4-followup`
**Pre-phase tag:** `fusion_accounting/pre-phase-4`
**Estimated tasks:** ~35
**Reference:** `/Users/gurpreet/Github/RePackaged-Odoo/accounting/account_followup/` (~1318 LOC Python)
## Goal
Replace Enterprise's `account_followup` module — multi-level dunning sequences for unpaid invoices, with AI augmentation: contextually-appropriate follow-up text generation + payment-risk scoring + tone adjustment based on customer history. Coexists with Enterprise.
## Architecture (HYBRID engine, Phases 1-3 pattern)
```
fusion.followup.engine (AbstractModel) ← shared primitives
├── compute_followup_level(partner)
├── get_overdue_for_partner(partner)
├── send_followup_email(partner, level=None)
├── escalate_to_next_level(partner)
├── pause_followup(partner, until_date)
├── reset_followup(partner)
└── snapshot_followup_history(partner) ← audit/history
services/ ← pure-Python
├── overdue_aging.py → bucket overdue lines (current/30/60/90/120+)
├── level_resolver.py → match aging buckets to follow-up levels
├── risk_scorer.py → payment-history risk score (0-100)
├── tone_selector.py → gentle/firm/legal based on level + risk
├── followup_text_generator.py → LLM-generated follow-up text
└── followup_text_prompt.py → provider-agnostic LLM prompt
models/
├── fusion_followup_level.py → level definition (delay days, template, action)
├── fusion_followup_run.py → execution record (per-partner per-level)
├── fusion_followup_text_cache.py → LLM-generated text cache (cost-saving)
├── fusion_followup_engine.py → AbstractModel orchestrator
├── res_partner.py (inherit) → fusion_followup_status, fusion_followup_paused_until
└── account_move_line.py (inherit) → followup_level_id (which level last contacted at)
controllers/followup_controller.py ← 6 JSON-RPC endpoints
├── /fusion/followup/list_overdue → list partners with overdue
├── /fusion/followup/get_partner_detail → single partner with aging + history
├── /fusion/followup/generate_text → AI-generate follow-up text
├── /fusion/followup/send → send a follow-up email
├── /fusion/followup/pause → pause follow-ups for a partner
└── /fusion/followup/reset → reset follow-up state
static/src/
├── scss/ ← follow-up design tokens
├── services/followup_service.js ← reactive state + RPC wrappers
├── views/followup_dashboard/ ← top-level OWL controller
└── components/ ← partner_card, aging_bucket_strip, ai_text_panel,
followup_history_table, risk_badge
```
## Coexistence
`group_fusion_show_when_enterprise_absent`. Follow-up menu visible only when `account_followup` NOT installed.
## Tasks (~35 total)
### Group 1: Foundation (1-2)
1. Safety net (DONE)
2. Plan doc + module skeleton
### Group 2: Pure-Python services TDD (3-7)
3. `services/overdue_aging.py` (TDD: bucket lines into 0/30/60/90/120+)
4. `services/level_resolver.py` (TDD: match aging to level)
5. `services/risk_scorer.py` (TDD: payment-history risk 0-100)
6. `services/tone_selector.py` (TDD: gentle/firm/legal)
7. `services/followup_text_generator.py` + `followup_text_prompt.py` (LLM)
### Group 3: Persisted models (8-12)
8. `models/fusion_followup_level.py` (level definition)
9. `models/fusion_followup_run.py` (execution record)
10. `models/fusion_followup_text_cache.py` (LLM cache)
11. `models/res_partner.py` (inherit: fusion_followup_status, paused_until)
12. `models/account_move_line.py` (inherit: followup_level_id)
### Group 4: Engine + integration tests (13-14)
13. `models/fusion_followup_engine.py` (7-method API)
14. Engine integration tests
### Group 5: Backend wiring (15-18)
15. JSON-RPC controller (6 endpoints)
16. FollowupAdapter wiring `_via_fusion` paths
17. 4 new AI tools (list_overdue, generate_text, send_followup, get_risk_score)
18. Cron — daily scan + escalate
### Group 6: Tests + perf (19-21)
19. Property-based tests (Hypothesis: aging buckets sum to total)
20. Integration tests (full follow-up flow: scan → escalate → send → reset)
21. Performance benchmarks (P95: scan < 500ms, generate_text < 5s incl. LLM)
### Group 7: Frontend (22-26)
22. SCSS tokens + main stylesheet
23. `followup_service.js`
24. `followup_dashboard` (top-level)
25. `partner_card` + `aging_bucket_strip` + `risk_badge`
26. `ai_text_panel` (Fusion-only) + `followup_history_table`
### Group 8: Wizards + data (27-29)
27. Default follow-up levels XML data (7-day reminder, 30-day, 60-day, legal)
28. Default mail templates XML data (3 escalation levels)
29. "Send batch follow-ups" wizard
### Group 9: Migration + coexistence (30-32)
30. Migration wizard inheritance — backfill from account_followup tables
31. Menu + window action with coexistence group filter
32. Coexistence test
### Group 10: Final tests + polish (33-37)
33. 5 OWL tour tests
34. Local LLM compat test for text_generator
35. Update meta-module manifest
36. CLAUDE.md, UPGRADE_NOTES.md, README.md
37. End-to-end smoke + tag phase-4-complete + push
## Performance Targets (P95)
- `compute_followup_level`: <50ms
- `get_overdue_for_partner`: <100ms
- `send_followup_email` (no LLM): <200ms
- `generate_text` (with LLM): <5s
- Controller `list_overdue` (50 partners): <500ms
## V19 Conventions (Phases 1-3 lessons)
- `models.Constraint` not `_sql_constraints`
- No `@api.depends('id')` on stored compute fields
- `@route(type='jsonrpc')` not `type='json'`
- `ir.cron` no `numbercall` field
- `res.groups.user_ids` not `users`
- `ir.ui.menu.group_ids` not `groups_id`
- `from odoo.exceptions import UserError, ValidationError` (NOT `self.env['ir.exceptions'].UserError`)
## Test Targets
Match Phases 1-3 test pyramid. Phase 4 target: ~80-100 additional tests → ~510-530 total project tests.