Phase 2: Deadlines + Calendar integration
Full fl_deadline.py: - Calendar event creation/update on every deadline (all-day events) - _cron_check_default_judgment: FL 12.922 — if respondent misses Day 20 answer deadline, auto-creates Motion for Default deadline (5 days), project task, and urgent chatter alert - _cron_deadline_alerts: 7/3/1-day and overdue chatter alerts - Complete service-anchored deadline set: response (Day 20), financial disclosure + mandatory disclosure cert (Day 45), discovery opens (Day 20), respondent parenting class (Day 60), 120-day service max - context-flag pattern (_no_calendar_sync) to prevent recursive write loops Full fl_hearing.py: - Calendar event sync (show_as=busy, confidential) - Pre-hearing checklist computed fields: parenting class (FL 61.21), discovery cutoff (hearing -30 days), financial disclosure status - Workflow buttons: Mark Completed, Mark Continued, Cancel - _create_hearing_deadline: creates fl.deadline record for each hearing New data/fl_deadline_rules.xml: - ir.cron: Daily Deadline Alerts (fl_deadline._cron_deadline_alerts) - ir.cron: Default Judgment Check (fl_deadline._cron_check_default_judgment) - ir.cron: Emancipation Alerts (fl_child._cron_emancipation_alerts) New data/mail_templates.xml: - Deadline Alert Upcoming (EN) - Deadline Alert OVERDUE (EN) - Portal Welcome (EN + ES bilingual) - Default Judgment Window Alert (EN) Enhanced views: deadline + hearing now have calendar view, search view with filters (overdue, due this week, by type), and group-by options. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,8 @@
|
||||
'data/fl_statute_data.xml',
|
||||
'data/fl_support_schedule.xml',
|
||||
'data/ir_sequence.xml',
|
||||
'data/fl_deadline_rules.xml',
|
||||
'data/mail_templates.xml',
|
||||
# Views — backend (actions before menus so menuitem refs resolve)
|
||||
'views/fl_case_views.xml',
|
||||
'views/fl_party_views.xml',
|
||||
|
||||
49
activeblue_familylaw/data/fl_deadline_rules.xml
Normal file
49
activeblue_familylaw/data/fl_deadline_rules.xml
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
SCHEDULED ACTIONS (ir.cron)
|
||||
══════════════════════════════════════════════════════ -->
|
||||
|
||||
<!-- Daily: Send deadline alerts at 7, 3, 1 days and on overdue -->
|
||||
<record id="cron_fl_deadline_alerts" model="ir.cron">
|
||||
<field name="name">FL Family Law: Daily Deadline Alerts</field>
|
||||
<field name="model_id" ref="model_fl_deadline"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._cron_deadline_alerts()</field>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="active">True</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
</record>
|
||||
|
||||
<!-- Daily: Check for default judgment triggers (FL 12.922) -->
|
||||
<record id="cron_fl_default_judgment" model="ir.cron">
|
||||
<field name="name">FL Family Law: Check Default Judgment Triggers (FL 12.922)</field>
|
||||
<field name="model_id" ref="model_fl_deadline"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._cron_check_default_judgment()</field>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="active">True</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
</record>
|
||||
|
||||
<!-- Daily: Check child emancipation approaching (90-day window) -->
|
||||
<record id="cron_fl_emancipation_alerts" model="ir.cron">
|
||||
<field name="name">FL Family Law: Child Emancipation Approaching Alerts</field>
|
||||
<field name="model_id" ref="model_fl_child"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._cron_emancipation_alerts()</field>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="active">True</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
298
activeblue_familylaw/data/mail_templates.xml
Normal file
298
activeblue_familylaw/data/mail_templates.xml
Normal file
@@ -0,0 +1,298 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
DEADLINE ALERT — UPCOMING (English)
|
||||
══════════════════════════════════════════════════════ -->
|
||||
<record id="mail_template_deadline_upcoming" model="mail.template">
|
||||
<field name="name">FL: Deadline Alert — Upcoming</field>
|
||||
<field name="model_id" ref="model_fl_deadline"/>
|
||||
<field name="subject">⏰ Deadline Alert: ${object.name} — Due ${object.due_date}</field>
|
||||
<field name="body_html"><![CDATA[
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<div style="background: #003366; color: #fff; padding: 16px; border-radius: 6px 6px 0 0;">
|
||||
<h2 style="margin: 0;">⏰ Upcoming Deadline Alert</h2>
|
||||
<p style="margin: 4px 0 0 0; font-size: 0.9em;">ActiveBlue Family Law Case Management</p>
|
||||
</div>
|
||||
<div style="padding: 20px; background: #f8f9fa; border: 1px solid #dee2e6;">
|
||||
<p>You have an upcoming deadline on your family law case.</p>
|
||||
<table style="width: 100%; border-collapse: collapse; margin: 16px 0;">
|
||||
<tr>
|
||||
<td style="padding: 8px; font-weight: bold; width: 40%;">Case:</td>
|
||||
<td style="padding: 8px;">${object.case_id.name}</td>
|
||||
</tr>
|
||||
<tr style="background: #fff;">
|
||||
<td style="padding: 8px; font-weight: bold;">Deadline:</td>
|
||||
<td style="padding: 8px;">${object.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; font-weight: bold;">Due Date:</td>
|
||||
<td style="padding: 8px; color: #dc3545; font-weight: bold;">${object.due_date}</td>
|
||||
</tr>
|
||||
<tr style="background: #fff;">
|
||||
<td style="padding: 8px; font-weight: bold;">Days Remaining:</td>
|
||||
<td style="padding: 8px;">${object.days_until_due} days</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; font-weight: bold;">Statute:</td>
|
||||
<td style="padding: 8px;">${object.statute_reference or 'N/A'}</td>
|
||||
</tr>
|
||||
</table>
|
||||
% if object.notes:
|
||||
<div style="background: #fff3cd; border: 1px solid #ffc107; padding: 12px; border-radius: 4px; margin-top: 12px;">
|
||||
<strong>Notes:</strong> ${object.notes}
|
||||
</div>
|
||||
% endif
|
||||
<div style="background: #d4edda; border: 1px solid #28a745; padding: 12px; border-radius: 4px; margin-top: 16px;">
|
||||
<strong>Action Required:</strong> Please complete this deadline by the due date.
|
||||
Missed deadlines in family law cases can result in default judgments
|
||||
or dismissal of your case.
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: #e9ecef; border-radius: 0 0 6px 6px; font-size: 0.8em; color: #6c757d;">
|
||||
<strong>DISCLAIMER:</strong> This is an automated reminder from ActiveBlue Family Law
|
||||
case management software. This is NOT legal advice. If you have questions about
|
||||
this deadline, consult a licensed Florida family law attorney.<br/><br/>
|
||||
Legal Services of Greater Miami: (305) 576-0080 |
|
||||
Florida Courts Self-Help: <a href="https://www.flcourts.gov">flcourts.gov</a>
|
||||
</div>
|
||||
</div>
|
||||
]]></field>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
DEADLINE ALERT — OVERDUE (English)
|
||||
══════════════════════════════════════════════════════ -->
|
||||
<record id="mail_template_deadline_overdue" model="mail.template">
|
||||
<field name="name">FL: Deadline Alert — OVERDUE</field>
|
||||
<field name="model_id" ref="model_fl_deadline"/>
|
||||
<field name="subject">🔴 OVERDUE DEADLINE: ${object.name} — Case ${object.case_id.name}</field>
|
||||
<field name="body_html"><![CDATA[
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<div style="background: #dc3545; color: #fff; padding: 16px; border-radius: 6px 6px 0 0;">
|
||||
<h2 style="margin: 0;">🔴 OVERDUE DEADLINE — URGENT ACTION REQUIRED</h2>
|
||||
<p style="margin: 4px 0 0 0; font-size: 0.9em;">ActiveBlue Family Law Case Management</p>
|
||||
</div>
|
||||
<div style="padding: 20px; background: #f8d7da; border: 1px solid #f5c6cb;">
|
||||
<p style="font-size: 1.1em; font-weight: bold; color: #721c24;">
|
||||
A deadline on your family law case is PAST DUE. Immediate action may be required.
|
||||
</p>
|
||||
<table style="width: 100%; border-collapse: collapse; margin: 16px 0;">
|
||||
<tr>
|
||||
<td style="padding: 8px; font-weight: bold; width: 40%;">Case:</td>
|
||||
<td style="padding: 8px;">${object.case_id.name}</td>
|
||||
</tr>
|
||||
<tr style="background: #fff;">
|
||||
<td style="padding: 8px; font-weight: bold;">Deadline:</td>
|
||||
<td style="padding: 8px; color: #dc3545; font-weight: bold;">${object.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; font-weight: bold;">Was Due:</td>
|
||||
<td style="padding: 8px; color: #dc3545; font-weight: bold;">${object.due_date}</td>
|
||||
</tr>
|
||||
<tr style="background: #fff;">
|
||||
<td style="padding: 8px; font-weight: bold;">Statute:</td>
|
||||
<td style="padding: 8px;">${object.statute_reference or 'N/A'}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="background: #f8d7da; border: 2px solid #dc3545; padding: 16px; border-radius: 4px; margin-top: 12px;">
|
||||
<strong>⚠️ WARNING:</strong> Missing court deadlines can result in:
|
||||
<ul>
|
||||
<li>Default judgment entered against you</li>
|
||||
<li>Dismissal of your case</li>
|
||||
<li>Contempt of court findings</li>
|
||||
<li>Loss of your legal rights</li>
|
||||
</ul>
|
||||
Contact the court clerk immediately to understand your options.
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: #e9ecef; border-radius: 0 0 6px 6px; font-size: 0.8em; color: #6c757d;">
|
||||
<strong>DISCLAIMER:</strong> This is an automated alert. This is NOT legal advice.
|
||||
Consult a licensed Florida family law attorney immediately regarding missed deadlines.<br/><br/>
|
||||
<strong>Emergency Legal Help:</strong><br/>
|
||||
Legal Services of Greater Miami: <strong>(305) 576-0080</strong><br/>
|
||||
Florida Courts Self-Help: <a href="https://www.flcourts.gov">flcourts.gov</a><br/>
|
||||
Miami-Dade Law Library: <a href="https://www.law.miami.edu/library">law.miami.edu/library</a>
|
||||
</div>
|
||||
</div>
|
||||
]]></field>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
PORTAL WELCOME EMAIL (English)
|
||||
══════════════════════════════════════════════════════ -->
|
||||
<record id="mail_template_portal_welcome" model="mail.template">
|
||||
<field name="name">FL: Portal Welcome — New Case (EN)</field>
|
||||
<field name="model_id" ref="model_fl_case"/>
|
||||
<field name="subject">Welcome to Your Family Law Case Portal — Case ${object.name}</field>
|
||||
<field name="body_html"><![CDATA[
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<div style="background: #003366; color: #fff; padding: 20px; border-radius: 6px 6px 0 0;">
|
||||
<h2 style="margin: 0;">Welcome to Your Family Law Case Portal</h2>
|
||||
<p style="margin: 8px 0 0 0;">ActiveBlue Family Law — Miami-Dade 11th Circuit</p>
|
||||
</div>
|
||||
<div style="padding: 24px; background: #fff; border: 1px solid #dee2e6;">
|
||||
<p>Dear ${object.petitioner_id.name},</p>
|
||||
<p>
|
||||
Your family law case has been created in our system. You can now
|
||||
track your case, deadlines, and documents through the portal.
|
||||
</p>
|
||||
<div style="background: #d4edda; border: 1px solid #28a745; padding: 16px; border-radius: 6px; margin: 16px 0;">
|
||||
<strong>Your Case Reference:</strong>
|
||||
<span style="font-size: 1.3em; font-weight: bold; color: #003366; display: block; margin-top: 4px;">
|
||||
${object.name}
|
||||
</span>
|
||||
</div>
|
||||
<table style="width: 100%; border-collapse: collapse; margin: 16px 0;">
|
||||
<tr>
|
||||
<td style="padding: 8px; font-weight: bold; width: 40%; background: #f8f9fa;">Case Type:</td>
|
||||
<td style="padding: 8px;">${object.case_type}</td>
|
||||
</tr>
|
||||
% if object.filing_date:
|
||||
<tr>
|
||||
<td style="padding: 8px; font-weight: bold; background: #f8f9fa;">Filing Date:</td>
|
||||
<td style="padding: 8px;">${object.filing_date}</td>
|
||||
</tr>
|
||||
% endif
|
||||
</table>
|
||||
<h3>What You Can Do in the Portal:</h3>
|
||||
<ul>
|
||||
<li>📋 View your case status and upcoming deadlines</li>
|
||||
<li>📄 Download and generate court forms</li>
|
||||
<li>💰 Use the FL 61.30 child support calculator</li>
|
||||
<li>📅 Track the visual timeline of your case</li>
|
||||
<li>💬 Communicate through the secure case messaging system</li>
|
||||
</ul>
|
||||
<div style="background: #fff3cd; border: 1px solid #ffc107; padding: 16px; border-radius: 6px; margin: 16px 0;">
|
||||
<strong>⚠️ Important Reminder:</strong>
|
||||
This system provides information and tools to assist you, but
|
||||
<strong>does not provide legal advice</strong>. Family law matters
|
||||
are complex. We strongly recommend consulting with a licensed
|
||||
Florida family law attorney.
|
||||
</div>
|
||||
<h3>Free Legal Resources:</h3>
|
||||
<ul>
|
||||
<li>Legal Services of Greater Miami: <strong>(305) 576-0080</strong></li>
|
||||
<li>Florida Courts Self-Help: <a href="https://www.flcourts.gov">flcourts.gov</a></li>
|
||||
<li>Miami-Dade Bar Lawyer Referral: <strong>(305) 371-2444</strong></li>
|
||||
<li>FL Courts e-Filing Portal: <a href="https://www.myflcourtaccess.com">myflcourtaccess.com</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div style="padding: 12px; background: #e9ecef; border-radius: 0 0 6px 6px; font-size: 0.8em; color: #6c757d;">
|
||||
ActiveBlue Family Law Case Management — Miami-Dade County, Florida<br/>
|
||||
<strong>DISCLAIMER:</strong> This software is not a substitute for legal advice.
|
||||
Verify all information with the court before filing.
|
||||
</div>
|
||||
</div>
|
||||
]]></field>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
PORTAL WELCOME EMAIL (Spanish / Español)
|
||||
══════════════════════════════════════════════════════ -->
|
||||
<record id="mail_template_portal_welcome_es" model="mail.template">
|
||||
<field name="name">FL: Portal Welcome — New Case (ES)</field>
|
||||
<field name="model_id" ref="model_fl_case"/>
|
||||
<field name="subject">Bienvenido a su Portal de Caso de Derecho Familiar — Caso ${object.name}</field>
|
||||
<field name="body_html"><![CDATA[
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<div style="background: #003366; color: #fff; padding: 20px; border-radius: 6px 6px 0 0;">
|
||||
<h2 style="margin: 0;">Bienvenido a su Portal de Caso de Derecho Familiar</h2>
|
||||
<p style="margin: 8px 0 0 0;">ActiveBlue Family Law — Circuito 11 de Miami-Dade</p>
|
||||
</div>
|
||||
<div style="padding: 24px; background: #fff; border: 1px solid #dee2e6;">
|
||||
<p>Estimado/a ${object.petitioner_id.name},</p>
|
||||
<p>
|
||||
Su caso de derecho familiar ha sido creado en nuestro sistema. Ahora puede
|
||||
seguir su caso, plazos y documentos a través del portal.
|
||||
</p>
|
||||
<div style="background: #d4edda; border: 1px solid #28a745; padding: 16px; border-radius: 6px; margin: 16px 0;">
|
||||
<strong>Número de Referencia de su Caso:</strong>
|
||||
<span style="font-size: 1.3em; font-weight: bold; color: #003366; display: block; margin-top: 4px;">
|
||||
${object.name}
|
||||
</span>
|
||||
</div>
|
||||
<h3>¿Qué puede hacer en el Portal?</h3>
|
||||
<ul>
|
||||
<li>📋 Ver el estado de su caso y los próximos plazos</li>
|
||||
<li>📄 Descargar y generar formularios del tribunal</li>
|
||||
<li>💰 Usar la calculadora de manutención de hijos FL 61.30</li>
|
||||
<li>📅 Ver el cronograma visual de su caso</li>
|
||||
<li>💬 Comunicarse a través del sistema de mensajería segura</li>
|
||||
</ul>
|
||||
<div style="background: #fff3cd; border: 1px solid #ffc107; padding: 16px; border-radius: 6px; margin: 16px 0;">
|
||||
<strong>⚠️ Aviso Importante:</strong>
|
||||
Este sistema proporciona información y herramientas para ayudarle, pero
|
||||
<strong>no proporciona asesoramiento legal</strong>. Los asuntos de derecho familiar
|
||||
son complejos. Recomendamos consultar con un abogado certificado de Florida.
|
||||
</div>
|
||||
<h3>Recursos Legales Gratuitos:</h3>
|
||||
<ul>
|
||||
<li>Servicios Legales del Gran Miami: <strong>(305) 576-0080</strong></li>
|
||||
<li>Autoayuda de los Tribunales de Florida: <a href="https://www.flcourts.gov">flcourts.gov</a></li>
|
||||
<li>Colegio de Abogados de Miami-Dade — Referidos: <strong>(305) 371-2444</strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div style="padding: 12px; background: #e9ecef; border-radius: 0 0 6px 6px; font-size: 0.8em; color: #6c757d;">
|
||||
ActiveBlue Family Law — Condado de Miami-Dade, Florida<br/>
|
||||
<strong>AVISO LEGAL:</strong> Este software no reemplaza el asesoramiento legal.
|
||||
Verifique toda la información con el tribunal antes de presentar documentos.
|
||||
</div>
|
||||
</div>
|
||||
]]></field>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
DEFAULT JUDGMENT ALERT (English)
|
||||
══════════════════════════════════════════════════════ -->
|
||||
<record id="mail_template_default_judgment" model="mail.template">
|
||||
<field name="name">FL: Default Judgment Window Alert (FL 12.922)</field>
|
||||
<field name="model_id" ref="model_fl_case"/>
|
||||
<field name="subject">⚖️ ACTION REQUIRED: File Motion for Default — Case ${object.name}</field>
|
||||
<field name="body_html"><![CDATA[
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<div style="background: #856404; color: #fff; padding: 20px; border-radius: 6px 6px 0 0;">
|
||||
<h2 style="margin: 0;">⚖️ Default Judgment Window — Action Required</h2>
|
||||
<p style="margin: 8px 0 0 0;">FL 12.922 — Motion for Default</p>
|
||||
</div>
|
||||
<div style="padding: 24px; background: #fff3cd; border: 1px solid #ffc107;">
|
||||
<p>
|
||||
The respondent in your case did not file an answer within the required
|
||||
20-day window after service. You may now request a <strong>Clerk's Default</strong>.
|
||||
</p>
|
||||
<div style="background: #fff; border: 1px solid #dee2e6; padding: 16px; border-radius: 6px; margin: 16px 0;">
|
||||
<strong>Case:</strong> ${object.name}<br/>
|
||||
<strong>Petitioner:</strong> ${object.petitioner_id.name}<br/>
|
||||
<strong>Respondent:</strong> ${object.respondent_id.name if object.respondent_id else 'N/A'}
|
||||
</div>
|
||||
<h3>Steps to Request a Default:</h3>
|
||||
<ol>
|
||||
<li>Download <strong>FL-12.922</strong> (Motion for Default) from the Florida Courts website</li>
|
||||
<li>Complete the form with your case number from Miami-Dade Clerk</li>
|
||||
<li>File at Miami-Dade Clerk's Office OR via the e-Filing portal</li>
|
||||
<li>After the Clerk enters the default, file a <strong>Motion for Final Judgment</strong></li>
|
||||
<li>Request a final hearing date from the court</li>
|
||||
</ol>
|
||||
<div style="background: #f8d7da; border: 1px solid #f5c6cb; padding: 12px; border-radius: 4px;">
|
||||
<strong>⚠️ Note:</strong> A default does not automatically mean you win.
|
||||
You must still present evidence at a final hearing.
|
||||
The respondent may still contest the default by filing a motion to vacate.
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: #e9ecef; border-radius: 0 0 6px 6px; font-size: 0.8em; color: #6c757d;">
|
||||
<strong>DISCLAIMER:</strong> This is NOT legal advice. Consult a licensed
|
||||
Florida family law attorney for guidance on default proceedings.<br/>
|
||||
Legal Services of Greater Miami: <strong>(305) 576-0080</strong>
|
||||
</div>
|
||||
</div>
|
||||
]]></field>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,29 +1,31 @@
|
||||
from datetime import date, datetime, time
|
||||
|
||||
from odoo import api, fields, models
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
||||
class FlDeadline(models.Model):
|
||||
"""
|
||||
Phase 2 — Full implementation with calendar integration and cron alerts.
|
||||
Phase 1: Core fields and generate_deadlines_for_case stub.
|
||||
Phase 2 — Full implementation with calendar integration, default judgment
|
||||
workflow, and complete FL procedural deadline generation engine.
|
||||
"""
|
||||
_name = 'fl.deadline'
|
||||
_description = 'Case Deadline'
|
||||
_inherit = ['mail.thread']
|
||||
_order = 'due_date asc'
|
||||
_rec_name = 'name'
|
||||
|
||||
case_id = fields.Many2one(
|
||||
'fl.case', required=True, ondelete='cascade', index=True
|
||||
'fl.case', required=True, ondelete='cascade', index=True,
|
||||
string='Case'
|
||||
)
|
||||
name = fields.Char(string='Deadline', required=True)
|
||||
deadline_type = fields.Selection([
|
||||
('filing', 'Filing'),
|
||||
('service', 'Service of Process'),
|
||||
('service', 'Service of Process (Target)'),
|
||||
('service_max', 'Service Deadline — 120-day Maximum'),
|
||||
('response', 'Response / Answer'),
|
||||
('discovery_open', 'Discovery Opens'),
|
||||
('financial_disclosure', 'Financial Disclosure Exchange'),
|
||||
('mandatory_disclosure_cert', 'Mandatory Disclosure Certificate'),
|
||||
('deposition_notice', 'Deposition Notice Deadline'),
|
||||
('deposition', 'Deposition'),
|
||||
('discovery_cutoff', 'Discovery Cutoff'),
|
||||
@@ -32,49 +34,56 @@ class FlDeadline(models.Model):
|
||||
('hearing', 'Hearing'),
|
||||
('compliance', 'Compliance Deadline'),
|
||||
('emancipation', 'Emancipation'),
|
||||
('default_motion', 'Motion for Default'),
|
||||
('default_motion', 'Motion for Default — Watch Period'),
|
||||
('default_motion_file', 'File Motion for Default (FL 12.922)'),
|
||||
('custom', 'Custom'),
|
||||
], required=True)
|
||||
], required=True, string='Type')
|
||||
|
||||
statute_reference = fields.Char(
|
||||
string='Statute / Rule',
|
||||
help='e.g. FL 1.140 — 20 days to answer'
|
||||
help='e.g. FL 1.140 — 20 days to answer after service'
|
||||
)
|
||||
due_date = fields.Date(string='Due Date', required=True, tracking=True)
|
||||
anchor_date = fields.Date(
|
||||
string='Anchor Date',
|
||||
help='Date this deadline is calculated from'
|
||||
help='Date this deadline is calculated from (filing date, service date, etc.)'
|
||||
)
|
||||
offset_days = fields.Integer(
|
||||
string='Offset Days',
|
||||
help='Days from anchor date to due date'
|
||||
help='Number of days from anchor date to this deadline'
|
||||
)
|
||||
|
||||
completed = fields.Boolean(string='Completed', tracking=True)
|
||||
completed_date = fields.Date(string='Completion Date')
|
||||
waived = fields.Boolean(string='Waived')
|
||||
waived = fields.Boolean(string='Waived / Not Applicable')
|
||||
notes = fields.Text(string='Notes')
|
||||
|
||||
# ── Calendar Integration (Phase 2) ────────────────────────────────────
|
||||
# ── Calendar Integration ──────────────────────────────────────────────────
|
||||
calendar_event_id = fields.Many2one(
|
||||
'calendar.event', string='Calendar Event'
|
||||
'calendar.event', string='Calendar Event', ondelete='set null'
|
||||
)
|
||||
|
||||
# ── Alerts ────────────────────────────────────────────────────────────
|
||||
alert_7day_sent = fields.Boolean(default=False)
|
||||
alert_3day_sent = fields.Boolean(default=False)
|
||||
alert_1day_sent = fields.Boolean(default=False)
|
||||
overdue_alert_sent = fields.Boolean(default=False)
|
||||
# ── Alert Tracking ────────────────────────────────────────────────────────
|
||||
alert_7day_sent = fields.Boolean(default=False, string='7-day Alert Sent')
|
||||
alert_3day_sent = fields.Boolean(default=False, string='3-day Alert Sent')
|
||||
alert_1day_sent = fields.Boolean(default=False, string='1-day Alert Sent')
|
||||
overdue_alert_sent = fields.Boolean(default=False, string='Overdue Alert Sent')
|
||||
|
||||
# ── Computed Status ───────────────────────────────────────────────────────
|
||||
is_overdue = fields.Boolean(
|
||||
string='Overdue',
|
||||
compute='_compute_is_overdue', store=True
|
||||
compute='_compute_is_overdue',
|
||||
store=True
|
||||
)
|
||||
days_until_due = fields.Integer(
|
||||
string='Days Until Due',
|
||||
compute='_compute_days_until_due'
|
||||
)
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# COMPUTED FIELDS
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@api.depends('due_date', 'completed', 'waived')
|
||||
def _compute_is_overdue(self):
|
||||
today = fields.Date.today()
|
||||
@@ -95,45 +104,167 @@ class FlDeadline(models.Model):
|
||||
else:
|
||||
rec.days_until_due = 0
|
||||
|
||||
def action_mark_complete(self):
|
||||
self.completed = True
|
||||
self.completed_date = fields.Date.today()
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# CRUD — calendar event lifecycle
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# ── Generation Engine ─────────────────────────────────────────────────
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
records = super().create(vals_list)
|
||||
for rec in records:
|
||||
rec.with_context(_no_calendar_sync=True)._create_or_update_calendar_event()
|
||||
return records
|
||||
|
||||
def write(self, vals):
|
||||
result = super().write(vals)
|
||||
if not self.env.context.get('_no_calendar_sync'):
|
||||
sync_fields = {'due_date', 'name', 'completed', 'waived', 'deadline_type'}
|
||||
if sync_fields.intersection(vals.keys()):
|
||||
for rec in self:
|
||||
rec.with_context(_no_calendar_sync=True)._create_or_update_calendar_event()
|
||||
return result
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# CALENDAR INTEGRATION
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def _create_or_update_calendar_event(self):
|
||||
"""Create or update a calendar.event linked to this deadline."""
|
||||
if not self.due_date or self.waived:
|
||||
# Archive the calendar event if waived or no date
|
||||
if self.calendar_event_id:
|
||||
self.calendar_event_id.write({'active': False})
|
||||
self.write({'calendar_event_id': False})
|
||||
return
|
||||
|
||||
if self.completed:
|
||||
# Archive if completed
|
||||
if self.calendar_event_id:
|
||||
self.calendar_event_id.write({'active': False})
|
||||
return
|
||||
|
||||
# Build attendee list
|
||||
partners = self.env['res.partner']
|
||||
if self.case_id.petitioner_id:
|
||||
partners |= self.case_id.petitioner_id
|
||||
if self.env.user.partner_id:
|
||||
partners |= self.env.user.partner_id
|
||||
|
||||
# All-day event: start at 08:00, stop at 09:00
|
||||
start_dt = fields.Datetime.from_string(
|
||||
'{} 08:00:00'.format(self.due_date)
|
||||
)
|
||||
stop_dt = fields.Datetime.from_string(
|
||||
'{} 09:00:00'.format(self.due_date)
|
||||
)
|
||||
|
||||
type_label = dict(self._fields['deadline_type'].selection).get(
|
||||
self.deadline_type, ''
|
||||
)
|
||||
event_name = '[{}] {} ({})'.format(
|
||||
self.case_id.name, self.name, type_label
|
||||
)
|
||||
description = (
|
||||
'FL Case Deadline\n'
|
||||
'Case: {}\n'
|
||||
'Type: {}\n'
|
||||
'Statute: {}\n'
|
||||
'Status: Pending'
|
||||
).format(
|
||||
self.case_id.name,
|
||||
type_label,
|
||||
self.statute_reference or 'N/A',
|
||||
)
|
||||
|
||||
if self.calendar_event_id:
|
||||
self.calendar_event_id.write({
|
||||
'name': event_name,
|
||||
'start': start_dt,
|
||||
'stop': stop_dt,
|
||||
'description': description,
|
||||
'active': True,
|
||||
})
|
||||
else:
|
||||
event = self.env['calendar.event'].sudo().create({
|
||||
'name': event_name,
|
||||
'start': start_dt,
|
||||
'stop': stop_dt,
|
||||
'allday': True,
|
||||
'description': description,
|
||||
'partner_ids': [(6, 0, partners.ids)],
|
||||
'show_as': 'free',
|
||||
'privacy': 'confidential',
|
||||
})
|
||||
# Use sudo write to avoid recursion on calendar_event_id
|
||||
self.write({'calendar_event_id': event.id})
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# WORKFLOW ACTIONS
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def action_mark_complete(self):
|
||||
"""Mark deadline as completed and archive the calendar event."""
|
||||
self.write({
|
||||
'completed': True,
|
||||
'completed_date': fields.Date.today(),
|
||||
})
|
||||
if self.calendar_event_id:
|
||||
self.calendar_event_id.write({'active': False})
|
||||
|
||||
def action_mark_waived(self):
|
||||
"""Mark deadline as not applicable for this case."""
|
||||
self.write({'waived': True})
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# DEADLINE GENERATION ENGINE
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@api.model
|
||||
def generate_deadlines_for_case(self, case):
|
||||
"""
|
||||
Auto-generate procedural deadlines from filing_date.
|
||||
Phase 1: Core deadlines only.
|
||||
Phase 2: Full calendar events + alert setup.
|
||||
Auto-generate procedural deadlines anchored to filing_date.
|
||||
Called on case create when filing_date is set, and whenever
|
||||
filing_date changes.
|
||||
"""
|
||||
if not case.filing_date:
|
||||
return
|
||||
|
||||
# Avoid duplicates on re-run
|
||||
existing_types = case.deadline_ids.mapped('deadline_type')
|
||||
existing_types = set(case.deadline_ids.mapped('deadline_type'))
|
||||
|
||||
rules = [
|
||||
# ── Filing-date anchored deadlines ────────────────────────────────────
|
||||
filing_rules = [
|
||||
{
|
||||
'name': 'Serve Respondent (Target)',
|
||||
'name': 'Serve Respondent — Target Date (FL 1.070)',
|
||||
'deadline_type': 'service',
|
||||
'offset_days': 30,
|
||||
'anchor': case.filing_date,
|
||||
'statute_reference': 'FL 1.070 — 120-day maximum',
|
||||
'statute_reference': 'FL 1.070 — Aim to serve within 30 days of filing',
|
||||
},
|
||||
{
|
||||
'name': 'Serve Respondent — MAXIMUM Deadline (FL 1.070)',
|
||||
'deadline_type': 'service_max',
|
||||
'offset_days': 120,
|
||||
'anchor': case.filing_date,
|
||||
'statute_reference': (
|
||||
'FL 1.070 — Case may be dismissed if not served within 120 days'
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
# Parenting class from filing (petitioner side — FL 61.21)
|
||||
if case.has_minor_children:
|
||||
rules.append({
|
||||
'name': 'Parenting Class — Petitioner (FL 61.21)',
|
||||
filing_rules.append({
|
||||
'name': 'Parenting Class — Petitioner Must Complete (FL 61.21)',
|
||||
'deadline_type': 'parenting_class',
|
||||
'offset_days': 45,
|
||||
'anchor': case.filing_date,
|
||||
'statute_reference': 'FL 61.21',
|
||||
'statute_reference': (
|
||||
'FL 61.21 — Both parents must complete parenting class '
|
||||
'before final hearing when minor children involved'
|
||||
),
|
||||
})
|
||||
|
||||
for rule in rules:
|
||||
for rule in filing_rules:
|
||||
if rule['deadline_type'] in existing_types:
|
||||
continue
|
||||
due = rule['anchor'] + relativedelta(days=rule['offset_days'])
|
||||
@@ -150,47 +281,72 @@ class FlDeadline(models.Model):
|
||||
@api.model
|
||||
def recalculate_service_deadlines(self, case):
|
||||
"""
|
||||
Recalculate deadlines anchored to service_date when it is set.
|
||||
Called from fl_case.write when service_date changes.
|
||||
Create or update deadlines anchored to service_date.
|
||||
Called from fl_case.write when service_date is set or changed.
|
||||
All FL procedural deadlines run from the date of service.
|
||||
"""
|
||||
if not case.service_date:
|
||||
return
|
||||
|
||||
service_anchored = [
|
||||
service_rules = [
|
||||
# FL 1.140: Respondent has 20 days to answer after service
|
||||
{
|
||||
'name': 'Respondent Answer Deadline',
|
||||
'name': 'Respondent Answer Deadline (FL 1.140)',
|
||||
'deadline_type': 'response',
|
||||
'offset_days': 20,
|
||||
'statute_reference': 'FL 1.140 — 20 days to answer',
|
||||
'statute_reference': 'FL 1.140 — 20 days to serve answer after service',
|
||||
},
|
||||
# FL 12.285: Mandatory financial disclosure within 45 days of service
|
||||
{
|
||||
'name': 'Mandatory Financial Disclosure Exchange',
|
||||
'name': 'Mandatory Financial Disclosure Exchange (FL 12.285)',
|
||||
'deadline_type': 'financial_disclosure',
|
||||
'offset_days': 45,
|
||||
'statute_reference': 'FL 12.285 — 45 days',
|
||||
'statute_reference': (
|
||||
'FL 12.285 — Exchange mandatory disclosure documents within '
|
||||
'45 days of service: last 3 years tax returns, paystubs, '
|
||||
'bank statements, financial affidavit'
|
||||
),
|
||||
},
|
||||
# FL 12.932: Certificate of mandatory disclosure
|
||||
{
|
||||
'name': 'Discovery Opens (Case at Issue)',
|
||||
'name': 'File Certificate of Mandatory Disclosure (FL 12.932)',
|
||||
'deadline_type': 'mandatory_disclosure_cert',
|
||||
'offset_days': 45,
|
||||
'statute_reference': (
|
||||
'FL 12.932 — Certificate confirming disclosure documents exchanged'
|
||||
),
|
||||
},
|
||||
# FL 12.280: Discovery opens once case is at issue
|
||||
{
|
||||
'name': 'Discovery Opens — Case at Issue (FL 12.280)',
|
||||
'deadline_type': 'discovery_open',
|
||||
'offset_days': 20,
|
||||
'statute_reference': 'FL 12.280',
|
||||
'statute_reference': (
|
||||
'FL 12.280 — Discovery may commence after respondent answers '
|
||||
'(or after Day 20 if no answer filed)'
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
# Respondent parenting class runs from service date
|
||||
if case.has_minor_children:
|
||||
service_anchored.append({
|
||||
'name': 'Parenting Class — Respondent (FL 61.21)',
|
||||
service_rules.append({
|
||||
'name': 'Parenting Class — Respondent Must Complete (FL 61.21)',
|
||||
'deadline_type': 'parenting_class',
|
||||
'offset_days': 60,
|
||||
'statute_reference': 'FL 61.21',
|
||||
'statute_reference': (
|
||||
'FL 61.21 — Respondent must complete parenting class '
|
||||
'before final hearing'
|
||||
),
|
||||
})
|
||||
|
||||
existing = {d.deadline_type: d for d in case.deadline_ids}
|
||||
|
||||
for rule in service_anchored:
|
||||
for rule in service_rules:
|
||||
due = case.service_date + relativedelta(days=rule['offset_days'])
|
||||
dl_type = rule['deadline_type']
|
||||
if dl_type in existing:
|
||||
# Update existing deadline
|
||||
existing[dl_type].write({
|
||||
'due_date': due,
|
||||
'anchor_date': case.service_date,
|
||||
@@ -206,9 +362,18 @@ class FlDeadline(models.Model):
|
||||
'statute_reference': rule.get('statute_reference', ''),
|
||||
})
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# CRON: Daily Deadline Alerts
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def _cron_deadline_alerts(self):
|
||||
"""Run daily — send deadline alerts at 7, 3, 1 days and overdue."""
|
||||
"""
|
||||
Run daily via ir.cron.
|
||||
Posts deadline alerts to case chatter at 7, 3, 1 days before
|
||||
due date and once when a deadline becomes overdue.
|
||||
"""
|
||||
today = fields.Date.today()
|
||||
|
||||
upcoming = self.search([
|
||||
('completed', '=', False),
|
||||
('waived', '=', False),
|
||||
@@ -236,13 +401,133 @@ class FlDeadline(models.Model):
|
||||
dl._send_deadline_alert('OVERDUE')
|
||||
dl.overdue_alert_sent = True
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# CRON: Default Judgment Workflow (FL 12.922)
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def _cron_check_default_judgment(self):
|
||||
"""
|
||||
Run daily via ir.cron.
|
||||
FL 12.922 / FL 1.500: If respondent has not filed an answer by Day 20
|
||||
after service, trigger the default judgment workflow:
|
||||
1. Create 'File Motion for Default' deadline (5 days from today)
|
||||
2. Create project task for petitioner
|
||||
3. Post urgent alert to case chatter
|
||||
|
||||
Only triggers once per case (checks for existing default_motion_file
|
||||
deadline to avoid duplicates).
|
||||
"""
|
||||
today = fields.Date.today()
|
||||
|
||||
# Find ALL overdue response deadlines
|
||||
overdue_responses = self.search([
|
||||
('deadline_type', '=', 'response'),
|
||||
('completed', '=', False),
|
||||
('waived', '=', False),
|
||||
('due_date', '<', today),
|
||||
])
|
||||
|
||||
for dl in overdue_responses:
|
||||
case = dl.case_id
|
||||
|
||||
# If respondent has now answered, mark deadline complete
|
||||
if case.respondent_answered:
|
||||
dl.action_mark_complete()
|
||||
continue
|
||||
|
||||
# Check if we already created the default motion deadline
|
||||
existing_default = self.search([
|
||||
('case_id', '=', case.id),
|
||||
('deadline_type', '=', 'default_motion_file'),
|
||||
], limit=1)
|
||||
if existing_default:
|
||||
continue
|
||||
|
||||
# Create the "File Motion for Default" deadline (5 days from today)
|
||||
default_due = today + relativedelta(days=5)
|
||||
self.create({
|
||||
'case_id': case.id,
|
||||
'name': 'File Motion for Default — Respondent Did Not Answer (FL 12.922)',
|
||||
'deadline_type': 'default_motion_file',
|
||||
'due_date': default_due,
|
||||
'anchor_date': today,
|
||||
'offset_days': 5,
|
||||
'statute_reference': (
|
||||
'FL 12.922 / FL 1.500 — Motion for Default. '
|
||||
'Respondent failed to respond within 20 days of service.'
|
||||
),
|
||||
'notes': (
|
||||
'File Clerk\'s Default (FL-12.922) with Miami-Dade Clerk.\n'
|
||||
'After clerk enters default, file Motion for Final Judgment.\n'
|
||||
'Form available at: https://www.flcourts.gov/Resources-Services/'
|
||||
'Court-Improvement/Family-Courts/Family-Law-Self-Help-Information'
|
||||
),
|
||||
})
|
||||
|
||||
# Create a project task to remind the petitioner
|
||||
if case.project_id:
|
||||
self.env['project.task'].create({
|
||||
'name': 'FILE: Motion for Default (FL 12.922) — URGENT',
|
||||
'project_id': case.project_id.id,
|
||||
'description': (
|
||||
'Respondent did not file an answer by the Day 20 deadline '
|
||||
'({}). You may now file a Clerk\'s Default.\n\n'
|
||||
'Steps:\n'
|
||||
'1. Download FL-12.922 from Florida Courts website\n'
|
||||
'2. Complete the form with your case number\n'
|
||||
'3. File at Miami-Dade Clerk\'s office OR via e-Filing portal\n'
|
||||
'4. After clerk enters default, file Motion for Final Judgment\n\n'
|
||||
'Deadline to file: {}\n'
|
||||
'Statute: FL 12.922, FL 1.500'
|
||||
).format(dl.due_date, default_due),
|
||||
'date_deadline': default_due,
|
||||
})
|
||||
|
||||
# Post urgent chatter alert
|
||||
case.message_post(
|
||||
body=(
|
||||
'<b>⚖️ DEFAULT JUDGMENT WINDOW OPEN (FL 12.922)</b><br/>'
|
||||
'Respondent did not file an answer by the Day 20 deadline '
|
||||
'(<b>{}</b>).<br/><br/>'
|
||||
'<b>Action Required:</b> File a Clerk\'s Default (FL-12.922) with the '
|
||||
'Miami-Dade Clerk within <b>5 days (by {})</b>.<br/><br/>'
|
||||
'<b>Steps:</b><br/>'
|
||||
'1. Download FL-12.922 from Florida Courts website<br/>'
|
||||
'2. File at Miami-Dade Clerk\'s office or via e-Filing portal<br/>'
|
||||
'3. After default is entered, file Motion for Final Judgment<br/><br/>'
|
||||
'<i>Statute: FL 12.922 / FL 1.500</i>'
|
||||
).format(dl.due_date, default_due),
|
||||
subtype_xmlid='mail.mt_note',
|
||||
)
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# ALERT HELPER
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def _send_deadline_alert(self, timing):
|
||||
icon = '🔴' if timing == 'OVERDUE' else '⏰'
|
||||
"""Post a deadline alert message to the case chatter."""
|
||||
if timing == 'OVERDUE':
|
||||
icon = '🔴'
|
||||
urgency = 'OVERDUE'
|
||||
elif timing == '1 day':
|
||||
icon = '🚨'
|
||||
urgency = 'Due Tomorrow'
|
||||
else:
|
||||
icon = '⏰'
|
||||
urgency = 'Due in {}'.format(timing)
|
||||
|
||||
self.case_id.message_post(
|
||||
body=(
|
||||
f'{icon} <b>Deadline Alert — {timing}</b><br/>'
|
||||
f'<b>{self.name}</b> is due on <b>{self.due_date}</b>.<br/>'
|
||||
f'Statute: {self.statute_reference or "N/A"}'
|
||||
'{icon} <b>Deadline Alert — {urgency}</b><br/>'
|
||||
'<b>{name}</b><br/>'
|
||||
'Due: <b>{due_date}</b><br/>'
|
||||
'Statute: {statute}'
|
||||
).format(
|
||||
icon=icon,
|
||||
urgency=urgency,
|
||||
name=self.name,
|
||||
due_date=self.due_date,
|
||||
statute=self.statute_reference or 'N/A',
|
||||
),
|
||||
subtype_xmlid='mail.mt_note',
|
||||
)
|
||||
|
||||
@@ -1,28 +1,37 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
||||
class FlHearing(models.Model):
|
||||
"""
|
||||
Phase 2 — Full implementation.
|
||||
Phase 1: Stub with fields needed by fl_case computed fields.
|
||||
Phase 2 — Full implementation with calendar integration, workflow buttons,
|
||||
and pre-hearing checklist for Florida family law hearings.
|
||||
"""
|
||||
_name = 'fl.hearing'
|
||||
_description = 'Case Hearing'
|
||||
_inherit = ['mail.thread']
|
||||
_order = 'hearing_date asc'
|
||||
_rec_name = 'name'
|
||||
|
||||
case_id = fields.Many2one(
|
||||
'fl.case', required=True, ondelete='cascade', index=True
|
||||
'fl.case', required=True, ondelete='cascade', index=True,
|
||||
string='Case'
|
||||
)
|
||||
name = fields.Char(string='Hearing Description', required=True)
|
||||
hearing_date = fields.Datetime(
|
||||
string='Hearing Date / Time', tracking=True
|
||||
)
|
||||
duration_hours = fields.Float(
|
||||
string='Duration (hours)', default=1.0,
|
||||
help='Expected hearing duration. Default 1 hour.'
|
||||
)
|
||||
location = fields.Char(
|
||||
string='Location',
|
||||
default='Lawson E. Thomas Courthouse Center, 175 NW 1st Ave, Miami, FL 33128'
|
||||
)
|
||||
courtroom = fields.Char(string='Courtroom')
|
||||
courtroom = fields.Char(string='Courtroom / Floor')
|
||||
judge_id = fields.Many2one(
|
||||
'res.partner', string='Judge',
|
||||
related='case_id.judge_id', store=True
|
||||
@@ -35,13 +44,283 @@ class FlHearing(models.Model):
|
||||
('final', 'Final Hearing'),
|
||||
('contempt', 'Contempt Hearing'),
|
||||
('other', 'Other'),
|
||||
], string='Hearing Type', default='final')
|
||||
], string='Hearing Type', default='final', required=True, tracking=True)
|
||||
|
||||
state = fields.Selection([
|
||||
('scheduled', 'Scheduled'),
|
||||
('completed', 'Completed'),
|
||||
('cancelled', 'Cancelled'),
|
||||
('continued', 'Continued'),
|
||||
], string='Status', default='scheduled', tracking=True)
|
||||
notes = fields.Text(string='Notes')
|
||||
|
||||
notes = fields.Text(string='Pre-Hearing Notes / Preparation')
|
||||
outcome = fields.Text(string='Outcome / Result')
|
||||
order_entered = fields.Boolean(string='Order Entered')
|
||||
order_entered = fields.Boolean(string='Order Entered After Hearing')
|
||||
continued_date = fields.Datetime(string='New Date (if Continued)')
|
||||
|
||||
# ── Calendar Integration ──────────────────────────────────────────────────
|
||||
calendar_event_id = fields.Many2one(
|
||||
'calendar.event', string='Calendar Event', ondelete='set null'
|
||||
)
|
||||
|
||||
# ── Pre-Hearing Checklist ─────────────────────────────────────────────────
|
||||
parenting_class_warning = fields.Char(
|
||||
string='Parenting Class Status',
|
||||
compute='_compute_parenting_class_warning',
|
||||
help='FL 61.21 — Both parents must complete before final hearing'
|
||||
)
|
||||
discovery_cutoff_warning = fields.Char(
|
||||
string='Discovery Cutoff',
|
||||
compute='_compute_discovery_cutoff_warning',
|
||||
help='Discovery cutoff is 30 days before hearing date'
|
||||
)
|
||||
financial_disclosure_warning = fields.Char(
|
||||
string='Financial Disclosure Status',
|
||||
compute='_compute_financial_disclosure_warning',
|
||||
)
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# COMPUTED: Pre-Hearing Checks
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@api.depends(
|
||||
'case_id.petitioner_parenting_class_done',
|
||||
'case_id.respondent_parenting_class_done',
|
||||
'case_id.parenting_class_required',
|
||||
)
|
||||
def _compute_parenting_class_warning(self):
|
||||
for rec in self:
|
||||
case = rec.case_id
|
||||
if not case.parenting_class_required:
|
||||
rec.parenting_class_warning = '✅ Not required (no minor children)'
|
||||
elif (case.petitioner_parenting_class_done
|
||||
and case.respondent_parenting_class_done):
|
||||
rec.parenting_class_warning = '✅ Both parties completed (FL 61.21)'
|
||||
else:
|
||||
missing = []
|
||||
if not case.petitioner_parenting_class_done:
|
||||
missing.append('Petitioner')
|
||||
if not case.respondent_parenting_class_done:
|
||||
missing.append('Respondent')
|
||||
rec.parenting_class_warning = (
|
||||
'⚠️ FL 61.21: Parenting class NOT completed by: {}. '
|
||||
'Final hearing may not proceed without completion.'
|
||||
).format(', '.join(missing))
|
||||
|
||||
@api.depends('hearing_date')
|
||||
def _compute_discovery_cutoff_warning(self):
|
||||
today = fields.Date.today()
|
||||
for rec in self:
|
||||
if not rec.hearing_date:
|
||||
rec.discovery_cutoff_warning = '⚪ Set hearing date to see discovery cutoff'
|
||||
continue
|
||||
cutoff = rec.hearing_date.date() - relativedelta(days=30)
|
||||
if today > cutoff:
|
||||
rec.discovery_cutoff_warning = (
|
||||
'🔴 Discovery cutoff PASSED ({}) — 30 days before hearing. '
|
||||
'No new discovery requests may be served.'
|
||||
).format(cutoff)
|
||||
else:
|
||||
days_left = (cutoff - today).days
|
||||
rec.discovery_cutoff_warning = (
|
||||
'✅ Discovery cutoff: {} ({} days remaining)'
|
||||
).format(cutoff, days_left)
|
||||
|
||||
@api.depends('case_id.deadline_ids.completed', 'case_id.deadline_ids.deadline_type')
|
||||
def _compute_financial_disclosure_warning(self):
|
||||
for rec in self:
|
||||
disc_deadline = rec.case_id.deadline_ids.filtered(
|
||||
lambda d: d.deadline_type == 'financial_disclosure'
|
||||
)
|
||||
if not disc_deadline:
|
||||
rec.financial_disclosure_warning = '⚪ No financial disclosure deadline set'
|
||||
elif any(d.completed for d in disc_deadline):
|
||||
rec.financial_disclosure_warning = '✅ Financial disclosure exchanged (FL 12.285)'
|
||||
elif any(d.is_overdue for d in disc_deadline):
|
||||
rec.financial_disclosure_warning = (
|
||||
'🔴 OVERDUE: Financial disclosure not yet exchanged (FL 12.285)'
|
||||
)
|
||||
else:
|
||||
due = min(d.due_date for d in disc_deadline)
|
||||
rec.financial_disclosure_warning = (
|
||||
'⏳ Financial disclosure due: {} (FL 12.285)'
|
||||
).format(due)
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# CRUD — calendar event lifecycle
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
records = super().create(vals_list)
|
||||
for rec in records:
|
||||
if rec.hearing_date:
|
||||
rec.with_context(_no_hearing_sync=True)._create_or_update_calendar_event()
|
||||
rec._create_hearing_deadline()
|
||||
return records
|
||||
|
||||
def write(self, vals):
|
||||
result = super().write(vals)
|
||||
if not self.env.context.get('_no_hearing_sync'):
|
||||
sync_fields = {'hearing_date', 'name', 'state', 'duration_hours', 'location', 'courtroom'}
|
||||
if sync_fields.intersection(vals.keys()):
|
||||
for rec in self:
|
||||
rec.with_context(_no_hearing_sync=True)._create_or_update_calendar_event()
|
||||
return result
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# CALENDAR INTEGRATION
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def _create_or_update_calendar_event(self):
|
||||
"""Create or sync a calendar.event for this hearing."""
|
||||
if not self.hearing_date:
|
||||
return
|
||||
|
||||
if self.state in ('cancelled',):
|
||||
if self.calendar_event_id:
|
||||
self.calendar_event_id.write({'active': False})
|
||||
return
|
||||
|
||||
# Build attendee list
|
||||
partners = self.env['res.partner']
|
||||
if self.case_id.petitioner_id:
|
||||
partners |= self.case_id.petitioner_id
|
||||
if self.env.user.partner_id:
|
||||
partners |= self.env.user.partner_id
|
||||
|
||||
stop_dt = self.hearing_date + timedelta(hours=self.duration_hours or 1.0)
|
||||
|
||||
type_label = dict(self._fields['hearing_type'].selection).get(
|
||||
self.hearing_type, 'Hearing'
|
||||
)
|
||||
event_name = '[{}] {} — {}'.format(
|
||||
self.case_id.name, type_label, self.name
|
||||
)
|
||||
description = (
|
||||
'Florida Family Law Hearing\n'
|
||||
'Case: {}\n'
|
||||
'Type: {}\n'
|
||||
'Location: {}\n'
|
||||
'Courtroom: {}\n'
|
||||
'Judge: {}\n'
|
||||
'Status: {}'
|
||||
).format(
|
||||
self.case_id.name,
|
||||
type_label,
|
||||
self.location or 'TBD',
|
||||
self.courtroom or 'TBD',
|
||||
self.judge_id.name if self.judge_id else 'TBD',
|
||||
dict(self._fields['state'].selection).get(self.state, ''),
|
||||
)
|
||||
|
||||
if self.calendar_event_id:
|
||||
self.calendar_event_id.write({
|
||||
'name': event_name,
|
||||
'start': self.hearing_date,
|
||||
'stop': stop_dt,
|
||||
'description': description,
|
||||
'active': self.state != 'cancelled',
|
||||
'show_as': 'busy',
|
||||
})
|
||||
else:
|
||||
event = self.env['calendar.event'].sudo().create({
|
||||
'name': event_name,
|
||||
'start': self.hearing_date,
|
||||
'stop': stop_dt,
|
||||
'description': description,
|
||||
'partner_ids': [(6, 0, partners.ids)],
|
||||
'show_as': 'busy',
|
||||
'privacy': 'confidential',
|
||||
})
|
||||
self.write({'calendar_event_id': event.id})
|
||||
|
||||
def _create_hearing_deadline(self):
|
||||
"""
|
||||
Create an fl.deadline 'hearing' record for this hearing
|
||||
so it appears in the case deadline tracker.
|
||||
"""
|
||||
if not self.hearing_date:
|
||||
return
|
||||
existing = self.env['fl.deadline'].search([
|
||||
('case_id', '=', self.case_id.id),
|
||||
('deadline_type', '=', 'hearing'),
|
||||
], limit=1)
|
||||
if not existing:
|
||||
self.env['fl.deadline'].create({
|
||||
'case_id': self.case_id.id,
|
||||
'name': '{} Hearing — {}'.format(
|
||||
dict(self._fields['hearing_type'].selection).get(
|
||||
self.hearing_type, 'Hearing'
|
||||
),
|
||||
self.name,
|
||||
),
|
||||
'deadline_type': 'hearing',
|
||||
'due_date': self.hearing_date.date(),
|
||||
'statute_reference': 'Florida Rules of Civil Procedure',
|
||||
'notes': (
|
||||
'Location: {}\n'
|
||||
'Courtroom: {}'
|
||||
).format(self.location or '', self.courtroom or 'TBD'),
|
||||
})
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# WORKFLOW BUTTONS
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def action_mark_completed(self):
|
||||
"""Mark hearing as completed and archive calendar event."""
|
||||
self.write({'state': 'completed'})
|
||||
if self.calendar_event_id:
|
||||
self.calendar_event_id.write({'active': False})
|
||||
# Update linked hearing deadline
|
||||
self.env['fl.deadline'].search([
|
||||
('case_id', '=', self.case_id.id),
|
||||
('deadline_type', '=', 'hearing'),
|
||||
]).write({'completed': True, 'completed_date': fields.Date.today()})
|
||||
self.case_id.message_post(
|
||||
body=(
|
||||
'✅ <b>Hearing Completed</b><br/>'
|
||||
'{} — {}<br/>'
|
||||
'<i>Update the Outcome field with the judge\'s ruling.</i>'
|
||||
).format(
|
||||
dict(self._fields['hearing_type'].selection).get(self.hearing_type, ''),
|
||||
self.name,
|
||||
),
|
||||
subtype_xmlid='mail.mt_note',
|
||||
)
|
||||
|
||||
def action_cancel(self):
|
||||
"""Cancel this hearing and archive calendar event."""
|
||||
self.write({'state': 'cancelled'})
|
||||
if self.calendar_event_id:
|
||||
self.calendar_event_id.write({'active': False})
|
||||
self.case_id.message_post(
|
||||
body=(
|
||||
'❌ <b>Hearing Cancelled</b><br/>'
|
||||
'{} — {} has been cancelled.'
|
||||
).format(
|
||||
dict(self._fields['hearing_type'].selection).get(self.hearing_type, ''),
|
||||
self.name,
|
||||
),
|
||||
subtype_xmlid='mail.mt_note',
|
||||
)
|
||||
|
||||
def action_mark_continued(self):
|
||||
"""
|
||||
Mark hearing as continued (rescheduled by court).
|
||||
User must enter the new hearing date separately.
|
||||
"""
|
||||
self.write({'state': 'continued'})
|
||||
self.case_id.message_post(
|
||||
body=(
|
||||
'⏸️ <b>Hearing Continued</b><br/>'
|
||||
'{} — {} has been continued (rescheduled by the court).<br/>'
|
||||
'<b>Action required:</b> Contact the court for the new hearing date '
|
||||
'and create a new hearing record.'
|
||||
).format(
|
||||
dict(self._fields['hearing_type'].selection).get(self.hearing_type, ''),
|
||||
self.name,
|
||||
),
|
||||
subtype_xmlid='mail.mt_note',
|
||||
)
|
||||
|
||||
@@ -2,65 +2,198 @@
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
TREE VIEW
|
||||
══════════════════════════════════════════════════════ -->
|
||||
<record id="view_fl_deadline_tree" model="ir.ui.view">
|
||||
<field name="name">fl.deadline.tree</field>
|
||||
<field name="model">fl.deadline</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Deadlines"
|
||||
decoration-danger="is_overdue == True"
|
||||
decoration-warning="days_until_due <= 7 and days_until_due >= 0 and completed == False"
|
||||
decoration-muted="completed == True or waived == True">
|
||||
decoration-warning="days_until_due <= 7 and days_until_due >= 0 and not completed and not waived"
|
||||
decoration-success="completed == True"
|
||||
decoration-muted="waived == True">
|
||||
<field name="case_id"/>
|
||||
<field name="name"/>
|
||||
<field name="deadline_type"/>
|
||||
<field name="due_date"/>
|
||||
<field name="days_until_due"/>
|
||||
<field name="statute_reference"/>
|
||||
<field name="days_until_due" string="Days Left"
|
||||
attrs="{'invisible': [('completed', '=', True)]}"/>
|
||||
<field name="statute_reference" optional="show"/>
|
||||
<field name="completed"/>
|
||||
<field name="waived"/>
|
||||
<field name="is_overdue" readonly="1"/>
|
||||
<button name="action_mark_complete" string="✔ Complete"
|
||||
type="object" icon="fa-check"
|
||||
attrs="{'invisible': ['|', ('completed', '=', True), ('waived', '=', True)]}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
FORM VIEW
|
||||
══════════════════════════════════════════════════════ -->
|
||||
<record id="view_fl_deadline_form" model="ir.ui.view">
|
||||
<field name="name">fl.deadline.form</field>
|
||||
<field name="model">fl.deadline</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Deadline">
|
||||
<header>
|
||||
<button name="action_mark_complete" string="Mark Complete"
|
||||
type="object" class="oe_highlight"
|
||||
attrs="{'invisible': ['|', ('completed', '=', True), ('waived', '=', True)]}"/>
|
||||
<button name="action_mark_waived" string="Mark Waived / N/A"
|
||||
type="object"
|
||||
attrs="{'invisible': ['|', ('completed', '=', True), ('waived', '=', True)]}"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<!-- Overdue alert banner -->
|
||||
<div class="alert alert-danger" role="alert"
|
||||
attrs="{'invisible': [('is_overdue', '=', False)]}">
|
||||
<strong>🔴 OVERDUE</strong> — This deadline has passed.
|
||||
Missed court deadlines can result in default judgment
|
||||
or case dismissal. Take action immediately.
|
||||
</div>
|
||||
<!-- 7-day warning banner -->
|
||||
<div class="alert alert-warning" role="alert"
|
||||
attrs="{'invisible': ['|', ('is_overdue', '=', True), ('days_until_due', '>', 7), ('completed', '=', True)]}">
|
||||
<strong>⏰ Due in ${days_until_due} days</strong> —
|
||||
This deadline is coming up soon.
|
||||
</div>
|
||||
<!-- Completed banner -->
|
||||
<div class="alert alert-success" role="alert"
|
||||
attrs="{'invisible': [('completed', '=', False)]}">
|
||||
<strong>✅ Completed</strong> on
|
||||
<field name="completed_date" readonly="1" nolabel="1"/>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="case_id"/>
|
||||
<field name="name"/>
|
||||
<field name="deadline_type"/>
|
||||
<field name="due_date"/>
|
||||
<field name="statute_reference"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="due_date"/>
|
||||
<field name="anchor_date"/>
|
||||
<field name="offset_days"/>
|
||||
<field name="days_until_due" readonly="1"/>
|
||||
<field name="is_overdue" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Status">
|
||||
<group>
|
||||
<field name="completed"/>
|
||||
<field name="completed_date"
|
||||
attrs="{'invisible': [('completed', '=', False)]}"/>
|
||||
<field name="waived"/>
|
||||
attrs="{'invisible': [('completed', '=', False)], 'required': [('completed', '=', True)]}"/>
|
||||
</group>
|
||||
<field name="notes"/>
|
||||
<group>
|
||||
<field name="waived"/>
|
||||
<field name="calendar_event_id" readonly="1"
|
||||
attrs="{'invisible': [('calendar_event_id', '=', False)]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Alerts Sent" groups="activeblue_familylaw.group_admin">
|
||||
<field name="alert_7day_sent" readonly="1"/>
|
||||
<field name="alert_3day_sent" readonly="1"/>
|
||||
<field name="alert_1day_sent" readonly="1"/>
|
||||
<field name="overdue_alert_sent" readonly="1"/>
|
||||
</group>
|
||||
<field name="notes" placeholder="Notes, instructions, or action steps for this deadline..."/>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
CALENDAR VIEW
|
||||
══════════════════════════════════════════════════════ -->
|
||||
<record id="view_fl_deadline_calendar" model="ir.ui.view">
|
||||
<field name="name">fl.deadline.calendar</field>
|
||||
<field name="model">fl.deadline</field>
|
||||
<field name="arch" type="xml">
|
||||
<calendar string="Deadline Calendar"
|
||||
date_start="due_date"
|
||||
color="deadline_type"
|
||||
mode="month"
|
||||
quick_create="False">
|
||||
<field name="name"/>
|
||||
<field name="case_id"/>
|
||||
<field name="deadline_type"/>
|
||||
<field name="is_overdue"/>
|
||||
</calendar>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
SEARCH VIEW
|
||||
══════════════════════════════════════════════════════ -->
|
||||
<record id="view_fl_deadline_search" model="ir.ui.view">
|
||||
<field name="name">fl.deadline.search</field>
|
||||
<field name="model">fl.deadline</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Deadlines">
|
||||
<field name="name" string="Deadline"/>
|
||||
<field name="case_id" string="Case"/>
|
||||
<field name="deadline_type"/>
|
||||
<!-- Quick filters -->
|
||||
<filter string="Overdue" name="filter_overdue"
|
||||
domain="[('is_overdue', '=', True)]"
|
||||
help="Show only past-due deadlines"/>
|
||||
<filter string="Due This Week" name="filter_this_week"
|
||||
domain="[('due_date', '<=', (context_today() + relativedelta(days=7)).strftime('%Y-%m-%d')), ('due_date', '>=', context_today().strftime('%Y-%m-%d')), ('completed', '=', False)]"/>
|
||||
<filter string="Pending" name="filter_pending"
|
||||
domain="[('completed', '=', False), ('waived', '=', False)]"/>
|
||||
<filter string="Completed" name="filter_completed"
|
||||
domain="[('completed', '=', True)]"/>
|
||||
<separator/>
|
||||
<filter string="Service" name="type_service"
|
||||
domain="[('deadline_type', 'in', ['service', 'service_max'])]"/>
|
||||
<filter string="Responses" name="type_response"
|
||||
domain="[('deadline_type', '=', 'response')]"/>
|
||||
<filter string="Financial Disclosure" name="type_disclosure"
|
||||
domain="[('deadline_type', '=', 'financial_disclosure')]"/>
|
||||
<filter string="Hearings" name="type_hearing"
|
||||
domain="[('deadline_type', '=', 'hearing')]"/>
|
||||
<filter string="Default Motion" name="type_default"
|
||||
domain="[('deadline_type', 'in', ['default_motion', 'default_motion_file'])]"/>
|
||||
<!-- Group By -->
|
||||
<separator/>
|
||||
<group string="Group By">
|
||||
<filter string="Case" name="group_case"
|
||||
context="{'group_by': 'case_id'}"/>
|
||||
<filter string="Type" name="group_type"
|
||||
context="{'group_by': 'deadline_type'}"/>
|
||||
<filter string="Due Month" name="group_month"
|
||||
context="{'group_by': 'due_date:month'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
ACTIONS
|
||||
══════════════════════════════════════════════════════ -->
|
||||
<record id="action_fl_deadline_list" model="ir.actions.act_window">
|
||||
<field name="name">Case Deadlines</field>
|
||||
<field name="res_model">fl.deadline</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_mode">tree,calendar,form</field>
|
||||
<field name="search_view_id" ref="view_fl_deadline_search"/>
|
||||
<field name="domain">[('completed', '=', False), ('waived', '=', False)]</field>
|
||||
<field name="context">{'search_default_filter_pending': 1, 'search_default_group_case': 1}</field>
|
||||
</record>
|
||||
|
||||
<!-- All deadlines including completed (for admin) -->
|
||||
<record id="action_fl_deadline_all" model="ir.actions.act_window">
|
||||
<field name="name">All Deadlines</field>
|
||||
<field name="res_model">fl.deadline</field>
|
||||
<field name="view_mode">tree,calendar,form</field>
|
||||
<field name="search_view_id" ref="view_fl_deadline_search"/>
|
||||
<field name="context">{'search_default_group_case': 1}</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -2,59 +2,199 @@
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
TREE VIEW
|
||||
══════════════════════════════════════════════════════ -->
|
||||
<record id="view_fl_hearing_tree" model="ir.ui.view">
|
||||
<field name="name">fl.hearing.tree</field>
|
||||
<field name="model">fl.hearing</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Hearings">
|
||||
<tree string="Hearings"
|
||||
decoration-success="state == 'completed'"
|
||||
decoration-danger="state == 'cancelled'"
|
||||
decoration-warning="state == 'continued'"
|
||||
decoration-info="state == 'scheduled'">
|
||||
<field name="case_id"/>
|
||||
<field name="name"/>
|
||||
<field name="hearing_type"/>
|
||||
<field name="hearing_date"/>
|
||||
<field name="location"/>
|
||||
<field name="courtroom"/>
|
||||
<field name="state"/>
|
||||
<field name="order_entered"/>
|
||||
<field name="location" optional="show"/>
|
||||
<field name="courtroom" optional="show"/>
|
||||
<field name="state" widget="badge"
|
||||
decoration-success="state == 'completed'"
|
||||
decoration-danger="state == 'cancelled'"
|
||||
decoration-warning="state == 'continued'"
|
||||
decoration-info="state == 'scheduled'"/>
|
||||
<field name="order_entered" optional="show"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
FORM VIEW
|
||||
══════════════════════════════════════════════════════ -->
|
||||
<record id="view_fl_hearing_form" model="ir.ui.view">
|
||||
<field name="name">fl.hearing.form</field>
|
||||
<field name="model">fl.hearing</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Hearing">
|
||||
<header>
|
||||
<field name="state" widget="statusbar"/>
|
||||
<button name="action_mark_completed" string="Mark Completed"
|
||||
type="object" class="oe_highlight"
|
||||
attrs="{'invisible': [('state', '!=', 'scheduled')]}"/>
|
||||
<button name="action_mark_continued" string="Mark Continued"
|
||||
type="object"
|
||||
attrs="{'invisible': [('state', '!=', 'scheduled')]}"/>
|
||||
<button name="action_cancel" string="Cancel Hearing"
|
||||
type="object" confirm="Cancel this hearing? This cannot be undone."
|
||||
attrs="{'invisible': [('state', 'not in', ['scheduled', 'continued'])]}"/>
|
||||
<field name="state" widget="statusbar"
|
||||
statusbar_visible="scheduled,completed,cancelled,continued"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<!-- Outcome section — shown after completion -->
|
||||
<div class="alert alert-success" role="alert"
|
||||
attrs="{'invisible': [('state', '!=', 'completed')]}">
|
||||
<strong>✅ Hearing Completed</strong>
|
||||
<span attrs="{'invisible': [('outcome', '=', False)]}">
|
||||
— <field name="outcome" readonly="1" nolabel="1"/>
|
||||
</span>
|
||||
<span attrs="{'invisible': [('outcome', '!=', False)]}">
|
||||
— Update the Outcome field below with the judge's ruling.
|
||||
</span>
|
||||
</div>
|
||||
<!-- Continued notice -->
|
||||
<div class="alert alert-warning" role="alert"
|
||||
attrs="{'invisible': [('state', '!=', 'continued')]}">
|
||||
<strong>⏸️ Hearing Continued</strong> — Contact the court for
|
||||
the rescheduled date and create a new hearing record.
|
||||
</div>
|
||||
<!-- Cancelled notice -->
|
||||
<div class="alert alert-danger" role="alert"
|
||||
attrs="{'invisible': [('state', '!=', 'cancelled')]}">
|
||||
<strong>❌ Hearing Cancelled</strong>
|
||||
</div>
|
||||
|
||||
<group>
|
||||
<group>
|
||||
<group string="Hearing Details">
|
||||
<field name="case_id"/>
|
||||
<field name="name"/>
|
||||
<field name="hearing_type"/>
|
||||
<field name="hearing_date"/>
|
||||
<field name="duration_hours"/>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Location">
|
||||
<field name="location"/>
|
||||
<field name="courtroom"/>
|
||||
<field name="judge_id"/>
|
||||
<field name="calendar_event_id" readonly="1"
|
||||
attrs="{'invisible': [('calendar_event_id', '=', False)]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Outcome">
|
||||
|
||||
<!-- Pre-Hearing Checklist -->
|
||||
<group string="Pre-Hearing Checklist (FL Requirements)">
|
||||
<div class="o_field_widget">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<strong>Parenting Class (FL 61.21):</strong><br/>
|
||||
<field name="parenting_class_warning" readonly="1" nolabel="1"/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<strong>Discovery Cutoff:</strong><br/>
|
||||
<field name="discovery_cutoff_warning" readonly="1" nolabel="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<div class="col-md-6">
|
||||
<strong>Financial Disclosure (FL 12.285):</strong><br/>
|
||||
<field name="financial_disclosure_warning" readonly="1" nolabel="1"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</group>
|
||||
|
||||
<!-- Outcome (visible after hearing) -->
|
||||
<group string="Outcome"
|
||||
attrs="{'invisible': [('state', 'not in', ['completed', 'continued'])]}">
|
||||
<field name="order_entered"/>
|
||||
<field name="outcome"/>
|
||||
<field name="outcome" placeholder="Describe the judge's ruling, any orders entered, next steps..."/>
|
||||
</group>
|
||||
<field name="notes"/>
|
||||
|
||||
<field name="notes"
|
||||
placeholder="Pre-hearing preparation notes: exhibits to bring, arguments to make, questions for the judge..."/>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
CALENDAR VIEW
|
||||
══════════════════════════════════════════════════════ -->
|
||||
<record id="view_fl_hearing_calendar" model="ir.ui.view">
|
||||
<field name="name">fl.hearing.calendar</field>
|
||||
<field name="model">fl.hearing</field>
|
||||
<field name="arch" type="xml">
|
||||
<calendar string="Hearing Calendar"
|
||||
date_start="hearing_date"
|
||||
date_stop="hearing_date"
|
||||
color="hearing_type"
|
||||
mode="month"
|
||||
quick_create="False">
|
||||
<field name="name"/>
|
||||
<field name="case_id"/>
|
||||
<field name="hearing_type"/>
|
||||
<field name="state"/>
|
||||
<field name="location"/>
|
||||
</calendar>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
SEARCH VIEW
|
||||
══════════════════════════════════════════════════════ -->
|
||||
<record id="view_fl_hearing_search" model="ir.ui.view">
|
||||
<field name="name">fl.hearing.search</field>
|
||||
<field name="model">fl.hearing</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Hearings">
|
||||
<field name="name"/>
|
||||
<field name="case_id"/>
|
||||
<field name="hearing_type"/>
|
||||
<filter string="Scheduled" name="filter_scheduled"
|
||||
domain="[('state', '=', 'scheduled')]"/>
|
||||
<filter string="Upcoming (30 days)" name="filter_upcoming"
|
||||
domain="[('hearing_date', '>=', context_today().strftime('%Y-%m-%d')), ('hearing_date', '<=', (context_today() + relativedelta(days=30)).strftime('%Y-%m-%d')), ('state', '=', 'scheduled')]"/>
|
||||
<filter string="Completed" name="filter_completed"
|
||||
domain="[('state', '=', 'completed')]"/>
|
||||
<filter string="Final Hearings" name="type_final"
|
||||
domain="[('hearing_type', '=', 'final')]"/>
|
||||
<group string="Group By">
|
||||
<filter string="Case" name="group_case"
|
||||
context="{'group_by': 'case_id'}"/>
|
||||
<filter string="Type" name="group_type"
|
||||
context="{'group_by': 'hearing_type'}"/>
|
||||
<filter string="Status" name="group_state"
|
||||
context="{'group_by': 'state'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
ACTIONS
|
||||
══════════════════════════════════════════════════════ -->
|
||||
<record id="action_fl_hearing_list" model="ir.actions.act_window">
|
||||
<field name="name">Hearings</field>
|
||||
<field name="res_model">fl.hearing</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_mode">tree,calendar,form</field>
|
||||
<field name="search_view_id" ref="view_fl_hearing_search"/>
|
||||
<field name="domain">[('state', '=', 'scheduled')]</field>
|
||||
<field name="context">{'search_default_filter_scheduled': 1}</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
||||
Reference in New Issue
Block a user