Phase 1: core models, security, seed data, and backend views
Implements full Phase 1 of the activeblue_familylaw Odoo 18 module: - 17 Python models (fl.case, fl.party, fl.child, fl.support.calculation, fl.fee.waiver, fl.income.withholding, fl.deadline, fl.hearing, fl.deposition, fl.discovery, fl.document, fl.caselaw, fl.analysis, fl.ai.engine, fl.argument, fl.statute, fl.issue.tag) + hr.expense extension - 3 wizard stubs (intake, analysis, generate-packet) - Security: 4 groups (admin/paralegal/portal-petitioner/portal-respondent) + record rules scoping portal users to their own cases - Seed data: issue tags, FL statutes, FL DCF support schedule, ir.sequence - 13 backend view XML files with FL 61.30 worksheet, fee waiver eligibility banner, DV safety resources, emancipation alerts - Static CSS/JS stubs for Phase 6 portal Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
166
activeblue_familylaw/models/fl_child.py
Normal file
166
activeblue_familylaw/models/fl_child.py
Normal file
@@ -0,0 +1,166 @@
|
||||
from odoo import api, fields, models
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
||||
class FlChild(models.Model):
|
||||
_name = 'fl.child'
|
||||
_description = 'Child on Case'
|
||||
_order = 'date_of_birth'
|
||||
|
||||
case_id = fields.Many2one(
|
||||
'fl.case', string='Case',
|
||||
required=True, ondelete='cascade', index=True
|
||||
)
|
||||
name = fields.Char(string='Full Name', required=True)
|
||||
date_of_birth = fields.Date(string='Date of Birth', required=True)
|
||||
age = fields.Integer(
|
||||
string='Age', compute='_compute_age', store=True
|
||||
)
|
||||
gender = fields.Selection([
|
||||
('m', 'Male'),
|
||||
('f', 'Female'),
|
||||
('x', 'Non-binary / Other'),
|
||||
], string='Gender')
|
||||
school = fields.Char(string='School')
|
||||
medical_provider = fields.Char(string='Primary Care Provider')
|
||||
|
||||
# ── Support Per Child ──────────────────────────────────────────────────
|
||||
support_amount = fields.Float(
|
||||
string='Support Amount This Child ($)',
|
||||
help='Per-child support amount from the calculation'
|
||||
)
|
||||
health_insurance_premium = fields.Float(
|
||||
string='Health Insurance Premium ($)',
|
||||
help='Monthly cost of health insurance for this child only'
|
||||
)
|
||||
childcare_cost = fields.Float(
|
||||
string='Work-Related Childcare ($)',
|
||||
help='FL 61.30(7): Only work or job-search related childcare costs qualify'
|
||||
)
|
||||
extraordinary_expenses = fields.Float(
|
||||
string='Extraordinary Expenses ($)',
|
||||
help='FL 61.30(9): Medical or educational expenses beyond ordinary child-rearing costs'
|
||||
)
|
||||
|
||||
# ── Emancipation Tracking ──────────────────────────────────────────────
|
||||
emancipation_date = fields.Date(
|
||||
string='Emancipation Date',
|
||||
compute='_compute_emancipation',
|
||||
store=True,
|
||||
help='18th birthday (or high school graduation, whichever is later). '
|
||||
'Support terminates on this date.'
|
||||
)
|
||||
approaching_emancipation = fields.Boolean(
|
||||
string='Approaching Emancipation',
|
||||
compute='_compute_approaching_emancipation',
|
||||
store=True,
|
||||
help='True when emancipation is within 90 days'
|
||||
)
|
||||
emancipation_alert_sent = fields.Boolean(
|
||||
string='Emancipation Alert Sent',
|
||||
default=False
|
||||
)
|
||||
emancipated = fields.Boolean(
|
||||
string='Emancipated',
|
||||
compute='_compute_emancipated',
|
||||
store=True
|
||||
)
|
||||
days_until_emancipation = fields.Integer(
|
||||
string='Days Until Emancipation',
|
||||
compute='_compute_approaching_emancipation'
|
||||
)
|
||||
|
||||
# ── Early Emancipation Factors ─────────────────────────────────────────
|
||||
married = fields.Boolean(
|
||||
string='Child is Married',
|
||||
help='Marriage constitutes emancipation under FL law'
|
||||
)
|
||||
active_military = fields.Boolean(
|
||||
string='Active Military Service',
|
||||
help='Active duty military service constitutes emancipation under FL law'
|
||||
)
|
||||
declared_emancipated = fields.Boolean(
|
||||
string='Court-Declared Emancipated',
|
||||
help='Court has entered an order declaring this child emancipated'
|
||||
)
|
||||
emancipation_notes = fields.Text(
|
||||
string='Emancipation Notes',
|
||||
help='Document basis for any early emancipation determination'
|
||||
)
|
||||
|
||||
# ── Computed ──────────────────────────────────────────────────────────
|
||||
|
||||
@api.depends('date_of_birth')
|
||||
def _compute_age(self):
|
||||
today = fields.Date.today()
|
||||
for rec in self:
|
||||
if rec.date_of_birth:
|
||||
delta = relativedelta(today, rec.date_of_birth)
|
||||
rec.age = delta.years
|
||||
else:
|
||||
rec.age = 0
|
||||
|
||||
@api.depends('date_of_birth')
|
||||
def _compute_emancipation(self):
|
||||
for rec in self:
|
||||
if rec.date_of_birth:
|
||||
rec.emancipation_date = rec.date_of_birth + relativedelta(years=18)
|
||||
else:
|
||||
rec.emancipation_date = False
|
||||
|
||||
@api.depends('emancipation_date', 'married', 'active_military', 'declared_emancipated')
|
||||
def _compute_approaching_emancipation(self):
|
||||
today = fields.Date.today()
|
||||
for rec in self:
|
||||
# Early emancipation factors
|
||||
if rec.married or rec.active_military or rec.declared_emancipated:
|
||||
rec.approaching_emancipation = False
|
||||
rec.days_until_emancipation = 0
|
||||
continue
|
||||
if rec.emancipation_date:
|
||||
days_remaining = (rec.emancipation_date - today).days
|
||||
rec.days_until_emancipation = max(days_remaining, 0)
|
||||
rec.approaching_emancipation = 0 < days_remaining <= 90
|
||||
else:
|
||||
rec.approaching_emancipation = False
|
||||
rec.days_until_emancipation = 0
|
||||
|
||||
@api.depends('emancipation_date', 'married', 'active_military', 'declared_emancipated')
|
||||
def _compute_emancipated(self):
|
||||
today = fields.Date.today()
|
||||
for rec in self:
|
||||
if rec.married or rec.active_military or rec.declared_emancipated:
|
||||
rec.emancipated = True
|
||||
elif rec.emancipation_date:
|
||||
rec.emancipated = today >= rec.emancipation_date
|
||||
else:
|
||||
rec.emancipated = False
|
||||
|
||||
# ── Cron ──────────────────────────────────────────────────────────────
|
||||
|
||||
def _cron_emancipation_alerts(self):
|
||||
"""
|
||||
Run daily via ir.cron.
|
||||
Sends chatter alert on cases where a child is approaching emancipation
|
||||
and no alert has been sent yet.
|
||||
"""
|
||||
approaching = self.search([
|
||||
('approaching_emancipation', '=', True),
|
||||
('emancipation_alert_sent', '=', False),
|
||||
])
|
||||
for child in approaching:
|
||||
days = child.days_until_emancipation
|
||||
child.case_id.message_post(
|
||||
body=(
|
||||
f'<strong>⚠️ EMANCIPATION ALERT</strong><br/>'
|
||||
f'<b>{child.name}</b> will turn 18 on '
|
||||
f'<b>{child.emancipation_date}</b> '
|
||||
f'({days} days from today).<br/>'
|
||||
f'Child support for this child terminates on that date.<br/>'
|
||||
f'<b>Action required:</b> A Motion to Modify should be filed '
|
||||
f'at least 60 days before emancipation to address the change '
|
||||
f'in support obligation.'
|
||||
),
|
||||
subtype_xmlid='mail.mt_note',
|
||||
)
|
||||
child.emancipation_alert_sent = True
|
||||
Reference in New Issue
Block a user