Add complexity-driven discovery suggestion wizard
fl_discovery_suggest_wizard.py:
- fl.discovery.suggest.wizard: reads case complexity (AI analysis or
rule-based fallback), case type, issue_tag_ids, and flags
(domestic_violence_flag, respondent_has_counsel, income_imputation_concern)
to build a pre-checked list of relevant discovery items
- fl.discovery.suggest.line: one row per suggested item with type,
directed_to, description, rationale, trigger badge, and min complexity
- 50+ templates across 10 trigger categories: base, modification,
dissolution, paternity, alimony, custody, imputation (Barner v. Barner),
self_employment, domestic_violence, respondent_counsel, complex_only
- action_create_selected: creates fl.discovery records (draft) and posts
a chatter summary with all created items; bound to fl.case form
fl_case.py:
- Add issue_tag_ids Many2many(fl.issue.tag) — field referenced by AI
engine rule-tagging but not previously declared on the model
fl_discovery_suggest_views.xml:
- Wizard form: complexity badge, alert box explaining level, editable
suggestion list with trigger/type/description/rationale columns
- Action bound to fl.case form via binding_model_id
- Inherits fl.case form to add issue_tag_ids widget to AI tab
ir.model.access.csv: access rows for both new wizard models
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,7 @@
|
|||||||
'views/fl_fee_waiver_views.xml',
|
'views/fl_fee_waiver_views.xml',
|
||||||
'views/fl_statute_views.xml',
|
'views/fl_statute_views.xml',
|
||||||
'views/fl_wizard_views.xml',
|
'views/fl_wizard_views.xml',
|
||||||
|
'views/fl_discovery_suggest_views.xml',
|
||||||
'views/menu_views.xml',
|
'views/menu_views.xml',
|
||||||
# Phase 4 — QWeb PDF Reports
|
# Phase 4 — QWeb PDF Reports
|
||||||
'report/report_financial_affidavit_short.xml',
|
'report/report_financial_affidavit_short.xml',
|
||||||
|
|||||||
@@ -389,6 +389,12 @@ class FlCase(models.Model):
|
|||||||
# CASE LAW & AI ANALYSIS
|
# CASE LAW & AI ANALYSIS
|
||||||
# ══════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
issue_tag_ids = fields.Many2many(
|
||||||
|
'fl.issue.tag',
|
||||||
|
'fl_case_issue_tag_rel', 'case_id', 'tag_id',
|
||||||
|
string='Issue Tags',
|
||||||
|
help='Set automatically by AI rule-tagging engine; can also be set manually'
|
||||||
|
)
|
||||||
caselaw_ids = fields.Many2many(
|
caselaw_ids = fields.Many2many(
|
||||||
'fl.caselaw', string='Applicable Case Law'
|
'fl.caselaw', string='Applicable Case Law'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -82,3 +82,9 @@ access_fl_analysis_wizard_admin,fl.analysis.wizard admin,model_fl_analysis_wizar
|
|||||||
# ── fl.generate.packet.wizard ────────────────────────────────────────────────
|
# ── fl.generate.packet.wizard ────────────────────────────────────────────────
|
||||||
access_fl_generate_packet_wizard_admin,fl.generate.packet.wizard admin,model_fl_generate_packet_wizard,group_admin,1,1,1,1
|
access_fl_generate_packet_wizard_admin,fl.generate.packet.wizard admin,model_fl_generate_packet_wizard,group_admin,1,1,1,1
|
||||||
access_fl_generate_packet_wizard_paralegal,fl.generate.packet.wizard paralegal,model_fl_generate_packet_wizard,group_paralegal,1,1,1,1
|
access_fl_generate_packet_wizard_paralegal,fl.generate.packet.wizard paralegal,model_fl_generate_packet_wizard,group_paralegal,1,1,1,1
|
||||||
|
# ── fl.discovery.suggest.wizard ──────────────────────────────────────────────
|
||||||
|
access_fl_discovery_suggest_wizard_admin,fl.discovery.suggest.wizard admin,model_fl_discovery_suggest_wizard,group_admin,1,1,1,1
|
||||||
|
access_fl_discovery_suggest_wizard_paralegal,fl.discovery.suggest.wizard paralegal,model_fl_discovery_suggest_wizard,group_paralegal,1,1,1,1
|
||||||
|
# ── fl.discovery.suggest.line ────────────────────────────────────────────────
|
||||||
|
access_fl_discovery_suggest_line_admin,fl.discovery.suggest.line admin,model_fl_discovery_suggest_line,group_admin,1,1,1,1
|
||||||
|
access_fl_discovery_suggest_line_paralegal,fl.discovery.suggest.line paralegal,model_fl_discovery_suggest_line,group_paralegal,1,1,1,1
|
||||||
|
|||||||
|
129
activeblue_familylaw/views/fl_discovery_suggest_views.xml
Normal file
129
activeblue_familylaw/views/fl_discovery_suggest_views.xml
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Discovery Suggestion Wizard views
|
||||||
|
Opened from the fl.case form (action button) or Cases menu.
|
||||||
|
Reads case complexity, issue tags, and flags to pre-populate a
|
||||||
|
checklist of relevant fl.discovery items for review.
|
||||||
|
-->
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- ══════════════════════════════════════════════════════════════
|
||||||
|
Wizard form view
|
||||||
|
══════════════════════════════════════════════════════════════ -->
|
||||||
|
|
||||||
|
<record id="view_fl_discovery_suggest_wizard_form" model="ir.ui.view">
|
||||||
|
<field name="name">fl.discovery.suggest.wizard.form</field>
|
||||||
|
<field name="model">fl.discovery.suggest.wizard</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Discovery Suggestions">
|
||||||
|
<sheet>
|
||||||
|
|
||||||
|
<div class="oe_title">
|
||||||
|
<h1>Suggested Discovery Items</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Case + Complexity header -->
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="case_id" readonly="1"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="complexity" readonly="1" widget="badge"
|
||||||
|
decoration-success="complexity == 'simple'"
|
||||||
|
decoration-warning="complexity == 'moderate'"
|
||||||
|
decoration-danger="complexity == 'complex'"/>
|
||||||
|
<field name="complexity_source" readonly="1" string="Source"/>
|
||||||
|
<field name="selected_count" readonly="1" string="Items Selected"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<!-- Complexity explanation -->
|
||||||
|
<div class="alert alert-success mb-0"
|
||||||
|
invisible="complexity != 'simple'">
|
||||||
|
<strong>Simple case</strong> — core financial disclosure items only.
|
||||||
|
Run an AI analysis to check for additional complexity factors.
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-warning mb-0"
|
||||||
|
invisible="complexity != 'moderate'">
|
||||||
|
<strong>Moderate complexity</strong> — standard income discovery +
|
||||||
|
targeted items based on case flags.
|
||||||
|
Items marked "Moderate" minimum are included.
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-danger mb-0"
|
||||||
|
invisible="complexity != 'complex'">
|
||||||
|
<strong>Complex case</strong> — full discovery suite including employer
|
||||||
|
subpoenas, bank subpoenas, and deposition.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info mt-2">
|
||||||
|
<strong>Review and deselect</strong> any items that don't apply.
|
||||||
|
Click <em>Create Selected</em> to add them to the case as draft
|
||||||
|
discovery items. Items are not served until you mark them "Served."
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Suggestion lines -->
|
||||||
|
<field name="line_ids" nolabel="1">
|
||||||
|
<list editable="bottom" decoration-muted="not selected">
|
||||||
|
<field name="selected" widget="boolean_toggle" optional="show"/>
|
||||||
|
<field name="trigger_label" string="Trigger"
|
||||||
|
widget="badge" optional="show"/>
|
||||||
|
<field name="discovery_type" optional="show"/>
|
||||||
|
<field name="directed_to" optional="show"/>
|
||||||
|
<field name="description"/>
|
||||||
|
<field name="rationale" optional="hide"/>
|
||||||
|
<field name="complexity_label" string="Min. Level"
|
||||||
|
optional="hide"/>
|
||||||
|
<field name="sequence" optional="hide"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
</sheet>
|
||||||
|
<footer>
|
||||||
|
<button name="action_create_selected"
|
||||||
|
string="Create Selected Discovery Items"
|
||||||
|
type="object" class="btn-primary"
|
||||||
|
invisible="selected_count == 0"/>
|
||||||
|
<button name="action_populate"
|
||||||
|
string="Re-generate Suggestions"
|
||||||
|
type="object" class="btn-secondary"/>
|
||||||
|
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ══════════════════════════════════════════════════════════════
|
||||||
|
Action — opened from fl.case form
|
||||||
|
══════════════════════════════════════════════════════════════ -->
|
||||||
|
|
||||||
|
<record id="action_fl_discovery_suggest_wizard" model="ir.actions.act_window">
|
||||||
|
<field name="name">Suggest Discovery Items</field>
|
||||||
|
<field name="res_model">fl.discovery.suggest.wizard</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="target">new</field>
|
||||||
|
<field name="binding_model_id" ref="model_fl_case"/>
|
||||||
|
<field name="binding_view_types">form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ══════════════════════════════════════════════════════════════
|
||||||
|
Add issue_tag_ids widget to fl.case form — AI tab
|
||||||
|
══════════════════════════════════════════════════════════════ -->
|
||||||
|
|
||||||
|
<record id="view_fl_case_issue_tags_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">fl.case.form.issue.tags</field>
|
||||||
|
<field name="model">fl.case</field>
|
||||||
|
<field name="inherit_id" ref="activeblue_familylaw.view_fl_case_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<!-- Add issue_tag_ids to the AI Analysis tab -->
|
||||||
|
<xpath expr="//page[@name='ai']//group[1]" position="after">
|
||||||
|
<group>
|
||||||
|
<field name="issue_tag_ids" widget="many2many_tags"
|
||||||
|
options="{'color_field': 'color'}"
|
||||||
|
string="Issue Tags (auto-tagged by AI)"/>
|
||||||
|
</group>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
from . import fl_intake_wizard
|
from . import fl_intake_wizard
|
||||||
from . import fl_analysis_wizard
|
from . import fl_analysis_wizard
|
||||||
from . import fl_generate_packet_wizard
|
from . import fl_generate_packet_wizard
|
||||||
|
from . import fl_discovery_suggest_wizard
|
||||||
|
|||||||
860
activeblue_familylaw/wizard/fl_discovery_suggest_wizard.py
Normal file
860
activeblue_familylaw/wizard/fl_discovery_suggest_wizard.py
Normal file
@@ -0,0 +1,860 @@
|
|||||||
|
"""
|
||||||
|
fl.discovery.suggest.wizard — Complexity-driven discovery suggestions.
|
||||||
|
|
||||||
|
Opens from the fl.case form. Reads:
|
||||||
|
- Case complexity (from latest AI analysis, or rule-based fallback)
|
||||||
|
- Case type (modification, dissolution, paternity, etc.)
|
||||||
|
- Active issue tags (income_imputation, self_employment_income, etc.)
|
||||||
|
- Case flags (domestic_violence_flag, respondent_has_counsel, etc.)
|
||||||
|
|
||||||
|
Builds a pre-checked list of relevant fl.discovery items. User reviews,
|
||||||
|
deselects unwanted items, then clicks "Create Selected" to create
|
||||||
|
fl.discovery records in draft state.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Discovery template data ───────────────────────────────────────────────────
|
||||||
|
#
|
||||||
|
# Each entry is a dict:
|
||||||
|
# discovery_type : 'interrogatories' | 'production' | 'admissions' |
|
||||||
|
# 'subpoena' | 'deposition'
|
||||||
|
# directed_to : 'petitioner' | 'respondent' | 'third_party'
|
||||||
|
# description : Short description shown in the wizard line
|
||||||
|
# rationale : Why this item is suggested (shown in tooltip/column)
|
||||||
|
# min_complexity : 'simple' | 'moderate' | 'complex' (minimum to show)
|
||||||
|
# trigger : Key matching one of the _TRIGGERS below; used for badge
|
||||||
|
#
|
||||||
|
# Trigger keys:
|
||||||
|
# 'base' — always suggested for all cases with financial issues
|
||||||
|
# 'modification' — case_type = 'modification'
|
||||||
|
# 'dissolution' — case_type in dissolution_children / dissolution_no_children
|
||||||
|
# 'paternity' — case_type = 'paternity'
|
||||||
|
# 'alimony' — case_type = 'alimony_modification'
|
||||||
|
# 'custody' — case_type = 'custody_modification'
|
||||||
|
# 'imputation' — income_imputation_concern flag OR imputation tag
|
||||||
|
# 'self_employment' — self_employment_income tag on analysis
|
||||||
|
# 'domestic_violence' — domestic_violence_flag
|
||||||
|
# 'respondent_counsel'— respondent_has_counsel flag
|
||||||
|
# 'property' — dissolution + assets involved (dissolution_children or dissolution_no_children)
|
||||||
|
# 'complex_only' — only at 'complex' complexity
|
||||||
|
|
||||||
|
_TEMPLATES = [
|
||||||
|
|
||||||
|
# ── Base: every case with financial issues ────────────────────────────────
|
||||||
|
|
||||||
|
{
|
||||||
|
'trigger': 'base',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Current employment — employer name, position, start date, and gross salary',
|
||||||
|
'rationale': 'Establish current income for FL 61.30 calculation',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'base',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'All sources of income — wages, self-employment, rental, investment, alimony received',
|
||||||
|
'rationale': 'FL 61.30(2)(a): all income sources are includable; prevents underdisclosure',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'base',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Federal income tax returns — last 3 years, all pages and schedules',
|
||||||
|
'rationale': 'FL 12.285 mandatory disclosure; primary income verification document',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'base',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Pay stubs — most recent 6 months from all employers',
|
||||||
|
'rationale': 'FL 12.285 mandatory disclosure; confirms current gross income',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'base',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'W-2s and 1099s — last 3 years from all sources',
|
||||||
|
'rationale': 'Cross-check against tax returns; identifies all income sources',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'base',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Bank statements — all accounts, last 6 months',
|
||||||
|
'rationale': 'Verify income deposits and identify unreported cash income',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'base',
|
||||||
|
'discovery_type': 'admissions',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Admit that respondent is currently employed and earning income',
|
||||||
|
'rationale': 'FL 1.370: establishes baseline income fact; deemed admitted if ignored',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
|
||||||
|
# ── Modification cases ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
{
|
||||||
|
'trigger': 'modification',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Changes in employment or income since the date of the existing order',
|
||||||
|
'rationale': 'FL 61.30(1)(b): modification requires showing substantial change in circumstances',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'modification',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Any bonuses, commissions, overtime received in last 24 months',
|
||||||
|
'rationale': 'FL 61.30(2)(a)(3): bonuses and commissions are gross income',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'modification',
|
||||||
|
'discovery_type': 'admissions',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Admit that a substantial change in circumstances has occurred since the last order',
|
||||||
|
'rationale': 'Establishes the modification threshold prerequisite (FL 61.30(1)(b))',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
|
||||||
|
# ── Dissolution (with or without children) ────────────────────────────────
|
||||||
|
|
||||||
|
{
|
||||||
|
'trigger': 'dissolution',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'All real property owned — address, assessed value, mortgage balance, title holder',
|
||||||
|
'rationale': 'FL 61.075: marital assets subject to equitable distribution',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'dissolution',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Mortgage statements — all real property, last 12 months',
|
||||||
|
'rationale': 'Establish equity in marital real estate',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'dissolution',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Retirement account statements — 401(k), IRA, pension — last 3 years',
|
||||||
|
'rationale': 'FL 61.075: retirement accounts accrued during marriage are marital assets',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'dissolution',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Investment and brokerage account statements — last 3 years',
|
||||||
|
'rationale': 'Identify marital investment assets for equitable distribution',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'dissolution',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'All marital debts — creditor, balance, monthly payment, whose name',
|
||||||
|
'rationale': 'FL 61.075: marital liabilities are also subject to equitable distribution',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'dissolution',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Life insurance policies with cash surrender value',
|
||||||
|
'rationale': 'Cash value life insurance is a marital asset subject to distribution',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'dissolution',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'All vehicles, boats, and recreational vehicles — make, model, year, value, loan balance',
|
||||||
|
'rationale': 'Personal property with significant value is subject to equitable distribution',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
|
||||||
|
# ── Paternity cases ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
{
|
||||||
|
'trigger': 'paternity',
|
||||||
|
'discovery_type': 'admissions',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Admit that respondent is the biological father of the minor child(ren)',
|
||||||
|
'rationale': 'Establishes paternity as a fact without requiring DNA testing if admitted',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'paternity',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Respondent\'s relationship with the child(ren) — frequency and nature of contact',
|
||||||
|
'rationale': 'Relevant to timesharing and parental responsibility determination',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'paternity',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Any prior acknowledgment of paternity (written, verbal communications)',
|
||||||
|
'rationale': 'Prior acknowledgments may establish paternity without court testing',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
|
||||||
|
# ── Alimony modification ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
{
|
||||||
|
'trigger': 'alimony',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Current financial status — income, assets, liabilities',
|
||||||
|
'rationale': 'FL 61.14: modification requires showing substantial change; need current financials',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'alimony',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Any cohabitation or remarriage since entry of alimony order',
|
||||||
|
'rationale': 'FL 61.14(1)(b): cohabitation can reduce or terminate alimony',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'alimony',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Documentation of cohabitation — lease agreements, utility bills, mail',
|
||||||
|
'rationale': 'Evidence of cohabitation as grounds for alimony modification',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
|
||||||
|
# ── Custody modification ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
{
|
||||||
|
'trigger': 'custody',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Changes in respondent\'s living situation since last custody order',
|
||||||
|
'rationale': 'Substantial change in circumstances affecting child\'s welfare required (FL 61.13)',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'custody',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Child\'s school records — attendance, grades, disciplinary records',
|
||||||
|
'rationale': 'Academic performance reflects whether current timesharing serves the child',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'custody',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Child\'s medical and counseling records (last 2 years)',
|
||||||
|
'rationale': 'Medical/mental health records may reflect parenting issues',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
|
||||||
|
# ── Income imputation concern ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
{
|
||||||
|
'trigger': 'imputation',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Prior employment history — all employers, positions, and salaries for last 5 years',
|
||||||
|
'rationale': (
|
||||||
|
'Barner v. Barner (4th DCA 2006): income may be imputed based on '
|
||||||
|
'prior earning history and capacity'
|
||||||
|
),
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'imputation',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'All education, degrees, professional licenses, and certifications',
|
||||||
|
'rationale': 'FL 61.30(2)(b): imputed income based on earning capacity and qualifications',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'imputation',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Job search efforts — all applications submitted in last 12 months',
|
||||||
|
'rationale': 'If claiming unemployed, must show genuine effort to obtain employment',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'imputation',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Monthly living expenses — rent, utilities, food, transportation, recreation',
|
||||||
|
'rationale': (
|
||||||
|
'Barner v. Barner: lifestyle inconsistent with claimed income is evidence '
|
||||||
|
'of imputable income'
|
||||||
|
),
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'imputation',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Credit card statements — all cards, last 12 months',
|
||||||
|
'rationale': 'Spending patterns establish actual lifestyle standard (Barner v. Barner)',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'imputation',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Loan applications — all applications last 3 years (state income reported)',
|
||||||
|
'rationale': 'Income stated on loan applications vs. claimed income discrepancy is powerful evidence',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'imputation',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Unemployment compensation records — claim forms, benefit amounts',
|
||||||
|
'rationale': 'Cross-check with claimed unemployment status',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'imputation',
|
||||||
|
'discovery_type': 'admissions',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Admit that respondent is voluntarily unemployed or underemployed',
|
||||||
|
'rationale': 'Admission avoids need to prove voluntary nature of unemployment at hearing',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'imputation',
|
||||||
|
'discovery_type': 'subpoena',
|
||||||
|
'directed_to': 'third_party',
|
||||||
|
'description': 'Subpoena to prior employers — employment dates, position, salary, reason for separation',
|
||||||
|
'rationale': 'Establishes earning history from third-party records that cannot be disputed',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
|
||||||
|
# ── Self-employment income ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
{
|
||||||
|
'trigger': 'self_employment',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Business structure — entity type, ownership %, officer/member roles',
|
||||||
|
'rationale': 'FL 61.30(2)(a)(4): self-employment income = gross revenue minus allowable expenses',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'self_employment',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Business gross revenues and net income — last 3 years',
|
||||||
|
'rationale': 'Court uses net business income (after legitimate deductions) for support calculation',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'self_employment',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Business expenses claimed as deductions — itemized list with amounts',
|
||||||
|
'rationale': 'FL 61.30(2)(a)(4): only expenses "necessary" to produce income are deductible',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'self_employment',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Business federal tax returns — last 3 years (Schedule C, 1120-S, K-1)',
|
||||||
|
'rationale': 'Primary document for self-employment income; check for non-recurring deductions',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'self_employment',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Business bank account statements — all accounts, last 12 months',
|
||||||
|
'rationale': 'Revenue deposits reveal true income vs. reported income',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'self_employment',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'All client contracts and invoices — last 12 months',
|
||||||
|
'rationale': 'Revenue documentation from contracts supports income calculation',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'self_employment',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Accounts receivable aging report and accounts payable summary',
|
||||||
|
'rationale': 'Unpaid invoices may reveal income deferred to avoid support obligation',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'self_employment',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'QuickBooks or accounting software export — last 2 years',
|
||||||
|
'rationale': 'Comprehensive business financial records; harder to manipulate than summary reports',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'self_employment',
|
||||||
|
'discovery_type': 'subpoena',
|
||||||
|
'directed_to': 'third_party',
|
||||||
|
'description': 'Subpoena to CPA / accountant — business financial records and communications',
|
||||||
|
'rationale': 'Third-party accountant records cannot be tailored; provides independent income picture',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'self_employment',
|
||||||
|
'discovery_type': 'deposition',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Deposition of respondent regarding business income, expenses, and financial management',
|
||||||
|
'rationale': 'Lock in testimony on income before hearing; identify inconsistencies',
|
||||||
|
'min_complexity': 'complex',
|
||||||
|
},
|
||||||
|
|
||||||
|
# ── Domestic violence ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
{
|
||||||
|
'trigger': 'domestic_violence',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'All police reports, incident reports, and arrest records involving either party',
|
||||||
|
'rationale': 'FL 44.102: DV history affects mediation requirements and attorney referral',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'domestic_violence',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Existing restraining orders, protective injunctions, or orders of protection',
|
||||||
|
'rationale': 'Active injunctions affect contact and timesharing arrangements',
|
||||||
|
'min_complexity': 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'domestic_violence',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Medical records for injuries attributed to domestic violence incidents',
|
||||||
|
'rationale': 'Medical records corroborate DV allegations and establish timeline',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'domestic_violence',
|
||||||
|
'discovery_type': 'admissions',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Admit that respondent has been subject to a domestic violence injunction',
|
||||||
|
'rationale': 'Establishes DV history without requiring full evidentiary hearing',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
|
||||||
|
# ── Respondent has counsel (moderate+) ───────────────────────────────────
|
||||||
|
|
||||||
|
{
|
||||||
|
'trigger': 'respondent_counsel',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'All documents, communications, or evidence respondent intends to use at hearing',
|
||||||
|
'rationale': 'With counsel, respondent is likely preparing exhibits — pre-identify them via discovery',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'respondent_counsel',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Identity of any expert witnesses respondent intends to call (FL 1.280(b)(4))',
|
||||||
|
'rationale': 'Expert disclosure required; income experts are common in modification cases',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
|
||||||
|
# ── Complex: employer subpoena ────────────────────────────────────────────
|
||||||
|
|
||||||
|
{
|
||||||
|
'trigger': 'complex_only',
|
||||||
|
'discovery_type': 'subpoena',
|
||||||
|
'directed_to': 'third_party',
|
||||||
|
'description': 'Subpoena to respondent\'s employer — payroll records, W-2s, bonuses (last 3 years)',
|
||||||
|
'rationale': 'FL 1.351: direct employer records are the most reliable income evidence',
|
||||||
|
'min_complexity': 'moderate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'complex_only',
|
||||||
|
'discovery_type': 'subpoena',
|
||||||
|
'directed_to': 'third_party',
|
||||||
|
'description': 'Subpoena to financial institution — respondent\'s deposit accounts (last 12 months)',
|
||||||
|
'rationale': 'Bank-certified records cannot be altered; reveals true income deposits',
|
||||||
|
'min_complexity': 'complex',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'complex_only',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Cryptocurrency and digital asset wallet records — all transactions last 2 years',
|
||||||
|
'rationale': 'Complex cases often involve unreported crypto income or hidden assets',
|
||||||
|
'min_complexity': 'complex',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'complex_only',
|
||||||
|
'discovery_type': 'deposition',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'Deposition of respondent — full financial picture, income history, and assets',
|
||||||
|
'rationale': 'FL 1.310: oral deposition locks in testimony; essential in complex income disputes',
|
||||||
|
'min_complexity': 'complex',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'complex_only',
|
||||||
|
'discovery_type': 'interrogatories',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'All financial accounts — bank, brokerage, retirement, cryptocurrency — account numbers and institutions',
|
||||||
|
'rationale': 'Complex asset cases require full account disclosure for subpoena targeting',
|
||||||
|
'min_complexity': 'complex',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'trigger': 'complex_only',
|
||||||
|
'discovery_type': 'production',
|
||||||
|
'directed_to': 'respondent',
|
||||||
|
'description': 'IRS Form 4506-T authorization — respondent to authorize IRS tax transcript release',
|
||||||
|
'rationale': 'IRS transcripts cannot be falsified; definitive income verification',
|
||||||
|
'min_complexity': 'complex',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Which triggers are active for a given case — maps trigger key to a
|
||||||
|
# function (case) -> bool. These are evaluated at wizard-open time.
|
||||||
|
_TRIGGER_LABELS = {
|
||||||
|
'base': 'All Cases',
|
||||||
|
'modification': 'Modification',
|
||||||
|
'dissolution': 'Dissolution',
|
||||||
|
'paternity': 'Paternity',
|
||||||
|
'alimony': 'Alimony Mod.',
|
||||||
|
'custody': 'Custody Mod.',
|
||||||
|
'imputation': 'Income Imputation',
|
||||||
|
'self_employment': 'Self-Employment',
|
||||||
|
'domestic_violence': 'Domestic Violence',
|
||||||
|
'respondent_counsel': 'Respondent Counsel',
|
||||||
|
'complex_only': 'Complex',
|
||||||
|
}
|
||||||
|
|
||||||
|
_COMPLEXITY_ORDER = {'simple': 0, 'moderate': 1, 'complex': 2}
|
||||||
|
|
||||||
|
|
||||||
|
class FlDiscoverySuggestLine(models.TransientModel):
|
||||||
|
_name = 'fl.discovery.suggest.line'
|
||||||
|
_description = 'Suggested Discovery Item'
|
||||||
|
_order = 'sequence asc'
|
||||||
|
|
||||||
|
wizard_id = fields.Many2one(
|
||||||
|
'fl.discovery.suggest.wizard', ondelete='cascade'
|
||||||
|
)
|
||||||
|
selected = fields.Boolean(string='Include', default=True)
|
||||||
|
sequence = fields.Integer(default=10)
|
||||||
|
|
||||||
|
discovery_type = fields.Selection([
|
||||||
|
('interrogatories', 'Interrogatories (FL 1.340)'),
|
||||||
|
('production', 'Request for Production (FL 1.350)'),
|
||||||
|
('admissions', 'Request for Admissions (FL 1.370)'),
|
||||||
|
('subpoena', 'Subpoena — Third Party (FL 1.351)'),
|
||||||
|
('deposition', 'Deposition Notice (FL 1.310)'),
|
||||||
|
], string='Type')
|
||||||
|
|
||||||
|
directed_to = fields.Selection([
|
||||||
|
('petitioner', 'Petitioner'),
|
||||||
|
('respondent', 'Respondent'),
|
||||||
|
('third_party', 'Third Party'),
|
||||||
|
], string='Directed To')
|
||||||
|
|
||||||
|
description = fields.Char(string='Description / Subject')
|
||||||
|
rationale = fields.Char(string='Basis / Why Suggested')
|
||||||
|
trigger_label = fields.Char(string='Trigger')
|
||||||
|
complexity_label = fields.Char(string='Min. Complexity')
|
||||||
|
|
||||||
|
|
||||||
|
class FlDiscoverySuggestWizard(models.TransientModel):
|
||||||
|
_name = 'fl.discovery.suggest.wizard'
|
||||||
|
_description = 'Discovery Suggestion Wizard'
|
||||||
|
|
||||||
|
case_id = fields.Many2one(
|
||||||
|
'fl.case', string='Case', required=True,
|
||||||
|
default=lambda self: self.env.context.get('active_id'),
|
||||||
|
)
|
||||||
|
complexity = fields.Selection([
|
||||||
|
('simple', 'Simple'),
|
||||||
|
('moderate', 'Moderate'),
|
||||||
|
('complex', 'Complex'),
|
||||||
|
], string='Case Complexity', compute='_compute_complexity', store=False)
|
||||||
|
complexity_source = fields.Char(
|
||||||
|
string='Complexity Source', compute='_compute_complexity', store=False
|
||||||
|
)
|
||||||
|
line_ids = fields.One2many(
|
||||||
|
'fl.discovery.suggest.line', 'wizard_id', string='Suggested Discovery'
|
||||||
|
)
|
||||||
|
selected_count = fields.Integer(
|
||||||
|
string='Selected', compute='_compute_selected_count'
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends('case_id')
|
||||||
|
def _compute_complexity(self):
|
||||||
|
for rec in self:
|
||||||
|
if not rec.case_id:
|
||||||
|
rec.complexity = 'simple'
|
||||||
|
rec.complexity_source = ''
|
||||||
|
continue
|
||||||
|
rec.complexity, rec.complexity_source = rec._assess_complexity()
|
||||||
|
|
||||||
|
@api.depends('line_ids.selected')
|
||||||
|
def _compute_selected_count(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.selected_count = sum(1 for l in rec.line_ids if l.selected)
|
||||||
|
|
||||||
|
# ── Complexity assessment ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _assess_complexity(self):
|
||||||
|
"""
|
||||||
|
Determine case complexity.
|
||||||
|
Priority: latest AI analysis → rule-based fallback.
|
||||||
|
Returns (complexity_str, source_label).
|
||||||
|
"""
|
||||||
|
case = self.case_id
|
||||||
|
# 1. Latest completed AI analysis
|
||||||
|
latest = self.env['fl.analysis'].search([
|
||||||
|
('case_id', '=', case.id),
|
||||||
|
('state', '=', 'complete'),
|
||||||
|
], order='create_date desc', limit=1)
|
||||||
|
if latest and latest.case_complexity:
|
||||||
|
c = latest.case_complexity
|
||||||
|
if c not in ('simple', 'moderate', 'complex'):
|
||||||
|
c = 'moderate'
|
||||||
|
return c, f'AI Analysis ({latest.create_date.strftime("%Y-%m-%d")})'
|
||||||
|
|
||||||
|
# 2. Rule-based fallback
|
||||||
|
score = 0
|
||||||
|
if case.domestic_violence_flag:
|
||||||
|
score += 3
|
||||||
|
if case.respondent_has_counsel:
|
||||||
|
score += 2
|
||||||
|
if case.income_imputation_concern:
|
||||||
|
score += 2
|
||||||
|
if case.case_type in ('dissolution_children', 'dissolution_no_children'):
|
||||||
|
score += 2
|
||||||
|
# Check issue tags on the case
|
||||||
|
tag_names = set(case.issue_tag_ids.mapped('name')) if case.issue_tag_ids else set()
|
||||||
|
if 'self_employment_income' in tag_names:
|
||||||
|
score += 3
|
||||||
|
if 'income_imputation' in tag_names:
|
||||||
|
score += 2
|
||||||
|
# Check respondent income gap
|
||||||
|
if (case.petitioner_id and case.respondent_id and
|
||||||
|
case.respondent_id.monthly_gross_income and
|
||||||
|
case.petitioner_id.monthly_gross_income):
|
||||||
|
ratio = max(
|
||||||
|
case.petitioner_id.monthly_gross_income,
|
||||||
|
case.respondent_id.monthly_gross_income
|
||||||
|
) / (min(
|
||||||
|
case.petitioner_id.monthly_gross_income,
|
||||||
|
case.respondent_id.monthly_gross_income
|
||||||
|
) or 1)
|
||||||
|
if ratio > 3:
|
||||||
|
score += 2
|
||||||
|
|
||||||
|
if score <= 2:
|
||||||
|
return 'simple', 'Rule-based estimate'
|
||||||
|
elif score <= 5:
|
||||||
|
return 'moderate', 'Rule-based estimate'
|
||||||
|
else:
|
||||||
|
return 'complex', 'Rule-based estimate'
|
||||||
|
|
||||||
|
# ── Trigger detection ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _active_triggers(self):
|
||||||
|
"""Return set of trigger keys that are active for this case."""
|
||||||
|
case = self.case_id
|
||||||
|
active = {'base'}
|
||||||
|
|
||||||
|
# Case type
|
||||||
|
ct = case.case_type
|
||||||
|
if ct == 'modification':
|
||||||
|
active.add('modification')
|
||||||
|
elif ct in ('dissolution_children', 'dissolution_no_children'):
|
||||||
|
active.add('dissolution')
|
||||||
|
elif ct == 'paternity':
|
||||||
|
active.add('paternity')
|
||||||
|
elif ct == 'alimony_modification':
|
||||||
|
active.add('alimony')
|
||||||
|
elif ct == 'custody_modification':
|
||||||
|
active.add('custody')
|
||||||
|
|
||||||
|
# Case flags
|
||||||
|
if case.domestic_violence_flag:
|
||||||
|
active.add('domestic_violence')
|
||||||
|
if case.respondent_has_counsel:
|
||||||
|
active.add('respondent_counsel')
|
||||||
|
if case.income_imputation_concern:
|
||||||
|
active.add('imputation')
|
||||||
|
|
||||||
|
# Issue tags stored on the case (written by AI engine + rule tagging)
|
||||||
|
tag_names = set(case.issue_tag_ids.mapped('name')) if case.issue_tag_ids else set()
|
||||||
|
if 'income_imputation' in tag_names:
|
||||||
|
active.add('imputation')
|
||||||
|
if 'self_employment_income' in tag_names:
|
||||||
|
active.add('self_employment')
|
||||||
|
if 'domestic_violence' in tag_names:
|
||||||
|
active.add('domestic_violence')
|
||||||
|
|
||||||
|
# Complex always add complex_only if complexity is complex
|
||||||
|
if self.complexity == 'complex':
|
||||||
|
active.add('complex_only')
|
||||||
|
elif self.complexity == 'moderate':
|
||||||
|
# Subpoena-to-employer is useful at moderate too
|
||||||
|
active.add('complex_only')
|
||||||
|
|
||||||
|
return active
|
||||||
|
|
||||||
|
# ── Populate suggestions ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@api.onchange('case_id')
|
||||||
|
def _onchange_case_id(self):
|
||||||
|
if not self.case_id:
|
||||||
|
self.line_ids = [(5, 0, 0)]
|
||||||
|
return
|
||||||
|
self._populate_suggestions()
|
||||||
|
|
||||||
|
def _populate_suggestions(self):
|
||||||
|
"""Build suggestion lines from templates based on case data."""
|
||||||
|
if not self.case_id:
|
||||||
|
return
|
||||||
|
# Force recompute of complexity
|
||||||
|
self._compute_complexity()
|
||||||
|
|
||||||
|
active_triggers = self._active_triggers()
|
||||||
|
complexity_val = _COMPLEXITY_ORDER.get(self.complexity, 0)
|
||||||
|
|
||||||
|
seen = set() # de-duplicate by (type, directed_to, description)
|
||||||
|
lines = []
|
||||||
|
seq = 10
|
||||||
|
|
||||||
|
for tmpl in _TEMPLATES:
|
||||||
|
trigger = tmpl['trigger']
|
||||||
|
if trigger not in active_triggers:
|
||||||
|
continue
|
||||||
|
min_c = _COMPLEXITY_ORDER.get(tmpl['min_complexity'], 0)
|
||||||
|
if min_c > complexity_val:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = (tmpl['discovery_type'], tmpl['directed_to'], tmpl['description'])
|
||||||
|
if key in seen:
|
||||||
|
continue
|
||||||
|
seen.add(key)
|
||||||
|
|
||||||
|
lines.append((0, 0, {
|
||||||
|
'selected': True,
|
||||||
|
'sequence': seq,
|
||||||
|
'discovery_type': tmpl['discovery_type'],
|
||||||
|
'directed_to': tmpl['directed_to'],
|
||||||
|
'description': tmpl['description'],
|
||||||
|
'rationale': tmpl['rationale'],
|
||||||
|
'trigger_label': _TRIGGER_LABELS.get(trigger, trigger),
|
||||||
|
'complexity_label': tmpl['min_complexity'].capitalize(),
|
||||||
|
}))
|
||||||
|
seq += 10
|
||||||
|
|
||||||
|
self.line_ids = [(5, 0, 0)] + lines
|
||||||
|
|
||||||
|
# ── Default: populate on creation ─────────────────────────────────────────
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def default_get(self, fields_list):
|
||||||
|
vals = super().default_get(fields_list)
|
||||||
|
return vals
|
||||||
|
|
||||||
|
def action_populate(self):
|
||||||
|
"""Manual re-populate button (after case_id is set)."""
|
||||||
|
self.ensure_one()
|
||||||
|
self._populate_suggestions()
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': self._name,
|
||||||
|
'res_id': self.id,
|
||||||
|
'view_mode': 'form',
|
||||||
|
'target': 'new',
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Create selected discovery items ───────────────────────────────────────
|
||||||
|
|
||||||
|
def action_create_selected(self):
|
||||||
|
"""Create fl.discovery records for all selected lines."""
|
||||||
|
self.ensure_one()
|
||||||
|
selected = self.line_ids.filtered(lambda l: l.selected)
|
||||||
|
if not selected:
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.client',
|
||||||
|
'tag': 'display_notification',
|
||||||
|
'params': {
|
||||||
|
'title': _('Nothing selected'),
|
||||||
|
'message': _('Select at least one discovery item to create.'),
|
||||||
|
'type': 'warning',
|
||||||
|
'sticky': False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
created = self.env['fl.discovery']
|
||||||
|
for line in selected:
|
||||||
|
created |= self.env['fl.discovery'].create({
|
||||||
|
'case_id': self.case_id.id,
|
||||||
|
'discovery_type': line.discovery_type,
|
||||||
|
'directed_to': line.directed_to,
|
||||||
|
'description': line.description,
|
||||||
|
'notes': f'Rationale: {line.rationale}',
|
||||||
|
'state': 'draft',
|
||||||
|
})
|
||||||
|
|
||||||
|
self.case_id.message_post(
|
||||||
|
body=(
|
||||||
|
'🔍 <b>{} Discovery Items Created</b> '
|
||||||
|
'(Complexity: {} — {})<br/>'
|
||||||
|
'{}'
|
||||||
|
).format(
|
||||||
|
len(created),
|
||||||
|
self.complexity.capitalize(),
|
||||||
|
self.complexity_source,
|
||||||
|
'<br/>'.join(
|
||||||
|
'• [{}] {}'.format(
|
||||||
|
dict(self.env['fl.discovery']._fields['discovery_type'].selection)
|
||||||
|
.get(d.discovery_type, d.discovery_type),
|
||||||
|
d.description or ''
|
||||||
|
)
|
||||||
|
for d in created
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtype_xmlid='mail.mt_note',
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'name': _('Case'),
|
||||||
|
'res_model': 'fl.case',
|
||||||
|
'res_id': self.case_id.id,
|
||||||
|
'view_mode': 'form',
|
||||||
|
'target': 'current',
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user