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:
@@ -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',
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user