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:
@@ -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': _('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': 'Case',
|
||||
'res_model': 'fl.case',
|
||||
'res_id': self.case_id.id,
|
||||
'name': _('AI Analysis Result'),
|
||||
'res_model': 'fl.analysis',
|
||||
'res_id': analysis.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current',
|
||||
'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',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user