fl_intake_wizard.py:
- Full multi-step intake: parties, income, children, DV flag, fee
waiver, AI analysis option
- Creates res.partner → fl.party → fl.case chain (mirrors portal)
- Triggers fee waiver record creation and Ollama AI analysis
- Residency warning computed field (FL 61.021 — 6-month check)
fl_generate_packet_wizard.py:
- Generates selected documents as PDFs via _render_qweb_pdf
- Handles 4 binding models: fl.case, fl.party, fl.fee.waiver,
fl.support.calculation, fl.income.withholding
- Attaches generated PDFs to case chatter with summary note
- Bound to fl.case form as an action button
fl_analysis_wizard.py:
- Checks for recent analysis (<24h) before running new one
- force_reanalysis flag bypasses the lock
- Shows last analysis age label in form; opens result as dialog
- Bound to fl.case form as an action button
fl_case.py:
- _CASE_TASK_TEMPLATES: standard task lists for 6 case types
- _generate_case_tasks(): creates project.task records from templates
- Called automatically from _create_case_project on case creation
fl_wizard_views.xml:
- Form views for all 3 wizards with inline help text
- Packet wizard bound to fl.case form via binding_model_id
data/case_task_templates.xml:
- ir.config_parameter records for Ollama URL, model, deadline days,
mandatory disclosure days, AI lockout hours — all admin-configurable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
267 lines
11 KiB
Python
267 lines
11 KiB
Python
import logging
|
|
from datetime import date
|
|
|
|
from odoo import api, fields, models, _
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class FlIntakeWizard(models.TransientModel):
|
|
"""
|
|
Phase 7 — Full guided case creation wizard for paralegals/attorneys.
|
|
|
|
Creates fl.party records (petitioner + optional respondent),
|
|
fl.case record with all intake fields, triggers fee waiver check
|
|
and AI analysis, then opens the new case form.
|
|
"""
|
|
_name = 'fl.intake.wizard'
|
|
_description = 'Family Law Case Intake Wizard'
|
|
|
|
# ── Step 1: Case identity ────────────────────────────────────────────────
|
|
|
|
case_type = fields.Selection([
|
|
('modification', 'Child Support Modification'),
|
|
('dissolution_children', 'Dissolution of Marriage — With Children'),
|
|
('dissolution_no_children', 'Dissolution of Marriage — No Children'),
|
|
('paternity', 'Paternity'),
|
|
('alimony_modification', 'Alimony Modification'),
|
|
('custody_modification', 'Timesharing / Custody Modification'),
|
|
], string='Case Type', required=True, default='modification')
|
|
|
|
court_case_number = fields.Char(
|
|
string='Court Case Number',
|
|
help='Leave blank if not yet assigned'
|
|
)
|
|
|
|
# ── Step 2: Parties ──────────────────────────────────────────────────────
|
|
|
|
# Petitioner
|
|
petitioner_name = fields.Char(string='Petitioner Full Name', required=True)
|
|
petitioner_email = fields.Char(string='Petitioner Email')
|
|
petitioner_phone = fields.Char(string='Petitioner Phone')
|
|
petitioner_address = fields.Char(string='Petitioner Street Address')
|
|
petitioner_city = fields.Char(string='Petitioner City')
|
|
petitioner_fl_resident_since = fields.Date(
|
|
string='FL Resident Since',
|
|
help='FL 61.021: Petitioner must be a FL resident for 6 months before filing'
|
|
)
|
|
|
|
# Respondent
|
|
respondent_name = fields.Char(string='Respondent Full Name')
|
|
respondent_has_counsel = fields.Boolean(
|
|
string='Respondent Has Legal Counsel?',
|
|
help='Affects attorney referral recommendation'
|
|
)
|
|
|
|
# ── Step 3: Children & Safety ────────────────────────────────────────────
|
|
|
|
num_children = fields.Integer(
|
|
string='Number of Minor Children', default=1,
|
|
help='Children under 18 covered by this case'
|
|
)
|
|
domestic_violence_flag = fields.Boolean(
|
|
string='History of Domestic Violence?',
|
|
help='FL 44.102: Requires separate mediation rooms. '
|
|
'Attorney referral is strongly recommended.'
|
|
)
|
|
income_imputation_concern = fields.Boolean(
|
|
string='Income Imputation Concern?',
|
|
help='Is one party voluntarily unemployed or underemployed?'
|
|
)
|
|
|
|
# ── Step 4: Financial Information ────────────────────────────────────────
|
|
|
|
petitioner_monthly_gross = fields.Float(
|
|
string='Petitioner Monthly Gross Income', digits=(10, 2)
|
|
)
|
|
respondent_monthly_gross = fields.Float(
|
|
string='Respondent Monthly Gross Income', digits=(10, 2)
|
|
)
|
|
current_order_amount = fields.Float(
|
|
string='Current Support Order Amount ($/month)',
|
|
help='0 if no existing order',
|
|
digits=(10, 2)
|
|
)
|
|
household_size = fields.Integer(
|
|
string='Household Size (petitioner)', default=3,
|
|
help='Used for fee waiver eligibility (FL 57.082)'
|
|
)
|
|
|
|
# ── Step 5: Options ──────────────────────────────────────────────────────
|
|
|
|
fee_waiver_request = fields.Boolean(
|
|
string='Request Fee Waiver? (FL 57.082)',
|
|
help='System will check eligibility based on income'
|
|
)
|
|
run_ai_analysis = fields.Boolean(
|
|
string='Run AI Case Analysis?',
|
|
default=True,
|
|
help='Automatically analyze the case using Ollama AI engine'
|
|
)
|
|
notes = fields.Text(string='Additional Notes / Case Summary')
|
|
|
|
# ── Computed helpers ─────────────────────────────────────────────────────
|
|
|
|
residency_warning = fields.Char(
|
|
string='Residency Status',
|
|
compute='_compute_residency_warning'
|
|
)
|
|
|
|
@api.depends('petitioner_fl_resident_since')
|
|
def _compute_residency_warning(self):
|
|
for rec in self:
|
|
if rec.petitioner_fl_resident_since:
|
|
days = (date.today() - rec.petitioner_fl_resident_since).days
|
|
if days < 180:
|
|
remaining = 180 - days
|
|
rec.residency_warning = (
|
|
f'⚠ Only {days} days as FL resident — '
|
|
f'{remaining} more days needed (FL 61.021)'
|
|
)
|
|
else:
|
|
rec.residency_warning = f'✓ {days} days — Residency requirement met (FL 61.021)'
|
|
else:
|
|
rec.residency_warning = ''
|
|
|
|
# ── Action ───────────────────────────────────────────────────────────────
|
|
|
|
def action_create_case(self):
|
|
"""
|
|
Create partner → party → case chain from wizard data.
|
|
Mirrors the portal intake_submit logic but for authenticated users.
|
|
"""
|
|
self.ensure_one()
|
|
|
|
petitioner_name = (self.petitioner_name or '').strip()
|
|
respondent_name = (self.respondent_name or '').strip()
|
|
|
|
# --- Petitioner partner ---
|
|
domain = []
|
|
if self.petitioner_email:
|
|
domain = [('email', '=', self.petitioner_email)]
|
|
else:
|
|
domain = [('name', '=', petitioner_name)]
|
|
|
|
petitioner_partner = self.env['res.partner'].search(domain, limit=1)
|
|
if not petitioner_partner:
|
|
vals = {
|
|
'name': petitioner_name,
|
|
'is_company': False,
|
|
}
|
|
if self.petitioner_email:
|
|
vals['email'] = self.petitioner_email
|
|
if self.petitioner_phone:
|
|
vals['phone'] = self.petitioner_phone
|
|
if self.petitioner_address:
|
|
vals['street'] = self.petitioner_address
|
|
if self.petitioner_city:
|
|
vals['city'] = self.petitioner_city
|
|
try:
|
|
vals['state_id'] = self.env.ref('base.state_us_10').id # Florida
|
|
vals['country_id'] = self.env.ref('base.us').id
|
|
except Exception:
|
|
pass
|
|
petitioner_partner = self.env['res.partner'].create(vals)
|
|
|
|
# --- Respondent partner ---
|
|
respondent_partner = False
|
|
if respondent_name:
|
|
respondent_partner = self.env['res.partner'].search(
|
|
[('name', '=', respondent_name)], limit=1
|
|
)
|
|
if not respondent_partner:
|
|
respondent_partner = self.env['res.partner'].create({
|
|
'name': respondent_name,
|
|
'is_company': False,
|
|
})
|
|
|
|
# --- Petitioner fl.party ---
|
|
petitioner_party = self.env['fl.party'].search(
|
|
[('partner_id', '=', petitioner_partner.id)], limit=1
|
|
)
|
|
if not petitioner_party:
|
|
party_vals = {
|
|
'name': petitioner_name,
|
|
'partner_id': petitioner_partner.id,
|
|
'employment_status': 'employed',
|
|
'monthly_gross_income': self.petitioner_monthly_gross,
|
|
}
|
|
if self.petitioner_email:
|
|
party_vals['email'] = self.petitioner_email
|
|
petitioner_party = self.env['fl.party'].create(party_vals)
|
|
elif self.petitioner_monthly_gross:
|
|
petitioner_party.write({'monthly_gross_income': self.petitioner_monthly_gross})
|
|
|
|
# --- Respondent fl.party ---
|
|
respondent_party = False
|
|
if respondent_partner:
|
|
respondent_party = self.env['fl.party'].search(
|
|
[('partner_id', '=', respondent_partner.id)], limit=1
|
|
)
|
|
if not respondent_party:
|
|
respondent_party = self.env['fl.party'].create({
|
|
'name': respondent_name,
|
|
'partner_id': respondent_partner.id,
|
|
'monthly_gross_income': self.respondent_monthly_gross,
|
|
})
|
|
elif self.respondent_monthly_gross:
|
|
respondent_party.write({'monthly_gross_income': self.respondent_monthly_gross})
|
|
|
|
# --- Residency check ---
|
|
filing_date = date.today()
|
|
residency_ok = True
|
|
if self.petitioner_fl_resident_since:
|
|
days = (filing_date - self.petitioner_fl_resident_since).days
|
|
residency_ok = days >= 180
|
|
|
|
# --- Create fl.case ---
|
|
case_vals = {
|
|
'case_type': self.case_type,
|
|
'petitioner_id': petitioner_party.id,
|
|
'respondent_id': respondent_party.id if respondent_party else False,
|
|
'court_case_number': self.court_case_number or False,
|
|
'domestic_violence_flag': self.domestic_violence_flag,
|
|
'respondent_has_counsel': self.respondent_has_counsel,
|
|
'current_order_amount': self.current_order_amount,
|
|
'filing_date': filing_date,
|
|
'residency_requirement_met': residency_ok,
|
|
'household_size': self.household_size,
|
|
}
|
|
if self.petitioner_fl_resident_since:
|
|
case_vals['petitioner_fl_resident_since'] = self.petitioner_fl_resident_since
|
|
if self.notes:
|
|
case_vals['description'] = self.notes
|
|
|
|
case = self.env['fl.case'].create(case_vals)
|
|
|
|
# --- Fee waiver check ---
|
|
if self.fee_waiver_request:
|
|
fwv = self.env['fl.fee.waiver'].search(
|
|
[('case_id', '=', case.id)], limit=1
|
|
)
|
|
if not fwv:
|
|
# Create a fee waiver record manually
|
|
self.env['fl.fee.waiver'].create({'case_id': case.id})
|
|
|
|
# --- AI analysis ---
|
|
if self.run_ai_analysis:
|
|
try:
|
|
self.env['fl.ai.engine'].analyze_case(case.id)
|
|
except Exception as e:
|
|
_logger.warning(
|
|
'Intake wizard: AI analysis failed for case %s: %s', case.id, e
|
|
)
|
|
case.message_post(
|
|
body=f'⚠ AI analysis could not run automatically: {e}',
|
|
subtype_xmlid='mail.mt_note',
|
|
)
|
|
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': _('New Case'),
|
|
'res_model': 'fl.case',
|
|
'res_id': case.id,
|
|
'view_mode': 'form',
|
|
'target': 'current',
|
|
}
|