Files
famlaw/activeblue_familylaw/wizard/fl_intake_wizard.py
Carlos Garcia 26f58952b4 Phase 7: full wizards, auto-generated case tasks, config parameters
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>
2026-05-06 23:49:10 -05:00

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',
}