Step 13: Miami-Dade auto-seed (AO 14-13) + course-before-judgment guard

familylaw.obligation (per case): status_quo_order / parenting_course / mediation,
state pending/completed/waived, complete + waive actions.

familylaw.case (_inherit):
- create() seeds AO 14-13 obligations for Miami-Dade matters (idempotent,
  _seed_miamidade_obligations): Status Quo Order on dissolutions; Parenting Course on
  children matters (dissolution_children/paternity/parenting_modification); Mediation
  on all Miami-Dade matters. Non-Miami-Dade seeds nothing.
- action_set_hearing() now guards course-before-judgment: blocked while a required
  parenting course is pending (children matters); allowed once completed.

Obligations tab on the case form. ACL added.

Tests (familylaw_step13): seeding by case type; no-children excludes course;
non-Miami-Dade no seed; idempotent; guard blocks/passes directly and across the full
lifecycle path; no-children not blocked; complete/waive.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tocmo0nlord
2026-06-02 05:02:56 +00:00
parent ec09f96943
commit 3f00ced566
7 changed files with 253 additions and 1 deletions

View File

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

View File

@@ -17,6 +17,7 @@ from . import familylaw_modification
from . import familylaw_emergency
from . import familylaw_docuseal
from . import familylaw_archive
from . import familylaw_obligation
from . import familylaw_ai_wizard
from . import familylaw_modification_wizard
from . import familylaw_emergency_wizard

View File

@@ -0,0 +1,128 @@
# -*- coding: utf-8 -*-
"""STEP 13 — Miami-Dade auto-seed (11th Circuit Administrative Order 14-13).
When a Miami-Dade family matter is opened, certain obligations attach automatically:
* a Status Quo Order (dissolution matters);
* a mandatory Parenting Course (matters involving minor children);
* mandatory Mediation before the final hearing.
And a COURSE-BEFORE-JUDGMENT GUARD: a matter with minor children cannot be set for
the final hearing (toward judgment) while the mandatory parenting course is still
pending.
VERIFY current rule — local administrative orders change; confirm AO 14-13's current
requirements and which case types they attach to before relying in production.
"""
from odoo import api, fields, models, _
from odoo.exceptions import UserError
_DISSOLUTION_TYPES = {"dissolution_no_children", "dissolution_children"}
_CHILDREN_TYPES = {"dissolution_children", "paternity", "parenting_modification"}
class FamilyLawObligation(models.Model):
_name = "familylaw.obligation"
_description = "Court-Imposed Obligation (AO 14-13)"
_inherit = ["mail.thread"]
_order = "id"
case_id = fields.Many2one(
"familylaw.case", required=True, ondelete="cascade", index=True, tracking=True,
)
name = fields.Char(required=True)
obligation_type = fields.Selection(
selection=[
("status_quo_order", "Status Quo Order"),
("parenting_course", "Parenting Course"),
("mediation", "Mediation"),
("other", "Other"),
],
required=True,
tracking=True,
)
required = fields.Boolean(default=True)
state = fields.Selection(
selection=[
("pending", "Pending"),
("completed", "Completed"),
("waived", "Waived"),
],
default="pending",
required=True,
tracking=True,
)
completed_date = fields.Date(readonly=True, copy=False)
notes = fields.Text()
def action_complete(self):
for ob in self:
ob.state = "completed"
ob.completed_date = fields.Date.context_today(ob)
ob.message_post(body=_("Obligation completed."))
return True
def action_waive(self):
for ob in self:
ob.state = "waived"
ob.message_post(body=_("Obligation waived (by the court / attorney)."))
return True
class FamilyLawCaseObligations(models.Model):
_inherit = "familylaw.case"
obligation_ids = fields.One2many(
"familylaw.obligation", "case_id", string="Obligations",
)
@api.model_create_multi
def create(self, vals_list):
cases = super().create(vals_list)
cases._seed_miamidade_obligations()
return cases
def _seed_miamidade_obligations(self):
"""Idempotently seed AO 14-13 obligations for Miami-Dade matters."""
Ob = self.env["familylaw.obligation"]
for case in self:
if case.county != "miami_dade":
continue
existing = set(case.obligation_ids.mapped("obligation_type"))
to_add = []
if case.case_type in _DISSOLUTION_TYPES:
to_add.append(("status_quo_order",
_("Status Quo Order (AO 14-13)")))
if case.case_type in _CHILDREN_TYPES:
to_add.append(("parenting_course",
_("Parenting Course (mandatory, AO 14-13)")))
to_add.append(("mediation", _("Mediation before final hearing")))
for otype, label in to_add:
if otype in existing:
continue
Ob.create({
"case_id": case.id,
"obligation_type": otype,
"name": label,
"required": True,
})
return True
def action_seed_obligations(self):
self._seed_miamidade_obligations()
return True
def _check_parenting_course_before_judgment(self):
for case in self:
pending = case.obligation_ids.filtered(
lambda o: o.obligation_type == "parenting_course"
and o.required and o.state == "pending")
if pending:
raise UserError(_(
"The mandatory parenting course must be completed before this "
"matter can be set for the final hearing / judgment (AO 14-13)."))
def action_set_hearing(self):
# Course-before-judgment guard layered on the Step 1 transition.
self._check_parenting_course_before_judgment()
return super().action_set_hearing()

View File

