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 17e9a7d..e6f1099 100644 --- a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/__manifest__.py +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/__manifest__.py @@ -1,15 +1,15 @@ # -*- coding: utf-8 -*- { "name": "Active Blue Family Law", - "version": "18.0.1.0.0", + "version": "18.0.2.0.0", "category": "Services/Legal", "summary": "Florida family law case management (Miami-Dade / 11th Judicial Circuit)", "description": """ Active Blue Family Law ====================== Case-management platform for a Florida family-law practice, built in verifiable -steps. STEP 1 delivers the case spine: the familylaw.case model, its lifecycle -state machine, security groups (attorney / staff), and the case views. +steps. Step 1 delivers the case spine; Step 2 adds parties, children, issues, +the proceeding layer, automated conflict screening, and the intake questionnaire. Each subsequent step adds one vertical, independently testable slice. See BUILD_PLAN.md for the full sequence and the test method. @@ -27,6 +27,11 @@ signing, citation verification, wire map, training, forms & playbook). "data": [ "security/familylaw_security.xml", "security/ir.model.access.csv", + "views/familylaw_party_views.xml", + "views/familylaw_child_views.xml", + "views/familylaw_issue_views.xml", + "views/familylaw_proceeding_views.xml", + "views/familylaw_intake_views.xml", "views/familylaw_case_views.xml", "views/familylaw_menus.xml", ], 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 6192781..7e2c72e 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 @@ -1 +1,7 @@ from . import familylaw_case +from . import familylaw_party +from . import familylaw_child +from . import familylaw_issue +from . import familylaw_proceeding +from . import familylaw_conflict +from . import familylaw_intake diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_case.py b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_case.py index 3103ad0..27a7405 100644 --- a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_case.py +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_case.py @@ -1,15 +1,6 @@ # -*- coding: utf-8 -*- -"""STEP 1 — The case spine. - -familylaw.case is the system of record for a matter. This step implements: - * the core identifying fields and the responsible-people links, - * the pro-se / attorney representation flag (drives later workflows), - * the lifecycle STATE MACHINE with guarded transitions, and - * two attorney-only gates (conflict clearance, closing a case). - -The guards are enforced in Python (server-side, testable) AND mirrored in the -view via `groups=` on the buttons (defence in depth). Later steps add parties, -children, documents, the review gate, deadlines, AI, discovery, etc. +"""STEP 1 — Case spine (updated in Step 2: case_number, county, emergency flag, +related records, initial-proceeding creation, conflict screening). """ from odoo import api, fields, models, _ @@ -17,7 +8,6 @@ from odoo.exceptions import UserError ATTORNEY_GROUP = "activeblue_familylaw.group_familylaw_attorney" -# The case lifecycle. Order matters: it defines the legal "forward" path. STATE_SEQUENCE = [ "intake", "engaged", @@ -28,6 +18,12 @@ STATE_SEQUENCE = [ "closed", ] +_MODIFICATION_TYPES = { + "support_modification", + "parenting_modification", + "alimony_modification", +} + class FamilyLawCase(models.Model): _name = "familylaw.case" @@ -48,6 +44,24 @@ class FamilyLawCase(models.Model): required=True, tracking=True, ) + case_number = fields.Char( + string="Court Case Number", + index=True, + tracking=True, + help="Court-assigned case number (e.g. 2024-DR-001234). " + "Leave blank until the court assigns one.", + ) + county = fields.Selection( + selection=[ + ("miami_dade", "Miami-Dade (11th Circuit)"), + ("broward", "Broward (17th Circuit)"), + ("palm_beach", "Palm Beach (15th Circuit)"), + ("other_fl", "Other Florida County"), + ], + string="County / Court", + default="miami_dade", + tracking=True, + ) case_type = fields.Selection( selection=[ ("dissolution_no_children", "Dissolution — no children"), @@ -78,7 +92,7 @@ class FamilyLawCase(models.Model): tracking=True, ) - # --- Representation (drives the subpoena workflow in a later step) ------ + # --- Representation (drives the Step 8 subpoena branch) ----------------- representation = fields.Selection( selection=[ ("attorney", "Attorney of Record"), @@ -89,7 +103,20 @@ class FamilyLawCase(models.Model): required=True, tracking=True, help="Pro-se matters route subpoena issuance through the clerk of court; " - "attorney-of-record matters issue directly. See Forms & Playbook, Part C.", + "attorney-of-record matters issue directly. See Forms & Playbook, Part C.", + ) + + # --- Emergency flags (Step 2 intake fast-path) -------------------------- + is_emergency = fields.Boolean( + string="Emergency Matter", + default=False, + tracking=True, + help="Set when the intake urgency screen trips. Matter opened on minimum " + "facts; staff must complete the record immediately.", + ) + urgency_notes = fields.Text( + string="Urgency Notes", + help="Emergency description captured at intake.", ) # --- Gates / status ----------------------------------------------------- @@ -99,7 +126,7 @@ class FamilyLawCase(models.Model): copy=False, tracking=True, help="Set by an attorney once the conflict check is documented and clear. " - "Required before the matter can be engaged.", + "Required before the matter can be engaged.", ) state = fields.Selection( selection=[ @@ -117,16 +144,158 @@ class FamilyLawCase(models.Model): copy=False, tracking=True, ) - date_opened = fields.Date( string="Date Opened", default=fields.Date.context_today, ) active = fields.Boolean(default=True) + # --- Related records (Step 2) ------------------------------------------- + party_ids = fields.One2many( + "familylaw.party", "case_id", + string="Parties", + ) + child_ids = fields.One2many( + "familylaw.child", "case_id", + string="Children", + ) + issue_ids = fields.One2many( + "familylaw.issue", "case_id", + string="Issues", + ) + proceeding_ids = fields.One2many( + "familylaw.proceeding", "case_id", + string="Proceedings", + ) + conflict_hit_ids = fields.One2many( + "familylaw.conflict.hit", "case_id", + string="Conflict Screening Hits", + ) + conflict_hit_count = fields.Integer( + compute="_compute_conflict_hit_count", + string="Conflict Hits", + ) + + _sql_constraints = [ + ( + "case_number_unique", + "UNIQUE(case_number)", + "Court case number must be unique. Check the existing cases.", + ), + ] + + @api.depends("conflict_hit_ids") + def _compute_conflict_hit_count(self): + for case in self: + case.conflict_hit_count = len(case.conflict_hit_ids) + + # --- Case creation: auto-open an initial proceeding -------------------- + @api.model_create_multi + def create(self, vals_list): + cases = super().create(vals_list) + type_labels = dict(self._fields["case_type"].selection) + for case in cases: + if case.case_type in _MODIFICATION_TYPES: + proc_type = "modification" + elif case.case_type == "enforcement": + proc_type = "enforcement" + else: + proc_type = "original" + self.env["familylaw.proceeding"].create({ + "case_id": case.id, + "name": _("Initial Proceeding — %s") + % type_labels.get(case.case_type, case.case_type), + "proceeding_type": proc_type, + }) + return cases + + # --- Conflict screening ------------------------------------------------- + def action_run_conflict_screening(self): + """Search all party records across all matters for potential conflicts. + + Checks opposing parties on this case against: + 1. Any party role on any other matter (name match). + 2. Any client name on any other matter. + + Creates familylaw.conflict.hit records for attorney review. + Never auto-clears conflict_check_cleared; that gate stays with the attorney. + """ + for case in self: + case.conflict_hit_ids.unlink() + opposing = case.party_ids.filtered( + lambda p: p.role == "opposing_party" + ) + if not opposing: + case.message_post( + body=_( + "Conflict screening: no opposing party on record. " + "Add an opposing party and re-run." + ) + ) + continue + + role_labels = dict(self.env["familylaw.party"]._fields["role"].selection) + hit_count = 0 + + for party in opposing: + name = (party.name or "").strip() + if not name: + continue + + # 1. Name appears as any party role on another matter + for hit in self.env["familylaw.party"].search([ + ("name", "ilike", name), + ("case_id", "!=", case.id), + ]): + self.env["familylaw.conflict.hit"].create({ + "case_id": case.id, + "party_name": name, + "hit_case_id": hit.case_id.id, + "hit_role": role_labels.get(hit.role, hit.role), + "reason": _( + "'%(n)s' appears as '%(r)s' in matter '%(m)s'", + n=name, + r=role_labels.get(hit.role, hit.role), + m=hit.case_id.name, + ), + }) + hit_count += 1 + + # 2. Name matches a client on another matter + for cc in self.env["familylaw.case"].search([ + ("client_id.name", "ilike", name), + ("id", "!=", case.id), + ]): + self.env["familylaw.conflict.hit"].create({ + "case_id": case.id, + "party_name": name, + "hit_case_id": cc.id, + "hit_role": _("Client"), + "reason": _( + "'%(n)s' matches client '%(c)s' in matter '%(m)s'", + n=name, + c=cc.client_id.name, + m=cc.name, + ), + }) + hit_count += 1 + + if hit_count: + case.message_post( + body=_( + "⚠️ Conflict screening found %(n)d potential conflict(s). " + "Review the Conflict Hits tab — attorney must clear before engaging.", + n=hit_count, + ) + ) + else: + case.message_post( + body=_("Conflict screening complete — no potential conflicts detected.") + ) + return True + # --- Internal helpers --------------------------------------------------- def _ensure_attorney(self): - """Raise unless the acting user is in the attorney group.""" if not self.env.user.has_group(ATTORNEY_GROUP): raise UserError( _("Only a licensed attorney (Family Law / Attorney group) may " @@ -134,7 +303,6 @@ class FamilyLawCase(models.Model): ) def _require_state(self, allowed): - """Raise if any record is not in one of the allowed states.""" bad = self.filtered(lambda c: c.state not in allowed) if bad: raise UserError( @@ -145,28 +313,26 @@ class FamilyLawCase(models.Model): ) def _advance_to(self, target): - """Write the new state and log it on the chatter.""" for case in self: case.state = target case.message_post( - body=_("Stage changed to: %s") % dict( - self._fields["state"].selection).get(target, target) + body=_("Stage changed to: %s") + % dict(self._fields["state"].selection).get(target, target) ) # --- Gated transitions -------------------------------------------------- def action_mark_conflict_cleared(self): - """Attorney-only: record that the conflict check is clear.""" self._ensure_attorney() for case in self: if case.conflict_check_cleared: continue case.conflict_check_cleared = True - case.message_post(body=_("Conflict check cleared by %s.") - % self.env.user.name) + case.message_post( + body=_("Conflict check cleared by %s.") % self.env.user.name + ) return True def action_engage(self): - """intake -> engaged. Requires a cleared conflict check.""" self._require_state(["intake"]) not_cleared = self.filtered(lambda c: not c.conflict_check_cleared) if not_cleared: @@ -178,38 +344,32 @@ class FamilyLawCase(models.Model): return True def action_start_disclosure(self): - """engaged -> disclosure (Rule 12.285 phase begins in a later step).""" self._require_state(["engaged"]) self._advance_to("disclosure") return True def action_start_discovery(self): - """disclosure -> discovery.""" self._require_state(["disclosure"]) self._advance_to("discovery") return True def action_start_mediation(self): - """discovery -> mediation (mandatory in Miami-Dade before final hearing).""" self._require_state(["discovery"]) self._advance_to("mediation") return True def action_set_hearing(self): - """mediation -> hearing / trial.""" self._require_state(["mediation"]) self._advance_to("hearing") return True def action_close(self): - """Attorney-only: close the matter from any non-closed stage.""" self._ensure_attorney() self._require_state([s for s in STATE_SEQUENCE if s != "closed"]) self._advance_to("closed") return True def action_reopen(self): - """Attorney-only: reopen a closed matter back to engaged.""" self._ensure_attorney() self._require_state(["closed"]) self._advance_to("engaged") diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_child.py b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_child.py new file mode 100644 index 0000000..169436e --- /dev/null +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_child.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +from datetime import date + +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class FamilyLawChild(models.Model): + _name = "familylaw.child" + _description = "Minor Child" + _order = "name" + + case_id = fields.Many2one( + "familylaw.case", + required=True, + ondelete="cascade", + index=True, + ) + name = fields.Char(required=True) + date_of_birth = fields.Date(string="Date of Birth") + age = fields.Integer( + compute="_compute_age", + string="Age", + ) + notes = fields.Text() + + @api.depends("date_of_birth") + def _compute_age(self): + today = date.today() + for child in self: + if not child.date_of_birth: + child.age = 0 + continue + dob = child.date_of_birth + child.age = ( + today.year - dob.year + - ((today.month, today.day) < (dob.month, dob.day)) + ) + + @api.constrains("date_of_birth") + def _check_date_of_birth(self): + today = date.today() + for child in self: + if not child.date_of_birth: + continue + dob = child.date_of_birth + if dob > today: + raise ValidationError( + "Date of birth cannot be in the future." + ) + age = ( + today.year - dob.year + - ((today.month, today.day) < (dob.month, dob.day)) + ) + if age > 25: + raise ValidationError( + f"Date of birth implies the child is {age} years old. " + "Verify the date — this field is for minor children in the matter." + ) diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_conflict.py b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_conflict.py new file mode 100644 index 0000000..9d9a0d7 --- /dev/null +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_conflict.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from odoo import fields, models + + +class FamilyLawConflictHit(models.Model): + _name = "familylaw.conflict.hit" + _description = "Conflict Screening Hit" + _order = "create_date desc" + + case_id = fields.Many2one( + "familylaw.case", + required=True, + ondelete="cascade", + index=True, + ) + party_name = fields.Char(string="Flagged Party Name", required=True) + hit_case_id = fields.Many2one( + "familylaw.case", + string="Found In Matter", + ) + hit_role = fields.Char(string="Role in Matching Matter") + reason = fields.Char(string="Reason for Flag") diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_intake.py b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_intake.py new file mode 100644 index 0000000..3db1192 --- /dev/null +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_intake.py @@ -0,0 +1,319 @@ +# -*- coding: utf-8 -*- +"""STEP 2 — Intake questionnaire wizard. + +Multi-step TransientModel that: + * runs triage FIRST (caller info, opposing party, urgency screen), + * branches on urgency: emergency → fast-path on minimum facts; + standard → strict (name + client + case_type + county required), + * branches into the child-support modification question set, + * creates a draft familylaw.case in 'intake' state (and its initial proceeding, + via the case model's create() override), + * runs conflict screening on the opposing party, + * and captures the caller's concern as an attorney question — never answers it. + +No external/network calls. Pure local DB. +""" + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError + +_MODIFICATION_TYPES = { + "support_modification", + "parenting_modification", + "alimony_modification", +} + +_CASE_TYPE_SELECTION = [ + ("dissolution_no_children", "Dissolution — no children"), + ("dissolution_children", "Dissolution — with children"), + ("paternity", "Paternity"), + ("support_modification", "Child Support Modification"), + ("parenting_modification", "Parenting / Time-Sharing Modification"), + ("alimony_modification", "Alimony Modification"), + ("enforcement", "Enforcement / Contempt"), + ("dv_injunction", "Domestic Violence Injunction"), + ("other", "Other"), +] + + +class FamilyLawIntakeWizard(models.TransientModel): + _name = "familylaw.intake.wizard" + _description = "Family Law Intake Questionnaire" + + step = fields.Selection( + selection=[ + ("triage", "1 — Triage"), + ("details", "2 — Case Details"), + ("modification", "3 — Modification Details"), + ("confirm", "4 — Complete"), + ], + default="triage", + required=True, + ) + + # === TRIAGE (step 1) ==================================================== + caller_name = fields.Char(string="Caller / Client Name") + caller_phone = fields.Char(string="Phone") + caller_email = fields.Char(string="Email") + county = fields.Selection( + selection=[ + ("miami_dade", "Miami-Dade (11th Circuit)"), + ("broward", "Broward (17th Circuit)"), + ("palm_beach", "Palm Beach (15th Circuit)"), + ("other_fl", "Other Florida County"), + ], + string="County / Court", + default="miami_dade", + ) + case_type = fields.Selection( + selection=_CASE_TYPE_SELECTION, + string="Case Type", + ) + opposing_party_name = fields.Char(string="Opposing Party Name") + has_children = fields.Boolean(string="Minor children involved?") + has_existing_order = fields.Boolean(string="Existing court order?") + + # Urgency screen — runs first; triggers the emergency fast-path + urgency_child_withheld = fields.Boolean( + string="Child being withheld / refused access?" + ) + urgency_removal_threat = fields.Boolean( + string="Threat of removal from Florida?" + ) + urgency_violence = fields.Boolean( + string="Domestic violence / immediate safety concern?" + ) + urgency_description = fields.Text( + string="Describe the Emergency", + help="Captured verbatim for the attorney. Do not interpret or advise.", + ) + is_emergency = fields.Boolean( + compute="_compute_is_emergency", + store=True, + string="Emergency", + ) + + # === STANDARD DETAILS (step 2 — strict path) ============================ + matter_name = fields.Char(string="Matter Name") + client_partner_id = fields.Many2one( + "res.partner", + string="Client (existing contact)", + help="Pick an existing contact or leave blank to create from caller name.", + ) + attorney_id = fields.Many2one("res.users", string="Responsible Attorney") + paralegal_id = fields.Many2one("res.users", string="Paralegal") + + # === MODIFICATION DETAILS (step 3 — modification types only) ============ + existing_order_summary = fields.Text( + string="Summary of Existing Order", + help="Captured facts from the prior judgment. Not interpreted here — " + "prior-judgment interpretation is Step 6+ (attorney-reviewed).", + ) + reason_for_modification = fields.Text(string="Reason for Modification") + income_change_details = fields.Text( + string="Income / Financial Change Details", + help="E.g. 'income dropped from $X to $Y in month/year'. Facts only.", + ) + + # Caller concern — CAPTURED AS ATTORNEY QUESTION, never answered here + caller_concern = fields.Text( + string="Caller's Question / Concern for the Attorney", + help="Record the caller's own words. This is logged on the case as a " + "question for the attorney. This software does not answer " + "'do I have a good case?' — that determination belongs to the attorney.", + ) + + # === RESULT (step 4) ==================================================== + created_case_id = fields.Many2one( + "familylaw.case", + string="Created Matter", + readonly=True, + ) + conflict_summary = fields.Text( + string="Conflict Screening Results", + readonly=True, + ) + + # ----------------------------------------------------------------------- + + @api.depends("urgency_child_withheld", "urgency_removal_threat", "urgency_violence") + def _compute_is_emergency(self): + for w in self: + w.is_emergency = bool( + w.urgency_child_withheld + or w.urgency_removal_threat + or w.urgency_violence + ) + + # --- Navigation --------------------------------------------------------- + + def action_next(self): + self.ensure_one() + if self.step == "triage": + if self.is_emergency: + self._create_emergency_case() + self.step = "confirm" + else: + self.step = "details" + + elif self.step == "details": + self._validate_standard_details() + if self.case_type in _MODIFICATION_TYPES: + self.step = "modification" + else: + self._create_standard_case() + self.step = "confirm" + + elif self.step == "modification": + self._create_standard_case() + self.step = "confirm" + + return self._reopen() + + def action_back(self): + self.ensure_one() + if self.step == "details": + self.step = "triage" + elif self.step == "modification": + self.step = "details" + return self._reopen() + + def action_open_case(self): + self.ensure_one() + return { + "type": "ir.actions.act_window", + "res_model": "familylaw.case", + "res_id": self.created_case_id.id, + "view_mode": "form", + "target": "current", + } + + def _reopen(self): + return { + "type": "ir.actions.act_window", + "res_model": "familylaw.intake.wizard", + "res_id": self.id, + "view_mode": "form", + "target": "new", + } + + # --- Validation --------------------------------------------------------- + + def _validate_standard_details(self): + errors = [] + if not self.matter_name: + errors.append(_("Matter Name is required.")) + if not self.client_partner_id and not self.caller_name: + errors.append(_("A client contact or caller name is required.")) + if not self.case_type: + errors.append(_("Case Type is required.")) + if not self.county: + errors.append(_("County / Court is required.")) + if errors: + raise UserError("\n".join(errors)) + + # --- Case creation ------------------------------------------------------ + + def _resolve_or_create_partner(self): + if self.client_partner_id: + return self.client_partner_id + return self.env["res.partner"].create({ + "name": self.caller_name or _("Unknown — Emergency Intake"), + "phone": self.caller_phone or False, + "email": self.caller_email or False, + }) + + def _create_emergency_case(self): + """Fast-path: open on minimum facts — who, which child, what's happening.""" + partner = self._resolve_or_create_partner() + + flags = [] + if self.urgency_child_withheld: + flags.append(_("Child withheld / refused access")) + if self.urgency_removal_threat: + flags.append(_("Threat of removal from Florida")) + if self.urgency_violence: + flags.append(_("Domestic violence / safety concern")) + urgency_text = "; ".join(flags) + if self.urgency_description: + urgency_text += "\n" + self.urgency_description + + case = self.env["familylaw.case"].create({ + "name": self.matter_name or _("EMERGENCY — %s") % partner.name, + "client_id": partner.id, + "case_type": self.case_type or "other", + "county": self.county or "miami_dade", + "is_emergency": True, + "urgency_notes": urgency_text, + }) + + if self.opposing_party_name: + self.env["familylaw.party"].create({ + "case_id": case.id, + "name": self.opposing_party_name, + "role": "opposing_party", + }) + + self._post_caller_concern(case) + self._run_screening_and_summarise(case) + self.created_case_id = case + + def _create_standard_case(self): + """Strict path: all required fields validated before reaching here.""" + partner = self._resolve_or_create_partner() + + case = self.env["familylaw.case"].create({ + "name": self.matter_name, + "client_id": partner.id, + "case_type": self.case_type, + "county": self.county or "miami_dade", + "attorney_id": self.attorney_id.id if self.attorney_id else False, + "paralegal_id": self.paralegal_id.id if self.paralegal_id else False, + "is_emergency": False, + }) + + if self.opposing_party_name: + self.env["familylaw.party"].create({ + "case_id": case.id, + "name": self.opposing_party_name, + "role": "opposing_party", + }) + + if self.reason_for_modification: + case.message_post( + body=_("Modification reason (captured at intake): %s") + % self.reason_for_modification + ) + if self.existing_order_summary: + case.message_post( + body=_("Existing order summary (captured at intake; not legally " + "interpreted here — see Step 6+ for prior-judgment extraction): %s") + % self.existing_order_summary + ) + + self._post_caller_concern(case) + self._run_screening_and_summarise(case) + self.created_case_id = case + + def _post_caller_concern(self, case): + if self.caller_concern: + case.message_post( + body=_( + "ATTORNEY QUESTION (captured at intake — this software does " + "not answer this; for attorney review only): %s" + ) % self.caller_concern + ) + + def _run_screening_and_summarise(self, case): + case.action_run_conflict_screening() + hits = case.conflict_hit_ids + if hits: + lines = [ + _("⚠️ %(n)d potential conflict(s) found — attorney review required " + "before engaging:", n=len(hits)) + ] + for h in hits: + lines.append(" • " + (h.reason or h.party_name)) + self.conflict_summary = "\n".join(lines) + else: + self.conflict_summary = _("No potential conflicts detected.") diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_issue.py b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_issue.py new file mode 100644 index 0000000..c6b5d69 --- /dev/null +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_issue.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +from odoo import api, fields, models + + +class FamilyLawIssue(models.Model): + _name = "familylaw.issue" + _description = "Contested Issue" + _order = "issue_type" + + case_id = fields.Many2one( + "familylaw.case", + required=True, + ondelete="cascade", + index=True, + ) + issue_type = fields.Selection( + selection=[ + ("time_sharing", "Time-Sharing / Parental Responsibility"), + ("child_support", "Child Support"), + ("equitable_distribution", "Equitable Distribution"), + ("alimony", "Alimony / Spousal Support"), + ("attorneys_fees", "Attorney's Fees"), + ("other", "Other"), + ], + string="Issue", + required=True, + ) + name = fields.Char( + compute="_compute_name", + store=True, + string="Issue", + ) + description = fields.Text() + + @api.depends("issue_type") + def _compute_name(self): + labels = dict(self._fields["issue_type"].selection) + for issue in self: + issue.name = labels.get(issue.issue_type, "") diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_party.py b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_party.py new file mode 100644 index 0000000..5f175f6 --- /dev/null +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_party.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +from odoo import fields, models + + +class FamilyLawParty(models.Model): + _name = "familylaw.party" + _description = "Case Party" + _inherit = ["mail.thread"] + _order = "role, name" + + case_id = fields.Many2one( + "familylaw.case", + required=True, + ondelete="cascade", + index=True, + ) + partner_id = fields.Many2one( + "res.partner", + string="Contact Record", + help="Link to an existing contact. Name is copied to the Name field.", + ) + name = fields.Char( + required=True, + tracking=True, + help="Full legal name of this party.", + ) + role = fields.Selection( + selection=[ + ("client", "Client"), + ("opposing_party", "Opposing Party"), + ("opposing_counsel", "Opposing Counsel"), + ("other", "Other"), + ], + required=True, + default="opposing_party", + tracking=True, + ) + notes = fields.Text() diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_proceeding.py b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_proceeding.py new file mode 100644 index 0000000..cd34fa4 --- /dev/null +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/models/familylaw_proceeding.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +from odoo import fields, models + + +class FamilyLawProceeding(models.Model): + _name = "familylaw.proceeding" + _description = "Proceeding" + _inherit = ["mail.thread"] + _order = "date_opened desc" + + case_id = fields.Many2one( + "familylaw.case", + required=True, + ondelete="cascade", + index=True, + tracking=True, + ) + name = fields.Char( + string="Proceeding", + required=True, + tracking=True, + ) + proceeding_type = fields.Selection( + selection=[ + ("original", "Original Action"), + ("modification", "Modification"), + ("enforcement", "Enforcement / Contempt"), + ("appeal", "Appeal"), + ("other", "Other"), + ], + string="Type", + required=True, + default="original", + tracking=True, + ) + state = fields.Selection( + selection=[ + ("open", "Open"), + ("closed", "Closed"), + ], + default="open", + required=True, + tracking=True, + ) + proceeding_number = fields.Char( + string="Court-Assigned Number", + help="If the court assigns a distinct number for this proceeding.", + ) + date_opened = fields.Date( + string="Date Opened", + default=fields.Date.context_today, + ) + date_closed = fields.Date(string="Date Closed") + notes = fields.Text() + + def action_close_proceeding(self): + for proc in self: + proc.state = "closed" + proc.date_closed = fields.Date.context_today(self) + proc.message_post(body="Proceeding closed.") + + def action_reopen_proceeding(self): + for proc in self: + proc.state = "open" + proc.date_closed = False + proc.message_post(body="Proceeding reopened.") 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 d54c036..2764a8c 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 @@ -1,3 +1,14 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_familylaw_case_user,familylaw.case staff,model_familylaw_case,group_familylaw_user,1,1,1,0 access_familylaw_case_attorney,familylaw.case attorney,model_familylaw_case,group_familylaw_attorney,1,1,1,1 +access_familylaw_party_user,familylaw.party staff,model_familylaw_party,group_familylaw_user,1,1,1,0 +access_familylaw_party_attorney,familylaw.party attorney,model_familylaw_party,group_familylaw_attorney,1,1,1,1 +access_familylaw_child_user,familylaw.child staff,model_familylaw_child,group_familylaw_user,1,1,1,0 +access_familylaw_child_attorney,familylaw.child attorney,model_familylaw_child,group_familylaw_attorney,1,1,1,1 +access_familylaw_issue_user,familylaw.issue staff,model_familylaw_issue,group_familylaw_user,1,1,1,0 +access_familylaw_issue_attorney,familylaw.issue attorney,model_familylaw_issue,group_familylaw_attorney,1,1,1,1 +access_familylaw_proceeding_user,familylaw.proceeding staff,model_familylaw_proceeding,group_familylaw_user,1,1,1,0 +access_familylaw_proceeding_attorney,familylaw.proceeding attorney,model_familylaw_proceeding,group_familylaw_attorney,1,1,1,1 +access_familylaw_conflict_hit_user,familylaw.conflict.hit staff,model_familylaw_conflict_hit,group_familylaw_user,1,1,1,0 +access_familylaw_conflict_hit_attorney,familylaw.conflict.hit attorney,model_familylaw_conflict_hit,group_familylaw_attorney,1,1,1,1 +access_familylaw_intake_wizard_user,familylaw.intake.wizard user,model_familylaw_intake_wizard,base.group_user,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 0b0a44e..7b2355e 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 @@ -1 +1,2 @@ from . import test_case_lifecycle +from . import test_step2 diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/tests/test_step2.py b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/tests/test_step2.py new file mode 100644 index 0000000..20f986e --- /dev/null +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/tests/test_step2.py @@ -0,0 +1,460 @@ +# -*- coding: utf-8 -*- +"""STEP 2 tests — parties, children, issues, proceedings, conflict screening, +intake questionnaire (strict / emergency fast-path). + +Run just this step: + odoo -d -u activeblue_familylaw --test-enable \ + --test-tags familylaw_step2 --stop-after-init + +All tests roll back inside a savepoint. No network calls. +""" + +from datetime import date, timedelta + +from odoo.tests.common import TransactionCase, new_test_user, tagged +from odoo.exceptions import UserError, ValidationError + + +@tagged("post_install", "-at_install", "familylaw", "familylaw_step2") +class TestStep2RelationsAndProceeding(TransactionCase): + """Relation integrity, DOB constraint, initial proceeding creation.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.partner_a = cls.env["res.partner"].create({"name": "Client A"}) + cls.partner_b = cls.env["res.partner"].create({"name": "Client B"}) + cls.attorney = new_test_user( + cls.env, + login="fl_atty2", + name="Test Attorney 2", + email="atty2@example.com", + groups="base.group_user,activeblue_familylaw.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", + ) + cls.Case = cls.env["familylaw.case"] + + def _make_case(self, case_type="dissolution_children", **kw): + vals = { + "name": "Test Matter", + "client_id": self.partner_a.id, + "case_type": case_type, + } + vals.update(kw) + return self.Case.create(vals) + + # --- initial proceeding ------------------------------------------------- + def test_01_case_create_opens_initial_proceeding(self): + case = self._make_case() + self.assertEqual(len(case.proceeding_ids), 1) + proc = case.proceeding_ids[0] + self.assertEqual(proc.state, "open") + + def test_02_modification_case_creates_modification_proceeding(self): + case = self._make_case(case_type="support_modification") + proc = case.proceeding_ids[0] + self.assertEqual(proc.proceeding_type, "modification") + + def test_03_original_case_creates_original_proceeding(self): + case = self._make_case(case_type="dissolution_children") + proc = case.proceeding_ids[0] + self.assertEqual(proc.proceeding_type, "original") + + # --- multiple proceedings ----------------------------------------------- + def test_04_case_can_hold_multiple_proceedings(self): + case = self._make_case() + self.env["familylaw.proceeding"].create({ + "case_id": case.id, + "name": "Second Proceeding", + "proceeding_type": "modification", + }) + self.assertEqual(len(case.proceeding_ids), 2) + states = set(case.proceeding_ids.mapped("state")) + self.assertEqual(states, {"open"}) + + def test_05_proceeding_states_are_independent(self): + case = self._make_case() + extra = self.env["familylaw.proceeding"].create({ + "case_id": case.id, + "name": "Extra", + "proceeding_type": "enforcement", + }) + extra.action_close_proceeding() + self.assertEqual(extra.state, "closed") + # Initial proceeding stays open + initial = case.proceeding_ids.filtered(lambda p: p != extra) + self.assertEqual(initial.state, "open") + + # --- parties ------------------------------------------------------------ + def test_06_party_links_to_case(self): + case = self._make_case() + party = self.env["familylaw.party"].create({ + "case_id": case.id, + "name": "Jane Doe", + "role": "opposing_party", + }) + self.assertIn(party, case.party_ids) + + # --- children ----------------------------------------------------------- + def test_07_child_links_to_case(self): + case = self._make_case() + child = self.env["familylaw.child"].create({ + "case_id": case.id, + "name": "Minor Child A", + "date_of_birth": date.today() - timedelta(days=365 * 5), + }) + self.assertIn(child, case.child_ids) + + def test_08_dob_future_rejected(self): + case = self._make_case() + with self.assertRaises(ValidationError): + self.env["familylaw.child"].create({ + "case_id": case.id, + "name": "Future Child", + "date_of_birth": date.today() + timedelta(days=1), + }) + + def test_09_dob_over_25_rejected(self): + case = self._make_case() + implausible_dob = date.today().replace(year=date.today().year - 26) + with self.assertRaises(ValidationError): + self.env["familylaw.child"].create({ + "case_id": case.id, + "name": "Old Child", + "date_of_birth": implausible_dob, + }) + + def test_10_dob_valid_accepted(self): + case = self._make_case() + valid_dob = date.today() - timedelta(days=365 * 8) + child = self.env["familylaw.child"].create({ + "case_id": case.id, + "name": "Young Child", + "date_of_birth": valid_dob, + }) + self.assertTrue(child.id) + self.assertEqual(child.age, 8) + + # --- issues ------------------------------------------------------------- + def test_11_issue_links_to_case(self): + case = self._make_case() + issue = self.env["familylaw.issue"].create({ + "case_id": case.id, + "issue_type": "child_support", + }) + self.assertIn(issue, case.issue_ids) + + +@tagged("post_install", "-at_install", "familylaw", "familylaw_step2") +class TestStep2SearchByPerson(TransactionCase): + """Find a case by party name, child name, and case_number.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.partner = cls.env["res.partner"].create({"name": "Search Client"}) + cls.case = cls.env["familylaw.case"].create({ + "name": "Search Matter", + "client_id": cls.partner.id, + "case_type": "dissolution_children", + "case_number": "2024-DR-999999", + }) + cls.env["familylaw.party"].create({ + "case_id": cls.case.id, + "name": "John Opposing", + "role": "opposing_party", + }) + cls.env["familylaw.child"].create({ + "case_id": cls.case.id, + "name": "Little One", + "date_of_birth": date.today() - timedelta(days=365 * 6), + }) + + def test_12_find_by_case_number(self): + results = self.env["familylaw.case"].search( + [("case_number", "=", "2024-DR-999999")] + ) + self.assertIn(self.case, results) + + def test_13_find_by_party_name(self): + results = self.env["familylaw.case"].search( + [("party_ids.name", "ilike", "John Opposing")] + ) + self.assertIn(self.case, results) + + def test_14_find_by_child_name(self): + results = self.env["familylaw.case"].search( + [("child_ids.name", "ilike", "Little One")] + ) + self.assertIn(self.case, results) + + +@tagged("post_install", "-at_install", "familylaw", "familylaw_step2") +class TestStep2ConflictScreening(TransactionCase): + """Conflict screening surfaces a past-client opposing party; does not auto-clear.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.attorney = new_test_user( + cls.env, + login="fl_atty_conflict", + name="Conflict Attorney", + email="catty@example.com", + groups="base.group_user,activeblue_familylaw.group_familylaw_attorney", + ) + # Case A: "Old Client" is the client + cls.old_client = cls.env["res.partner"].create({"name": "Old Client"}) + cls.case_a = cls.env["familylaw.case"].create({ + "name": "Old Matter", + "client_id": cls.old_client.id, + "case_type": "dissolution_children", + }) + + # Case B: "Old Client" appears as an opposing party + cls.new_client = cls.env["res.partner"].create({"name": "New Client"}) + cls.case_b = cls.env["familylaw.case"].create({ + "name": "New Matter", + "client_id": cls.new_client.id, + "case_type": "support_modification", + }) + cls.env["familylaw.party"].create({ + "case_id": cls.case_b.id, + "name": "Old Client", + "role": "opposing_party", + }) + + def test_15_conflict_screening_finds_client_match(self): + self.case_b.action_run_conflict_screening() + hits = self.case_b.conflict_hit_ids + self.assertTrue(hits, "Expected at least one conflict hit for 'Old Client'") + + def test_16_conflict_does_not_auto_clear(self): + self.case_b.action_run_conflict_screening() + self.assertFalse( + self.case_b.conflict_check_cleared, + "Conflict screening must never auto-clear the attorney gate.", + ) + + def test_17_conflict_hit_count_updates(self): + self.case_b.action_run_conflict_screening() + self.assertGreater(self.case_b.conflict_hit_count, 0) + + def test_18_engage_blocked_with_uncleared_conflict(self): + """Ensure Step 1's engage gate still works after Step 2 additions.""" + with self.assertRaises(UserError): + self.case_b.action_engage() + + def test_19_attorney_can_clear_conflict_after_reviewing_hits(self): + self.case_b.action_run_conflict_screening() + self.case_b.with_user(self.attorney).action_mark_conflict_cleared() + self.assertTrue(self.case_b.conflict_check_cleared) + + def test_20_no_conflict_for_unrelated_opposing_party(self): + unique_client = self.env["res.partner"].create({"name": "Completely Unique Person XYZ"}) + case_c = self.env["familylaw.case"].create({ + "name": "Unrelated Matter", + "client_id": unique_client.id, + "case_type": "paternity", + }) + self.env["familylaw.party"].create({ + "case_id": case_c.id, + "name": "Completely Unique Opposing XYZ", + "role": "opposing_party", + }) + case_c.action_run_conflict_screening() + self.assertEqual(case_c.conflict_hit_count, 0) + + +@tagged("post_install", "-at_install", "familylaw", "familylaw_step2") +class TestStep2IntakeWizard(TransactionCase): + """Intake questionnaire — strict path, emergency fast-path, triage, question capture.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.Wizard = cls.env["familylaw.intake.wizard"] + cls.partner = cls.env["res.partner"].create({"name": "Existing Client"}) + + def _make_wizard(self, **vals): + return self.Wizard.create(vals) + + # --- standard path strict validation ------------------------------------ + def test_21_standard_path_requires_matter_name(self): + wiz = self._make_wizard( + step="details", + case_type="dissolution_children", + county="miami_dade", + caller_name="Caller X", + ) + with self.assertRaises(UserError): + wiz._validate_standard_details() + + def test_22_standard_path_requires_case_type(self): + wiz = self._make_wizard( + step="details", + matter_name="Test Matter", + county="miami_dade", + caller_name="Caller X", + ) + with self.assertRaises(UserError): + wiz._validate_standard_details() + + def test_23_standard_path_requires_county(self): + wiz = self._make_wizard( + step="details", + matter_name="Test Matter", + case_type="dissolution_children", + caller_name="Caller X", + county=False, + ) + with self.assertRaises(UserError): + wiz._validate_standard_details() + + def test_24_standard_path_requires_client_or_caller_name(self): + wiz = self._make_wizard( + step="details", + matter_name="Test Matter", + case_type="dissolution_children", + county="miami_dade", + ) + with self.assertRaises(UserError): + wiz._validate_standard_details() + + def test_25_standard_path_creates_case(self): + wiz = self._make_wizard( + caller_name="John Smith", + case_type="dissolution_children", + county="miami_dade", + ) + wiz.matter_name = "In re Smith" + wiz.client_partner_id = self.partner.id + wiz._create_standard_case() + case = wiz.created_case_id + self.assertTrue(case) + self.assertEqual(case.state, "intake") + self.assertFalse(case.is_emergency) + + def test_26_standard_case_gets_initial_proceeding(self): + wiz = self._make_wizard( + caller_name="Jane Doe", + case_type="support_modification", + county="miami_dade", + ) + wiz.matter_name = "In re Doe" + wiz.client_partner_id = self.partner.id + wiz._create_standard_case() + case = wiz.created_case_id + self.assertEqual(len(case.proceeding_ids), 1) + self.assertEqual(case.proceeding_ids[0].proceeding_type, "modification") + + # --- triage sets case type ---------------------------------------------- + def test_27_triage_case_type_propagates_to_case(self): + wiz = self._make_wizard( + caller_name="Alice", + case_type="paternity", + county="miami_dade", + ) + wiz.matter_name = "Paternity Matter" + wiz.client_partner_id = self.partner.id + wiz._create_standard_case() + self.assertEqual(wiz.created_case_id.case_type, "paternity") + + # --- emergency fast-path ------------------------------------------------ + def test_28_emergency_path_creates_case_on_minimum_facts(self): + wiz = self._make_wizard( + caller_name="Emergency Caller", + urgency_child_withheld=True, + ) + self.assertTrue(wiz.is_emergency) + wiz._create_emergency_case() + case = wiz.created_case_id + self.assertTrue(case, "Emergency case should be created") + self.assertEqual(case.state, "intake") + self.assertTrue(case.is_emergency) + + def test_29_emergency_flag_set_on_case(self): + wiz = self._make_wizard( + caller_name="Urgent Caller", + urgency_violence=True, + ) + wiz._create_emergency_case() + self.assertTrue(wiz.created_case_id.is_emergency) + + def test_30_emergency_creates_partner_from_caller_name(self): + wiz = self._make_wizard( + caller_name="Brand New Person", + urgency_removal_threat=True, + ) + wiz._create_emergency_case() + case = wiz.created_case_id + self.assertEqual(case.client_id.name, "Brand New Person") + + def test_31_emergency_urgency_notes_captured(self): + wiz = self._make_wizard( + caller_name="Person In Danger", + urgency_violence=True, + urgency_description="Ex-partner threatened caller this morning.", + ) + wiz._create_emergency_case() + self.assertIn("Ex-partner", wiz.created_case_id.urgency_notes) + + # --- caller concern captured as attorney question, never answered ------- + def test_32_caller_concern_logged_on_case_not_answered(self): + wiz = self._make_wizard( + caller_name="Concerned Caller", + case_type="support_modification", + county="miami_dade", + caller_concern="Do I have a good case?", + ) + wiz.matter_name = "Concern Matter" + wiz.client_partner_id = self.partner.id + wiz._create_standard_case() + case = wiz.created_case_id + chatter_bodies = " ".join( + msg.body for msg in case.message_ids if msg.body + ) + self.assertIn("Do I have a good case?", chatter_bodies) + self.assertIn("attorney", chatter_bodies.lower()) + + def test_33_emergency_concern_also_logged(self): + wiz = self._make_wizard( + caller_name="Emergency Concern", + urgency_child_withheld=True, + caller_concern="Am I going to win this?", + ) + wiz._create_emergency_case() + case = wiz.created_case_id + chatter_bodies = " ".join(msg.body for msg in case.message_ids if msg.body) + self.assertIn("Am I going to win this?", chatter_bodies) + + # --- opposing party + conflict screening via wizard --------------------- + def test_34_wizard_adds_opposing_party_and_screens(self): + # Set up an existing case with this person as a client + existing_client = self.env["res.partner"].create({"name": "Opposing Client Match"}) + self.env["familylaw.case"].create({ + "name": "Existing Matter", + "client_id": existing_client.id, + "case_type": "dissolution_children", + }) + + wiz = self._make_wizard( + caller_name="New Caller", + case_type="enforcement", + county="miami_dade", + opposing_party_name="Opposing Client Match", + ) + wiz.matter_name = "Enforcement Matter" + wiz.client_partner_id = self.partner.id + wiz._create_standard_case() + case = wiz.created_case_id + # Should have at least one party record + self.assertTrue(case.party_ids) + # Should have at least one conflict hit + self.assertGreater(case.conflict_hit_count, 0) 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 4675d30..8032c92 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 @@ -1,7 +1,7 @@ - + familylaw.case.list familylaw.case @@ -9,10 +9,12 @@ + +
+ +
+ + + + + +
+ + + @@ -72,11 +96,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
-
@@ -86,7 +145,7 @@ - + familylaw.case.search familylaw.case @@ -95,11 +154,18 @@ + + + + @@ -123,8 +189,9 @@ {'search_default_open_cases': 1}

