From 3f00ced56692e41a017b6ee482d8c677eb06bdc1 Mon Sep 17 00:00:00 2001 From: tocmo0nlord Date: Tue, 2 Jun 2026 05:02:56 +0000 Subject: [PATCH] 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 --- .../activeblue_familylaw/__manifest__.py | 2 +- .../activeblue_familylaw/models/__init__.py | 1 + .../models/familylaw_obligation.py | 128 ++++++++++++++++++ .../security/ir.model.access.csv | 2 + .../activeblue_familylaw/tests/__init__.py | 1 + .../activeblue_familylaw/tests/test_step13.py | 107 +++++++++++++++ .../views/familylaw_case_views.xml | 13 ++ 7 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_obligation.py create mode 100644 activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/tests/test_step13.py diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/__manifest__.py b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/__manifest__.py index 9725b5c..0f2ac37 100644 --- a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/__manifest__.py +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/__manifest__.py @@ -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": """ diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/__init__.py b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/__init__.py index 6bed1b1..3a4b467 100644 --- a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/__init__.py +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/__init__.py @@ -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 diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_obligation.py b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_obligation.py new file mode 100644 index 0000000..e6fd4e2 --- /dev/null +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_obligation.py @@ -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() diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/security/ir.model.access.csv b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/security/ir.model.access.csv index d2d8afc..6eacde3 100644 --- a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/security/ir.model.access.csv +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/security/ir.model.access.csv @@ -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 diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/tests/__init__.py b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/tests/__init__.py index 04cef93..7e5b68b 100644 --- a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/tests/__init__.py +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/tests/__init__.py @@ -10,3 +10,4 @@ from . import test_step9 from . import test_step10 from . import test_step11 from . import test_step12 +from . import test_step13 diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/tests/test_step13.py b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/tests/test_step13.py new file mode 100644 index 0000000..7cd863a --- /dev/null +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/tests/test_step13.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +"""STEP 13 tests — Miami-Dade auto-seed (AO 14-13). + + odoo -d -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) diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_case_views.xml b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_case_views.xml index 75452ff..9ff7e9e 100644 --- a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_case_views.xml +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_case_views.xml @@ -157,6 +157,19 @@ + + + + + + + + + + +