Phase 3: Full Discovery + Deposition workflow

fl_deposition.py — Full implementation:
- Calendar event sync for all scheduled depositions
- Notice validation: FL 1.310(b) 10-day minimum; days_notice + notice_valid computed
- Duces tecum document list field with production instructions
- Workflow buttons: Mark Noticed, Confirm, Completed, No-Show, Reschedule, Cancel
- action_no_show: auto-creates Motion to Compel deadline (FL 1.380, 20 days),
  project task with step-by-step instructions, urgent chatter alert
- Court reporter field, transcript tracking, key findings summary

fl_discovery.py — Full implementation:
- action_mark_served: creates fl.deadline for 30-day response window
- action_flag_deficient: creates deficiency notice deadline (good-faith prerequisite)
- action_file_motion_to_compel: FL 1.380 deadline + project task with instructions
- admissions_deemed computed: FL 1.370 auto-deemed-admitted after 30 days (critical)
- _cron_discovery_overdue_alerts: daily check — overdue responses + deemed admissions
  with urgent chatter alerts distinguishing regular overdue vs. deemed-admitted
- sanctions_requested field for FL 1.380(a)(4) expense awards

Enhanced views:
- fl_deposition_views: calendar view, notice validation banners, no-show alert,
  duces tecum section, workflow status bar, results section
- fl_discovery_views: FL 1.370 deemed-admitted critical red banner,
  overdue response warning, subpoena info section, Motion to Compel section,
  tree with inline action buttons, full search with filter presets

ir.cron: added daily discovery overdue alert job to fl_deadline_rules.xml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Carlos Garcia
2026-05-04 23:20:40 -04:00
parent 7c865c8c22
commit fa0905ddbb
5 changed files with 1136 additions and 67 deletions

View File

@@ -45,5 +45,18 @@
<field name="user_id" ref="base.user_root"/>
</record>
<!-- Daily: Check for overdue discovery responses and deemed admissions (FL 1.370) -->
<record id="cron_fl_discovery_overdue" model="ir.cron">
<field name="name">FL Family Law: Discovery Overdue Alerts (FL 1.370 / 1.380)</field>
<field name="model_id" ref="model_fl_discovery"/>
<field name="state">code</field>
<field name="code">model._cron_discovery_overdue_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>

View File

