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>
255 lines
10 KiB
Python
255 lines
10 KiB
Python
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):
|
|
"""
|
|
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,
|
|
default=lambda self: self.env.context.get('active_id'),
|
|
)
|
|
|
|
# ── 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
|
|
)
|
|
include_motion_to_modify = fields.Boolean(
|
|
string='Motion to Modify Child Support', default=True
|
|
)
|
|
include_notice_ssn = fields.Boolean(
|
|
string='Notice of Social Security Number (FL-12.930(a))', default=True
|
|
)
|
|
include_fee_waiver = fields.Boolean(
|
|
string='Fee Waiver Application (FL 57.082)', default=False
|
|
)
|
|
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):
|
|
"""
|
|
Render each selected report PDF, create attachments on the case,
|
|
post a chatter summary, and return to the case form.
|
|
"""
|
|
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'),
|
|
'res_model': 'fl.case',
|
|
'res_id': case.id,
|
|
'view_mode': 'form',
|
|
'target': 'current',
|
|
}
|