Rename module to activeblue_familylaw_v2 (coexist with legacy prod module)

Production already has a DIFFERENT, earlier module installed as `activeblue_familylaw`
(models fl.*, real data). Renamed this build's technical name to
`activeblue_familylaw_v2` so it installs ALONGSIDE the legacy app instead of replacing
it. Models (familylaw.*) and test tags (familylaw_step<N>) are unchanged — only the
module name and its group XML IDs change.

Changes:
- Folder activeblue_familylaw -> activeblue_familylaw_v2 (git mv)
- All 44 dotted refs activeblue_familylaw. -> activeblue_familylaw_v2.
  (security group XML IDs in views/python; test patch targets odoo.addons.*)
- Manifest display name -> "Active Blue Family Law v2"; root menu -> "Family Law (v2)"
- scripts/validate_module.py ROOT path; BUILD_STATUS.md run commands + coexistence
  note; START_HERE.md pointer

Verified in live Odoo 18:
- Fresh install + full suite: 200 tests, 0 failed, 0 errors.
- COEXISTENCE on a clone of prod db1: installing _v2 alongside the installed legacy
  activeblue_familylaw left the legacy untouched (still installed 18.0.1.0.0, fl.*
  models registered, fl_caselaw 25 rows intact) while adding the 30 familylaw.* models.
  Exit 0, no errors. Clone dropped; prod DBs untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tocmo0nlord
2026-06-02 11:23:34 +00:00
parent 7839f04591
commit 935394620b
66 changed files with 73 additions and 52 deletions

View File

