Step 4: deadline engine (per-proceeding clocks, weekend roll, overdue cron, calendar mirror)

familylaw.deadline — a procedural clock attached to a PROCEEDING:
- Deterministic due_date: trigger_date + days, with Rule-2.514-style weekend
  roll-forward (_roll_forward); _holiday_dates() hook left empty by design
  (holidays are jurisdiction/year-specific — content-maintenance concern)
- STANDARD_OFFSETS (answer 20 / disclosure 45 / discovery 30 / objection 10) as
  defaults; per-record `days` override. Flagged "verify current rule".
- state pending/done/waived/overdue; is_overdue computed + searchable
- _cron_flag_overdue daily ir.cron flips pending+past -> overdue, audited
- calendar.event mirror auto-created/updated on date changes (allday)
- proceeding.deadline_ids + action_seed_standard_deadlines (idempotent)

Adds 'calendar' dependency, data/familylaw_cron.xml, deadline views + menu,
Deadlines tab + Seed button on proceeding form, security rules.

Tests (familylaw_step4): 20 tests with FIXED dates — 20/45/30-day math, weekend
roll (Sat/Sun -> Mon), type-default vs explicit days, overdue detect, cron flag,
is_overdue search, calendar mirror create+update, per-proceeding isolation,
idempotent seeding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tocmo0nlord
2026-06-02 04:02:20 +00:00
parent 979bbfa14a
commit a6f8d31316
11 changed files with 600 additions and 2 deletions

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
{
"name": "Active Blue Family Law",
"version": "18.0.3.0.0",
"version": "18.0.4.0.0",
"category": "Services/Legal",
"summary": "Florida family law case management (Miami-Dade / 11th Judicial Circuit)",
"description": """
@@ -9,7 +9,8 @@ Active Blue Family Law
======================
Case-management platform for a Florida family-law practice, built in verifiable
steps. Step 1: case spine. Step 2: parties/children/issues/proceedings, conflict
screening, intake. Step 3: documents + the attorney review gate (Gate 1).
screening, intake. Step 3: documents + review gate. Step 4: deadline engine
(per-proceeding clocks, weekend roll, overdue cron, calendar mirror).
Each step adds one vertical, independently testable slice. See BUILD_PLAN.md.
""",
@@ -19,15 +20,18 @@ Each step adds one vertical, independently testable slice. See BUILD_PLAN.md.
"depends": [
"base",
"mail",
"calendar",
],
"data": [
"security/familylaw_security.xml",
"security/ir.model.access.csv",
"data/familylaw_cron.xml",
"views/familylaw_party_views.xml",
"views/familylaw_child_views.xml",
"views/familylaw_issue_views.xml",
"views/familylaw_proceeding_views.xml",
"views/familylaw_document_views.xml",
"views/familylaw_deadline_views.xml",
"views/familylaw_intake_views.xml",
"views/familylaw_case_views.xml",
"views/familylaw_menus.xml",

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Daily: flag pending deadlines whose due date has passed. -->
<record id="cron_flag_overdue_deadlines" model="ir.cron">
<field name="name">Family Law: Flag Overdue Deadlines</field>
<field name="model_id" ref="model_familylaw_deadline"/>
<field name="state">code</field>
<field name="code">model._cron_flag_overdue()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="active" eval="True"/>
</record>
</odoo>

View File

@@ -6,3 +6,4 @@ from . import familylaw_proceeding
from . import familylaw_conflict
from . import familylaw_intake
from . import familylaw_document
from . import familylaw_deadline

View File

@@ -0,0 +1,232 @@
# -*- coding: utf-8 -*-
"""STEP 4 — Deadline engine.
A family-law deadline is a statutory/procedural clock. It attaches to a
PROCEEDING (locked decision — so the 2024 dissolution's disclosure clock never
collides with a 2027 modification's), computes its due date deterministically with
Florida-style weekend roll-forward, mirrors itself onto the shared calendar, and is
flagged overdue by a daily cron.
DATE-MATH NOTE (verify current rule at build time):
Florida Rule of Judicial Administration 2.514 computes day-periods by excluding
the triggering day, counting every following day, and — if the last day falls on a
Saturday, Sunday, or legal holiday — rolling forward to the next day that is none
of those. We implement the exclude-trigger + weekend roll here. LEGAL HOLIDAYS are
jurisdiction- and year-specific and are intentionally NOT hardcoded; see
_holiday_dates() for the hook. Standard day-counts (20/45/30/10) are common values
and MUST be confirmed against the current rule for each matter type.
"""
from datetime import timedelta
from odoo import api, fields, models, _
# Common Florida day-counts. VERIFY CURRENT RULE per matter type — do not treat as
# permanent. Used as defaults; the `days` field can override per record.
STANDARD_OFFSETS = {
"answer": 20, # Respondent's answer window (Rule 12.140 family practice)
"disclosure": 45, # Mandatory disclosure (Rule 12.285)
"discovery": 30, # Interrogatory / production responses (Rule 12.340/12.350)
"objection": 10, # Subpoena objection window (Step 8)
}
class FamilyLawDeadline(models.Model):
_name = "familylaw.deadline"
_description = "Procedural Deadline"
_inherit = ["mail.thread"]
_order = "due_date asc"
name = fields.Char(required=True, tracking=True)
proceeding_id = fields.Many2one(
"familylaw.proceeding",
string="Proceeding",
required=True,
ondelete="cascade",
index=True,
tracking=True,
help="Deadlines attach to a proceeding, not directly to the case.",
)
case_id = fields.Many2one(
"familylaw.case",
related="proceeding_id.case_id",
store=True,
index=True,
)
deadline_type = fields.Selection(
selection=[
("answer", "Answer / Response"),
("disclosure", "Mandatory Disclosure"),
("discovery", "Discovery Response"),
("objection", "Objection Window"),
("hearing", "Hearing"),
("custom", "Custom"),
],
string="Type",
required=True,
default="custom",
tracking=True,
)
trigger_date = fields.Date(
string="Trigger Date",
required=True,
tracking=True,
help="The event that starts the clock (e.g. date of service). Excluded from "
"the count per Rule 2.514.",
)
days = fields.Integer(
string="Days",
required=True,
default=0,
tracking=True,
help="Number of days in the period. Defaults from the type but can be "
"overridden. Verify the current rule.",
)
due_date = fields.Date(
string="Due Date",
compute="_compute_due_date",
store=True,
tracking=True,
)
state = fields.Selection(
selection=[
("pending", "Pending"),
("done", "Completed"),
("waived", "Waived"),
("overdue", "Overdue"),
],
default="pending",
required=True,
copy=False,
tracking=True,
)
is_overdue = fields.Boolean(
compute="_compute_is_overdue",
search="_search_is_overdue",
string="Overdue?",
)
calendar_event_id = fields.Many2one(
"calendar.event",
string="Calendar Entry",
copy=False,
readonly=True,
)
notes = fields.Text()
# --- date math ----------------------------------------------------------
def _holiday_dates(self):
"""Hook for legal holidays. Intentionally empty — holidays are
jurisdiction/year specific and must be supplied by the content-maintenance
process (Requirements for Success #7). Return a set of date objects."""
return set()
def _roll_forward(self, due):
"""Roll a due date off weekends (and holidays, when supplied)."""
holidays = self._holiday_dates()
while due.weekday() >= 5 or due in holidays:
due += timedelta(days=1)
return due
@api.depends("trigger_date", "days")
def _compute_due_date(self):
for dl in self:
if not dl.trigger_date or not dl.days:
dl.due_date = dl.trigger_date or False
continue
raw = dl.trigger_date + timedelta(days=dl.days)
dl.due_date = dl._roll_forward(raw)
@api.depends("due_date", "state")
def _compute_is_overdue(self):
today = fields.Date.context_today(self)
for dl in self:
dl.is_overdue = bool(
dl.due_date
and dl.state == "pending"
and dl.due_date < today
)
def _search_is_overdue(self, operator, value):
if operator not in ("=", "!="):
raise ValueError(_("Unsupported operator for is_overdue search."))
today = fields.Date.context_today(self)
want_overdue = (operator == "=" and value) or (operator == "!=" and not value)
if want_overdue:
return [("state", "=", "pending"), ("due_date", "<", today)]
return ["|", ("state", "!=", "pending"), ("due_date", ">=", today)]
# --- defaults from type -------------------------------------------------
@api.onchange("deadline_type")
def _onchange_deadline_type(self):
if self.deadline_type in STANDARD_OFFSETS and not self.days:
self.days = STANDARD_OFFSETS[self.deadline_type]
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if not vals.get("days") and vals.get("deadline_type") in STANDARD_OFFSETS:
vals["days"] = STANDARD_OFFSETS[vals["deadline_type"]]
records = super().create(vals_list)
records._sync_calendar_event()
return records
def write(self, vals):
res = super().write(vals)
if {"name", "due_date", "trigger_date", "days", "state"} & set(vals):
self._sync_calendar_event()
return res
# --- calendar mirror ----------------------------------------------------
def _sync_calendar_event(self):
Cal = self.env["calendar.event"]
for dl in self:
if not dl.due_date:
continue
title = _("[Deadline] %(name)s%(matter)s",
name=dl.name, matter=dl.case_id.name or "")
vals = {
"name": title,
"allday": True,
"start_date": dl.due_date,
"stop_date": dl.due_date,
}
if dl.calendar_event_id:
dl.calendar_event_id.write(vals)
else:
dl.calendar_event_id = Cal.create(vals).id
# --- actions ------------------------------------------------------------
def action_mark_done(self):
for dl in self:
dl.state = "done"
dl.message_post(body=_("Deadline completed."))
return True
def action_waive(self):
for dl in self:
dl.state = "waived"
dl.message_post(body=_("Deadline waived."))
return True
def action_reset_pending(self):
for dl in self:
dl.state = "pending"
dl.message_post(body=_("Deadline reset to pending."))
return True
# --- cron ---------------------------------------------------------------
@api.model
def _cron_flag_overdue(self):
"""Daily: flag pending deadlines whose due date has passed."""
today = fields.Date.context_today(self)
overdue = self.search([
("state", "=", "pending"),
("due_date", "<", today),
])
for dl in overdue:
dl.state = "overdue"
dl.message_post(
body=_("Deadline is OVERDUE (due %s) — flagged by the deadline engine.")
% dl.due_date
)
return len(overdue)

View File

@@ -57,6 +57,34 @@ class FamilyLawProceeding(models.Model):
document_ids = fields.One2many(
"familylaw.document", "proceeding_id", string="Documents",
)
deadline_ids = fields.One2many(
"familylaw.deadline", "proceeding_id", string="Deadlines",
)
def action_seed_standard_deadlines(self):
"""Create the common procedural deadlines for this proceeding, dated from
today. The day-counts come from familylaw.deadline.STANDARD_OFFSETS and MUST
be confirmed against the current rule — see that module's date-math note."""
Deadline = self.env["familylaw.deadline"]
today = fields.Date.context_today(self)
labels = {
"answer": "Answer / Response due",
"disclosure": "Mandatory Disclosure due (Rule 12.285)",
"discovery": "Discovery responses due",
}
for proc in self:
existing = set(proc.deadline_ids.mapped("deadline_type"))
for dtype, label in labels.items():
if dtype in existing:
continue
Deadline.create({
"proceeding_id": proc.id,
"name": label,
"deadline_type": dtype,
"trigger_date": today,
})
proc.message_post(body="Standard deadlines seeded.")
return True
def action_close_proceeding(self):
for proc in self:

View File

@@ -14,3 +14,5 @@ access_familylaw_conflict_hit_attorney,familylaw.conflict.hit attorney,model_fam
access_familylaw_intake_wizard_user,familylaw.intake.wizard user,model_familylaw_intake_wizard,base.group_user,1,1,1,1
access_familylaw_document_user,familylaw.document staff,model_familylaw_document,group_familylaw_user,1,1,1,0
access_familylaw_document_attorney,familylaw.document attorney,model_familylaw_document,group_familylaw_attorney,1,1,1,1
access_familylaw_deadline_user,familylaw.deadline staff,model_familylaw_deadline,group_familylaw_user,1,1,1,0
access_familylaw_deadline_attorney,familylaw.deadline attorney,model_familylaw_deadline,group_familylaw_attorney,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
14 access_familylaw_intake_wizard_user familylaw.intake.wizard user model_familylaw_intake_wizard base.group_user 1 1 1 1
15 access_familylaw_document_user familylaw.document staff model_familylaw_document group_familylaw_user 1 1 1 0
16 access_familylaw_document_attorney familylaw.document attorney model_familylaw_document group_familylaw_attorney 1 1 1 1
17 access_familylaw_deadline_user familylaw.deadline staff model_familylaw_deadline group_familylaw_user 1 1 1 0
18 access_familylaw_deadline_attorney familylaw.deadline attorney model_familylaw_deadline group_familylaw_attorney 1 1 1 1

View File

@@ -1,3 +1,4 @@
from . import test_case_lifecycle
from . import test_step2
from . import test_step3
from . import test_step4

View File

@@ -0,0 +1,181 @@
# -*- coding: utf-8 -*-
"""STEP 4 tests — deadline engine.
Run just this step:
odoo -d <db> -u activeblue_familylaw --test-enable \
--test-tags familylaw_step4 --stop-after-init
Date math is tested with FIXED dates (never today()) for determinism. Overdue
behaviour is tested with dates safely in the past/future relative to any run date.
"""
from datetime import date
from odoo.tests.common import TransactionCase, tagged
@tagged("post_install", "-at_install", "familylaw", "familylaw_step4")
class TestStep4DeadlineEngine(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner = cls.env["res.partner"].create({"name": "Deadline Client"})
cls.case = cls.env["familylaw.case"].create({
"name": "Deadline Matter",
"client_id": cls.partner.id,
"case_type": "support_modification",
})
cls.proc = cls.case.proceeding_ids[0]
cls.Deadline = cls.env["familylaw.deadline"]
def _mk(self, **kw):
vals = {
"proceeding_id": self.proc.id,
"name": "Test Deadline",
"deadline_type": "custom",
"trigger_date": date(2025, 1, 1),
"days": 20,
}
vals.update(kw)
return self.Deadline.create(vals)
# --- basic math (exclude trigger day, add N days) -----------------------
def test_01_twenty_day_math(self):
# 2025-01-01 (Wed) + 20 days = 2025-01-21 (Tue), no weekend roll needed
dl = self._mk(trigger_date=date(2025, 1, 1), days=20)
self.assertEqual(dl.due_date, date(2025, 1, 21))
def test_02_forty_five_day_math(self):
# 2025-01-01 + 45 = 2025-02-15 (Sat) -> roll to 2025-02-17 (Mon)
dl = self._mk(trigger_date=date(2025, 1, 1), days=45)
self.assertEqual(dl.due_date, date(2025, 2, 17))
def test_03_thirty_day_math(self):
# 2025-01-01 + 30 = 2025-01-31 (Fri), no roll
dl = self._mk(trigger_date=date(2025, 1, 1), days=30)
self.assertEqual(dl.due_date, date(2025, 1, 31))
# --- weekend roll-forward -----------------------------------------------
def test_04_saturday_rolls_to_monday(self):
# 2025-03-07 (Fri) + 1 = 2025-03-08 (Sat) -> 2025-03-10 (Mon)
dl = self._mk(trigger_date=date(2025, 3, 7), days=1)
self.assertEqual(dl.due_date, date(2025, 3, 10))
def test_05_sunday_rolls_to_monday(self):
# 2025-03-07 (Fri) + 2 = 2025-03-09 (Sun) -> 2025-03-10 (Mon)
dl = self._mk(trigger_date=date(2025, 3, 7), days=2)
self.assertEqual(dl.due_date, date(2025, 3, 10))
def test_06_weekday_no_roll(self):
# 2025-03-07 (Fri) + 4 = 2025-03-11 (Tue), no roll
dl = self._mk(trigger_date=date(2025, 3, 7), days=4)
self.assertEqual(dl.due_date, date(2025, 3, 11))
# --- standard offsets default from type ---------------------------------
def test_07_answer_default_days(self):
dl = self.Deadline.create({
"proceeding_id": self.proc.id,
"name": "Answer due",
"deadline_type": "answer",
"trigger_date": date(2025, 1, 1),
})
self.assertEqual(dl.days, 20)
def test_08_disclosure_default_days(self):
dl = self.Deadline.create({
"proceeding_id": self.proc.id,
"name": "Disclosure due",
"deadline_type": "disclosure",
"trigger_date": date(2025, 1, 1),
})
self.assertEqual(dl.days, 45)
def test_09_explicit_days_overrides_default(self):
dl = self.Deadline.create({
"proceeding_id": self.proc.id,
"name": "Custom answer",
"deadline_type": "answer",
"trigger_date": date(2025, 1, 1),
"days": 5,
})
self.assertEqual(dl.days, 5)
# --- overdue detection --------------------------------------------------
def test_10_past_deadline_is_overdue(self):
dl = self._mk(trigger_date=date(2000, 1, 1), days=20)
self.assertTrue(dl.is_overdue)
def test_11_future_deadline_not_overdue(self):
dl = self._mk(trigger_date=date(2999, 1, 1), days=20)
self.assertFalse(dl.is_overdue)
def test_12_done_deadline_not_overdue(self):
dl = self._mk(trigger_date=date(2000, 1, 1), days=20)
dl.action_mark_done()
self.assertFalse(dl.is_overdue)
self.assertEqual(dl.state, "done")
def test_13_cron_flags_overdue(self):
dl = self._mk(trigger_date=date(2000, 1, 1), days=20)
self.assertEqual(dl.state, "pending")
self.Deadline._cron_flag_overdue()
self.assertEqual(dl.state, "overdue")
def test_14_cron_ignores_future(self):
dl = self._mk(trigger_date=date(2999, 1, 1), days=20)
self.Deadline._cron_flag_overdue()
self.assertEqual(dl.state, "pending")
def test_15_is_overdue_search(self):
past = self._mk(trigger_date=date(2000, 1, 1), days=20)
self._mk(trigger_date=date(2999, 1, 1), days=20)
found = self.Deadline.search([
("is_overdue", "=", True),
("proceeding_id", "=", self.proc.id),
])
self.assertIn(past, found)
# --- calendar mirror ----------------------------------------------------
def test_16_calendar_event_created(self):
dl = self._mk()
self.assertTrue(dl.calendar_event_id)
self.assertEqual(dl.calendar_event_id.start_date, dl.due_date)
def test_17_calendar_event_updates_on_date_change(self):
dl = self._mk(trigger_date=date(2025, 1, 1), days=20)
first = dl.calendar_event_id.start_date
dl.days = 30
self.assertNotEqual(dl.calendar_event_id.start_date, first)
self.assertEqual(dl.calendar_event_id.start_date, dl.due_date)
# --- per-proceeding isolation -------------------------------------------
def test_18_deadlines_isolated_per_proceeding(self):
proc2 = self.env["familylaw.proceeding"].create({
"case_id": self.case.id,
"name": "Second Proceeding",
"proceeding_type": "modification",
})
d1 = self._mk()
d2 = self.Deadline.create({
"proceeding_id": proc2.id,
"name": "Other deadline",
"deadline_type": "custom",
"trigger_date": date(2025, 1, 1),
"days": 10,
})
self.assertIn(d1, self.proc.deadline_ids)
self.assertNotIn(d2, self.proc.deadline_ids)
self.assertIn(d2, proc2.deadline_ids)
# --- seeding ------------------------------------------------------------
def test_19_seed_standard_deadlines(self):
self.proc.action_seed_standard_deadlines()
types = set(self.proc.deadline_ids.mapped("deadline_type"))
self.assertEqual(types, {"answer", "disclosure", "discovery"})
def test_20_seed_is_idempotent(self):
self.proc.action_seed_standard_deadlines()
n1 = len(self.proc.deadline_ids)
self.proc.action_seed_standard_deadlines()
self.assertEqual(len(self.proc.deadline_ids), n1)

View File

@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_familylaw_deadline_list" model="ir.ui.view">
<field name="name">familylaw.deadline.list</field>
<field name="model">familylaw.deadline</field>
<field name="arch" type="xml">
<list string="Deadlines" decoration-danger="is_overdue"
decoration-muted="state in ('done','waived')">
<field name="name"/>
<field name="case_id"/>
<field name="proceeding_id"/>
<field name="deadline_type"/>
<field name="trigger_date"/>
<field name="days"/>
<field name="due_date"/>
<field name="is_overdue" column_invisible="1"/>
<field name="state" widget="badge"
decoration-success="state == 'done'"
decoration-danger="state == 'overdue'"
decoration-info="state == 'pending'"/>
</list>
</field>
</record>
<record id="view_familylaw_deadline_form" model="ir.ui.view">
<field name="name">familylaw.deadline.form</field>
<field name="model">familylaw.deadline</field>
<field name="arch" type="xml">
<form string="Deadline">
<header>
<button name="action_mark_done" type="object" string="Mark Completed"
class="btn-primary" invisible="state in ('done','waived')"/>
<button name="action_waive" type="object" string="Waive"
invisible="state in ('done','waived')"/>
<button name="action_reset_pending" type="object" string="Reset"
invisible="state == 'pending'"/>
<field name="state" widget="statusbar"
statusbar_visible="pending,done"/>
</header>
<sheet>
<div class="alert alert-danger" role="alert" invisible="not is_overdue">
This deadline is OVERDUE.
</div>
<div class="oe_title">
<label for="name"/>
<h1><field name="name" placeholder="e.g. Answer to Petition due"/></h1>
</div>
<group>
<group string="Clock">
<field name="deadline_type"/>
<field name="trigger_date"/>
<field name="days"/>
<field name="due_date" readonly="1"/>
</group>
<group string="Matter">
<field name="proceeding_id"/>
<field name="case_id" readonly="1"/>
<field name="calendar_event_id" readonly="1"/>
<field name="is_overdue" readonly="1"/>
</group>
</group>
<group string="Notes">
<field name="notes" nolabel="1"/>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<record id="view_familylaw_deadline_search" model="ir.ui.view">
<field name="name">familylaw.deadline.search</field>
<field name="model">familylaw.deadline</field>
<field name="arch" type="xml">
<search string="Deadlines">
<field name="name"/>
<field name="case_id"/>
<field name="proceeding_id"/>
<filter name="overdue" string="Overdue"
domain="[('is_overdue','=',True)]"/>
<filter name="pending" string="Pending"
domain="[('state','=','pending')]"/>
<separator/>
<filter name="this_week" string="Due Within 7 Days"
domain="[('due_date','&gt;=', context_today()),
('due_date','&lt;=', context_today() + relativedelta(days=7))]"/>
<group expand="0" string="Group By">
<filter name="group_state" string="Status"
context="{'group_by':'state'}"/>
<filter name="group_type" string="Type"
context="{'group_by':'deadline_type'}"/>
<filter name="group_case" string="Matter"
context="{'group_by':'case_id'}"/>
</group>
</search>
</field>
</record>
<record id="action_familylaw_deadline" model="ir.actions.act_window">
<field name="name">Deadlines</field>
<field name="res_model">familylaw.deadline</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_familylaw_deadline_search"/>
<field name="context">{'search_default_pending': 1}</field>
</record>
</odoo>

View File

@@ -27,6 +27,13 @@
action="action_familylaw_document"
sequence="20"/>
<!-- Deadlines -->
<menuitem id="menu_familylaw_deadlines"
name="Deadlines"
parent="menu_familylaw_root"
action="action_familylaw_deadline"
sequence="30"/>
<!-- Configuration placeholder (populated in later steps) -->
<menuitem id="menu_familylaw_config"
name="Configuration"

View File

@@ -24,6 +24,8 @@
<field name="arch" type="xml">
<form string="Proceeding">
<header>
<button name="action_seed_standard_deadlines" type="object"
string="Seed Standard Deadlines"/>
<button name="action_close_proceeding" type="object"
string="Close Proceeding"
invisible="state == 'closed'"/>
@@ -65,6 +67,21 @@
</list>
</field>
</page>
<page string="Deadlines" name="deadlines">
<field name="deadline_ids"
context="{'default_proceeding_id': id}">
<list decoration-danger="is_overdue"
decoration-muted="state in ('done','waived')">
<field name="name"/>
<field name="deadline_type"/>
<field name="trigger_date"/>
<field name="days"/>
<field name="due_date"/>
<field name="is_overdue" column_invisible="1"/>
<field name="state" widget="badge"/>
</list>
</field>
</page>
</notebook>
</sheet>
<div class="oe_chatter">