@@ -41,3 +41,5 @@ access_familylaw_retention_class_user,familylaw.retention.class staff,model_fami
access_familylaw_retention_class_attorney,familylaw.retention.class attorney,model_familylaw_retention_class,group_familylaw_attorney,1,1,1,1
access_familylaw_archive_user,familylaw.archive staff,model_familylaw_archive,group_familylaw_user,1,1,1,0
access_familylaw_archive_attorney,familylaw.archive attorney,model_familylaw_archive,group_familylaw_attorney,1,1,1,1
access_familylaw_obligation_user,familylaw.obligation staff,model_familylaw_obligation,group_familylaw_user,1,1,1,0
access_familylaw_obligation_attorney,familylaw.obligation attorney,model_familylaw_obligation,group_familylaw_attorney,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
41 access_familylaw_retention_class_attorney familylaw.retention.class attorney model_familylaw_retention_class group_familylaw_attorney 1 1 1 1
42 access_familylaw_archive_user familylaw.archive staff model_familylaw_archive group_familylaw_user 1 1 1 0
43 access_familylaw_archive_attorney familylaw.archive attorney model_familylaw_archive group_familylaw_attorney 1 1 1 1
44 access_familylaw_obligation_user familylaw.obligation staff model_familylaw_obligation group_familylaw_user 1 1 1 0
45 access_familylaw_obligation_attorney familylaw.obligation attorney model_familylaw_obligation group_familylaw_attorney 1 1 1 1

View File

@@ -10,3 +10,4 @@ from . import test_step9
from . import test_step10
from . import test_step11
from . import test_step12
from . import test_step13

View File

@@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
"""STEP 13 tests — Miami-Dade auto-seed (AO 14-13).
odoo -d <db> -u activeblue_familylaw --test-enable \
--test-tags familylaw_step13 --stop-after-init
Proves:
* obligations seed on case open for Miami-Dade matters (by case type);
* non-Miami-Dade matters do not seed;
* the course-before-judgment guard blocks setting the final hearing while the
parenting course is pending, and allows it once completed;
* seeding is idempotent.
"""
from odoo.tests.common import TransactionCase, new_test_user, tagged
from odoo.exceptions import UserError
@tagged("post_install", "-at_install", "familylaw", "familylaw_step13")
class TestStep13Obligations(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
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",
)
cls.partner = cls.env["res.partner"].create({"name": "AO Client"})
cls.Case = cls.env["familylaw.case"]
def _case(self, **kw):
vals = {"name": "AO Matter", "client_id": self.partner.id,
"case_type": "dissolution_children", "county": "miami_dade"}
vals.update(kw)
return self.Case.create(vals)
# --- seeding ------------------------------------------------------------
def test_01_seed_dissolution_children(self):
case = self._case(case_type="dissolution_children")
types = set(case.obligation_ids.mapped("obligation_type"))
self.assertEqual(types, {"status_quo_order", "parenting_course", "mediation"})
def test_02_seed_dissolution_no_children(self):
case = self._case(case_type="dissolution_no_children")
types = set(case.obligation_ids.mapped("obligation_type"))
self.assertIn("status_quo_order", types)
self.assertIn("mediation", types)
self.assertNotIn("parenting_course", types)
def test_03_seed_paternity_has_course(self):
case = self._case(case_type="paternity")
types = set(case.obligation_ids.mapped("obligation_type"))
self.assertIn("parenting_course", types)
def test_04_non_miamidade_no_seed(self):
case = self._case(county="broward")
self.assertFalse(case.obligation_ids)
def test_05_seed_idempotent(self):
case = self._case()
n1 = len(case.obligation_ids)
case.action_seed_obligations()
self.assertEqual(len(case.obligation_ids), n1)
# --- course-before-judgment guard ---------------------------------------
def test_06_guard_blocks_hearing_with_pending_course(self):
case = self._case(case_type="dissolution_children")
with self.assertRaises(UserError):
case._check_parenting_course_before_judgment()
def test_07_guard_passes_after_course_complete(self):
case = self._case(case_type="dissolution_children")
course = case.obligation_ids.filtered(
lambda o: o.obligation_type == "parenting_course")
course.action_complete()
# should not raise
case._check_parenting_course_before_judgment()
def test_08_guard_in_full_path(self):
case = self._case(case_type="dissolution_children")
case.with_user(self.attorney).action_mark_conflict_cleared()
case.action_engage()
case.action_start_disclosure()
case.action_start_discovery()
case.action_start_mediation()
# parenting course still pending -> set hearing blocked
with self.assertRaises(UserError):
case.action_set_hearing()
# complete course -> allowed
case.obligation_ids.filtered(
lambda o: o.obligation_type == "parenting_course").action_complete()
case.action_set_hearing()
self.assertEqual(case.state, "hearing")
def test_09_no_children_not_blocked(self):
case = self._case(case_type="dissolution_no_children")
# no parenting_course obligation -> guard passes
case._check_parenting_course_before_judgment()
def test_10_obligation_complete_waive(self):
case = self._case()
ob = case.obligation_ids[0]
ob.action_complete()
self.assertEqual(ob.state, "completed")
self.assertTrue(ob.completed_date)

View File

@@ -157,6 +157,19 @@
</list>
</field>
</page>
<page string="Obligations" name="obligations">
<field name="obligation_ids">
<list editable="bottom"
decoration-success="state == 'completed'"
decoration-warning="state == 'pending' and required">
<field name="name"/>
<field name="obligation_type"/>
<field name="required"/>
<field name="state" widget="badge"/>
<field name="completed_date"/>
</list>
</field>
</page>
<page string="Conflict Hits" name="conflict_hits"
invisible="conflict_hit_count == 0">
<field name="conflict_hit_ids">