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:
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
{
|
{
|
||||||
"name": "Active Blue Family Law",
|
"name": "Active Blue Family Law",
|
||||||
"version": "18.0.12.0.0",
|
"version": "18.0.13.0.0",
|
||||||
"category": "Services/Legal",
|
"category": "Services/Legal",
|
||||||
"summary": "Florida family law case management (Miami-Dade / 11th Judicial Circuit)",
|
"summary": "Florida family law case management (Miami-Dade / 11th Judicial Circuit)",
|
||||||
"description": """
|
"description": """
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from . import familylaw_modification
|
|||||||
from . import familylaw_emergency
|
from . import familylaw_emergency
|
||||||
from . import familylaw_docuseal
|
from . import familylaw_docuseal
|
||||||
from . import familylaw_archive
|
from . import familylaw_archive
|
||||||
|
from . import familylaw_obligation
|
||||||
from . import familylaw_ai_wizard
|
from . import familylaw_ai_wizard
|
||||||
from . import familylaw_modification_wizard
|
from . import familylaw_modification_wizard
|
||||||
from . import familylaw_emergency_wizard
|
from . import familylaw_emergency_wizard
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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_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_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_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
|
||||||
|
|||||||
|
@@ -10,3 +10,4 @@ from . import test_step9
|
|||||||
from . import test_step10
|
from . import test_step10
|
||||||
from . import test_step11
|
from . import test_step11
|
||||||
from . import test_step12
|
from . import test_step12
|
||||||
|
from . import test_step13
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -157,6 +157,19 @@
|
|||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</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"
|
<page string="Conflict Hits" name="conflict_hits"
|
||||||
invisible="conflict_hit_count == 0">
|
invisible="conflict_hit_count == 0">
|
||||||
<field name="conflict_hit_ids">
|
<field name="conflict_hit_ids">
|
||||||
|
|||||||
Reference in New Issue
Block a user