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>
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
'data/fl_deadline_rules.xml',
|
||||
'data/mail_templates.xml',
|
||||
'data/fl_caselaw_data.xml',
|
||||
'data/case_task_templates.xml',
|
||||
# Views — backend (actions before menus so menuitem refs resolve)
|
||||
'views/fl_case_views.xml',
|
||||
'views/fl_party_views.xml',
|
||||
@@ -51,6 +52,7 @@
|
||||
'views/fl_analysis_views.xml',
|
||||
'views/fl_fee_waiver_views.xml',
|
||||
'views/fl_statute_views.xml',
|
||||
'views/fl_wizard_views.xml',
|
||||
'views/menu_views.xml',
|
||||
# Phase 4 — QWeb PDF Reports
|
||||
'report/report_financial_affidavit_short.xml',
|
||||
|
||||
70
activeblue_familylaw/data/case_task_templates.xml
Normal file
70
activeblue_familylaw/data/case_task_templates.xml
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Case Task Template Reference Data
|
||||
These ir.config_parameter records document the standard task templates
|
||||
for each case type. The actual tasks are auto-created via
|
||||
fl.case._generate_case_tasks() (defined in models/fl_case.py).
|
||||
|
||||
Admins may extend the Python _CASE_TASK_TEMPLATES dict in fl_case.py
|
||||
to add firm-specific tasks for each case type.
|
||||
|
||||
Supported case types:
|
||||
modification — Child Support Modification
|
||||
dissolution_children — Dissolution with Minor Children
|
||||
dissolution_no_children — Dissolution without Minor Children
|
||||
paternity — Paternity
|
||||
alimony_modification — Alimony Modification
|
||||
custody_modification — Timesharing / Custody Modification
|
||||
-->
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- Mark that Phase 7 task templates have been initialized -->
|
||||
<record id="config_fl_task_templates_version" model="ir.config_parameter">
|
||||
<field name="key">activeblue_familylaw.task_templates_version</field>
|
||||
<field name="value">7.0</field>
|
||||
</record>
|
||||
|
||||
<!-- Parenting class reminder mail template trigger threshold (days before hearing) -->
|
||||
<record id="config_fl_parenting_class_reminder_days" model="ir.config_parameter">
|
||||
<field name="key">activeblue_familylaw.parenting_class_reminder_days</field>
|
||||
<field name="value">30</field>
|
||||
</record>
|
||||
|
||||
<!-- Default discovery response deadline (calendar days from service) -->
|
||||
<record id="config_fl_discovery_response_days" model="ir.config_parameter">
|
||||
<field name="key">activeblue_familylaw.discovery_response_days</field>
|
||||
<field name="value">30</field>
|
||||
</record>
|
||||
|
||||
<!-- Mandatory disclosure deadline (calendar days from service, FL 12.285) -->
|
||||
<record id="config_fl_mandatory_disclosure_days" model="ir.config_parameter">
|
||||
<field name="key">activeblue_familylaw.mandatory_disclosure_days</field>
|
||||
<field name="value">45</field>
|
||||
</record>
|
||||
|
||||
<!-- Answer deadline (calendar days from service, Rule 1.140) -->
|
||||
<record id="config_fl_answer_deadline_days" model="ir.config_parameter">
|
||||
<field name="key">activeblue_familylaw.answer_deadline_days</field>
|
||||
<field name="value">20</field>
|
||||
</record>
|
||||
|
||||
<!-- AI analysis: hours before a re-analysis is allowed without force flag -->
|
||||
<record id="config_fl_ai_reanalysis_hours" model="ir.config_parameter">
|
||||
<field name="key">activeblue_familylaw.ai_reanalysis_lockout_hours</field>
|
||||
<field name="value">24</field>
|
||||
</record>
|
||||
|
||||
<!-- Ollama endpoint (override to change AI server) -->
|
||||
<record id="config_fl_ollama_url" model="ir.config_parameter">
|
||||
<field name="key">activeblue_familylaw.ollama_url</field>
|
||||
<field name="value">http://192.168.2.10:11434/api/generate</field>
|
||||
</record>
|
||||
|
||||
<!-- Ollama model -->
|
||||
<record id="config_fl_ollama_model" model="ir.config_parameter">
|
||||
<field name="key">activeblue_familylaw.ollama_model</field>
|
||||
<field name="value">llama3.1</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -808,6 +808,99 @@ class FlCase(models.Model):
|
||||
# WORKFLOW METHODS
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Standard task templates keyed by case_type.
|
||||
# Each entry: (name, description, sequence)
|
||||
_CASE_TASK_TEMPLATES = {
|
||||
'modification': [
|
||||
('Gather Financial Documents',
|
||||
'Collect last 3 years tax returns, pay stubs, bank statements '
|
||||
'(FL 12.285 mandatory disclosure — 45 days from service)',
|
||||
10),
|
||||
('Complete Financial Affidavit',
|
||||
'Complete FL-12.902(b) Short Form or FL-12.902(c) Long Form '
|
||||
'financial affidavit (required for support proceedings)',
|
||||
20),
|
||||
('Calculate New Support Amount',
|
||||
'Run FL 61.30 child support calculation using updated income figures. '
|
||||
'Verify modification threshold: 15% AND $50 difference (FL 61.30(1)(b))',
|
||||
30),
|
||||
('File Supplemental Petition',
|
||||
'File FL-12.905 Supplemental Petition to Modify Child Support '
|
||||
'with the clerk and pay filing fee (or submit fee waiver)',
|
||||
40),
|
||||
('Serve Respondent',
|
||||
'Serve Respondent with Summons + Petition. '
|
||||
'Start 20-day answer deadline clock (FL 12.285, Rule 1.070)',
|
||||
50),
|
||||
('Track Answer Deadline',
|
||||
'Monitor 20-day answer deadline. If no response, prepare '
|
||||
'FL 12.922 default motion packet after deadline passes.',
|
||||
60),
|
||||
('Schedule / Attend Mediation',
|
||||
'Attend court-ordered mediation (required in most family cases). '
|
||||
'Confirm separate rooms if DV flag is set (FL 44.102)',
|
||||
70),
|
||||
('Attend Final Hearing',
|
||||
'Appear at final hearing with all documents. '
|
||||
'Bring original + 2 copies of all filed pleadings.',
|
||||
80),
|
||||
],
|
||||
'dissolution_children': [
|
||||
('Gather Financial Documents', 'FL 12.285 mandatory disclosure package', 10),
|
||||
('Complete Financial Affidavit (Long Form)',
|
||||
'FL-12.902(c) required when income > $50,000/year', 20),
|
||||
('Calculate Child Support', 'FL 61.30 worksheet (FL-12.902(e))', 30),
|
||||
('Draft Parenting Plan', 'FL-12.995(a) Parenting Plan — timesharing schedule', 40),
|
||||
('File Petition for Dissolution', 'FL-12.901(b)(1) with all required attachments', 50),
|
||||
('Serve Respondent', 'Serve Summons + Petition; track 20-day answer deadline', 60),
|
||||
('Attend Parenting Class',
|
||||
'FL 61.21 — both parties must complete before final hearing', 70),
|
||||
('Schedule Mediation', 'Court-ordered mediation (FL 44.102)', 80),
|
||||
('Attend Final Hearing', 'Bring parenting plan, support worksheet, all exhibits', 90),
|
||||
],
|
||||
'dissolution_no_children': [
|
||||
('Gather Financial Documents', 'FL 12.285 mandatory disclosure', 10),
|
||||
('Complete Financial Affidavit', 'FL-12.902(b) Short Form', 20),
|
||||
('Identify and Value Marital Assets', 'Real property, accounts, retirement funds', 30),
|
||||
('File Petition for Dissolution', 'FL-12.901(b)(2) — no minor children', 40),
|
||||
('Serve Respondent', 'Serve Summons + Petition; track 20-day answer deadline', 50),
|
||||
('Schedule Mediation', 'Property division mediation if contested', 60),
|
||||
('Attend Final Hearing', 'Bring financial affidavit, marital settlement agreement', 70),
|
||||
],
|
||||
'paternity': [
|
||||
('Gather Birth Records', 'Obtain certified birth certificate', 10),
|
||||
('Complete Financial Affidavit', 'FL-12.902(b) required for support', 20),
|
||||
('Calculate Child Support', 'FL 61.30 worksheet', 30),
|
||||
('File Petition to Determine Paternity', 'FL-12.983(a)', 40),
|
||||
('Serve Respondent', 'Summons + Petition; 20-day deadline', 50),
|
||||
('Draft Parenting Plan', 'FL-12.995(a) if timesharing is requested', 60),
|
||||
('Attend Parenting Class', 'FL 61.21 — required before final hearing', 70),
|
||||
('Attend Final Hearing', 'Bring all documents and parenting plan', 80),
|
||||
],
|
||||
'alimony_modification': [
|
||||
('Gather Financial Documents', 'Tax returns, pay stubs — show substantial change', 10),
|
||||
('Complete Financial Affidavit (Long Form)', 'FL-12.902(c) required', 20),
|
||||
('Document Change in Circumstances',
|
||||
'Document the substantial change in circumstances required for modification', 30),
|
||||
('File Supplemental Petition', 'FL-12.905 Supplemental Petition to Modify Alimony', 40),
|
||||
('Serve Respondent', 'Summons + Petition; track 20-day answer deadline', 50),
|
||||
('Schedule Mediation', 'FL 44.102 mediation', 60),
|
||||
('Attend Final Hearing', 'Bring all financial documents and exhibits', 70),
|
||||
],
|
||||
'custody_modification': [
|
||||
('Document Changed Circumstances',
|
||||
'Substantial change must affect child welfare — document thoroughly', 10),
|
||||
('Gather Supporting Evidence', 'School records, medical records, witness statements', 20),
|
||||
('Complete Financial Affidavit', 'FL-12.902(b) if support change is involved', 30),
|
||||
('Draft Proposed Parenting Plan', 'FL-12.995(a) — proposed new timesharing', 40),
|
||||
('File Supplemental Petition', 'FL-12.905 Supplemental Petition to Modify Custody', 50),
|
||||
('Serve Respondent', 'Summons + Petition; track 20-day answer deadline', 60),
|
||||
('Attend Parenting Class', 'FL 61.21 if not already completed', 70),
|
||||
('Schedule Mediation', 'Court-ordered mediation (FL 44.102)', 80),
|
||||
('Attend Final Hearing', 'Bring parenting plan, evidence, exhibits', 90),
|
||||
],
|
||||
}
|
||||
|
||||
def _create_case_project(self):
|
||||
"""Create a linked Odoo project for case task management."""
|
||||
project = self.env['project.project'].create({
|
||||
@@ -819,6 +912,26 @@ class FlCase(models.Model):
|
||||
),
|
||||
})
|
||||
self.project_id = project
|
||||
self._generate_case_tasks()
|
||||
|
||||
def _generate_case_tasks(self):
|
||||
"""
|
||||
Create standard project tasks for this case based on its case_type.
|
||||
Templates are defined in _CASE_TASK_TEMPLATES above.
|
||||
"""
|
||||
if not self.project_id:
|
||||
return
|
||||
templates = self._CASE_TASK_TEMPLATES.get(self.case_type, [])
|
||||
task_vals_list = []
|
||||
for name, description, sequence in templates:
|
||||
task_vals_list.append({
|
||||
'name': name,
|
||||
'description': description,
|
||||
'project_id': self.project_id.id,
|
||||
'sequence': sequence,
|
||||
})
|
||||
if task_vals_list:
|
||||
self.env['project.task'].create(task_vals_list)
|
||||
|
||||
def _check_fee_waiver_eligibility(self):
|
||||
"""Post a chatter note if petitioner appears to qualify for fee waiver."""
|
||||
|
||||
248
activeblue_familylaw/views/fl_wizard_views.xml
Normal file
248
activeblue_familylaw/views/fl_wizard_views.xml
Normal file
@@ -0,0 +1,248 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Wizard form views:
|
||||
- fl.intake.wizard → New Case Intake
|
||||
- fl.generate.packet.wizard → Generate Filing Packet
|
||||
- fl.analysis.wizard → Trigger AI Analysis
|
||||
-->
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
FL Intake Wizard
|
||||
══════════════════════════════════════════════════════════════ -->
|
||||
|
||||
<record id="view_fl_intake_wizard_form" model="ir.ui.view">
|
||||
<field name="name">fl.intake.wizard.form</field>
|
||||
<field name="model">fl.intake.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="New Case Intake">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>New Family Law Case</h1>
|
||||
</div>
|
||||
|
||||
<!-- Step 1: Case Identity -->
|
||||
<group string="Step 1 — Case Information">
|
||||
<group>
|
||||
<field name="case_type" widget="selection"/>
|
||||
<field name="court_case_number"
|
||||
placeholder="Leave blank if not yet assigned"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<!-- Step 2: Parties -->
|
||||
<group string="Step 2 — Parties">
|
||||
<group string="Petitioner">
|
||||
<field name="petitioner_name" required="1"/>
|
||||
<field name="petitioner_email"/>
|
||||
<field name="petitioner_phone"/>
|
||||
<field name="petitioner_address"/>
|
||||
<field name="petitioner_city"/>
|
||||
<field name="petitioner_fl_resident_since"/>
|
||||
<field name="residency_warning" readonly="1"
|
||||
invisible="not petitioner_fl_resident_since"/>
|
||||
</group>
|
||||
<group string="Respondent">
|
||||
<field name="respondent_name"/>
|
||||
<field name="respondent_has_counsel"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<!-- Step 3: Children & Safety -->
|
||||
<group string="Step 3 — Children & Safety">
|
||||
<group>
|
||||
<field name="num_children"/>
|
||||
<field name="domestic_violence_flag"/>
|
||||
<field name="income_imputation_concern"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<!-- DV Warning -->
|
||||
<div class="alert alert-danger"
|
||||
invisible="not domestic_violence_flag">
|
||||
<strong>⚠ Domestic Violence — Attorney Referral Required</strong><br/>
|
||||
Separate mediation rooms are mandatory (FL 44.102).
|
||||
Pro se representation is strongly discouraged.
|
||||
Resources: Legal Services of Greater Miami (305) 576-0080 |
|
||||
Safespace: (305) 536-5565 | National Hotline: 1-800-799-7233
|
||||
</div>
|
||||
|
||||
<!-- Step 4: Financial Information -->
|
||||
<group string="Step 4 — Financial Information">
|
||||
<group>
|
||||
<field name="petitioner_monthly_gross"
|
||||
string="Petitioner Monthly Gross Income ($)"/>
|
||||
<field name="respondent_monthly_gross"
|
||||
string="Respondent Monthly Gross Income ($)"/>
|
||||
<field name="current_order_amount"
|
||||
string="Current Support Order ($/month)"/>
|
||||
<field name="household_size"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<!-- Step 5: Options -->
|
||||
<group string="Step 5 — Options">
|
||||
<group>
|
||||
<field name="fee_waiver_request"/>
|
||||
<field name="run_ai_analysis"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<field name="notes" widget="text" nolabel="1"
|
||||
placeholder="Additional notes or case summary…"/>
|
||||
</group>
|
||||
|
||||
</sheet>
|
||||
<footer>
|
||||
<button name="action_create_case" string="Create Case"
|
||||
type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fl_intake_wizard" model="ir.actions.act_window">
|
||||
<field name="name">New Case Intake</field>
|
||||
<field name="res_model">fl.intake.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
Generate Filing Packet Wizard
|
||||
══════════════════════════════════════════════════════════════ -->
|
||||
|
||||
<record id="view_fl_generate_packet_wizard_form" model="ir.ui.view">
|
||||
<field name="name">fl.generate.packet.wizard.form</field>
|
||||
<field name="model">fl.generate.packet.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Generate Filing Packet">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>Generate Filing Packet</h1>
|
||||
</div>
|
||||
|
||||
<group>
|
||||
<field name="case_id" readonly="1"/>
|
||||
</group>
|
||||
|
||||
<group string="Select Documents to Include">
|
||||
<group string="Case Documents">
|
||||
<field name="include_mandatory_disclosure"/>
|
||||
<field name="include_motion_to_modify"/>
|
||||
<field name="include_default_motion"/>
|
||||
<field name="include_parenting_plan"/>
|
||||
</group>
|
||||
<group string="Financial Documents">
|
||||
<field name="include_financial_affidavit_short"/>
|
||||
<field name="include_financial_affidavit_long"/>
|
||||
<field name="include_support_worksheet"/>
|
||||
<field name="include_notice_ssn"/>
|
||||
<field name="include_fee_waiver"/>
|
||||
<field name="include_income_withholding"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Note:</strong> Generated PDFs will be attached to the case
|
||||
chatter and available for download. Documents that require related
|
||||
records (e.g. a completed Support Calculation or Fee Waiver) will
|
||||
be skipped if those records don't yet exist on the case.
|
||||
</div>
|
||||
|
||||
<group>
|
||||
<field name="attach_to_case"/>
|
||||
</group>
|
||||
|
||||
</sheet>
|
||||
<footer>
|
||||
<button name="action_generate" string="Generate PDFs"
|
||||
type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fl_generate_packet_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Generate Filing Packet</field>
|
||||
<field name="res_model">fl.generate.packet.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>
|
||||
<field name="context">{'active_id': active_id}</field>
|
||||
</record>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
AI Analysis Wizard
|
||||
══════════════════════════════════════════════════════════════ -->
|
||||
|
||||
<record id="view_fl_analysis_wizard_form" model="ir.ui.view">
|
||||
<field name="name">fl.analysis.wizard.form</field>
|
||||
<field name="model">fl.analysis.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Run AI Case Analysis">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>AI Case Analysis</h1>
|
||||
<h3>Powered by Ollama / LLaMA 3.1</h3>
|
||||
</div>
|
||||
|
||||
<group>
|
||||
<field name="case_id" readonly="1"/>
|
||||
</group>
|
||||
|
||||
<!-- Recent analysis info -->
|
||||
<group string="Previous Analysis">
|
||||
<field name="recent_analysis_id" readonly="1"/>
|
||||
<field name="recent_analysis_age_label" readonly="1"
|
||||
string="Last Run"/>
|
||||
</group>
|
||||
|
||||
<!-- Warning when recent analysis exists -->
|
||||
<div class="alert alert-warning"
|
||||
invisible="not has_recent or force_reanalysis">
|
||||
<strong>⚠ Recent analysis found</strong> (<field name="recent_analysis_age_label" readonly="1" nolabel="1"/>).
|
||||
Clicking "Run Analysis" will open the existing result.
|
||||
Enable "Force Re-analysis" to generate a fresh one.
|
||||
</div>
|
||||
|
||||
<group>
|
||||
<field name="force_reanalysis"/>
|
||||
<field name="has_recent" invisible="1"/>
|
||||
</group>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>What this does:</strong>
|
||||
The AI engine analyzes the case data against Florida Family Law rules,
|
||||
matches relevant case law, and produces a plain-English summary with
|
||||
attorney referral recommendation. Analysis takes 30–90 seconds.
|
||||
</div>
|
||||
|
||||
</sheet>
|
||||
<footer>
|
||||
<button name="action_run_analysis" string="Run Analysis"
|
||||
type="object" class="btn-primary"/>
|
||||
<button name="action_view_recent" string="View Last Analysis"
|
||||
type="object" class="btn-secondary"
|
||||
invisible="not recent_analysis_id"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fl_analysis_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Run AI Analysis</field>
|
||||
<field name="res_model">fl.analysis.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>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,30 +1,135 @@
|
||||
from odoo import fields, models
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FlAnalysisWizard(models.TransientModel):
|
||||
"""
|
||||
Trigger AI analysis on a case.
|
||||
Phase 7 — full implementation.
|
||||
Phase 1: Stub.
|
||||
Phase 7 — Full AI analysis trigger wizard.
|
||||
|
||||
Checks for a recent analysis (<24 h) before running a new one unless
|
||||
force_reanalysis is True. Opens the resulting fl.analysis form on success.
|
||||
"""
|
||||
_name = 'fl.analysis.wizard'
|
||||
_description = 'Trigger AI Analysis Wizard'
|
||||
_description = 'Trigger AI Case Analysis'
|
||||
|
||||
case_id = fields.Many2one(
|
||||
'fl.case', string='Case', required=True
|
||||
'fl.case', string='Case', required=True,
|
||||
default=lambda self: self.env.context.get('active_id'),
|
||||
)
|
||||
force_reanalysis = fields.Boolean(
|
||||
string='Force Re-analysis',
|
||||
help='Run a new analysis even if a recent one exists'
|
||||
help='Run a new analysis even if one was completed in the last 24 hours'
|
||||
)
|
||||
|
||||
# Computed info fields shown in the form
|
||||
recent_analysis_id = fields.Many2one(
|
||||
'fl.analysis', string='Most Recent Analysis',
|
||||
compute='_compute_recent_analysis',
|
||||
)
|
||||
recent_analysis_age_label = fields.Char(
|
||||
string='Last Analysis', compute='_compute_recent_analysis'
|
||||
)
|
||||
has_recent = fields.Boolean(compute='_compute_recent_analysis')
|
||||
|
||||
@api.depends('case_id')
|
||||
def _compute_recent_analysis(self):
|
||||
for rec in self:
|
||||
if not rec.case_id:
|
||||
rec.recent_analysis_id = False
|
||||
rec.recent_analysis_age_label = ''
|
||||
rec.has_recent = False
|
||||
continue
|
||||
|
||||
latest = self.env['fl.analysis'].search(
|
||||
[('case_id', '=', rec.case_id.id), ('state', '=', 'complete')],
|
||||
order='create_date desc',
|
||||
limit=1,
|
||||
)
|
||||
rec.recent_analysis_id = latest
|
||||
if latest:
|
||||
age = datetime.now() - latest.create_date
|
||||
hours = int(age.total_seconds() // 3600)
|
||||
minutes = int((age.total_seconds() % 3600) // 60)
|
||||
if hours == 0:
|
||||
rec.recent_analysis_age_label = f'{minutes} minute(s) ago'
|
||||
elif hours < 24:
|
||||
rec.recent_analysis_age_label = f'{hours} hour(s) ago'
|
||||
else:
|
||||
days = age.days
|
||||
rec.recent_analysis_age_label = f'{days} day(s) ago'
|
||||
rec.has_recent = age < timedelta(hours=24)
|
||||
else:
|
||||
rec.recent_analysis_age_label = 'No analysis yet'
|
||||
rec.has_recent = False
|
||||
|
||||
def action_run_analysis(self):
|
||||
self.env['fl.ai.engine'].analyze_case(self.case_id.id)
|
||||
"""
|
||||
Run the AI analysis pipeline via fl.ai.engine.
|
||||
If a recent analysis exists and force_reanalysis is False,
|
||||
just open the existing one.
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
if self.has_recent and not self.force_reanalysis:
|
||||
# Surface the existing analysis instead
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Case',
|
||||
'name': _('AI Analysis'),
|
||||
'res_model': 'fl.analysis',
|
||||
'res_id': self.recent_analysis_id.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
# Previous analyses remain in history — new one will appear first
|
||||
# (fl.analysis is ordered by create_date desc)
|
||||
|
||||
# Post start note
|
||||
self.case_id.message_post(
|
||||
body='🤖 AI analysis started by %s…' % self.env.user.name,
|
||||
subtype_xmlid='mail.mt_note',
|
||||
)
|
||||
|
||||
try:
|
||||
analysis = self.env['fl.ai.engine'].analyze_case(self.case_id.id)
|
||||
except Exception as e:
|
||||
_logger.error('Analysis wizard: engine error for case %s: %s', self.case_id.id, e)
|
||||
self.case_id.message_post(
|
||||
body=f'❌ AI analysis failed: {e}',
|
||||
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',
|
||||
}
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('AI Analysis Result'),
|
||||
'res_model': 'fl.analysis',
|
||||
'res_id': analysis.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
def action_view_recent(self):
|
||||
"""Open the most recent analysis form view."""
|
||||
self.ensure_one()
|
||||
if not self.recent_analysis_id:
|
||||
return False
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('AI Analysis'),
|
||||
'res_model': 'fl.analysis',
|
||||
'res_id': self.recent_analysis_id.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
@@ -1,20 +1,88 @@
|
||||
from odoo import fields, models
|
||||
import base64
|
||||
import logging
|
||||
|
||||
from odoo import fields, models, _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Report XML IDs and their binding models
|
||||
# (xml_id, binding_model, label, needs_sub_record)
|
||||
# needs_sub_record: which related record to use as the report target
|
||||
_REPORT_DEFS = {
|
||||
# fl.case bound
|
||||
'mandatory_disclosure': (
|
||||
'activeblue_familylaw.action_report_mandatory_disclosure',
|
||||
'fl.case', 'Certificate of Mandatory Disclosure (FL-12.932)', None
|
||||
),
|
||||
'motion_to_modify': (
|
||||
'activeblue_familylaw.action_report_motion_to_modify',
|
||||
'fl.case', 'Motion to Modify Child Support', None
|
||||
),
|
||||
'default_motion': (
|
||||
'activeblue_familylaw.action_report_default_motion',
|
||||
'fl.case', 'Default Judgment Packet (FL 12.922)', None
|
||||
),
|
||||
'parenting_plan': (
|
||||
'activeblue_familylaw.action_report_parenting_plan',
|
||||
'fl.case', 'Parenting Plan (FL-12.995(a))', None
|
||||
),
|
||||
# fl.party bound — petitioner
|
||||
'financial_affidavit_short': (
|
||||
'activeblue_familylaw.action_report_financial_affidavit_short',
|
||||
'fl.party', 'Financial Affidavit — Short Form (FL-12.902(b))', 'petitioner'
|
||||
),
|
||||
'financial_affidavit_long': (
|
||||
'activeblue_familylaw.action_report_financial_affidavit_long',
|
||||
'fl.party', 'Financial Affidavit — Long Form (FL-12.902(c))', 'petitioner'
|
||||
),
|
||||
'notice_ssn': (
|
||||
'activeblue_familylaw.action_report_notice_ssn',
|
||||
'fl.party', 'Notice of Social Security Number (FL-12.930(a))', 'petitioner'
|
||||
),
|
||||
# fl.support.calculation bound
|
||||
'support_worksheet': (
|
||||
'activeblue_familylaw.action_report_child_support_worksheet',
|
||||
'fl.support.calculation', 'Child Support Worksheet (FL-12.902(e))', 'support_calc'
|
||||
),
|
||||
# fl.fee.waiver bound
|
||||
'fee_waiver': (
|
||||
'activeblue_familylaw.action_report_fee_waiver',
|
||||
'fl.fee.waiver', 'Fee Waiver Application (FL 57.082)', 'fee_waiver'
|
||||
),
|
||||
# fl.income.withholding bound
|
||||
'income_withholding': (
|
||||
'activeblue_familylaw.action_report_income_withholding',
|
||||
'fl.income.withholding', 'Income Withholding Order (FL 61.1301)', 'income_withholding'
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class FlGeneratePacketWizard(models.TransientModel):
|
||||
"""
|
||||
Generate full filing packet for a case.
|
||||
Phase 7 — full implementation with batch PDF generation.
|
||||
Phase 1: Stub.
|
||||
Phase 7 — Generate a filing packet of selected PDF reports for a case.
|
||||
|
||||
Renders each selected report, creates ir.attachment records on the case,
|
||||
posts a chatter summary, and returns to the case form.
|
||||
"""
|
||||
_name = 'fl.generate.packet.wizard'
|
||||
_description = 'Generate Filing Packet Wizard'
|
||||
|
||||
case_id = fields.Many2one(
|
||||
'fl.case', string='Case', required=True
|
||||
'fl.case', string='Case', required=True,
|
||||
default=lambda self: self.env.context.get('active_id'),
|
||||
)
|
||||
include_financial_affidavit = fields.Boolean(
|
||||
string='Financial Affidavit', default=True
|
||||
|
||||
# ── Document selection checkboxes ────────────────────────────────────────
|
||||
|
||||
include_mandatory_disclosure = fields.Boolean(
|
||||
string='Certificate of Mandatory Disclosure (FL-12.932)', default=True
|
||||
)
|
||||
include_financial_affidavit_short = fields.Boolean(
|
||||
string='Financial Affidavit — Short Form (FL-12.902(b))', default=False
|
||||
)
|
||||
include_financial_affidavit_long = fields.Boolean(
|
||||
string='Financial Affidavit — Long Form (FL-12.902(c))', default=True
|
||||
)
|
||||
include_support_worksheet = fields.Boolean(
|
||||
string='Child Support Worksheet (FL-12.902(e))', default=True
|
||||
@@ -25,32 +93,162 @@ class FlGeneratePacketWizard(models.TransientModel):
|
||||
include_notice_ssn = fields.Boolean(
|
||||
string='Notice of Social Security Number (FL-12.930(a))', default=True
|
||||
)
|
||||
include_mandatory_disclosure = fields.Boolean(
|
||||
string='Certificate of Mandatory Disclosure (FL-12.932)', default=True
|
||||
)
|
||||
include_fee_waiver = fields.Boolean(
|
||||
string='Fee Waiver Application (if eligible)', default=False
|
||||
string='Fee Waiver Application (FL 57.082)', default=False
|
||||
)
|
||||
language = fields.Selection([
|
||||
('en', 'English'),
|
||||
('es', 'Spanish / Español'),
|
||||
('both', 'Bilingual'),
|
||||
], string='Document Language', default='en')
|
||||
include_income_withholding = fields.Boolean(
|
||||
string='Income Withholding Order (FL 61.1301)', default=False
|
||||
)
|
||||
include_default_motion = fields.Boolean(
|
||||
string='Default Judgment Packet (FL 12.922)', default=False
|
||||
)
|
||||
include_parenting_plan = fields.Boolean(
|
||||
string='Parenting Plan (FL-12.995(a))', default=False
|
||||
)
|
||||
|
||||
# ── Options ──────────────────────────────────────────────────────────────
|
||||
|
||||
attach_to_case = fields.Boolean(
|
||||
string='Attach generated PDFs to case chatter',
|
||||
default=True
|
||||
)
|
||||
|
||||
# ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
def _get_selected_keys(self):
|
||||
"""Return list of _REPORT_DEFS keys that the user has selected."""
|
||||
mapping = {
|
||||
'mandatory_disclosure': self.include_mandatory_disclosure,
|
||||
'financial_affidavit_short': self.include_financial_affidavit_short,
|
||||
'financial_affidavit_long': self.include_financial_affidavit_long,
|
||||
'support_worksheet': self.include_support_worksheet,
|
||||
'motion_to_modify': self.include_motion_to_modify,
|
||||
'notice_ssn': self.include_notice_ssn,
|
||||
'fee_waiver': self.include_fee_waiver,
|
||||
'income_withholding': self.include_income_withholding,
|
||||
'default_motion': self.include_default_motion,
|
||||
'parenting_plan': self.include_parenting_plan,
|
||||
}
|
||||
return [k for k, selected in mapping.items() if selected]
|
||||
|
||||
def _resolve_record_id(self, key, case):
|
||||
"""
|
||||
Return the (record_id, model) tuple to pass to _render_qweb_pdf.
|
||||
Returns (None, None) if the required sub-record doesn't exist.
|
||||
"""
|
||||
xml_id, model, label, sub = _REPORT_DEFS[key]
|
||||
if sub is None:
|
||||
return case.id, model
|
||||
if sub == 'petitioner':
|
||||
if not case.petitioner_id:
|
||||
_logger.warning('Packet: no petitioner_id on case %s', case.id)
|
||||
return None, None
|
||||
return case.petitioner_id.id, model
|
||||
if sub == 'support_calc':
|
||||
calc = case.support_calc_ids[:1] if case.support_calc_ids else None
|
||||
if not calc:
|
||||
_logger.warning('Packet: no support calculation on case %s', case.id)
|
||||
return None, None
|
||||
return calc.id, model
|
||||
if sub == 'fee_waiver':
|
||||
fwv = case.fee_waiver_id
|
||||
if not fwv:
|
||||
_logger.warning('Packet: no fee_waiver_id on case %s', case.id)
|
||||
return None, None
|
||||
return fwv.id, model
|
||||
if sub == 'income_withholding':
|
||||
iwo = self.env['fl.income.withholding'].search(
|
||||
[('case_id', '=', case.id)], limit=1
|
||||
)
|
||||
if not iwo:
|
||||
_logger.warning('Packet: no income withholding on case %s', case.id)
|
||||
return None, None
|
||||
return iwo.id, model
|
||||
return None, None
|
||||
|
||||
# ── Main action ──────────────────────────────────────────────────────────
|
||||
|
||||
def action_generate(self):
|
||||
"""
|
||||
Phase 7 — generate all selected documents as PDFs.
|
||||
Phase 1: Stub — posts a note and returns to case.
|
||||
Render each selected report PDF, create attachments on the case,
|
||||
post a chatter summary, and return to the case form.
|
||||
"""
|
||||
self.case_id.message_post(
|
||||
body='📄 Filing packet generation will be available in Phase 4 (Document Templates).',
|
||||
self.ensure_one()
|
||||
case = self.case_id
|
||||
selected_keys = self._get_selected_keys()
|
||||
|
||||
if not selected_keys:
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': _('Nothing selected'),
|
||||
'message': _('Please select at least one document to generate.'),
|
||||
'type': 'warning',
|
||||
'sticky': False,
|
||||
},
|
||||
}
|
||||
|
||||
generated = []
|
||||
failed = []
|
||||
attachment_ids = []
|
||||
|
||||
for key in selected_keys:
|
||||
xml_id, model, label, sub = _REPORT_DEFS[key]
|
||||
record_id, binding_model = self._resolve_record_id(key, case)
|
||||
if record_id is None:
|
||||
failed.append(f'{label} — required record not found on case')
|
||||
continue
|
||||
|
||||
try:
|
||||
report_action = self.env.ref(xml_id)
|
||||
pdf_content, _mime = report_action._render_qweb_pdf([record_id])
|
||||
safe_name = label.replace('/', '-').replace(':', '').strip()
|
||||
filename = f'{case.name} — {safe_name}.pdf'
|
||||
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'name': filename,
|
||||
'type': 'binary',
|
||||
'datas': base64.b64encode(pdf_content),
|
||||
'res_model': 'fl.case',
|
||||
'res_id': case.id,
|
||||
'mimetype': 'application/pdf',
|
||||
})
|
||||
attachment_ids.append(attachment.id)
|
||||
generated.append(label)
|
||||
_logger.info('Packet: generated %s for case %s', label, case.id)
|
||||
|
||||
except Exception as e:
|
||||
_logger.error(
|
||||
'Packet: failed to generate %s for case %s: %s', label, case.id, e
|
||||
)
|
||||
failed.append(f'{label} — {e}')
|
||||
|
||||
# Post chatter summary
|
||||
if self.attach_to_case and (generated or failed):
|
||||
lines = []
|
||||
if generated:
|
||||
lines.append('<b>✅ Documents generated:</b><ul>')
|
||||
for doc in generated:
|
||||
lines.append(f'<li>{doc}</li>')
|
||||
lines.append('</ul>')
|
||||
if failed:
|
||||
lines.append('<b>⚠ Failed:</b><ul>')
|
||||
for doc in failed:
|
||||
lines.append(f'<li>{doc}</li>')
|
||||
lines.append('</ul>')
|
||||
|
||||
case.message_post(
|
||||
body=''.join(lines),
|
||||
attachment_ids=attachment_ids,
|
||||
subtype_xmlid='mail.mt_note',
|
||||
)
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Case',
|
||||
'name': _('Case'),
|
||||
'res_model': 'fl.case',
|
||||
'res_id': self.case_id.id,
|
||||
'res_id': case.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
from odoo import api, fields, models
|
||||
import logging
|
||||
from datetime import date
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FlIntakeWizard(models.TransientModel):
|
||||
"""
|
||||
Guided case creation wizard.
|
||||
Phase 7 — full multi-step intake form.
|
||||
Phase 1: Stub with basic fields.
|
||||
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'),
|
||||
@@ -17,38 +26,239 @@ class FlIntakeWizard(models.TransientModel):
|
||||
('paternity', 'Paternity'),
|
||||
('alimony_modification', 'Alimony Modification'),
|
||||
('custody_modification', 'Timesharing / Custody Modification'),
|
||||
], string='Case Type', required=True)
|
||||
], string='Case Type', required=True, default='modification')
|
||||
|
||||
petitioner_id = fields.Many2one(
|
||||
'res.partner', string='Petitioner', required=True
|
||||
court_case_number = fields.Char(
|
||||
string='Court Case Number',
|
||||
help='Leave blank if not yet assigned'
|
||||
)
|
||||
respondent_id = fields.Many2one(
|
||||
'res.partner', string='Respondent'
|
||||
|
||||
# ── 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='Is there a history of domestic violence?',
|
||||
help='Your answer affects mediation and safety procedures'
|
||||
string='History of Domestic Violence?',
|
||||
help='FL 44.102: Requires separate mediation rooms. '
|
||||
'Attorney referral is strongly recommended.'
|
||||
)
|
||||
has_minor_children = fields.Boolean(
|
||||
string='Are there minor children involved?'
|
||||
)
|
||||
petitioner_fl_resident_since = fields.Date(
|
||||
string='When did the petitioner become a FL resident?',
|
||||
help='FL 61.021: Must be 6 months before filing'
|
||||
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 the fl.case record from intake data."""
|
||||
case = self.env['fl.case'].create({
|
||||
'case_type': self.case_type,
|
||||
'petitioner_id': self.petitioner_id.id,
|
||||
'respondent_id': self.respondent_id.id if self.respondent_id else False,
|
||||
'domestic_violence_flag': self.domestic_violence_flag,
|
||||
'petitioner_fl_resident_since': self.petitioner_fl_resident_since,
|
||||
"""
|
||||
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',
|
||||
'name': _('New Case'),
|
||||
'res_model': 'fl.case',
|
||||
'res_id': case.id,
|
||||
'view_mode': 'form',
|
||||
|
||||
Reference in New Issue
Block a user