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 -*-
|
||||
{
|
||||
"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": """
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_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
|
||||
|
||||
|
@@ -10,3 +10,4 @@ from . import test_step9
|
||||
from . import test_step10
|
||||
from . import test_step11
|
||||
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>
|
||||
</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">
|
||||
|
||||
Reference in New Issue
Block a user