@@ -1,37 +1,86 @@
from datetime import timedelta
from odoo import api, fields, models
from dateutil.relativedelta import relativedelta
class FlDeposition(models.Model):
"""
Phase 3 — Full implementation with notice validation, duces tecum, no-show workflow.
Phase 1: Stub with core fields.
Phase 3 — Full implementation.
FL 1.310: Notice of Taking Deposition
- Minimum 10 days notice required (FL 1.310(b))
- Maximum 7 hours per deponent per day (FL 1.310(d))
- Duces tecum: document production requirement
- No-show workflow: auto-trigger Motion to Compel (FL 1.380)
"""
_name = 'fl.deposition'
_description = 'Deposition Record'
_inherit = ['mail.thread']
_order = 'scheduled_date asc'
_rec_name = 'deponent_id'
case_id = fields.Many2one(
'fl.case', required=True, ondelete='cascade', index=True
'fl.case', required=True, ondelete='cascade', index=True,
string='Case'
)
deponent_id = fields.Many2one(
'res.partner', string='Deponent', required=True
)
deponent_type = fields.Selection([
('opposing_party', 'Opposing Party'),
('employer', 'Employer'),
('employer', "Deponent's Employer"),
('accountant_cpa', 'Accountant / CPA'),
('business_partner', 'Business Partner'),
('vocational_expert', 'Vocational Expert'),
('witness', 'Witness'),
('other', 'Other'),
], string='Deponent Type', required=True)
# ── Notice ────────────────────────────────────────────────────────────────
notice_date = fields.Date(
string='Notice Served Date',
help='FL 1.310(b): Minimum 10 days notice required before deposition'
help='Date the Notice of Taking Deposition was served on the deponent. '
'FL 1.310(b): Minimum 10 days notice required.'
)
scheduled_date = fields.Datetime(string='Deposition Date / Time')
location = fields.Char(string='Location / Zoom Link')
days_notice = fields.Integer(
string='Days of Notice',
compute='_compute_notice_info',
help='Number of days between notice date and scheduled deposition'
)
notice_valid = fields.Boolean(
string='Notice Valid (≥10 days)',
compute='_compute_notice_info',
help='FL 1.310(b): At least 10 days notice required'
)
notice_warning = fields.Char(
string='Notice Warning',
compute='_compute_notice_info'
)
# ── Schedule ──────────────────────────────────────────────────────────────
scheduled_date = fields.Datetime(
string='Deposition Date / Time', tracking=True
)
location = fields.Char(
string='Location / Zoom Link',
help='Physical address or Zoom link. Include court reporter contact info.'
)
court_reporter = fields.Char(
string='Court Reporter',
help='Court reporter name and contact information'
)
max_duration_hours = fields.Float(
string='Max Duration (hours)',
default=7.0,
help='FL 1.310(d): Maximum 7 hours per deponent per day. '
'Parties may agree to extend.'
)
days_until_deposition = fields.Integer(
string='Days Until Deposition',
compute='_compute_days_until_deposition'
)
# ── State ─────────────────────────────────────────────────────────────────
state = fields.Selection([
('draft', 'Drafting Notice'),
('noticed', 'Notice Served'),
@@ -41,15 +90,305 @@ class FlDeposition(models.Model):
('cancelled', 'Cancelled'),
('rescheduled', 'Rescheduled'),
], string='Status', default='draft', tracking=True)
# ── Duces Tecum ───────────────────────────────────────────────────────────
duces_tecum = fields.Boolean(
string='Duces Tecum (Document Production)',
help='Deponent is required to bring documents'
string='Duces Tecum (Bring Documents)',
help='Require deponent to produce documents at deposition'
)
max_duration_hours = fields.Float(
default=7.0,
help='FL 1.310(d): Maximum 7 hours per deponent per day'
duces_tecum_items = fields.Text(
string='Documents Required (Duces Tecum)',
help=(
'List documents deponent must bring. Examples:\n'
'- Last 3 years federal tax returns\n'
'- Last 6 months paystubs / payroll records\n'
'- Bank statements (all accounts, last 12 months)\n'
'- Business financial statements (if self-employed)\n'
'- Corporate tax returns (if business owner)\n'
'- Quickbooks / accounting records'
)
)
# ── Calendar ──────────────────────────────────────────────────────────────
calendar_event_id = fields.Many2one(
'calendar.event', string='Calendar Event', ondelete='set null'
)
# ── Results ───────────────────────────────────────────────────────────────
income_verified = fields.Boolean(string='Income Figures Verified')
income_verified_amount = fields.Float(string='Verified Income Amount ($)')
key_findings = fields.Text(string='Key Findings')
income_verified_amount = fields.Float(
string='Verified Monthly Income ($)',
help='Net monthly income as established at deposition'
)
key_findings = fields.Text(
string='Key Findings / Testimony Summary',
help='Summarize key admissions, income figures, contradictions, '
'and relevant testimony obtained'
)
transcript_received = fields.Boolean(string='Transcript Received')
transcript_notes = fields.Text(string='Transcript Notes')
# ── No-Show / Compel ──────────────────────────────────────────────────────
motion_to_compel_filed = fields.Boolean(
string='Motion to Compel Filed (FL 1.380)'
)
motion_to_compel_date = fields.Date(string='Motion to Compel Date')
sanctions_requested = fields.Boolean(string='Sanctions Requested')
notes = fields.Text(string='Notes')
# ══════════════════════════════════════════════════════════════════════════
# COMPUTED FIELDS
# ══════════════════════════════════════════════════════════════════════════
@api.depends('notice_date', 'scheduled_date')
def _compute_notice_info(self):
for rec in self:
if rec.notice_date and rec.scheduled_date:
days = (rec.scheduled_date.date() - rec.notice_date).days
rec.days_notice = days
rec.notice_valid = days >= 10
if days < 10:
rec.notice_warning = (
'⚠️ FL 1.310(b): Only {} days notice. '
'Minimum 10 days required. Deponent may object.'.format(days)
)
else:
rec.notice_warning = (
'✅ FL 1.310(b): {} days notice — requirement met.'.format(days)
)
else:
rec.days_notice = 0
rec.notice_valid = False
rec.notice_warning = '⚪ Set notice date and scheduled date to validate'
@api.depends('scheduled_date')
def _compute_days_until_deposition(self):
today = fields.Date.today()
for rec in self:
if rec.scheduled_date:
rec.days_until_deposition = (rec.scheduled_date.date() - today).days
else:
rec.days_until_deposition = 0
# ══════════════════════════════════════════════════════════════════════════
# CRUD — calendar event lifecycle
# ══════════════════════════════════════════════════════════════════════════
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
for rec in records:
if rec.scheduled_date:
rec.with_context(_no_dep_sync=True)._create_or_update_calendar_event()
return records
def write(self, vals):
result = super().write(vals)
if not self.env.context.get('_no_dep_sync'):
sync_fields = {'scheduled_date', 'state', 'location', 'deponent_id'}
if sync_fields.intersection(vals.keys()):
for rec in self:
rec.with_context(_no_dep_sync=True)._create_or_update_calendar_event()
return result
# ══════════════════════════════════════════════════════════════════════════
# CALENDAR INTEGRATION
# ══════════════════════════════════════════════════════════════════════════
def _create_or_update_calendar_event(self):
"""Create or update a calendar.event for this deposition."""
if not self.scheduled_date or self.state in ('cancelled',):
if self.calendar_event_id:
self.calendar_event_id.write({'active': False})
return
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
duration = min(self.max_duration_hours or 7.0, 7.0)
stop_dt = self.scheduled_date + timedelta(hours=duration)
type_label = dict(self._fields['deponent_type'].selection).get(
self.deponent_type, 'Deponent'
)
event_name = '[{}] Deposition — {} ({})'.format(
self.case_id.name,
self.deponent_id.name,
type_label,
)
description = (
'FL 1.310 Deposition\n'
'Case: {}\n'
'Deponent: {} ({})\n'
'Location: {}\n'
'Court Reporter: {}\n'
'Max Duration: {} hours\n'
'Duces Tecum: {}\n'
'Status: {}'
).format(
self.case_id.name,
self.deponent_id.name,
type_label,
self.location or 'TBD',
self.court_reporter or 'TBD',
duration,
'YES — document production required' if self.duces_tecum else 'No',
dict(self._fields['state'].selection).get(self.state, ''),
)
if self.calendar_event_id:
self.calendar_event_id.write({
'name': event_name,
'start': self.scheduled_date,
'stop': stop_dt,
'description': description,
'active': self.state not in ('cancelled',),
'show_as': 'busy',
})
else:
event = self.env['calendar.event'].sudo().create({
'name': event_name,
'start': self.scheduled_date,
'stop': stop_dt,
'description': description,
'partner_ids': [(6, 0, partners.ids)],
'show_as': 'busy',
'privacy': 'confidential',
})
self.write({'calendar_event_id': event.id})
# ══════════════════════════════════════════════════════════════════════════
# WORKFLOW ACTIONS
# ══════════════════════════════════════════════════════════════════════════
def action_mark_noticed(self):
"""Notice of Taking Deposition has been served on deponent."""
if not self.notice_date:
self.notice_date = fields.Date.today()
self.write({'state': 'noticed'})
self.case_id.message_post(
body=(
'📋 <b>Deposition Notice Served</b><br/>'
'Deponent: {} ({})<br/>'
'Notice date: {}<br/>'
'Scheduled: {}<br/>'
'Notice valid: {} (FL 1.310(b): 10 days required)'
).format(
self.deponent_id.name,
dict(self._fields['deponent_type'].selection).get(self.deponent_type, ''),
self.notice_date,
self.scheduled_date,
'✅ Yes' if self.notice_valid else '⚠️ INSUFFICIENT NOTICE',
),
subtype_xmlid='mail.mt_note',
)
def action_confirm(self):
"""Deponent and court reporter confirmed."""
self.write({'state': 'confirmed'})
def action_mark_completed(self):
"""Deposition completed successfully."""
self.write({'state': 'completed'})
if self.calendar_event_id:
self.calendar_event_id.write({'active': False})
self.case_id.message_post(
body=(
'✅ <b>Deposition Completed</b><br/>'
'Deponent: {} ({})<br/>'
'Income verified: {} {}<br/>'
'<i>Update Key Findings with testimony summary.</i>'
).format(
self.deponent_id.name,
dict(self._fields['deponent_type'].selection).get(self.deponent_type, ''),
'✅ ${}' .format(self.income_verified_amount) if self.income_verified else '❌ Not yet',
'/month' if self.income_verified else '',
),
subtype_xmlid='mail.mt_note',
)
def action_no_show(self):
"""
Deponent did not appear at deposition.
FL 1.380: Motion to Compel / Motion for Sanctions.
"""
self.write({'state': 'no_show'})
# Create Motion to Compel deadline (20 days to file)
mtc_due = fields.Date.today() + relativedelta(days=20)
self.env['fl.deadline'].create({
'case_id': self.case_id.id,
'name': 'File Motion to Compel Deposition — {} No-Show (FL 1.380)'.format(
self.deponent_id.name
),
'deadline_type': 'compliance',
'due_date': mtc_due,
'anchor_date': fields.Date.today(),
'offset_days': 20,
'statute_reference': (
'FL 1.380 — Motion to Compel Attendance at Deposition. '
'May also request sanctions under FL 1.380(b)(2).'
),
})
# Create project task
if self.case_id.project_id:
self.env['project.task'].create({
'name': 'File Motion to Compel: {} Deposition No-Show'.format(
self.deponent_id.name
),
'project_id': self.case_id.project_id.id,
'description': (
'{} failed to appear at the scheduled deposition on {}.\n\n'
'Steps:\n'
'1. Prepare Motion to Compel Attendance (FL 1.380)\n'
'2. Include Certificate of Good Faith Effort to Resolve\n'
'3. Request sanctions under FL 1.380(b)(2) — costs and attorney fees\n'
'4. File with Miami-Dade Clerk and set hearing date\n\n'
'Deadline to file: {}'
).format(
self.deponent_id.name,
self.scheduled_date,
mtc_due,
),
'date_deadline': mtc_due,
})
self.case_id.message_post(
body=(
'<b>⚠️ DEPOSITION NO-SHOW — Motion to Compel Required</b><br/>'
'{} did not appear at the deposition scheduled for {}.<br/><br/>'
'<b>Action Required:</b> File a Motion to Compel Attendance (FL 1.380) '
'within 20 days ({}).<br/>'
'You may also request sanctions (attorney fees / costs) '
'under FL 1.380(b)(2).'
).format(
self.deponent_id.name,
self.scheduled_date,
mtc_due,
),
subtype_xmlid='mail.mt_note',
)
def action_cancel(self):
"""Cancel this deposition (e.g. case settled, rescheduled)."""
self.write({'state': 'cancelled'})
if self.calendar_event_id:
self.calendar_event_id.write({'active': False})
def action_reschedule(self):
"""Mark as rescheduled — user should create a new deposition record."""
self.write({'state': 'rescheduled'})
self.case_id.message_post(
body=(
'🔄 <b>Deposition Rescheduled</b><br/>'
'Deposition for {} has been marked rescheduled. '
'Create a new deposition record with the new date and '
'serve a new Notice of Taking Deposition.'
).format(self.deponent_id.name),
subtype_xmlid='mail.mt_note',
)

View File

@@ -5,14 +5,19 @@ from dateutil.relativedelta import relativedelta
class FlDiscovery(models.Model):
"""
Phase 3 — Full implementation.
Phase 1: Stub with core fields.
Covers all Florida discovery methods with response tracking,
deficiency workflow, and Motion to Compel automation.
Covers all FL discovery methods:
- Interrogatories (FL 1.340) — 30 days to respond
- Request for Production (FL 1.350) — 30 days to respond
- Request for Admissions (FL 1.370) — 30 days to respond
- Subpoena — third party documents (FL 1.351)
- Depositions tracked in fl.deposition
Discovery types covered (FL Rules of Civil Procedure):
FL 1.340 — Interrogatories (30 days to respond)
FL 1.350 — Request for Production (30 days to respond)
FL 1.370 — Request for Admissions (30 days to respond; deemed admitted if no response)
FL 1.351 — Subpoena (Third-party document production)
FL 1.310 — Depositions (tracked separately in fl.deposition)
FL 1.280 — General discovery scope
Motion to Compel: FL 1.380
Subpoena to Employer: FL 1.351 (key tool when respondent income unknown)
"""
_name = 'fl.discovery'
_description = 'Discovery Item'
@@ -20,51 +25,419 @@ class FlDiscovery(models.Model):
_order = 'served_date asc'
case_id = fields.Many2one(
'fl.case', required=True, ondelete='cascade', index=True
'fl.case', required=True, ondelete='cascade', index=True,
string='Case'
)
discovery_type = fields.Selection([
('interrogatories', 'Interrogatories (FL 1.340)'),
('production', 'Request for Production (FL 1.350)'),
('production', 'Request for Production of Documents (FL 1.350)'),
('admissions', 'Request for Admissions (FL 1.370)'),
('subpoena', 'Subpoena — Third Party (FL 1.351)'),
('deposition', 'Deposition (FL 1.310)'),
], string='Discovery Type', required=True)
('deposition', 'Deposition Notice (FL 1.310)'),
], string='Discovery Type', required=True, tracking=True)
directed_to = fields.Selection([
('petitioner', 'Petitioner'),
('respondent', 'Respondent'),
('third_party', 'Third Party'),
], string='Directed To', required=True)
third_party_id = fields.Many2one(
'res.partner', string='Third Party'
'res.partner', string='Third Party',
help='Employer, bank, or other third party for subpoena'
)
description = fields.Char(string='Description / Subject')
served_date = fields.Date(string='Served Date')
description = fields.Char(
string='Description / Subject',
help='e.g. "Interrogatories regarding income and employment" or '
'"Request for Production — bank statements 2022-2024"'
)
# ── Dates and Deadlines ───────────────────────────────────────────────────
served_date = fields.Date(string='Date Served', tracking=True)
response_due_date = fields.Date(
string='Response Due Date',
compute='_compute_response_due', store=True,
help='FL 1.340/1.350/1.370: 30 days to respond'
compute='_compute_response_due',
store=True,
help='30 days from service date (FL 1.340/1.350/1.370)'
)
response_received_date = fields.Date(string='Response Received Date')
response_complete = fields.Boolean(string='Response Complete')
objections_raised = fields.Boolean(string='Objections Raised')
objection_detail = fields.Text(string='Objection Details')
deficiency_notice_sent = fields.Boolean(string='Deficiency Notice Sent')
response_received_date = fields.Date(
string='Response Date Received', tracking=True
)
# ── Tracking ──────────────────────────────────────────────────────────────
response_complete = fields.Boolean(
string='Response Complete / Adequate',
help='Mark True when you have received a complete, non-deficient response'
)
objections_raised = fields.Boolean(string='Objections Raised by Respondent')
objection_detail = fields.Text(
string='Objection Details',
help='Document specific objections raised: privilege, overbroad, '
'not reasonably calculated to lead to admissible evidence, etc.'
)
deficiency_notice_sent = fields.Boolean(
string='Deficiency Notice Sent',
help='A written notice identifying deficiencies was sent before Motion to Compel'
)
deficiency_notice_date = fields.Date(string='Deficiency Notice Date')
# ── Status ────────────────────────────────────────────────────────────────
state = fields.Selection([
('draft', 'Drafting'),
('served', 'Served'),
('served', 'Served — Awaiting Response'),
('responded', 'Response Received'),
('deficient', 'Response Deficient'),
('compelled', 'Motion to Compel Filed'),
('complete', 'Complete'),
], string='Status', default='draft', tracking=True)
# ── Computed Status Fields ────────────────────────────────────────────────
is_overdue = fields.Boolean(
string='Response Overdue',
compute='_compute_overdue_status',
store=True
)
days_until_response = fields.Integer(
string='Days Until Response Due',
compute='_compute_overdue_status'
)
admissions_deemed = fields.Boolean(
string='Admissions Deemed Admitted',
compute='_compute_admissions_deemed',
store=True,
help='FL 1.370: If no response to Request for Admissions within 30 days, '
'each matter is AUTOMATICALLY deemed admitted'
)
# ── Motion to Compel ──────────────────────────────────────────────────────
motion_to_compel_filed = fields.Boolean(
string='Motion to Compel Filed (FL 1.380)'
)
motion_to_compel_date = fields.Date(string='Motion to Compel Date')
sanctions_requested = fields.Boolean(
string='Attorney Fees / Sanctions Requested',
help='FL 1.380(a)(4): If motion is granted, court shall award expenses. '
'If motion is denied, court may award expenses to the opposing party.'
)
# ── Subpoena-Specific ─────────────────────────────────────────────────────
subpoena_employer_flag = fields.Boolean(
string='Employer Subpoena',
compute='_compute_subpoena_employer_flag',
help='True when this is a subpoena directed to an employer for income records'
)
notes = fields.Text(string='Notes')
# ══════════════════════════════════════════════════════════════════════════
# COMPUTED FIELDS
# ══════════════════════════════════════════════════════════════════════════
@api.depends('served_date', 'discovery_type')
def _compute_response_due(self):
for rec in self:
if rec.served_date and rec.discovery_type != 'deposition':
rec.response_due_date = (
rec.served_date + relativedelta(days=30)
)
if rec.served_date and rec.discovery_type in (
'interrogatories', 'production', 'admissions'
):
rec.response_due_date = rec.served_date + relativedelta(days=30)
else:
rec.response_due_date = False
@api.depends('response_due_date', 'state', 'response_received_date')
def _compute_overdue_status(self):
today = fields.Date.today()
for rec in self:
terminal_states = ('responded', 'complete')
if rec.state in terminal_states or not rec.response_due_date:
rec.is_overdue = False
rec.days_until_response = 0
else:
rec.days_until_response = (rec.response_due_date - today).days
rec.is_overdue = rec.response_due_date < today
@api.depends('discovery_type', 'response_due_date', 'response_received_date', 'state')
def _compute_admissions_deemed(self):
"""
FL 1.370: Each matter in a Request for Admissions is AUTOMATICALLY
deemed admitted if no response within 30 days.
"""
today = fields.Date.today()
for rec in self:
if (
rec.discovery_type == 'admissions'
and rec.response_due_date
and rec.response_due_date < today
and not rec.response_received_date
and rec.state not in ('responded', 'complete')
):
rec.admissions_deemed = True
else:
rec.admissions_deemed = False
@api.depends('discovery_type', 'directed_to')
def _compute_subpoena_employer_flag(self):
for rec in self:
rec.subpoena_employer_flag = (
rec.discovery_type == 'subpoena'
and rec.third_party_id
)
# ══════════════════════════════════════════════════════════════════════════
# WORKFLOW ACTIONS
# ══════════════════════════════════════════════════════════════════════════
def action_mark_served(self):
"""
Mark discovery as served.
Sets served_date to today and creates a deadline for the response.
"""
if not self.served_date:
self.served_date = fields.Date.today()
self.write({'state': 'served'})
# Create response deadline in fl.deadline
if self.response_due_date:
type_label = dict(self._fields['discovery_type'].selection).get(
self.discovery_type, 'Discovery'
)
to_label = dict(self._fields['directed_to'].selection).get(
self.directed_to, ''
)
deadline_name = 'Response Due: {} to {}{}'.format(
type_label, to_label, self.description or ''
)
self.env['fl.deadline'].create({
'case_id': self.case_id.id,
'name': deadline_name[:200],
'deadline_type': 'compliance',
'due_date': self.response_due_date,
'anchor_date': self.served_date,
'offset_days': 30,
'statute_reference': self._get_statute_ref(),
'notes': (
'Discovery response deadline. If no response received, '
'send deficiency notice, then file Motion to Compel (FL 1.380).'
),
})
self.case_id.message_post(
body=(
'📬 <b>Discovery Served</b><br/>'
'Type: {}<br/>'
'Directed to: {}<br/>'
'Description: {}<br/>'
'Response due: {}'
).format(
dict(self._fields['discovery_type'].selection).get(self.discovery_type, ''),
dict(self._fields['directed_to'].selection).get(self.directed_to, ''),
self.description or 'N/A',
self.response_due_date or 'N/A',
),
subtype_xmlid='mail.mt_note',
)
def action_mark_responded(self):
"""Response received — set date if not already set."""
if not self.response_received_date:
self.response_received_date = fields.Date.today()
self.write({'state': 'responded'})
def action_flag_deficient(self):
"""
Response received but is deficient (incomplete, evasive, objectionable).
Creates a deadline to send deficiency notice.
"""
self.write({'state': 'deficient'})
# Create deadline: send deficiency notice within 10 days
notice_due = fields.Date.today() + relativedelta(days=10)
self.env['fl.deadline'].create({
'case_id': self.case_id.id,
'name': 'Send Deficiency Notice for {} (FL 1.380 prerequisite)'.format(
self.description or dict(self._fields['discovery_type'].selection).get(
self.discovery_type, 'Discovery'
)
),
'deadline_type': 'compliance',
'due_date': notice_due,
'statute_reference': (
'FL 1.380: Must demonstrate good-faith effort to resolve '
'before filing Motion to Compel. Send written deficiency notice.'
),
})
self.case_id.message_post(
body=(
'⚠️ <b>Discovery Response Deficient</b><br/>'
'{} response from {} is incomplete or evasive.<br/><br/>'
'<b>Next Steps:</b><br/>'
'1. Send written deficiency notice ({}) — required before Motion to Compel<br/>'
'2. Allow 10-14 days for supplemental response<br/>'
'3. If no adequate response: file Motion to Compel (FL 1.380)'
).format(
dict(self._fields['discovery_type'].selection).get(self.discovery_type, ''),
dict(self._fields['directed_to'].selection).get(self.directed_to, ''),
notice_due,
),
subtype_xmlid='mail.mt_note',
)
def action_file_motion_to_compel(self):
"""
File Motion to Compel (FL 1.380).
Creates deadline and project task. Updates state to 'compelled'.
"""
self.write({
'state': 'compelled',
'motion_to_compel_filed': True,
'motion_to_compel_date': fields.Date.today(),
})
# Deadline: file motion within 7 days
mtc_due = fields.Date.today() + relativedelta(days=7)
self.env['fl.deadline'].create({
'case_id': self.case_id.id,
'name': 'File Motion to Compel Discovery — {} (FL 1.380)'.format(
self.description or dict(self._fields['discovery_type'].selection).get(
self.discovery_type, 'Discovery'
)
),
'deadline_type': 'compliance',
'due_date': mtc_due,
'statute_reference': (
'FL 1.380 — Motion to Compel Discovery. '
'Include: certificate of good faith, deficiency notice copy, '
'request for sanctions under FL 1.380(a)(4).'
),
})
if self.case_id.project_id:
self.env['project.task'].create({
'name': 'FILE: Motion to Compel — {} (FL 1.380)'.format(
self.description or dict(self._fields['discovery_type'].selection).get(
self.discovery_type, 'Discovery'
)
),
'project_id': self.case_id.project_id.id,
'description': (
'File Motion to Compel for {} directed to {}.\n\n'
'Required elements:\n'
'1. Certificate of Good Faith (prior notice of deficiency)\n'
'2. Copy of original discovery request\n'
'3. Copy of deficiency notice sent\n'
'4. Request for sanctions (attorney fees) under FL 1.380(a)(4)\n\n'
'File with Miami-Dade Clerk and set hearing date.\n'
'Deadline: {}'
).format(
dict(self._fields['discovery_type'].selection).get(
self.discovery_type, 'discovery'
),
dict(self._fields['directed_to'].selection).get(
self.directed_to, ''
),
mtc_due,
),
'date_deadline': mtc_due,
})
self.case_id.message_post(
body=(
'⚖️ <b>Motion to Compel Filed (FL 1.380)</b><br/>'
'Discovery: {}{}<br/>'
'Directed to: {}<br/>'
'Sanctions requested: {}'
).format(
dict(self._fields['discovery_type'].selection).get(self.discovery_type, ''),
self.description or 'N/A',
dict(self._fields['directed_to'].selection).get(self.directed_to, ''),
'✅ Yes' if self.sanctions_requested else 'No',
),
subtype_xmlid='mail.mt_note',
)
def action_mark_complete(self):
"""Mark discovery item fully resolved."""
self.write({'state': 'complete', 'response_complete': True})
# ══════════════════════════════════════════════════════════════════════════
# HELPERS
# ══════════════════════════════════════════════════════════════════════════
def _get_statute_ref(self):
"""Return the applicable statute reference for this discovery type."""
refs = {
'interrogatories': 'FL 1.340 — Interrogatories (30 days to respond)',
'production': 'FL 1.350 — Request for Production (30 days to respond)',
'admissions': (
'FL 1.370 — Request for Admissions (30 days to respond). '
'WARNING: Unanswered admissions are DEEMED ADMITTED automatically.'
),
'subpoena': 'FL 1.351 — Subpoena for Documents from Third Party',
'deposition': 'FL 1.310 — Notice of Taking Deposition',
}
return refs.get(self.discovery_type, 'FL Rules of Civil Procedure')
# ══════════════════════════════════════════════════════════════════════════
# CRON: Daily Discovery Overdue Alerts
# ══════════════════════════════════════════════════════════════════════════
def _cron_discovery_overdue_alerts(self):
"""
Run daily. Check for:
1. Overdue responses → post chatter alert
2. Deemed admissions (FL 1.370) → urgent chatter alert
"""
today = fields.Date.today()
# Overdue responses
overdue = self.search([
('state', 'in', ['served']),
('response_due_date', '<', today),
('is_overdue', '=', True),
])
for item in overdue:
days_overdue = (today - item.response_due_date).days
item.case_id.message_post(
body=(
'🔴 <b>Discovery Response OVERDUE ({} days)</b><br/>'
'Type: {}<br/>'
'Directed to: {}<br/>'
'Description: {}<br/>'
'Was due: {}<br/><br/>'
'<b>Next Step:</b> Send deficiency notice, then '
'consider Motion to Compel (FL 1.380).'
).format(
days_overdue,
dict(item._fields['discovery_type'].selection).get(item.discovery_type, ''),
dict(item._fields['directed_to'].selection).get(item.directed_to, ''),
item.description or 'N/A',
item.response_due_date,
),
subtype_xmlid='mail.mt_note',
)
# Auto-update to served state stays but mark overdue flag is computed
# Deemed admissions (FL 1.370) — extra urgent alert
deemed = self.search([
('discovery_type', '=', 'admissions'),
('admissions_deemed', '=', True),
('state', 'not in', ['responded', 'complete', 'compelled']),
])
for item in deemed:
item.case_id.message_post(
body=(
'<b>🚨 DEEMED ADMITTED — FL 1.370 CRITICAL ALERT</b><br/>'
'A Request for Admissions directed to <b>{}</b> received NO response '
'within 30 days.<br/>'
'<b>Under FL 1.370, each matter is now AUTOMATICALLY DEEMED ADMITTED</b> '
'and may be used against that party at hearing.<br/><br/>'
'If you are the <b>responding party</b>: immediately file a Motion to '
'Withdraw or Amend the Admissions (court has discretion to allow this '
'if no prejudice to the requesting party).<br/><br/>'
'If you are the <b>requesting party</b>: these deemed admissions are '
'now established as facts for trial purposes.'
).format(
dict(item._fields['directed_to'].selection).get(item.directed_to, '')
),
subtype_xmlid='mail.mt_note',
)