@@ -1,9 +1,17 @@
# Build Status — Active Blue Family Law (`activeblue_familylaw`)
# Build Status — Active Blue Family Law (`activeblue_familylaw_v2`)
**All 14 roadmap steps are implemented, validated, and committed.**
Module version `18.0.14.0.0`. Built on Odoo 18 Community.
> **Tests run green in a live Odoo 18:** `0 failed, 0 error(s) of 198 tests`
> ⚠️ **Technical name is `activeblue_familylaw_v2`** (folder
> `activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw_v2`).
> It was renamed from `activeblue_familylaw` because production already has a
> **different, earlier** module installed under that name (models `fl.*`, real data) —
> see "Production coexistence" below. The `_v2` module uses `familylaw.*` models and
> its own security groups, so it installs **alongside** the legacy app without
> collision. Models/tags are unchanged (`familylaw.case`, `familylaw_step<N>`).
> ✅ **Tests run green in a live Odoo 18:** `0 failed, 0 error(s) of 200 tests`
> (installed clean into a throwaway DB on the local `odoo:18.0` image against
> Postgres 16, then dropped — production DBs untouched). Also validated statically
> via `scripts/validate_module.py` (Python compile, XML well-formed, no Odoo-18
@@ -15,16 +23,23 @@ Module version `18.0.14.0.0`. Built on Odoo 18 Community.
## Run the tests
```bash
# whole module (install + all tests)
odoo -d <db> -i activeblue_familylaw --test-enable --stop-after-init
odoo -d <db> -i activeblue_familylaw_v2 --test-enable --stop-after-init
# one step
odoo -d <db> -u activeblue_familylaw --test-enable --test-tags familylaw_step7 --stop-after-init
odoo -d <db> -u activeblue_familylaw_v2 --test-enable --test-tags familylaw_step7 --stop-after-init
# all family-law tests
odoo -d <db> -u activeblue_familylaw --test-enable --test-tags familylaw --stop-after-init
odoo -d <db> -u activeblue_familylaw_v2 --test-enable --test-tags familylaw --stop-after-init
```
Re-run static checks any time: `python3 scripts/validate_module.py`
## Production coexistence
Prod `db1` has a **separate legacy** `activeblue_familylaw` installed (different
lineage: `fl.*` models incl. `fl.caselaw`, `fl.statute`, `fl.support.schedule.entry`,
with real rows). This module is `activeblue_familylaw_v2` with `familylaw.*` models and
distinct group XML IDs, so it **coexists** — installing it will NOT touch or remove the
legacy app or its data. Verified by cloning `db1` and installing `_v2` into the clone.
## Steps
| Step | Slice | Tag |
|---|---|---|

View File

@@ -1,5 +1,11 @@
# START HERE — Claude Code kickoff
> **Note (current state):** the module is fully built (Steps 114) and its technical
> name is now **`activeblue_familylaw_v2`** (renamed from `activeblue_familylaw` to
> coexist with a different legacy module of that name already in production). The
> design docs below still say `activeblue_familylaw` — that's the historical narrative;
> the live module folder/name is `activeblue_familylaw_v2`. See `BUILD_STATUS.md`.
You are picking up an in-progress build. Read in this order, then start the task at
the bottom. Do **not** re-derive the design — it exists; follow it.

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
{
"name": "Active Blue Family Law",
"name": "Active Blue Family Law v2",
"version": "18.0.14.0.0",
"category": "Services/Legal",
"summary": "Florida family law case management (Miami-Dade / 11th Judicial Circuit)",

View File

@@ -23,7 +23,7 @@ from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError
ATTORNEY_GROUP = "activeblue_familylaw.group_familylaw_attorney"
ATTORNEY_GROUP = "activeblue_familylaw_v2.group_familylaw_attorney"
class FamilyLawRetentionClass(models.Model):

View File

@@ -6,7 +6,7 @@ related records, initial-proceeding creation, conflict screening).
from odoo import api, fields, models, _
from odoo.exceptions import UserError
ATTORNEY_GROUP = "activeblue_familylaw.group_familylaw_attorney"
ATTORNEY_GROUP = "activeblue_familylaw_v2.group_familylaw_attorney"
STATE_SEQUENCE = [
"intake",

View File

@@ -14,7 +14,7 @@ hallucinated cites — this ledger is what makes that mechanically impossible.
from odoo import api, fields, models, _
from odoo.exceptions import UserError
ATTORNEY_GROUP = "activeblue_familylaw.group_familylaw_attorney"
ATTORNEY_GROUP = "activeblue_familylaw_v2.group_familylaw_attorney"
class FamilyLawCitation(models.Model):

View File

@@ -19,7 +19,7 @@ audit trail / DocuSeal host are operational requirements (Requirements for Succe
from odoo import api, fields, models, _
from odoo.exceptions import UserError
ATTORNEY_GROUP = "activeblue_familylaw.group_familylaw_attorney"
ATTORNEY_GROUP = "activeblue_familylaw_v2.group_familylaw_attorney"
class FamilyLawComms(models.Model):

View File

@@ -20,7 +20,7 @@ top of the same outbound guard.
from odoo import api, fields, models, _
from odoo.exceptions import UserError
ATTORNEY_GROUP = "activeblue_familylaw.group_familylaw_attorney"
ATTORNEY_GROUP = "activeblue_familylaw_v2.group_familylaw_attorney"
class FamilyLawDocument(models.Model):

View File

@@ -30,14 +30,14 @@ class TestCaseLifecycle(TransactionCase):
login="fl_attorney",
name="Test Attorney",
email="attorney@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_attorney",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_attorney",
)
cls.paralegal = new_test_user(
cls.env,
login="fl_paralegal",
name="Test Paralegal",
email="paralegal@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_user",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_user",
)
cls.Case = cls.env["familylaw.case"]

View File

@@ -16,7 +16,7 @@ from unittest.mock import patch
from odoo.tests.common import TransactionCase, new_test_user, tagged
from odoo.exceptions import UserError
DSCLIENT = ("odoo.addons.activeblue_familylaw.models.familylaw_docuseal."
DSCLIENT = ("odoo.addons.activeblue_familylaw_v2.models.familylaw_docuseal."
"FamilyLawDocusealClient")
@@ -29,7 +29,7 @@ class TestStep11Docuseal(TransactionCase):
cls.attorney = new_test_user(
cls.env, login="fl_atty11", name="Attorney 11",
email="atty11@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_attorney",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_attorney",
)
cls.partner = cls.env["res.partner"].create({"name": "Sign Client"})
cls.case = cls.env["familylaw.case"].create({
@@ -90,7 +90,7 @@ class TestStep11Docuseal(TransactionCase):
self._approve(doc)
para = new_test_user(
self.env, login="fl_para11", name="Para 11", email="p11@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_user",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_user",
)
with patch(DSCLIENT + "._create_submission",
return_value={"id": "S1", "status": "sent"}):

View File

@@ -32,7 +32,7 @@ class TestStep12Archive(TransactionCase):
cls.attorney = new_test_user(
cls.env, login="fl_atty12", name="Attorney 12",
email="atty12@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_attorney",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_attorney",
)
cls.partner = cls.env["res.partner"].create({"name": "Arch Client"})
cls.case = cls.env["familylaw.case"].create({
@@ -116,7 +116,7 @@ class TestStep12Archive(TransactionCase):
})
para = new_test_user(
self.env, login="fl_para12", name="Para 12", email="p12@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_user",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_user",
)
with self.assertRaises(UserError):
arch.with_user(para).action_destroy()

View File

@@ -25,7 +25,7 @@ class TestStep13Obligations(TransactionCase):
cls.attorney = new_test_user(
cls.env, login="fl_atty13", name="Attorney 13",
email="atty13@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_attorney",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_attorney",
)
cls.partner = cls.env["res.partner"].create({"name": "AO Client"})
cls.Case = cls.env["familylaw.case"]

View File

@@ -25,15 +25,15 @@ class TestStep14Comms(TransactionCase):
cls.attorney = new_test_user(
cls.env, login="fl_atty14", name="Attorney 14",
email="atty14@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_attorney",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_attorney",
)
cls.para = new_test_user(
cls.env, login="fl_para14", name="Para 14", email="para14@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_user",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_user",
)
cls.para_other = new_test_user(
cls.env, login="fl_para14b", name="Para 14b", email="para14b@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_user",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_user",
)
cls.partner = cls.env["res.partner"].create({"name": "Comms Client"})
cls.case = cls.env["familylaw.case"].create({

View File

@@ -31,14 +31,14 @@ class TestStep2RelationsAndProceeding(TransactionCase):
login="fl_atty2",
name="Test Attorney 2",
email="atty2@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_attorney",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_attorney",
)
cls.paralegal = new_test_user(
cls.env,
login="fl_para2",
name="Test Paralegal 2",
email="para2@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_user",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_user",
)
cls.Case = cls.env["familylaw.case"]
@@ -209,7 +209,7 @@ class TestStep2ConflictScreening(TransactionCase):
login="fl_atty_conflict",
name="Conflict Attorney",
email="catty@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_attorney",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_attorney",
)
# Case A: "Old Client" is the client
cls.old_client = cls.env["res.partner"].create({"name": "Old Client"})

View File

@@ -25,12 +25,12 @@ class TestStep3ReviewGate(TransactionCase):
cls.attorney = new_test_user(
cls.env, login="fl_atty3", name="Attorney 3",
email="atty3@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_attorney",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_attorney",
)
cls.paralegal = new_test_user(
cls.env, login="fl_para3", name="Paralegal 3",
email="para3@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_user",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_user",
)
cls.partner = cls.env["res.partner"].create({"name": "Doc Client"})
cls.case = cls.env["familylaw.case"].create({

View File

@@ -20,7 +20,7 @@ from unittest.mock import patch
from odoo.tests.common import TransactionCase, new_test_user, tagged
from odoo.exceptions import UserError
CLIENT = "odoo.addons.activeblue_familylaw.models.familylaw_ai.FamilyLawAIClient"
CLIENT = "odoo.addons.activeblue_familylaw_v2.models.familylaw_ai.FamilyLawAIClient"
def _fake_response(text="DRAFT BODY", citations=None, pt=100, ct=200):
@@ -49,7 +49,7 @@ class TestStep6AIClient(TransactionCase):
cls.attorney = new_test_user(
cls.env, login="fl_atty6", name="Attorney 6",
email="atty6@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_attorney",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_attorney",
)
# configure a key + model so config-dependent code paths work
icp = cls.env["ir.config_parameter"].sudo()

View File

@@ -19,9 +19,9 @@ from unittest.mock import patch
from odoo.tests.common import TransactionCase, new_test_user, tagged
from odoo.exceptions import UserError
VERIFIER = ("odoo.addons.activeblue_familylaw.models.familylaw_verifier."
VERIFIER = ("odoo.addons.activeblue_familylaw_v2.models.familylaw_verifier."
"FamilyLawCitationVerifier")
CLIENT = "odoo.addons.activeblue_familylaw.models.familylaw_ai.FamilyLawAIClient"
CLIENT = "odoo.addons.activeblue_familylaw_v2.models.familylaw_ai.FamilyLawAIClient"
def _ai_response(text="MEMO", citations=None, pt=10, ct=20):
@@ -38,7 +38,7 @@ class TestStep7CitationGate(TransactionCase):
cls.attorney = new_test_user(
cls.env, login="fl_atty7", name="Attorney 7",
email="atty7@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_attorney",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_attorney",
)
cls.partner = cls.env["res.partner"].create({"name": "Cite Client"})
cls.case = cls.env["familylaw.case"].create({
@@ -134,7 +134,7 @@ class TestStep7CitationGate(TransactionCase):
doc = self._doc(); cite = self._cite(doc)
para = new_test_user(
self.env, login="fl_para7", name="Para 7", email="p7@example.com",
groups="base.group_user,activeblue_familylaw.group_familylaw_user",
groups="base.group_user,activeblue_familylaw_v2.group_familylaw_user",
)
with self.assertRaises(UserError):
cite.with_user(para).action_attorney_verify()

View File

@@ -19,7 +19,7 @@ from unittest.mock import patch
from odoo.tests.common import TransactionCase, tagged
CLIENT = "odoo.addons.activeblue_familylaw.models.familylaw_ai.FamilyLawAIClient"
CLIENT = "odoo.addons.activeblue_familylaw_v2.models.familylaw_ai.FamilyLawAIClient"
def _ai(text="EXTRACTION OUTPUT"):

View File

@@ -96,7 +96,7 @@
<button name="action_attorney_verify" type="object"
string="Attorney Attest"
invisible="status == 'verified'"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
<button name="action_reject" type="object" string="Reject"
invisible="status in ('verified','rejected')"/>
</header>

View File

@@ -52,7 +52,7 @@
record that destruction occurred; the content
itself will not be retained."
invisible="retention_state == 'destroyed'"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
<field name="retention_state" widget="statusbar"/>
</header>
<sheet>

View File

@@ -38,7 +38,7 @@
<button name="action_mark_conflict_cleared" type="object"
string="Clear Conflict Check" class="btn-warning"
invisible="conflict_check_cleared"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
<!-- Forward transitions -->
<button name="action_engage" type="object" string="Engage"
class="btn-primary" invisible="state != 'intake'"/>
@@ -63,14 +63,14 @@
<!-- Attorney-only: close / reopen -->
<button name="action_close" type="object" string="Close Case"
invisible="state == 'closed'"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
<button name="action_reopen" type="object" string="Reopen"
invisible="state != 'closed'"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
<button name="action_return_client_file" type="object"
string="Return Client File"
invisible="state != 'closed' or client_file_returned"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
<field name="state" widget="statusbar"
statusbar_visible="intake,engaged,disclosure,discovery,mediation,hearing,closed"/>
</header>

View File

@@ -26,7 +26,7 @@
<header>
<button name="action_approve" type="object" string="Approve"
class="btn-primary" invisible="state != 'draft'"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
<button name="action_send" type="object" string="Send"
invisible="state != 'approved'"/>
<field name="state" widget="statusbar"

View File

@@ -38,27 +38,27 @@
<button name="action_approve" type="object"
string="Approve" class="btn-primary"
invisible="state != 'attorney_review'"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
<button name="action_reject" type="object"
string="Reject"
invisible="state not in ('attorney_review','approved')"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
<button name="action_mark_filed" type="object"
string="Mark Filed"
invisible="state != 'approved'"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
<button name="action_mark_sent" type="object"
string="Mark Sent"
invisible="state != 'approved'"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
<button name="action_send_for_signature" type="object"
string="Send for E-Signature"
invisible="state != 'approved' or signature_status != 'none'"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
<button name="action_reset_to_draft" type="object"
string="Reset to Draft"
invisible="state in ('ai_draft','filed','sent')"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
<field name="state" widget="statusbar"
statusbar_visible="ai_draft,attorney_review,approved,filed"/>
</header>

View File

@@ -3,7 +3,7 @@
<!-- Top-level application menu -->
<menuitem id="menu_familylaw_root"
name="Family Law"
name="Family Law (v2)"
sequence="50"/>
<!-- New Intake — opens the triage wizard -->
@@ -75,7 +75,7 @@
parent="menu_familylaw_root"
action="action_familylaw_ai_task"
sequence="60"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
<!-- Communications -->
<menuitem id="menu_familylaw_comms"
@@ -96,7 +96,7 @@
name="Configuration"
parent="menu_familylaw_root"
sequence="90"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
<!-- Configuration > Retention Classes -->
<menuitem id="menu_familylaw_retention_class"
@@ -104,6 +104,6 @@
parent="menu_familylaw_config"
action="action_familylaw_retention_class"
sequence="10"
groups="activeblue_familylaw.group_familylaw_attorney"/>
groups="activeblue_familylaw_v2.group_familylaw_attorney"/>
</odoo>

View File

@@ -24,7 +24,7 @@ ROOT = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"activeblue_familylaw_handoff",
"activeblue_familylaw_build",
"activeblue_familylaw",
"activeblue_familylaw_v2",
)
errors = []