Open your first matter

-

Create a case to begin. New matters start in Intake; an attorney - clears the conflict check, then the matter can be engaged.

+

Create a case or use New Intake to start from the triage questionnaire. + New matters start in Intake; an attorney clears the conflict check, + then the matter can be engaged.

diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_child_views.xml b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_child_views.xml new file mode 100644 index 0000000..60cf92d --- /dev/null +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_child_views.xml @@ -0,0 +1,55 @@ + + + + + familylaw.child.list + familylaw.child + + + + + + + + + + + + familylaw.child.form + familylaw.child + + + + + + + + + + + + + + + + + + + + + + + + familylaw.child.inline.list + familylaw.child + + + + + + + + + + + diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_intake_views.xml b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_intake_views.xml new file mode 100644 index 0000000..13c0a68 --- /dev/null +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_intake_views.xml @@ -0,0 +1,123 @@ + + + + + familylaw.intake.wizard.form + familylaw.intake.wizard + +
+ + + + + + +
+

Step 1 — Triage

+ + + + + + + + + + + + + + + + + +
+ + +
+

Step 2 — Case Details

+ + + + + + + + + + + + + +
+ + +
+

Step 3 — Modification Details

+ + + + + + + + + + +
+ + +
+

Step 4 — Intake Complete

+ + + + + + + + +
+ +
+
+
+
+
+
+ + + New Intake + familylaw.intake.wizard + form + new + + +
diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_issue_views.xml b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_issue_views.xml new file mode 100644 index 0000000..ba8ab6b --- /dev/null +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_issue_views.xml @@ -0,0 +1,16 @@ + + + + + + familylaw.issue.inline.list + familylaw.issue + + + + + + + + + diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_menus.xml b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_menus.xml index dabcd13..8b00426 100644 --- a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_menus.xml +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_menus.xml @@ -6,6 +6,13 @@ name="Family Law" sequence="50"/> + + + + + + + familylaw.party.list + familylaw.party + + + + + + + + + + + + familylaw.party.form + familylaw.party + +
+ + + + + + + + + + + + + + + +
+
+
+ + + + familylaw.party.inline.list + familylaw.party + + + + + + + + + + +
diff --git a/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_proceeding_views.xml b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_proceeding_views.xml new file mode 100644 index 0000000..c34ff3a --- /dev/null +++ b/activeblue_familylaw_handoff/activeblue_familylaw_build/activeblue_familylaw/views/familylaw_proceeding_views.xml @@ -0,0 +1,80 @@ + + + + + familylaw.proceeding.list + familylaw.proceeding + + + + + + + + + + + + + + familylaw.proceeding.form + familylaw.proceeding + +
+
+
+ + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+ + + + familylaw.proceeding.inline.list + familylaw.proceeding + + + + + + + + + + + + +