View File

@@ -2,67 +2,234 @@
<odoo>
<data>
<!-- ══════════════════════════════════════════════════════
TREE VIEW
══════════════════════════════════════════════════════ -->
<record id="view_fl_deposition_tree" model="ir.ui.view">
<field name="name">fl.deposition.tree</field>
<field name="model">fl.deposition</field>
<field name="arch" type="xml">
<tree string="Depositions">
<tree string="Depositions"
decoration-danger="state == 'no_show'"
decoration-success="state == 'completed'"
decoration-muted="state in ('cancelled', 'rescheduled')"
decoration-warning="state == 'noticed' and not notice_valid">
<field name="case_id"/>
<field name="deponent_id"/>
<field name="deponent_type"/>
<field name="notice_date"/>
<field name="scheduled_date"/>
<field name="state"/>
<field name="duces_tecum"/>
<field name="income_verified"/>
<field name="days_until_deposition" string="Days Away"
attrs="{'invisible': [('state', 'in', ['completed', 'cancelled'])]}"/>
<field name="notice_valid" string="Notice OK" optional="show"/>
<field name="state" widget="badge"
decoration-success="state == 'completed'"
decoration-danger="state == 'no_show'"
decoration-warning="state in ('noticed', 'draft')"
decoration-info="state == 'confirmed'"/>
<field name="duces_tecum" optional="show"/>
<field name="income_verified" optional="show"/>
</tree>
</field>
</record>
<!-- ══════════════════════════════════════════════════════
FORM VIEW
══════════════════════════════════════════════════════ -->
<record id="view_fl_deposition_form" model="ir.ui.view">
<field name="name">fl.deposition.form</field>
<field name="model">fl.deposition</field>
<field name="arch" type="xml">
<form string="Deposition">
<header>
<field name="state" widget="statusbar"/>
<button name="action_mark_noticed" string="Mark Notice Served"
type="object" class="oe_highlight"
attrs="{'invisible': [('state', '!=', 'draft')]}"/>
<button name="action_confirm" string="Confirm"
type="object"
attrs="{'invisible': [('state', '!=', 'noticed')]}"/>
<button name="action_mark_completed" string="Mark Completed"
type="object" class="oe_highlight"
attrs="{'invisible': [('state', 'not in', ['confirmed', 'noticed'])]}"/>
<button name="action_no_show" string="Deponent No-Show"
type="object"
confirm="Mark this deponent as a no-show? This will trigger the Motion to Compel workflow."
attrs="{'invisible': [('state', 'not in', ['confirmed', 'noticed'])]}"/>
<button name="action_reschedule" string="Reschedule"
type="object"
attrs="{'invisible': [('state', 'not in', ['draft', 'noticed', 'confirmed'])]}"/>
<button name="action_cancel" string="Cancel"
type="object"
confirm="Cancel this deposition?"
attrs="{'invisible': [('state', 'in', ['completed', 'cancelled'])]}"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,noticed,confirmed,completed"/>
</header>
<sheet>
<!-- FL 1.310 Notice Alert -->
<div class="alert alert-info" role="alert">
<strong>FL 1.310(b):</strong> Minimum <strong>10 days notice</strong>
required before deposition.
FL 1.310(d): Maximum <strong>7 hours</strong> per deponent per day.
<strong>FL 1.310 Requirements:</strong>
<span class="ml-2">Minimum <strong>10 days notice</strong> (b) | Maximum <strong>7 hours/day</strong> per deponent (d)</span>
</div>
<!-- Notice validity warning -->
<div class="alert alert-warning" role="alert"
attrs="{'invisible': ['|', ('notice_valid', '=', True), ('notice_date', '=', False), ('scheduled_date', '=', False)]}">
<strong>⚠️ INSUFFICIENT NOTICE</strong>
<field name="notice_warning" readonly="1" nolabel="1"/>
</div>
<div class="alert alert-success" role="alert"
attrs="{'invisible': ['|', ('notice_valid', '=', False), ('notice_date', '=', False)]}">
<field name="notice_warning" readonly="1" nolabel="1"/>
</div>
<!-- No-Show alert -->
<div class="alert alert-danger" role="alert"
attrs="{'invisible': [('state', '!=', 'no_show')]}">
<strong>⚠️ DEPONENT NO-SHOW</strong> — File Motion to Compel
Attendance (FL 1.380). Check the Deadlines tab for the deadline.
You may also request sanctions under FL 1.380(b)(2).
</div>
<!-- Completed summary -->
<div class="alert alert-success" role="alert"
attrs="{'invisible': [('state', '!=', 'completed')]}">
<strong>✅ Deposition Completed</strong>
<span attrs="{'invisible': [('income_verified', '=', False)]}">
— Income verified at
<field name="income_verified_amount" readonly="1" nolabel="1"/> /month
</span>
</div>
<group>
<group>
<group string="Deponent">
<field name="case_id"/>
<field name="deponent_id"/>
<field name="deponent_type"/>
</group>
<group string="Schedule (FL 1.310)">
<field name="notice_date"/>
<field name="scheduled_date"/>
</group>
<group>
<field name="location"/>
<field name="days_notice" readonly="1"/>
<field name="days_until_deposition" readonly="1"/>
<field name="max_duration_hours"/>
<field name="duces_tecum"/>
</group>
</group>
<group string="Results">
<group>
<group string="Location">
<field name="location"/>
<field name="court_reporter"/>
<field name="calendar_event_id" readonly="1"
attrs="{'invisible': [('calendar_event_id', '=', False)]}"/>
</group>
</group>
<!-- Duces Tecum -->
<group string="Duces Tecum — Document Production Requirement">
<field name="duces_tecum"/>
<field name="duces_tecum_items"
attrs="{'invisible': [('duces_tecum', '=', False)], 'required': [('duces_tecum', '=', True)]}"
placeholder="List documents deponent must bring to the deposition:&#10;- Last 3 years federal/state tax returns&#10;- Last 6 months paystubs&#10;- All bank statements last 12 months&#10;- Business financial statements&#10;- Corporate tax returns&#10;- Quickbooks / accounting records"/>
</group>
<!-- Results (shown when completed) -->
<group string="Results / Findings"
attrs="{'invisible': [('state', 'not in', ['completed', 'no_show'])]}">
<field name="income_verified"/>
<field name="income_verified_amount"
attrs="{'invisible': [('income_verified', '=', False)]}"/>
<field name="key_findings"/>
<field name="transcript_received"/>
</group>
<field name="notes"/>
<field name="key_findings"
attrs="{'invisible': [('state', 'not in', ['completed'])]}"
placeholder="Summarize key testimony: income figures, contradictions, admissions, document admissions..."/>
<!-- Motion to Compel (no-show only) -->
<group string="Motion to Compel (FL 1.380)"
attrs="{'invisible': [('state', '!=', 'no_show')]}">
<field name="motion_to_compel_filed"/>
<field name="motion_to_compel_date"
attrs="{'invisible': [('motion_to_compel_filed', '=', False)]}"/>
<field name="sanctions_requested"/>
</group>
<field name="notes" placeholder="Notes, preparation strategy, questions to ask..."/>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<!-- ══════════════════════════════════════════════════════
CALENDAR VIEW
══════════════════════════════════════════════════════ -->
<record id="view_fl_deposition_calendar" model="ir.ui.view">
<field name="name">fl.deposition.calendar</field>
<field name="model">fl.deposition</field>
<field name="arch" type="xml">
<calendar string="Deposition Calendar"
date_start="scheduled_date"
date_stop="scheduled_date"
color="deponent_type"
mode="month"
quick_create="False">
<field name="deponent_id"/>
<field name="case_id"/>
<field name="deponent_type"/>
<field name="state"/>
<field name="location"/>
</calendar>
</field>
</record>
<!-- ══════════════════════════════════════════════════════
SEARCH VIEW
══════════════════════════════════════════════════════ -->
<record id="view_fl_deposition_search" model="ir.ui.view">
<field name="name">fl.deposition.search</field>
<field name="model">fl.deposition</field>
<field name="arch" type="xml">
<search string="Search Depositions">
<field name="deponent_id"/>
<field name="case_id"/>
<field name="deponent_type"/>
<filter string="Pending" name="filter_pending"
domain="[('state', 'in', ['draft', 'noticed', 'confirmed'])]"/>
<filter string="No-Show" name="filter_no_show"
domain="[('state', '=', 'no_show')]"/>
<filter string="Completed" name="filter_completed"
domain="[('state', '=', 'completed')]"/>
<filter string="Invalid Notice" name="filter_invalid"
domain="[('notice_valid', '=', False), ('notice_date', '!=', False)]"/>
<filter string="Duces Tecum" name="filter_duces"
domain="[('duces_tecum', '=', True)]"/>
<group string="Group By">
<filter string="Case" name="group_case"
context="{'group_by': 'case_id'}"/>
<filter string="Status" name="group_state"
context="{'group_by': 'state'}"/>
<filter string="Deponent Type" name="group_type"
context="{'group_by': 'deponent_type'}"/>
</group>
</search>
</field>
</record>
<!-- ══════════════════════════════════════════════════════
ACTIONS
══════════════════════════════════════════════════════ -->
<record id="action_fl_deposition_list" model="ir.actions.act_window">
<field name="name">Depositions</field>
<field name="res_model">fl.deposition</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">tree,calendar,form</field>
<field name="search_view_id" ref="view_fl_deposition_search"/>
<field name="domain">[('state', 'not in', ['cancelled'])]</field>
<field name="context">{'search_default_filter_pending': 1}</field>
</record>
</data>

View File

@@ -2,65 +2,242 @@
<odoo>
<data>
<!-- ══════════════════════════════════════════════════════
TREE VIEW
══════════════════════════════════════════════════════ -->
<record id="view_fl_discovery_tree" model="ir.ui.view">
<field name="name">fl.discovery.tree</field>
<field name="model">fl.discovery</field>
<field name="arch" type="xml">
<tree string="Discovery" decoration-danger="state == 'deficient'"
decoration-warning="state == 'served'">
<tree string="Discovery"
decoration-danger="is_overdue == True or admissions_deemed == True"
decoration-warning="state == 'deficient'"
decoration-success="state == 'complete'"
decoration-muted="state == 'compelled'">
<field name="case_id"/>
<field name="discovery_type"/>
<field name="directed_to"/>
<field name="third_party_id"/>
<field name="third_party_id"
attrs="{'invisible': [('directed_to', '!=', 'third_party')]}"/>
<field name="description"/>
<field name="served_date"/>
<field name="response_due_date"/>
<field name="response_complete"/>
<field name="state"/>
<field name="days_until_response" string="Days Left"
attrs="{'invisible': [('state', 'in', ['draft', 'responded', 'complete'])]}"/>
<field name="is_overdue" string="Overdue"/>
<field name="admissions_deemed" string="Deemed Admitted" optional="show"/>
<field name="state" widget="badge"
decoration-danger="state in ('deficient', 'compelled')"
decoration-warning="state == 'served'"
decoration-success="state == 'complete'"
decoration-muted="state == 'draft'"/>
<button name="action_mark_served" string="Served" type="object"
icon="fa-paper-plane"
attrs="{'invisible': [('state', '!=', 'draft')]}"/>
<button name="action_mark_responded" string="Responded" type="object"
icon="fa-check"
attrs="{'invisible': [('state', '!=', 'served')]}"/>
<button name="action_file_motion_to_compel" string="Compel"
type="object" icon="fa-gavel"
attrs="{'invisible': [('state', 'not in', ['served', 'deficient'])]}"/>
</tree>
</field>
</record>
<!-- ══════════════════════════════════════════════════════
FORM VIEW
══════════════════════════════════════════════════════ -->
<record id="view_fl_discovery_form" model="ir.ui.view">
<field name="name">fl.discovery.form</field>
<field name="model">fl.discovery</field>
<field name="arch" type="xml">
<form string="Discovery Item">
<header>
<field name="state" widget="statusbar"/>
<button name="action_mark_served" string="Mark Served"
type="object" class="oe_highlight"
attrs="{'invisible': [('state', '!=', 'draft')]}"/>
<button name="action_mark_responded" string="Response Received"
type="object"
attrs="{'invisible': [('state', '!=', 'served')]}"/>
<button name="action_flag_deficient" string="Flag Deficient"
type="object"
attrs="{'invisible': [('state', '!=', 'responded')]}"/>
<button name="action_file_motion_to_compel"
string="File Motion to Compel (FL 1.380)"
type="object"
attrs="{'invisible': [('state', 'not in', ['served', 'deficient'])]}"/>
<button name="action_mark_complete" string="Mark Complete"
type="object"
attrs="{'invisible': [('state', 'in', ['draft', 'complete'])]}"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,served,responded,complete"/>
</header>
<sheet>
<!-- Deemed Admitted Alert (FL 1.370) — CRITICAL -->
<div class="alert alert-danger" role="alert"
attrs="{'invisible': [('admissions_deemed', '=', False)]}">
<strong>🚨 FL 1.370 — ADMISSIONS DEEMED ADMITTED</strong><br/>
No response was filed within 30 days. Each matter in this
Request for Admissions is <strong>automatically deemed admitted</strong>
and may be used as established facts at hearing.
If you are the responding party, immediately file a
<strong>Motion to Withdraw or Amend Admissions</strong>.
</div>
<!-- Overdue response alert -->
<div class="alert alert-warning" role="alert"
attrs="{'invisible': ['|', ('is_overdue', '=', False), ('admissions_deemed', '=', True)]}">
<strong>⏰ Response OVERDUE</strong> — Was due
<field name="response_due_date" readonly="1" nolabel="1"/>.
Consider sending a deficiency notice, then filing
Motion to Compel (FL 1.380).
</div>
<!-- Complete badge -->
<div class="alert alert-success" role="alert"
attrs="{'invisible': [('state', '!=', 'complete')]}">
<strong>✅ Discovery Item Complete</strong>
</div>
<group>
<group>
<group string="Discovery Details">
<field name="case_id"/>
<field name="discovery_type"/>
<field name="directed_to"/>
<field name="third_party_id"
attrs="{'invisible': [('directed_to', '!=', 'third_party')]}"/>
attrs="{'invisible': [('directed_to', '!=', 'third_party')], 'required': [('directed_to', '=', 'third_party')]}"/>
<field name="description"/>
</group>
<group>
<group string="Timeline">
<field name="served_date"/>
<field name="response_due_date" readonly="1"/>
<field name="days_until_response" readonly="1"
attrs="{'invisible': [('state', 'in', ['draft', 'responded', 'complete'])]}"/>
<field name="is_overdue" readonly="1"/>
<field name="response_received_date"/>
<field name="response_complete"/>
</group>
</group>
<!-- Subpoena info (third party) -->
<group string="Subpoena Information (FL 1.351)"
attrs="{'invisible': [('discovery_type', '!=', 'subpoena')]}">
<div class="alert alert-info" role="alert">
<strong>FL 1.351 Subpoena for Production:</strong>
Third party must respond within the time stated in the subpoena
(typically 15-30 days). Third party may object within 14 days.
If opposing party's employer: excellent tool to verify income
when self-employment or cash income is suspected.
</div>
</group>
<!-- Admissions specific note -->
<group string="Admissions (FL 1.370) — WARNING"
attrs="{'invisible': [('discovery_type', '!=', 'admissions')]}">
<div class="alert alert-warning" role="alert">
<strong>FL 1.370 Critical Rule:</strong>
If the responding party <strong>fails to respond within 30 days</strong>,
each matter is <strong>automatically deemed admitted</strong> without
any court order. This is one of the most powerful discovery tools
in Florida family law — it can establish income, assets, and conduct
as admitted facts.
</div>
<field name="admissions_deemed" readonly="1"/>
</group>
<group string="Objections / Deficiencies">
<field name="objections_raised"/>
<field name="objection_detail"
attrs="{'invisible': [('objections_raised', '=', False)]}"/>
<field name="deficiency_notice_sent"/>
<field name="deficiency_notice_date"
attrs="{'invisible': [('deficiency_notice_sent', '=', False)]}"/>
</group>
<field name="notes"/>
<group string="Motion to Compel (FL 1.380)"
attrs="{'invisible': [('state', '!=', 'compelled')]}">
<field name="motion_to_compel_filed" readonly="1"/>
<field name="motion_to_compel_date" readonly="1"/>
<field name="sanctions_requested"/>
</group>
<field name="notes"
placeholder="Notes on what was requested, what was received, gaps, strategy..."/>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<!-- ══════════════════════════════════════════════════════
SEARCH VIEW
══════════════════════════════════════════════════════ -->
<record id="view_fl_discovery_search" model="ir.ui.view">
<field name="name">fl.discovery.search</field>
<field name="model">fl.discovery</field>
<field name="arch" type="xml">
<search string="Search Discovery">
<field name="case_id"/>
<field name="discovery_type"/>
<field name="directed_to"/>
<field name="description"/>
<filter string="Overdue Responses" name="filter_overdue"
domain="[('is_overdue', '=', True)]"/>
<filter string="Deemed Admitted" name="filter_deemed"
domain="[('admissions_deemed', '=', True)]"/>
<filter string="Deficient" name="filter_deficient"
domain="[('state', '=', 'deficient')]"/>
<filter string="Motion to Compel Filed" name="filter_compelled"
domain="[('state', '=', 'compelled')]"/>
<filter string="Pending Response" name="filter_pending"
domain="[('state', 'in', ['served'])]"/>
<filter string="Complete" name="filter_complete"
domain="[('state', '=', 'complete')]"/>
<separator/>
<filter string="Interrogatories" name="type_interrog"
domain="[('discovery_type', '=', 'interrogatories')]"/>
<filter string="Production Requests" name="type_production"
domain="[('discovery_type', '=', 'production')]"/>
<filter string="Admissions" name="type_admissions"
domain="[('discovery_type', '=', 'admissions')]"/>
<filter string="Subpoenas" name="type_subpoena"
domain="[('discovery_type', '=', 'subpoena')]"/>
<group string="Group By">
<filter string="Case" name="group_case"
context="{'group_by': 'case_id'}"/>
<filter string="Type" name="group_type"
context="{'group_by': 'discovery_type'}"/>
<filter string="Directed To" name="group_directed"
context="{'group_by': 'directed_to'}"/>
<filter string="Status" name="group_state"
context="{'group_by': 'state'}"/>
</group>
</search>
</field>
</record>
<!-- ══════════════════════════════════════════════════════
ACTIONS
══════════════════════════════════════════════════════ -->
<record id="action_fl_discovery_list" model="ir.actions.act_window">
<field name="name">Discovery</field>
<field name="res_model">fl.discovery</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_fl_discovery_search"/>
<field name="domain">[('state', 'not in', ['complete'])]</field>
<field name="context">{'search_default_group_case': 1}</field>
</record>
<!-- All discovery (admin view) -->
<record id="action_fl_discovery_all" model="ir.actions.act_window">
<field name="name">All Discovery</field>
<field name="res_model">fl.discovery</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_fl_discovery_search"/>
</record>
</data>