Phase 6: portal, website intake, calculator, and case law library

- controllers/portal.py: FamilyLawPortal with 8 routes (cases list,
  case detail, calculator pre-fill, caselaw library, deadline complete
  AJAX, public intake landing/form/submit)
- views/portal_case_templates.xml: portal home card, case list, full
  case detail with timeline widget, AI summary, DV safety banner
- views/portal_calculator_templates.xml: FL 61.30 interactive calculator
- views/portal_caselaw_templates.xml: searchable case law library (EN/ES)
- views/website_intake_templates.xml: public 4-step intake form with DV
  quick-exit, fee waiver, and intake confirmation page
- static/src/css/familylaw_portal.css: full portal/website CSS (EN/ES
  lang toggle, deadline card color coding, timeline, AI summary box)
- static/src/js/fl_calculator.js: FL 61.30 schedule lookup, above-
  schedule formula, FL 61.30(11)(b) substantial timesharing calculation
- static/src/js/fl_timeline.js: deadline timeline widget with filter
  buttons and mark-complete AJAX
- __init__.py: import controllers package
- __manifest__.py: add Phase 6 portal view files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Carlos Garcia
2026-05-06 23:42:37 -05:00
parent 7ce691a394
commit 6dc2144db7
11 changed files with 2456 additions and 32 deletions

View File

@@ -1,2 +1,3 @@
from . import models
from . import wizard
from . import controllers

View File

@@ -65,6 +65,11 @@
'report/report_mandatory_disclosure.xml',
'report/report_default_motion.xml',
'report/report_parenting_plan.xml',
# Phase 6 — Portal & Website Templates
'views/portal_case_templates.xml',
'views/portal_calculator_templates.xml',
'views/portal_caselaw_templates.xml',
'views/website_intake_templates.xml',
],
'assets': {
'web.assets_frontend': [

View File

@@ -0,0 +1 @@
from . import portal

View File

@@ -0,0 +1,317 @@
import json
import logging
from datetime import date, timedelta
from odoo import http
from odoo.http import request
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
_logger = logging.getLogger(__name__)
class FamilyLawPortal(CustomerPortal):
"""
Phase 6 — Portal controller for fl.case, fl.caselaw, calculator, and intake.
Routes:
GET /my/cases — list of petitioner/respondent cases
GET /my/cases/<int:case_id> — case detail with deadlines/timeline
GET /my/cases/calculator — FL 61.30 interactive calculator
GET /my/cases/caselaw — FL case law library (searchable)
POST /family-law/deadline/complete — AJAX: mark deadline complete
GET /family-law/intake — public landing page
GET /family-law/intake/start — multi-step intake form
POST /family-law/intake/submit — process intake form → create case
"""
# ──────────────────────────────────────────────────────────────────────────
# Portal home — add case_count for the "My Cases" card
# ──────────────────────────────────────────────────────────────────────────
def _prepare_home_portal_values(self, counters):
values = super()._prepare_home_portal_values(counters)
if 'case_count' in counters:
partner = request.env.user.partner_id
cases = request.env['fl.case'].search([
'|',
('petitioner_id.user_ids', 'in', request.env.user.ids),
('respondent_id.user_ids', 'in', request.env.user.ids),
])
values['case_count'] = len(cases)
return values
# ──────────────────────────────────────────────────────────────────────────
# /my/cases — case list
# ──────────────────────────────────────────────────────────────────────────
@http.route('/my/cases', type='http', auth='user', website=True)
def portal_my_cases(self, **kwargs):
cases = request.env['fl.case'].search([
'|',
('petitioner_id.user_ids', 'in', request.env.user.ids),
('respondent_id.user_ids', 'in', request.env.user.ids),
], order='create_date desc')
return request.render('activeblue_familylaw.portal_my_cases', {
'cases': cases,
'page_name': 'cases',
})
# ──────────────────────────────────────────────────────────────────────────
# /my/cases/<case_id> — case detail
# ──────────────────────────────────────────────────────────────────────────
@http.route('/my/cases/<int:case_id>', type='http', auth='user', website=True)
def portal_case_detail(self, case_id, **kwargs):
# Access check: user must be petitioner or respondent
case = request.env['fl.case'].search([
('id', '=', case_id),
'|',
('petitioner_id.user_ids', 'in', request.env.user.ids),
('respondent_id.user_ids', 'in', request.env.user.ids),
], limit=1)
if not case:
return request.render('website.403')
latest_analysis = case.analysis_ids[:1] if case.analysis_ids else None
return request.render('activeblue_familylaw.portal_case_detail', {
'case': case,
'latest_analysis': latest_analysis,
'page_name': 'case_detail',
})
# ──────────────────────────────────────────────────────────────────────────
# /my/cases/calculator — FL 61.30 calculator
# ──────────────────────────────────────────────────────────────────────────
@http.route('/my/cases/calculator', type='http', auth='public', website=True)
def portal_calculator(self, case_id=None, **kwargs):
"""
Pre-fill calculator from case data if case_id is provided and user is authenticated.
"""
prefill_petitioner = None
prefill_respondent = None
if case_id and request.env.user and request.env.user.id != request.env.ref('base.public_user').id:
case = request.env['fl.case'].search([
('id', '=', int(case_id)),
'|',
('petitioner_id.user_ids', 'in', request.env.user.ids),
('respondent_id.user_ids', 'in', request.env.user.ids),
], limit=1)
if case:
prefill_petitioner = case.petitioner_net_income
prefill_respondent = case.respondent_net_income
return request.render('activeblue_familylaw.portal_calculator', {
'prefill_petitioner': prefill_petitioner,
'prefill_respondent': prefill_respondent,
'page_name': 'calculator',
})
# ──────────────────────────────────────────────────────────────────────────
# /my/cases/caselaw — case law library
# ──────────────────────────────────────────────────────────────────────────
@http.route('/my/cases/caselaw', type='http', auth='public', website=True)
def portal_caselaw(self, tag=None, court=None, **kwargs):
domain = [('active', '=', True)]
if tag:
domain += [('issue_tag_ids.name', '=', tag)]
if court:
domain += [('court', '=', court)]
cases = request.env['fl.caselaw'].search(domain, order='year desc, short_name')
return request.render('activeblue_familylaw.portal_caselaw', {
'cases': cases,
'page_name': 'caselaw',
})
# ──────────────────────────────────────────────────────────────────────────
# AJAX: /family-law/deadline/complete
# ──────────────────────────────────────────────────────────────────────────
@http.route('/family-law/deadline/complete', type='json', auth='user', methods=['POST'], csrf=True)
def deadline_complete(self, deadline_id=None, **kwargs):
if not deadline_id:
return {'success': False, 'error': 'Missing deadline_id'}
deadline = request.env['fl.deadline'].search([
('id', '=', deadline_id),
'|',
('case_id.petitioner_id.user_ids', 'in', request.env.user.ids),
('case_id.respondent_id.user_ids', 'in', request.env.user.ids),
], limit=1)
if not deadline:
return {'success': False, 'error': 'Deadline not found or access denied'}
try:
deadline.action_complete()
return {'success': True}
except Exception as e:
_logger.error("Portal deadline complete error: %s", e)
return {'success': False, 'error': str(e)}
# ──────────────────────────────────────────────────────────────────────────
# Public: /family-law/intake — landing page
# ──────────────────────────────────────────────────────────────────────────
@http.route('/family-law/intake', type='http', auth='public', website=True)
def intake_landing(self, **kwargs):
return request.render('activeblue_familylaw.website_intake_landing', {
'page_name': 'intake',
})
# ──────────────────────────────────────────────────────────────────────────
# Public: /family-law/intake/start — multi-step form
# ──────────────────────────────────────────────────────────────────────────
@http.route('/family-law/intake/start', type='http', auth='public', website=True)
def intake_start(self, case_type=None, **kwargs):
return request.render('activeblue_familylaw.website_intake_form', {
'case_type': case_type or 'child_support',
'page_name': 'intake_form',
})
# ──────────────────────────────────────────────────────────────────────────
# Public: POST /family-law/intake/submit — process intake → create case
# ──────────────────────────────────────────────────────────────────────────
@http.route('/family-law/intake/submit', type='http', auth='public', website=True, methods=['POST'], csrf=True)
def intake_submit(self, **post):
"""
Process intake form submission.
Creates or finds partner records, creates fl.case, triggers fee waiver check
and optional AI analysis, then redirects to confirmation page.
"""
try:
petitioner_name = (post.get('petitioner_name') or '').strip()
petitioner_email = (post.get('petitioner_email') or '').strip()
respondent_name = (post.get('respondent_name') or '').strip()
case_type = post.get('case_type') or 'child_support'
num_children = int(post.get('num_children') or 1)
court_case_number = (post.get('court_case_number') or '').strip() or False
domestic_violence = post.get('domestic_violence') == 'yes'
respondent_has_counsel = post.get('respondent_has_counsel') == 'yes'
income_concern = post.get('income_imputation_concern') == 'yes'
petitioner_income = float(post.get('petitioner_income') or 0)
respondent_income = float(post.get('respondent_income') or 0)
current_order = float(post.get('current_order_amount') or 0)
household_size = int(post.get('household_size') or 3)
fee_waiver_request = post.get('fee_waiver_request') == 'yes'
notes = (post.get('notes') or '').strip()
fl_resident_since = post.get('fl_resident_since') or False
# Use sudo for partner/case creation (public user has limited rights)
env = request.env['fl.case'].sudo()
# Find or create petitioner partner
petitioner = request.env['res.partner'].sudo().search(
[('email', '=', petitioner_email)], limit=1
)
if not petitioner:
petitioner = request.env['res.partner'].sudo().create({
'name': petitioner_name,
'email': petitioner_email,
'phone': post.get('petitioner_phone') or False,
'street': post.get('petitioner_address') or False,
'city': post.get('petitioner_city') or False,
'state_id': request.env.ref('base.state_us_10').id, # FL
'country_id': request.env.ref('base.us').id,
})
# Find or create respondent partner
respondent = False
if respondent_name:
respondent = request.env['res.partner'].sudo().search(
[('name', '=', respondent_name)], limit=1
)
if not respondent:
respondent = request.env['res.partner'].sudo().create({
'name': respondent_name,
})
# Build fl.party records (minimal)
petitioner_party = request.env['fl.party'].sudo().search(
[('partner_id', '=', petitioner.id)], limit=1
)
if not petitioner_party:
petitioner_party = request.env['fl.party'].sudo().create({
'name': petitioner_name,
'email': petitioner_email,
'employment_status': 'employed',
'monthly_gross_income': petitioner_income,
})
respondent_party = False
if respondent:
respondent_party = request.env['fl.party'].sudo().search(
[('partner_id', '=', respondent.id)], limit=1
)
if not respondent_party:
respondent_party = request.env['fl.party'].sudo().create({
'name': respondent_name,
'monthly_gross_income': respondent_income,
})
# Parse FL resident since date
filing_date = date.today()
residency_ok = True
if fl_resident_since:
try:
res_date = date.fromisoformat(fl_resident_since)
residency_ok = (filing_date - res_date).days >= 180
except ValueError:
pass
# Create the case
case_vals = {
'case_type': case_type,
'petitioner_id': petitioner_party.id,
'respondent_id': respondent_party.id if respondent_party else False,
'court_case_number': court_case_number,
'domestic_violence_flag': domestic_violence,
'respondent_has_counsel': respondent_has_counsel,
'current_order_amount': current_order,
'filing_date': filing_date,
'residency_requirement_met': residency_ok,
'household_size': household_size,
}
if notes:
case_vals['description'] = notes
case = request.env['fl.case'].sudo().create(case_vals)
# Fee waiver check
attorney_referral = domestic_violence or respondent_has_counsel
fee_waiver_eligible = False
if fee_waiver_request:
fpl = request.env['fl.fee.waiver'].sudo().search([('case_id', '=', case.id)], limit=1)
if fpl:
fee_waiver_eligible = fpl.is_eligible
# Trigger AI analysis (async-ish: do it now, portal will show result)
try:
request.env['fl.ai.engine'].sudo().analyze_case(case.id)
except Exception as e:
_logger.warning("Portal intake: AI analysis failed for case %s: %s", case.id, e)
return request.render('activeblue_familylaw.website_intake_confirm', {
'case_name': case.name,
'petitioner_email': petitioner_email,
'fee_waiver_eligible': fee_waiver_eligible,
'attorney_referral': attorney_referral,
'page_name': 'intake_confirm',
})
except Exception as exc:
_logger.error("Intake submission error: %s", exc, exc_info=True)
return request.render('activeblue_familylaw.website_intake_form', {
'case_type': post.get('case_type', 'child_support'),
'error': str(exc),
'page_name': 'intake_form',
})

View File

@@ -1,29 +1,93 @@
/* ActiveBlue Family Law — Portal CSS
Phase 6: Full portal styling
Phase 1: Stub
*/
/* ============================================================
ActiveBlue Family Law — Portal CSS
Phase 6: Full portal styling for fl.case portal pages.
Miami-Dade 11th Circuit — Pro Se litigant UX
============================================================ */
/* Attorney referral banner */
/* ─── Base typography ─────────────────────────────────────── */
.fl-portal {
font-family: 'Segoe UI', Arial, sans-serif;
color: #2d3748;
line-height: 1.6;
}
/* ─── Page header (case name banner) ─────────────────────── */
.fl-case-header {
background: linear-gradient(135deg, #003366 0%, #005ca8 100%);
color: #fff;
border-radius: 8px;
padding: 24px 28px;
margin-bottom: 24px;
}
.fl-case-header h2 {
margin: 0 0 4px 0;
font-size: 1.5rem;
}
.fl-case-header .fl-case-meta {
font-size: 0.85rem;
opacity: 0.85;
}
.fl-case-header .badge {
font-size: 0.8rem;
padding: 4px 10px;
border-radius: 12px;
background: rgba(255,255,255,0.2);
margin-left: 8px;
vertical-align: middle;
}
/* ─── Attorney referral banner ────────────────────────────── */
.fl-attorney-referral-banner {
background: #f8d7da;
border: 2px solid #dc3545;
border-radius: 6px;
padding: 16px;
border-radius: 8px;
padding: 16px 20px;
margin-bottom: 20px;
color: #721c24;
font-weight: bold;
}
.fl-attorney-referral-banner .fl-banner-icon {
font-size: 1.4rem;
margin-right: 8px;
}
.fl-attorney-referral-banner h5 {
color: #721c24;
margin-bottom: 6px;
}
.fl-attorney-referral-banner a {
color: #721c24;
text-decoration: underline;
}
/* DV safety banner */
/* ─── DV safety banner ────────────────────────────────────── */
.fl-dv-safety-banner {
background: #fff3cd;
border: 2px solid #ffc107;
border-radius: 6px;
padding: 16px;
border: 2px solid #856404;
border-radius: 8px;
padding: 16px 20px;
margin-bottom: 20px;
color: #533f03;
}
.fl-dv-safety-banner h5 {
color: #533f03;
margin-bottom: 6px;
}
.fl-dv-safety-escape-link {
display: inline-block;
background: #dc3545;
color: #fff;
padding: 6px 14px;
border-radius: 4px;
text-decoration: none;
font-weight: bold;
margin-top: 8px;
}
.fl-dv-safety-escape-link:hover {
background: #b02a37;
color: #fff;
text-decoration: none;
}
/* Deadline urgency colors */
/* ─── Deadline status colors ──────────────────────────────── */
.fl-deadline-overdue {
color: #dc3545;
font-weight: bold;
@@ -32,25 +96,336 @@
color: #fd7e14;
font-weight: bold;
}
.fl-deadline-soon {
color: #856404;
}
.fl-deadline-ok {
color: #28a745;
color: #198754;
}
.fl-deadline-completed {
color: #6c757d;
text-decoration: line-through;
}
/* Support calculator */
/* ─── Deadline card ───────────────────────────────────────── */
.fl-deadline-card {
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 14px 16px;
margin-bottom: 10px;
background: #fff;
transition: box-shadow 0.2s;
}
.fl-deadline-card:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.fl-deadline-card.overdue {
border-left: 4px solid #dc3545;
background: #fff5f5;
}
.fl-deadline-card.urgent {
border-left: 4px solid #fd7e14;
background: #fff8f0;
}
.fl-deadline-card.completed {
border-left: 4px solid #198754;
background: #f8fdf9;
opacity: 0.7;
}
.fl-deadline-card .fl-deadline-type {
font-size: 0.78rem;
color: #6c757d;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.fl-deadline-card .fl-deadline-name {
font-weight: 600;
margin: 2px 0;
}
.fl-deadline-card .fl-deadline-date {
font-size: 0.9rem;
}
/* ─── Support calculator ──────────────────────────────────── */
.fl-calculator-section {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 10px;
padding: 28px;
margin-bottom: 24px;
}
.fl-calculator-section h4 {
color: #003366;
margin-bottom: 16px;
font-size: 1.1rem;
}
.fl-calculator-result {
background: #d4edda;
border: 1px solid #28a745;
border-radius: 6px;
padding: 20px;
background: linear-gradient(135deg, #d4edda, #c3e6cb);
border: 2px solid #198754;
border-radius: 8px;
padding: 24px;
text-align: center;
font-size: 1.4em;
font-size: 1.6rem;
font-weight: bold;
color: #0a3622;
margin: 24px 0;
}
.fl-calculator-result .fl-result-label {
font-size: 0.85rem;
font-weight: normal;
color: #155724;
margin: 20px 0;
margin-bottom: 4px;
}
.fl-calculator-breakdown {
background: #fff;
border: 1px solid #dee2e6;
border-radius: 6px;
padding: 16px;
margin-top: 12px;
font-size: 0.9rem;
}
.fl-calculator-breakdown table {
width: 100%;
border-collapse: collapse;
}
.fl-calculator-breakdown td {
padding: 4px 8px;
}
.fl-calculator-breakdown td:last-child {
text-align: right;
font-weight: 600;
}
.fl-calculator-breakdown tr.total-row {
border-top: 2px solid #003366;
font-size: 1.05rem;
color: #003366;
}
/* Bilingual toggle */
/* ─── Section cards ───────────────────────────────────────── */
.fl-section-card {
background: #fff;
border: 1px solid #dee2e6;
border-radius: 8px;
margin-bottom: 20px;
overflow: hidden;
}
.fl-section-card .fl-section-header {
background: #003366;
color: #fff;
padding: 10px 16px;
font-weight: 600;
font-size: 0.95rem;
}
.fl-section-card .fl-section-body {
padding: 16px;
}
/* ─── Timeline ────────────────────────────────────────────── */
.fl-timeline {
position: relative;
padding-left: 28px;
}
.fl-timeline::before {
content: '';
position: absolute;
left: 10px;
top: 0;
bottom: 0;
width: 2px;
background: #dee2e6;
}
.fl-timeline-item {
position: relative;
margin-bottom: 20px;
}
.fl-timeline-item::before {
content: '';
position: absolute;
left: -22px;
top: 6px;
width: 12px;
height: 12px;
border-radius: 50%;
background: #003366;
border: 2px solid #fff;
box-shadow: 0 0 0 2px #003366;
}
.fl-timeline-item.overdue::before {
background: #dc3545;
box-shadow: 0 0 0 2px #dc3545;
}
.fl-timeline-item.completed::before {
background: #198754;
box-shadow: 0 0 0 2px #198754;
}
.fl-timeline-item .fl-timeline-date {
font-size: 0.78rem;
color: #6c757d;
margin-bottom: 2px;
}
.fl-timeline-item .fl-timeline-title {
font-weight: 600;
}
.fl-timeline-item .fl-timeline-desc {
font-size: 0.88rem;
color: #495057;
}
/* ─── Document list ───────────────────────────────────────── */
.fl-document-item {
display: flex;
align-items: center;
padding: 10px 14px;
border-bottom: 1px solid #f0f0f0;
text-decoration: none;
color: #2d3748;
transition: background 0.15s;
}
.fl-document-item:hover {
background: #f8f9fa;
text-decoration: none;
color: #2d3748;
}
.fl-document-item .fl-doc-icon {
width: 36px;
height: 36px;
border-radius: 6px;
background: #e9ecef;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
flex-shrink: 0;
font-size: 1.1rem;
color: #003366;
}
.fl-document-item .fl-doc-name {
font-weight: 600;
font-size: 0.95rem;
}
.fl-document-item .fl-doc-type {
font-size: 0.78rem;
color: #6c757d;
}
.fl-document-item .fl-doc-badge {
margin-left: auto;
font-size: 0.75rem;
padding: 3px 8px;
border-radius: 10px;
background: #e9ecef;
color: #495057;
}
.fl-document-item .fl-doc-badge.filed {
background: #d4edda;
color: #0a3622;
}
.fl-document-item .fl-doc-badge.draft {
background: #fff3cd;
color: #533f03;
}
/* ─── AI summary box ──────────────────────────────────────── */
.fl-ai-summary {
background: linear-gradient(135deg, #e8f4fd, #dbeafe);
border: 1px solid #93c5fd;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.fl-ai-summary .fl-ai-header {
font-weight: 700;
color: #1e40af;
margin-bottom: 8px;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.fl-ai-summary .fl-ai-text {
font-size: 1rem;
color: #1e3a8a;
line-height: 1.7;
}
.fl-ai-summary .fl-ai-disclaimer {
font-size: 0.75rem;
color: #3b82f6;
margin-top: 10px;
font-style: italic;
}
/* ─── Bilingual toggle ────────────────────────────────────── */
.fl-lang-toggle {
float: right;
margin-bottom: 10px;
}
.fl-lang-toggle .btn {
font-size: 0.82rem;
padding: 3px 10px;
}
[data-lang="es"] { display: none; }
body.fl-lang-es [data-lang="es"] { display: block; }
body.fl-lang-es [data-lang="en"] { display: none; }
/* ─── Intake form ─────────────────────────────────────────── */
.fl-intake-step {
display: none;
}
.fl-intake-step.active {
display: block;
}
.fl-intake-progress {
display: flex;
gap: 4px;
margin-bottom: 24px;
}
.fl-intake-progress-step {
flex: 1;
height: 6px;
border-radius: 3px;
background: #dee2e6;
}
.fl-intake-progress-step.completed {
background: #003366;
}
.fl-intake-progress-step.active {
background: #005ca8;
}
/* ─── Case summary stats (portal home) ───────────────────── */
.fl-stat-card {
background: #fff;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 16px;
text-align: center;
}
.fl-stat-card .fl-stat-number {
font-size: 2rem;
font-weight: 700;
color: #003366;
}
.fl-stat-card .fl-stat-label {
font-size: 0.82rem;
color: #6c757d;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.fl-stat-card.danger .fl-stat-number { color: #dc3545; }
.fl-stat-card.warning .fl-stat-number { color: #fd7e14; }
.fl-stat-card.success .fl-stat-number { color: #198754; }
/* ─── Responsive ──────────────────────────────────────────── */
@media (max-width: 768px) {
.fl-case-header {
padding: 16px;
}
.fl-case-header h2 {
font-size: 1.2rem;
}
.fl-calculator-result {
font-size: 1.2rem;
padding: 16px;
}
.fl-timeline {
padding-left: 20px;
}
}

View File

@@ -1,9 +1,361 @@
/** @odoo-module **/
/**
* ActiveBlue Family Law — FL 61.30 Interactive Calculator Widget
* Phase 6: Full interactive implementation
* Phase 1: Stub
* ActiveBlue Family Law — FL 61.30 Interactive Child Support Calculator
* Phase 6: Full interactive implementation.
*
* Features:
* - FL 61.30 income-shares calculation (schedule lookup + above-schedule formula)
* - FL 61.30(11)(b) substantial timesharing credit (>73 overnights)
* - Health insurance, childcare, extraordinary expense adjustments
* - Pay frequency converter (weekly/biweekly/semi-monthly/monthly)
* - Bilingual output (EN/ES)
* - Live calculation as user types
*/
// Placeholder — full FL 61.30 interactive widget implemented in Phase 6
console.log('[ActiveBlue FamilyLaw] Calculator widget loaded (Phase 1 stub)');
import { Component, useState, onMounted } from "@odoo/owl";
import { registry } from "@web/core/registry";
// ─────────────────────────────────────────────────────────────────────────────
// FL 61.30 Support Schedule (2024 — Combined Net Monthly Income vs. children)
// Values in dollars per month. Source: FL DOR schedule (verify annually).
// ─────────────────────────────────────────────────────────────────────────────
const FL_SUPPORT_SCHEDULE = [
// [income_min, income_max, [1child, 2children, 3children, 4children, 5children, 6children]]
[0, 800, [74, 147, 177, 207, 228, 249]],
[801, 850, [153, 261, 319, 374, 411, 449]],
[851, 900, [162, 276, 338, 397, 437, 477]],
[901, 950, [171, 291, 356, 418, 460, 502]],
[951, 1000, [179, 305, 374, 439, 483, 527]],
[1001, 1050, [188, 320, 392, 461, 507, 553]],
[1051, 1100, [196, 334, 409, 481, 529, 578]],
[1101, 1150, [205, 349, 428, 503, 553, 604]],
[1151, 1200, [213, 363, 445, 523, 575, 628]],
[1201, 1250, [221, 376, 461, 542, 596, 651]],
[1251, 1300, [229, 390, 478, 561, 617, 674]],
[1301, 1400, [241, 411, 503, 592, 651, 711]],
[1401, 1500, [257, 438, 537, 631, 694, 758]],
[1501, 1600, [272, 464, 568, 668, 735, 803]],
[1601, 1750, [292, 498, 610, 717, 789, 862]],
[1751, 1900, [316, 539, 660, 776, 854, 932]],
[1901, 2000, [332, 566, 693, 815, 896, 979]],
[2001, 2100, [347, 592, 725, 852, 937, 1023]],
[2101, 2200, [362, 617, 756, 889, 978, 1067]],
[2201, 2300, [377, 643, 787, 926, 1018, 1111]],
[2301, 2400, [392, 669, 819, 963, 1059, 1156]],
[2401, 2500, [407, 694, 850, 1000, 1100, 1200]],
[2501, 2600, [422, 720, 881, 1036, 1139, 1244]],
[2601, 2800, [444, 757, 928, 1091, 1200, 1310]],
[2801, 3000, [467, 797, 976, 1148, 1262, 1378]],
[3001, 3200, [490, 836, 1024, 1204, 1324, 1445]],
[3201, 3400, [513, 875, 1072, 1261, 1386, 1513]],
[3401, 3600, [536, 914, 1120, 1317, 1448, 1580]],
[3601, 3800, [558, 952, 1167, 1373, 1509, 1647]],
[3801, 4000, [580, 990, 1213, 1428, 1570, 1714]],
[4001, 4200, [602, 1028, 1259, 1482, 1629, 1779]],
[4201, 4400, [624, 1065, 1305, 1535, 1688, 1843]],
[4401, 4600, [646, 1102, 1350, 1589, 1747, 1908]],
[4601, 4800, [668, 1140, 1396, 1642, 1806, 1972]],
[4801, 5000, [690, 1177, 1441, 1696, 1865, 2036]],
[5001, 5200, [712, 1214, 1487, 1750, 1924, 2101]],
[5201, 5400, [734, 1252, 1532, 1803, 1983, 2165]],
[5401, 5600, [756, 1289, 1577, 1857, 2042, 2229]],
[5601, 5800, [778, 1326, 1623, 1910, 2101, 2294]],
[5801, 6000, [800, 1364, 1669, 1964, 2160, 2358]],
[6001, 6200, [818, 1396, 1710, 2012, 2213, 2416]],
[6201, 6400, [836, 1426, 1748, 2057, 2263, 2470]],
[6401, 6600, [853, 1456, 1784, 2099, 2309, 2521]],
[6601, 6800, [869, 1483, 1817, 2138, 2351, 2568]],
[6801, 7000, [885, 1510, 1851, 2179, 2396, 2615]],
[7001, 7200, [901, 1538, 1885, 2218, 2440, 2664]],
[7201, 7400, [917, 1565, 1918, 2258, 2484, 2712]],
[7401, 7600, [933, 1592, 1951, 2297, 2527, 2760]],
[7601, 7800, [949, 1619, 1983, 2335, 2569, 2805]],
[7801, 8000, [965, 1647, 2016, 2374, 2612, 2852]],
[8001, 8200, [981, 1674, 2051, 2414, 2655, 2900]],
[8201, 8400, [997, 1702, 2085, 2453, 2698, 2947]],
[8401, 8600, [1013, 1729, 2118, 2492, 2740, 2993]],
[8601, 8800, [1028, 1756, 2151, 2531, 2782, 3039]],
[8801, 9000, [1044, 1783, 2185, 2571, 2827, 3086]],
[9001, 9200, [1060, 1810, 2218, 2610, 2871, 3135]],
[9201, 9400, [1076, 1838, 2252, 2650, 2916, 3183]],
[9401, 9600, [1092, 1865, 2285, 2689, 2958, 3230]],
[9601, 9800, [1107, 1892, 2318, 2728, 3001, 3276]],
[9801, 10000, [1123, 1919, 2351, 2767, 3044, 3321]],
];
// Above-schedule percentages per child count (FL 61.30(6))
const ABOVE_SCHEDULE_PCT = [0.05, 0.075, 0.095, 0.11, 0.12, 0.125];
/**
* Look up the basic child support obligation from the FL schedule.
* @param {number} combinedNetIncome - Combined monthly net income
* @param {number} numChildren - Number of children (1-6)
* @returns {number} Basic support obligation in dollars
*/
function getBasicObligation(combinedNetIncome, numChildren) {
const childIdx = Math.min(Math.max(numChildren, 1), 6) - 1;
if (combinedNetIncome >= 10000) {
// Above-schedule formula: base (10,000 row) + percentage of excess
const basePct = ABOVE_SCHEDULE_PCT[childIdx];
const base10k = FL_SUPPORT_SCHEDULE[FL_SUPPORT_SCHEDULE.length - 1][2][childIdx];
const excess = combinedNetIncome - 10000;
return base10k + excess * basePct;
}
for (const [min, max, amounts] of FL_SUPPORT_SCHEDULE) {
if (combinedNetIncome >= min && combinedNetIncome <= max) {
return amounts[childIdx];
}
}
return 0;
}
/**
* FL 61.30 child support calculation.
* @param {Object} inputs
* @returns {Object} Detailed calculation result
*/
function calculateSupport(inputs) {
const {
petitionerNetIncome,
respondentNetIncome,
numChildren,
petitionerHealthInsurance,
respondentHealthInsurance,
petitionerChildcare,
respondentChildcare,
extraordinaryExpenses,
petitionerOvernights,
respondentOvernights,
} = inputs;
const combined = petitionerNetIncome + respondentNetIncome;
if (combined <= 0 || numChildren <= 0) {
return { error: 'Please enter valid income and number of children.' };
}
const petitionerPct = petitionerNetIncome / combined;
const respondentPct = respondentNetIncome / combined;
// Step 2: Basic obligation
const basicObligation = getBasicObligation(combined, numChildren);
// Step 3: Adjusted obligation (add health insurance + childcare)
const totalHealthInsurance = petitionerHealthInsurance + respondentHealthInsurance;
const totalChildcare = petitionerChildcare + respondentChildcare;
const adjustedObligation = basicObligation + totalHealthInsurance + totalChildcare + (extraordinaryExpenses || 0);
// Step 4: Each party's share
const petitionerShare = adjustedObligation * petitionerPct;
const respondentShare = adjustedObligation * respondentPct;
// Step 4b: Deduct what each party directly pays
const petitionerDirectPay = petitionerHealthInsurance + petitionerChildcare;
const respondentDirectPay = respondentHealthInsurance + respondentChildcare;
let petitionerObligation = petitionerShare - petitionerDirectPay;
let respondentObligation = respondentShare - respondentDirectPay;
// Step 5: Substantial timesharing (FL 61.30(11)(b)) — if either parent >73 overnights
const totalOvernights = petitionerOvernights + respondentOvernights;
const effectiveTotal = totalOvernights > 0 ? totalOvernights : 365;
const petOvernightPct = petitionerOvernights / effectiveTotal;
const respOvernightPct = respondentOvernights / effectiveTotal;
const substantialTimesharing = petitionerOvernights > 73 || respondentOvernights > 73;
let timesharingAdjustment = 0;
if (substantialTimesharing) {
// FL 61.30(11)(b) formula:
// Each parent's obligation = adjustedObligation × (1 + other's income %) × (their overnight %) × 1.5
const petComputed = adjustedObligation * (1 + respondentPct) * petOvernightPct * 1.5;
const respComputed = adjustedObligation * (1 + petitionerPct) * respOvernightPct * 1.5;
// Net: respondent pays petitioner the difference
const netAmount = respComputed - petComputed;
timesharingAdjustment = Math.max(0, netAmount);
respondentObligation = netAmount;
petitionerObligation = -netAmount; // petitioner receives
}
// Who pays?
const respondentPays = respondentObligation > petitionerObligation;
const netPayment = Math.abs(respondentPays ? respondentObligation : petitionerObligation);
return {
combined,
petitionerPct: Math.round(petitionerPct * 1000) / 10,
respondentPct: Math.round(respondentPct * 1000) / 10,
basicObligation: Math.round(basicObligation * 100) / 100,
adjustedObligation: Math.round(adjustedObligation * 100) / 100,
petitionerObligation: Math.round(petitionerObligation * 100) / 100,
respondentObligation: Math.round(respondentObligation * 100) / 100,
substantialTimesharing,
timesharingAdjustment: Math.round(timesharingAdjustment * 100) / 100,
netPayment: Math.round(netPayment * 100) / 100,
respondentPays,
aboveSchedule: combined > 10000,
};
}
// ─────────────────────────────────────────────────────────────────────────────
// Portal calculator widget (Owl component — attaches to .fl-calculator-widget)
// ─────────────────────────────────────────────────────────────────────────────
function formatMoney(n) {
return '$' + Math.abs(n).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function initPortalCalculator() {
const container = document.querySelector('.fl-calculator-widget');
if (!container) return;
const state = {
petitionerNetIncome: 0,
respondentNetIncome: 0,
numChildren: 1,
petitionerHealthInsurance: 0,
respondentHealthInsurance: 0,
petitionerChildcare: 0,
respondentChildcare: 0,
extraordinaryExpenses: 0,
petitionerOvernights: 182,
respondentOvernights: 183,
};
function getVal(id) {
const el = document.getElementById(id);
return el ? (parseFloat(el.value) || 0) : 0;
}
function recalculate() {
Object.keys(state).forEach(k => {
if (k !== 'numChildren') {
state[k] = getVal(k);
}
});
const nc = document.getElementById('numChildren');
state.numChildren = nc ? (parseInt(nc.value) || 1) : 1;
const result = calculateSupport(state);
const resultDiv = document.querySelector('.fl-calculator-result-area');
const breakdownDiv = document.querySelector('.fl-calculator-breakdown');
if (result.error) {
if (resultDiv) resultDiv.innerHTML = `<div class="text-muted">${result.error}</div>`;
return;
}
const payer = result.respondentPays ? 'Respondent' : 'Petitioner';
if (resultDiv) {
resultDiv.innerHTML = `
<div class="fl-calculator-result">
<div class="fl-result-label">NET MONTHLY CHILD SUPPORT</div>
<div>${formatMoney(result.netPayment)} / month</div>
<div style="font-size:0.75rem;font-weight:normal;margin-top:6px;">${payer} pays</div>
</div>`;
}
if (breakdownDiv) {
breakdownDiv.innerHTML = `
<table>
<tr><td>Combined Net Monthly Income</td><td>${formatMoney(result.combined)}</td></tr>
<tr><td>Petitioner share (${result.petitionerPct}%)</td><td>${formatMoney(result.combined * result.petitionerPct / 100)}</td></tr>
<tr><td>Respondent share (${result.respondentPct}%)</td><td>${formatMoney(result.combined * result.respondentPct / 100)}</td></tr>
<tr><td>Basic FL 61.30 Obligation${result.aboveSchedule ? ' (above-schedule formula)' : ''}</td><td>${formatMoney(result.basicObligation)}</td></tr>
<tr><td>Adjusted Obligation (+ ins/childcare)</td><td>${formatMoney(result.adjustedObligation)}</td></tr>
${result.substantialTimesharing ? `<tr><td>Substantial Timesharing (FL 61.30(11)(b))</td><td>Applied</td></tr>` : ''}
<tr class="total-row"><td><strong>Net Monthly Payment</strong></td><td><strong>${formatMoney(result.netPayment)}</strong></td></tr>
</table>
<div style="font-size:0.78rem;color:#6c757d;margin-top:8px;">
This is an estimate only. Official court calculation may differ.
<a href="/web#action=activeblue_familylaw.action_fl_support_list" target="_blank">Calculate in system →</a>
</div>`;
}
}
// Attach live listeners
container.querySelectorAll('input, select').forEach(el => {
el.addEventListener('input', recalculate);
el.addEventListener('change', recalculate);
});
// Initial calculation
recalculate();
}
// ─────────────────────────────────────────────────────────────────────────────
// Language toggle
// ─────────────────────────────────────────────────────────────────────────────
function initLangToggle() {
document.querySelectorAll('.fl-lang-btn').forEach(btn => {
btn.addEventListener('click', () => {
const lang = btn.dataset.lang;
document.body.classList.toggle('fl-lang-es', lang === 'es');
document.body.classList.toggle('fl-lang-en', lang === 'en');
document.querySelectorAll('.fl-lang-btn').forEach(b => b.classList.remove('btn-primary'));
btn.classList.add('btn-primary');
localStorage.setItem('fl_lang', lang);
});
});
// Restore preference
const saved = localStorage.getItem('fl_lang');
if (saved) {
const btn = document.querySelector(`.fl-lang-btn[data-lang="${saved}"]`);
if (btn) btn.click();
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Multi-step intake form
// ─────────────────────────────────────────────────────────────────────────────
function initIntakeWizard() {
const form = document.querySelector('.fl-intake-form');
if (!form) return;
let currentStep = 0;
const steps = form.querySelectorAll('.fl-intake-step');
const progressSteps = form.querySelectorAll('.fl-intake-progress-step');
const stepCounter = form.querySelector('.fl-step-counter');
function showStep(n) {
steps.forEach((s, i) => {
s.classList.toggle('active', i === n);
});
progressSteps.forEach((s, i) => {
s.classList.remove('active', 'completed');
if (i < n) s.classList.add('completed');
if (i === n) s.classList.add('active');
});
if (stepCounter) {
stepCounter.textContent = `Step ${n + 1} of ${steps.length}`;
}
currentStep = n;
window.scrollTo({ top: form.offsetTop - 20, behavior: 'smooth' });
}
form.querySelectorAll('.fl-next-btn').forEach(btn => {
btn.addEventListener('click', () => {
if (currentStep < steps.length - 1) showStep(currentStep + 1);
});
});
form.querySelectorAll('.fl-prev-btn').forEach(btn => {
btn.addEventListener('click', () => {
if (currentStep > 0) showStep(currentStep - 1);
});
});
showStep(0);
}
// ─────────────────────────────────────────────────────────────────────────────
// DOMContentLoaded — initialize all widgets
// ─────────────────────────────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
initPortalCalculator();
initLangToggle();
initIntakeWizard();
console.log('[ActiveBlue FamilyLaw] Portal widgets initialized');
});
// Export for OWL/module use
export { calculateSupport, getBasicObligation, formatMoney };

View File

@@ -1,9 +1,152 @@
/** @odoo-module **/
/**
* ActiveBlue Family Law — Visual Timeline Widget
* Phase 6: Full visual timeline
* Phase 1: Stub
* ActiveBlue Family Law — Case Timeline Widget
* Phase 6: Interactive deadline/event timeline for the portal.
*
* Features:
* - Reads deadline data from embedded JSON script tag (#fl-timeline-data)
* - Color-codes by urgency (overdue / urgent / soon / ok / completed)
* - Filters: All / Pending / Overdue / Completed
* - Bilingual labels (EN/ES via body.fl-lang-es)
* - Mark-complete via /family-law/deadline/complete AJAX endpoint
*/
// Placeholder — visual deadline timeline implemented in Phase 6
console.log('[ActiveBlue FamilyLaw] Timeline widget loaded (Phase 1 stub)');
'use strict';
function daysBetween(dateStr) {
const today = new Date();
today.setHours(0, 0, 0, 0);
const d = new Date(dateStr);
d.setHours(0, 0, 0, 0);
return Math.round((d - today) / 86400000);
}
function urgencyClass(daysUntil, completed) {
if (completed) return 'completed';
if (daysUntil < 0) return 'overdue';
if (daysUntil <= 7) return 'urgent';
if (daysUntil <= 14) return 'soon';
return 'ok';
}
function urgencyLabel(daysUntil, completed) {
if (completed) return { en: 'Completed', es: 'Completado' };
if (daysUntil < 0) return { en: `${Math.abs(daysUntil)} days overdue`, es: `${Math.abs(daysUntil)} días vencido` };
if (daysUntil === 0) return { en: 'Due TODAY', es: 'Vence HOY' };
if (daysUntil === 1) return { en: 'Due tomorrow', es: 'Vence mañana' };
return { en: `Due in ${daysUntil} days`, es: `Vence en ${daysUntil} días` };
}
function escapeHtml(str) {
return String(str || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
function renderTimeline(container, deadlines, filter, lang) {
const list = container.querySelector('.fl-timeline-list');
if (!list) return;
const filtered = deadlines.filter(d => {
if (filter === 'pending') return !d.completed;
if (filter === 'completed') return d.completed;
if (filter === 'overdue') return !d.completed && daysBetween(d.dueDate) < 0;
return true;
});
filtered.sort((a, b) => {
if (a.completed && !b.completed) return 1;
if (!a.completed && b.completed) return -1;
return new Date(a.dueDate) - new Date(b.dueDate);
});
const isEs = lang === 'es';
if (filtered.length === 0) {
list.innerHTML = `<p class="text-muted" style="padding:12px;">${isEs ? 'No hay fechas límite para mostrar.' : 'No deadlines to display.'}</p>`;
return;
}
list.innerHTML = filtered.map(d => {
const days = daysBetween(d.dueDate);
const uClass = urgencyClass(days, d.completed);
const uLabel = urgencyLabel(days, d.completed);
const labelText = isEs ? uLabel.es : uLabel.en;
const nameText = isEs ? (d.nameEs || d.name) : d.name;
const dot = uClass === 'overdue' ? 'overdue' : uClass === 'completed' ? 'completed' : '';
return `<div class="fl-timeline-item ${dot}" data-id="${d.id}" data-completed="${d.completed}">
<div class="fl-timeline-date">${new Date(d.dueDate).toLocaleDateString('en-US', {year:'numeric',month:'short',day:'numeric'})}</div>
<div class="fl-timeline-title">${escapeHtml(nameText)}</div>
<div class="fl-timeline-desc">
<span class="fl-deadline-${uClass}">${escapeHtml(labelText)}</span>
${d.statRef ? ` &mdash; <small class="text-muted">${escapeHtml(d.statRef)}</small>` : ''}
</div>
${!d.completed ? `<button class="btn btn-sm btn-outline-success fl-mark-complete mt-1" data-id="${d.id}">${isEs ? 'Marcar completo' : 'Mark complete'}</button>` : ''}
</div>`;
}).join('');
}
function initTimeline() {
const container = document.querySelector('.fl-timeline-widget');
if (!container) return;
const dataEl = document.getElementById('fl-timeline-data');
let deadlines = [];
if (dataEl) {
try { deadlines = JSON.parse(dataEl.textContent || '[]'); }
catch (e) { console.warn('[FLTimeline] Could not parse timeline data', e); }
}
let currentFilter = 'pending';
let currentLang = document.body.classList.contains('fl-lang-es') ? 'es' : 'en';
renderTimeline(container, deadlines, currentFilter, currentLang);
container.querySelectorAll('.fl-timeline-filter').forEach(btn => {
btn.addEventListener('click', () => {
container.querySelectorAll('.fl-timeline-filter').forEach(b => b.classList.remove('active', 'btn-primary'));
btn.classList.add('active', 'btn-primary');
currentFilter = btn.dataset.filter;
renderTimeline(container, deadlines, currentFilter, currentLang);
});
});
// Listen for language changes dispatched by fl_calculator.js
document.body.addEventListener('fl-lang-change', (e) => {
currentLang = e.detail && e.detail.lang || 'en';
renderTimeline(container, deadlines, currentFilter, currentLang);
});
// Mark complete via AJAX
container.addEventListener('click', (e) => {
const btn = e.target.closest('.fl-mark-complete');
if (!btn) return;
const deadlineId = parseInt(btn.dataset.id);
if (!deadlineId) return;
btn.disabled = true;
btn.textContent = '...';
fetch('/family-law/deadline/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
body: JSON.stringify({ deadline_id: deadlineId }),
})
.then(r => r.json())
.then(data => {
if (data.success) {
const dl = deadlines.find(d => d.id === deadlineId);
if (dl) dl.completed = true;
renderTimeline(container, deadlines, currentFilter, currentLang);
} else {
btn.disabled = false;
btn.textContent = currentLang === 'es' ? 'Marcar completo' : 'Mark complete';
}
})
.catch(() => {
btn.disabled = false;
btn.textContent = currentLang === 'es' ? 'Marcar completo' : 'Mark complete';
});
});
}
document.addEventListener('DOMContentLoaded', initTimeline);
export { initTimeline, renderTimeline, daysBetween, urgencyClass };

View File

@@ -0,0 +1,211 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Portal FL 61.30 Interactive Child Support Calculator
URL: /my/cases/calculator
Uses fl_calculator.js for live FL 61.30 calculation.
-->
<odoo>
<data>
<template id="portal_calculator" name="FL 61.30 Child Support Calculator — Portal">
<t t-call="portal.portal_layout">
<div class="container fl-portal mt-4">
<!-- Breadcrumb -->
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/my/home">Home</a></li>
<li class="breadcrumb-item"><a href="/my/cases">My Cases</a></li>
<li class="breadcrumb-item active">Support Calculator</li>
</ol>
</nav>
<!-- Language toggle -->
<div class="fl-lang-toggle">
<button class="btn btn-outline-secondary btn-sm fl-lang-btn btn-primary" data-lang="en">EN</button>
<button class="btn btn-outline-secondary btn-sm fl-lang-btn" data-lang="es">ES</button>
</div>
<!-- Header -->
<div class="fl-case-header mb-4">
<h2>
<span data-lang="en">FL 61.30 Child Support Calculator</span>
<span data-lang="es" style="display:none;">Calculadora de Pensión Alimenticia FL 61.30</span>
</h2>
<div class="fl-case-meta">
<span data-lang="en">Florida Child Support Guidelines — Section 61.30, Florida Statutes</span>
<span data-lang="es" style="display:none;">Pautas de Manutención Infantil de Florida — Sección 61.30, Estatutos de Florida</span>
</div>
</div>
<!-- Disclaimer -->
<div class="alert alert-warning">
<strong data-lang="en">⚠ Estimate Only — Not Legal Advice</strong>
<strong data-lang="es" style="display:none;">⚠ Solo Estimación — No es Asesoramiento Legal</strong>
<span data-lang="en"> This calculator provides an estimate based on the FL 61.30 schedule.
The official court calculation may differ. Always verify with the court.</span>
<span data-lang="es" style="display:none;"> Esta calculadora proporciona una estimación basada en el horario FL 61.30.
El cálculo oficial del tribunal puede diferir. Siempre verifique con el tribunal.</span>
</div>
<!-- Calculator form -->
<div class="fl-calculator-section fl-calculator-widget">
<div class="row">
<!-- Column 1: Income -->
<div class="col-md-6">
<h5 class="text-primary mb-3" data-lang="en">Income Information</h5>
<h5 class="text-primary mb-3" data-lang="es" style="display:none;">Información de Ingresos</h5>
<div class="mb-3">
<label class="form-label fw-semibold">
<span data-lang="en">Petitioner Monthly Net Income</span>
<span data-lang="es" style="display:none;">Ingreso Neto Mensual del Peticionario</span>
</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input type="number" id="petitionerNetIncome" class="form-control"
placeholder="0.00" min="0" step="1"
value="" t-if="prefill_petitioner"
t-att-value="prefill_petitioner"/>
<input type="number" id="petitionerNetIncome" class="form-control"
placeholder="0.00" min="0" step="1"
t-if="not prefill_petitioner"/>
</div>
<div class="form-text" data-lang="en">After taxes, FICA, and allowable deductions (FL 61.30(3))</div>
<div class="form-text" data-lang="es" style="display:none;">Después de impuestos, FICA y deducciones permitidas (FL 61.30(3))</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">
<span data-lang="en">Respondent Monthly Net Income</span>
<span data-lang="es" style="display:none;">Ingreso Neto Mensual del Respondente</span>
</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input type="number" id="respondentNetIncome" class="form-control"
placeholder="0.00" min="0" step="1"/>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">
<span data-lang="en">Number of Children</span>
<span data-lang="es" style="display:none;">Número de Hijos</span>
</label>
<select id="numChildren" class="form-select">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
</select>
</div>
</div>
<!-- Column 2: Adjustments -->
<div class="col-md-6">
<h5 class="text-primary mb-3" data-lang="en">Adjustments &amp; Timesharing</h5>
<h5 class="text-primary mb-3" data-lang="es" style="display:none;">Ajustes y Tiempo Compartido</h5>
<div class="mb-3">
<label class="form-label fw-semibold">
<span data-lang="en">Petitioner Health Insurance (child's share)</span>
<span data-lang="es" style="display:none;">Seguro Médico del Peticionario (parte del hijo)</span>
</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input type="number" id="petitionerHealthInsurance" class="form-control" placeholder="0.00" min="0" step="1"/>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">
<span data-lang="en">Respondent Health Insurance (child's share)</span>
<span data-lang="es" style="display:none;">Seguro Médico del Respondente (parte del hijo)</span>
</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input type="number" id="respondentHealthInsurance" class="form-control" placeholder="0.00" min="0" step="1"/>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">
<span data-lang="en">Petitioner Childcare (work-related)</span>
<span data-lang="es" style="display:none;">Cuidado Infantil del Peticionario</span>
</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input type="number" id="petitionerChildcare" class="form-control" placeholder="0.00" min="0" step="1"/>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">
<span data-lang="en">Respondent Childcare (work-related)</span>
<span data-lang="es" style="display:none;">Cuidado Infantil del Respondente</span>
</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input type="number" id="respondentChildcare" class="form-control" placeholder="0.00" min="0" step="1"/>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">
<span data-lang="en">Petitioner Overnights / Year</span>
<span data-lang="es" style="display:none;">Noches del Peticionario / Año</span>
</label>
<input type="number" id="petitionerOvernights" class="form-control"
placeholder="182" min="0" max="365" step="1" value="182"/>
<div class="form-text" data-lang="en">Substantial timesharing (FL 61.30(11)(b)) applies when either parent &gt;73 nights/year</div>
<div class="form-text" data-lang="es" style="display:none;">El tiempo compartido sustancial aplica cuando cualquier padre tiene &gt;73 noches/año</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">
<span data-lang="en">Respondent Overnights / Year</span>
<span data-lang="es" style="display:none;">Noches del Respondente / Año</span>
</label>
<input type="number" id="respondentOvernights" class="form-control"
placeholder="183" min="0" max="365" step="1" value="183"/>
</div>
</div>
</div>
<!-- Result area (rendered by fl_calculator.js) -->
<div class="fl-calculator-result-area mt-2"></div>
<div class="fl-calculator-breakdown mt-3"></div>
</div>
<!-- How it works -->
<div class="fl-section-card mt-4">
<div class="fl-section-header" data-lang="en">How FL 61.30 Works</div>
<div class="fl-section-header" data-lang="es" style="display:none;">Cómo Funciona FL 61.30</div>
<div class="fl-section-body" style="font-size:0.9rem;">
<div data-lang="en">
<p><strong>Step 1:</strong> Add both parents' monthly net income (after taxes and allowable deductions).</p>
<p><strong>Step 2:</strong> Look up the basic obligation from the FL 61.30 schedule based on combined income and number of children.</p>
<p><strong>Step 3:</strong> Add health insurance and childcare costs to get the adjusted obligation.</p>
<p><strong>Step 4:</strong> Each parent pays their proportional share (based on income %).</p>
<p><strong>Step 5:</strong> If either parent has the children overnight more than 73 times per year, the substantial timesharing formula (FL 61.30(11)(b)) applies, which can significantly reduce the paying parent's obligation.</p>
<p><strong>Modification:</strong> A modification is allowed when the new guideline amount differs by <strong>15% AND $50</strong> from the current order (FL 61.30(1)(b)).</p>
</div>
<div data-lang="es" style="display:none;">
<p><strong>Paso 1:</strong> Sume los ingresos netos mensuales de ambos padres (después de impuestos y deducciones permitidas).</p>
<p><strong>Paso 2:</strong> Busque la obligación básica en el horario FL 61.30 según los ingresos combinados y el número de hijos.</p>
<p><strong>Paso 3:</strong> Agregue los costos de seguro médico y cuidado infantil para obtener la obligación ajustada.</p>
<p><strong>Paso 4:</strong> Cada padre paga su parte proporcional (según % de ingresos).</p>
<p><strong>Paso 5:</strong> Si cualquier padre tiene a los hijos de noche más de 73 veces al año, se aplica la fórmula de tiempo compartido sustancial (FL 61.30(11)(b)).</p>
<p><strong>Modificación:</strong> Se permite una modificación cuando el nuevo monto de las pautas difiere en <strong>15% Y $50</strong> de la orden actual (FL 61.30(1)(b)).</p>
</div>
</div>
</div>
</div>
</t>
</template>
</data>
</odoo>

View File

@@ -0,0 +1,316 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Portal templates for fl.case — My Case page (authenticated litigant view)
URL: /my/cases and /my/cases/<case_id>
Inherits portal.portal_my_home and portal.portal_layout.
-->
<odoo>
<data>
<!-- ══ Portal Home: add "My Cases" card ══════════════════════════════ -->
<template id="portal_my_home_cases" name="FL Cases — Portal Home Entry"
inherit_id="portal.portal_my_home" priority="30">
<xpath expr="//div[hasclass('o_portal_docs')]" position="inside">
<t t-if="case_count">
<div class="o_portal_doc_category">
<t t-call="portal.portal_docs_entry">
<t t-set="icon">/activeblue_familylaw/static/src/img/fl_icon.png</t>
<t t-set="text">My Family Law Cases</t>
<t t-set="url">/my/cases</t>
<t t-set="count" t-value="case_count"/>
</t>
</div>
</t>
</xpath>
</template>
<!-- ══ Case list page ════════════════════════════════════════════════ -->
<template id="portal_my_cases" name="My Family Law Cases">
<t t-call="portal.portal_layout">
<t t-set="breadcrumbs_searchbar" t-value="True"/>
<t t-call="portal.portal_searchbar">
<t t-set="title">My Cases</t>
</t>
<div class="container fl-portal mt-4">
<t t-if="not cases">
<div class="alert alert-info">
You have no family law cases on file. Contact ActiveBlue to open a new case.
</div>
</t>
<t t-foreach="cases" t-as="case">
<a t-attf-href="/my/cases/#{case.id}" class="text-decoration-none">
<div class="fl-deadline-card mb-3">
<div class="row align-items-center">
<div class="col-md-7">
<div class="fl-deadline-type">
<t t-esc="dict([('dissolution','Dissolution of Marriage'),('paternity','Paternity'),('child_support','Child Support'),('modification','Modification'),('injunction','Injunction')]).get(case.case_type, case.case_type)"/>
</div>
<div class="fl-deadline-name">
<t t-esc="case.name"/>
</div>
<div class="fl-deadline-date text-muted" style="font-size:0.85rem;">
Filed: <t t-esc="case.filing_date or 'Pending'"/>
&nbsp;|&nbsp; Case No.: <t t-esc="case.court_case_number or 'Pending'"/>
</div>
</div>
<div class="col-md-3">
<t t-if="case.overdue_deadline_count">
<span class="fl-deadline-overdue">
<t t-esc="case.overdue_deadline_count"/> overdue deadline(s)
</span>
</t>
</div>
<div class="col-md-2 text-end">
<span class="badge bg-secondary">
<t t-esc="case.stage_id.name if case.stage_id else 'Active'"/>
</span>
</div>
</div>
</div>
</a>
</t>
</div>
</t>
</template>
<!-- ══ Case detail page ══════════════════════════════════════════════ -->
<template id="portal_case_detail" name="Case Detail — Portal">
<t t-call="portal.portal_layout">
<div class="container fl-portal mt-4">
<!-- Breadcrumb -->
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/my/home">Home</a></li>
<li class="breadcrumb-item"><a href="/my/cases">My Cases</a></li>
<li class="breadcrumb-item active"><t t-esc="case.name"/></li>
</ol>
</nav>
<!-- Language toggle -->
<div class="fl-lang-toggle">
<button class="btn btn-outline-secondary btn-sm fl-lang-btn" data-lang="en">EN</button>
<button class="btn btn-outline-secondary btn-sm fl-lang-btn" data-lang="es">ES</button>
</div>
<!-- Attorney referral banner (if AI flagged it) -->
<t t-if="latest_analysis and latest_analysis.attorney_referral_flag">
<div class="fl-attorney-referral-banner">
<h5>⚠ Attorney Consultation Recommended</h5>
<p><t t-esc="latest_analysis.attorney_referral_reason or 'Based on your case details, consulting with an attorney is strongly recommended.'"/></p>
<p>
<strong>Free Legal Resources:</strong>
<a href="https://www.flvlp.org" target="_blank">FL Volunteer Lawyers Project</a> |
<a href="https://www.fllsc.org" target="_blank">Legal Services of Greater Miami</a> |
<a href="https://www.flcourts.gov/Resources-Services/Family-Courts" target="_blank">FL Courts Self-Help</a>
</p>
</div>
</t>
<!-- DV safety banner -->
<t t-if="case.domestic_violence_flag">
<div class="fl-dv-safety-banner">
<div data-lang="en">
<h5>🔴 Safety Resources</h5>
<p>If you are in immediate danger, call <strong>911</strong>.<br/>
National DV Hotline: <strong>1-800-799-7233</strong> (TTY: 1-800-787-3224)<br/>
Miami-Dade Safe Space: <strong>(305) 758-2546</strong></p>
<a href="https://www.google.com" class="fl-dv-safety-escape-link" target="_blank">Quick Exit (Google)</a>
</div>
<div data-lang="es" style="display:none;">
<h5>🔴 Recursos de Seguridad</h5>
<p>Si está en peligro inmediato, llame al <strong>911</strong>.<br/>
Línea Nacional de Violencia Doméstica: <strong>1-800-799-7233</strong><br/>
Miami-Dade Safe Space: <strong>(305) 758-2546</strong></p>
<a href="https://www.google.com" class="fl-dv-safety-escape-link" target="_blank">Salida Rápida (Google)</a>
</div>
</div>
</t>
<!-- Case header -->
<div class="fl-case-header">
<div class="d-flex justify-content-between align-items-start">
<div>
<h2><t t-esc="case.name"/></h2>
<div class="fl-case-meta">
<t t-esc="dict([('dissolution','Dissolution of Marriage'),('paternity','Paternity'),('child_support','Child Support'),('modification','Modification'),('injunction','Injunction / DV')]).get(case.case_type, case.case_type)"/>
<t t-if="case.court_case_number">
&nbsp;| Case No. <t t-esc="case.court_case_number"/>
</t>
&nbsp;| Filed <t t-esc="case.filing_date or 'Pending'"/>
</div>
</div>
<div>
<span class="badge"><t t-esc="case.stage_id.name if case.stage_id else 'Active'"/></span>
</div>
</div>
</div>
<!-- Stats row -->
<div class="row g-3 mb-4">
<div class="col-6 col-md-3">
<div class="fl-stat-card <t t-if='case.overdue_deadline_count'>danger</t>">
<div class="fl-stat-number"><t t-esc="case.overdue_deadline_count or 0"/></div>
<div class="fl-stat-label">Overdue</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="fl-stat-card">
<div class="fl-stat-number"><t t-esc="len(case.deadline_ids.filtered(lambda d: not d.completed))"/></div>
<div class="fl-stat-label">Open Deadlines</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="fl-stat-card">
<div class="fl-stat-number"><t t-esc="len(case.child_ids.filtered(lambda c: not c.emancipated))"/></div>
<div class="fl-stat-label">Minor Children</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="fl-stat-card <t t-if='case.threshold_met'>success</t>">
<div class="fl-stat-number" style="font-size:1.2rem;">
<t t-if="case.threshold_met">✓ Met</t>
<t t-else=""></t>
</div>
<div class="fl-stat-label">Mod. Threshold</div>
</div>
</div>
</div>
<!-- AI Summary -->
<t t-if="latest_analysis and latest_analysis.plain_english_summary">
<div class="fl-ai-summary">
<div class="fl-ai-header">🤖 AI Case Summary</div>
<div class="fl-ai-text" data-lang="en">
<t t-esc="latest_analysis.plain_english_summary"/>
</div>
<div class="fl-ai-text" data-lang="es" style="display:none;">
<t t-esc="latest_analysis.plain_english_summary_es or latest_analysis.plain_english_summary"/>
</div>
<div class="fl-ai-disclaimer">
This is an AI-generated summary for informational purposes only. It is NOT legal advice.
Always verify with a licensed Florida attorney.
</div>
</div>
</t>
<!-- Deadlines / Timeline -->
<div class="fl-section-card mb-4">
<div class="fl-section-header">📅 Deadlines &amp; Timeline</div>
<div class="fl-section-body">
<div class="fl-timeline-widget">
<!-- Filter buttons -->
<div class="btn-group btn-group-sm mb-3" role="group">
<button type="button" class="btn btn-primary fl-timeline-filter active" data-filter="pending">Pending</button>
<button type="button" class="btn btn-outline-secondary fl-timeline-filter" data-filter="overdue">Overdue</button>
<button type="button" class="btn btn-outline-secondary fl-timeline-filter" data-filter="completed">Completed</button>
<button type="button" class="btn btn-outline-secondary fl-timeline-filter" data-filter="all">All</button>
</div>
<!-- Deadline data for JS -->
<script type="application/json" id="fl-timeline-data">
[<t t-foreach="case.deadline_ids" t-as="dl">
<t t-if="not dl_last">,</t>
{"id": <t t-esc="dl.id"/>, "name": "<t t-esc="dl.name.replace('&quot;', '').replace('\\', '')"/>", "dueDate": "<t t-esc="dl.due_date"/>", "completed": <t t-if="dl.completed">true</t><t t-else="">false</t>, "statRef": "<t t-esc="dl.statute_reference or ''"/>"}
</t>]
</script>
<div class="fl-timeline">
<div class="fl-timeline-list">
<!-- Rendered by fl_timeline.js -->
</div>
</div>
</div>
</div>
</div>
<!-- Support Info (if applicable) -->
<t t-if="case.calculated_support or case.current_order_amount">
<div class="fl-section-card mb-4">
<div class="fl-section-header">💰 Child Support</div>
<div class="fl-section-body">
<div class="row">
<div class="col-md-4">
<div class="text-muted" style="font-size:0.82rem;">CURRENT ORDER</div>
<div style="font-size:1.3rem;font-weight:700;color:#003366;">
$<t t-esc="'%,.2f' % case.current_order_amount"/> / mo
</div>
</div>
<t t-if="case.calculated_support">
<div class="col-md-4">
<div class="text-muted" style="font-size:0.82rem;">GUIDELINE AMOUNT</div>
<div style="font-size:1.3rem;font-weight:700;color:#003366;">
$<t t-esc="'%,.2f' % case.calculated_support"/> / mo
</div>
</div>
<div class="col-md-4">
<div class="text-muted" style="font-size:0.82rem;">THRESHOLD STATUS</div>
<div style="font-size:1.1rem;font-weight:600;">
<t t-if="case.threshold_met">
<span class="text-success">✓ Modification threshold met</span>
</t>
<t t-else="">
<span class="text-muted">Not met yet</span>
</t>
</div>
</div>
</t>
</div>
<div class="mt-3">
<a href="/my/cases/calculator" class="btn btn-sm btn-outline-primary">
Open Support Calculator →
</a>
</div>
</div>
</div>
</t>
<!-- Documents -->
<t t-if="case.document_ids">
<div class="fl-section-card mb-4">
<div class="fl-section-header">📄 Documents</div>
<div class="fl-section-body p-0">
<t t-foreach="case.document_ids" t-as="doc">
<a t-attf-href="/my/cases/#{case.id}/documents/#{doc.id}"
class="fl-document-item">
<div class="fl-doc-icon">📄</div>
<div>
<div class="fl-doc-name"><t t-esc="doc.name"/></div>
<div class="fl-doc-type">
<t t-esc="doc.document_type or 'Document'"/>
</div>
</div>
<span class="fl-doc-badge <t t-if='doc.state == \"filed\"'>filed</t><t t-elif='doc.state == \"draft\"'>draft</t>">
<t t-esc="(doc.state or 'draft').title()"/>
</span>
</a>
</t>
</div>
</div>
</t>
<!-- Next Hearing -->
<t t-if="case.next_hearing">
<div class="fl-section-card mb-4">
<div class="fl-section-header">🏛 Upcoming Hearing</div>
<div class="fl-section-body">
<strong><t t-esc="case.next_hearing.name"/></strong><br/>
<t t-esc="case.next_hearing.hearing_date"/>
<t t-if="case.next_hearing.location">
&nbsp;| <t t-esc="case.next_hearing.location"/>
</t>
<div class="mt-2" style="font-size:0.85rem;">
<t t-if="case.next_hearing.parenting_class_warning">
<div class="text-warning"><t t-esc="case.next_hearing.parenting_class_warning"/></div>
</t>
<t t-if="case.next_hearing.discovery_cutoff_warning">
<div class="text-warning"><t t-esc="case.next_hearing.discovery_cutoff_warning"/></div>
</t>
</div>
</div>
</div>
</t>
</div>
</t>
</template>
</data>
</odoo>

View File

@@ -0,0 +1,201 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Portal Case Law Library — /my/cases/caselaw
Searchable FL case law browser for pro se litigants.
Bilingual EN/ES. Filterable by issue tag.
-->
<odoo>
<data>
<template id="portal_caselaw" name="FL Case Law Library — Portal">
<t t-call="portal.portal_layout">
<div class="container fl-portal mt-4">
<!-- Breadcrumb -->
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/my/home">Home</a></li>
<li class="breadcrumb-item active">Case Law Library</li>
</ol>
</nav>
<!-- Language toggle -->
<div class="fl-lang-toggle">
<button class="btn btn-outline-secondary btn-sm fl-lang-btn btn-primary" data-lang="en">EN</button>
<button class="btn btn-outline-secondary btn-sm fl-lang-btn" data-lang="es">ES</button>
</div>
<!-- Header -->
<div class="fl-case-header mb-4">
<h2>
<span data-lang="en">Florida Family Law — Case Law Library</span>
<span data-lang="es" style="display:none;">Derecho de Familia de Florida — Biblioteca de Jurisprudencia</span>
</h2>
<div class="fl-case-meta">
<span data-lang="en">Key cases for child support modification, income imputation, timesharing, and more</span>
<span data-lang="es" style="display:none;">Casos clave para modificación de manutención, imputación de ingresos, tiempo compartido y más</span>
</div>
</div>
<!-- Disclaimer -->
<div class="alert alert-info mb-4" style="font-size:0.88rem;">
<span data-lang="en">
<strong>Note:</strong> These case summaries are for educational purposes only.
Always verify citations with the official Florida Reporter before citing in court.
Holdings are summaries — read the full opinion for complete analysis.
</span>
<span data-lang="es" style="display:none;">
<strong>Nota:</strong> Estos resúmenes de casos son solo para fines educativos.
Siempre verifique las citas con el Florida Reporter oficial antes de citar en el tribunal.
</span>
</div>
<!-- Filter bar -->
<div class="mb-4">
<div class="row g-2 align-items-center">
<div class="col-md-5">
<input type="text" id="caselaw-search" class="form-control form-control-sm"
placeholder="Search cases, citations, holdings..."/>
</div>
<div class="col-md-4">
<select id="caselaw-tag-filter" class="form-select form-select-sm">
<option value="">All Issues</option>
<option value="modification_threshold">Modification Threshold (FL 61.30(1)(b))</option>
<option value="income_imputation">Income Imputation</option>
<option value="self_employment_income">Self-Employment Income</option>
<option value="timesharing_deviation">Timesharing / FL 61.30(11)(b)</option>
<option value="domestic_violence">Domestic Violence</option>
<option value="default_judgment">Default Judgment</option>
<option value="residency">Residency Requirement</option>
<option value="parenting_class">Parenting Class</option>
<option value="fee_waiver">Fee Waiver</option>
<option value="post_order">Post-Order / Modification</option>
</select>
</div>
<div class="col-md-3">
<select id="caselaw-court-filter" class="form-select form-select-sm">
<option value="">All Courts</option>
<option value="fl_supreme">FL Supreme Court</option>
<option value="3rd_dca">3rd DCA (Miami-Dade)</option>
<option value="4th_dca">4th DCA</option>
<option value="2nd_dca">2nd DCA</option>
<option value="1st_dca">1st DCA</option>
</select>
</div>
</div>
</div>
<!-- Case list -->
<div id="caselaw-list">
<t t-foreach="cases" t-as="cl">
<div class="fl-section-card mb-3 caselaw-item"
t-att-data-tags="','.join(cl.issue_tag_ids.mapped('name'))"
t-att-data-court="cl.court"
t-att-data-text="(cl.citation + ' ' + cl.holding).lower()">
<div class="fl-section-header d-flex justify-content-between align-items-center">
<span>
<em><t t-esc="cl.citation"/></em>
</span>
<span>
<t t-foreach="cl.issue_tag_ids" t-as="tag">
<span class="badge bg-secondary ms-1" style="font-size:0.72rem;">
<t t-esc="tag.name"/>
</span>
</t>
</span>
</div>
<div class="fl-section-body">
<div class="row">
<div class="col-md-8">
<div class="mb-2">
<strong data-lang="en">Holding:</strong>
<strong data-lang="es" style="display:none;">Decisión:</strong>
<span style="font-size:0.9rem;"> <t t-esc="cl.holding"/></span>
</div>
<t t-if="cl.plain_english">
<div class="fl-ai-summary" style="padding:10px;">
<div class="fl-ai-header" data-lang="en">Plain English</div>
<div class="fl-ai-header" data-lang="es" style="display:none;">En Español Simple</div>
<div class="fl-ai-text" data-lang="en" style="font-size:0.88rem;">
<t t-esc="cl.plain_english"/>
</div>
<div class="fl-ai-text" data-lang="es" style="display:none;font-size:0.88rem;">
<t t-esc="cl.plain_english_es or cl.plain_english"/>
</div>
</div>
</t>
</div>
<div class="col-md-4">
<table class="table table-sm" style="font-size:0.82rem;">
<tr>
<td class="text-muted">Court:</td>
<td>
<t t-esc="dict([('fl_supreme','FL Supreme Court'),('3rd_dca','3rd DCA'),('4th_dca','4th DCA'),('2nd_dca','2nd DCA'),('1st_dca','1st DCA'),('5th_dca','5th DCA')]).get(cl.court, cl.court)"/>
</td>
</tr>
<tr>
<td class="text-muted">Year:</td>
<td><t t-esc="cl.year"/></td>
</tr>
<tr>
<td class="text-muted">Favorable to:</td>
<td>
<t t-esc="dict([('petitioner','Petitioner'),('respondent','Respondent'),('neutral','Neutral'),('depends','Depends')]).get(cl.favorable_to, cl.favorable_to)"/>
</td>
</tr>
</table>
<t t-if="cl.google_scholar_url">
<a t-att-href="cl.google_scholar_url" target="_blank"
class="btn btn-sm btn-outline-secondary w-100"
rel="noopener noreferrer">
View on Google Scholar →
</a>
</t>
</div>
</div>
</div>
</div>
</t>
<t t-if="not cases">
<div class="alert alert-info">No cases in the library yet.</div>
</t>
</div>
<!-- No results message (shown by JS) -->
<div id="caselaw-no-results" class="alert alert-warning" style="display:none;">
<span data-lang="en">No cases match your search. Try different keywords or clear the filter.</span>
<span data-lang="es" style="display:none;">Ningún caso coincide con su búsqueda. Pruebe diferentes palabras clave o elimine el filtro.</span>
</div>
</div>
<!-- Inline filter/search JS -->
<script type="text/javascript">
(function() {
function filterCases() {
var search = document.getElementById('caselaw-search').value.toLowerCase();
var tag = document.getElementById('caselaw-tag-filter').value;
var court = document.getElementById('caselaw-court-filter').value;
var items = document.querySelectorAll('.caselaw-item');
var visible = 0;
items.forEach(function(item) {
var matchText = !search || (item.dataset.text || '').includes(search);
var matchTag = !tag || (item.dataset.tags || '').split(',').includes(tag);
var matchCourt = !court || item.dataset.court === court;
var show = matchText &amp;&amp; matchTag &amp;&amp; matchCourt;
item.style.display = show ? '' : 'none';
if (show) visible++;
});
document.getElementById('caselaw-no-results').style.display = visible === 0 ? '' : 'none';
}
document.getElementById('caselaw-search').addEventListener('input', filterCases);
document.getElementById('caselaw-tag-filter').addEventListener('change', filterCases);
document.getElementById('caselaw-court-filter').addEventListener('change', filterCases);
})();
</script>
</t>
</template>
</data>
</odoo>

View File

@@ -0,0 +1,502 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Public Website Intake Form — /family-law/intake
Anonymous/public form for prospective pro se litigants.
Multi-step wizard. Bilingual EN/ES.
On submit → creates fl.intake.wizard record → creates fl.case.
-->
<odoo>
<data>
<!-- ══ Website navbar item ══════════════════════════════════════════ -->
<template id="website_navbar_intake" inherit_id="website.layout" name="FL Intake Nav">
<xpath expr="//ul[hasclass('navbar-nav')]" position="inside">
<li class="nav-item">
<a href="/family-law/intake" class="nav-link">
<span data-lang="en">Start My Case</span>
<span data-lang="es" style="display:none;">Iniciar Mi Caso</span>
</a>
</li>
</xpath>
</template>
<!-- ══ Public landing page ══════════════════════════════════════════ -->
<template id="website_intake_landing" name="FL Intake — Landing">
<t t-call="website.layout">
<div class="container mt-5 mb-5">
<!-- Language toggle -->
<div class="text-end mb-3">
<button class="btn btn-outline-secondary btn-sm fl-lang-btn btn-primary" data-lang="en">EN</button>
<button class="btn btn-outline-secondary btn-sm fl-lang-btn" data-lang="es">ES</button>
</div>
<!-- Hero -->
<div class="text-center mb-5">
<h1 style="color:#003366;" data-lang="en">Florida Family Law — Self-Help Portal</h1>
<h1 style="color:#003366;" data-lang="es" style="display:none;">Portal de Autoayuda — Derecho de Familia de Florida</h1>
<p class="lead text-muted" data-lang="en">
Free tools for pro se litigants in Miami-Dade County (11th Circuit).<br/>
Child support modification, paternity, dissolution, and more.
</p>
<p class="lead text-muted" data-lang="es" style="display:none;">
Herramientas gratuitas para litigantes pro se en el Condado de Miami-Dade (Circuito 11).<br/>
Modificación de manutención, paternidad, disolución y más.
</p>
<a href="/family-law/intake/start" class="btn btn-lg btn-primary mt-3 me-2"
style="background:#003366;border-color:#003366;">
<span data-lang="en">Start My Case →</span>
<span data-lang="es" style="display:none;">Iniciar Mi Caso →</span>
</a>
<a href="/my/cases/calculator" class="btn btn-lg btn-outline-secondary mt-3">
<span data-lang="en">Support Calculator</span>
<span data-lang="es" style="display:none;">Calculadora de Pensión</span>
</a>
</div>
<!-- Service cards -->
<div class="row g-4 mb-5">
<div class="col-md-4">
<div class="fl-section-card h-100">
<div class="fl-section-header">Child Support Modification</div>
<div class="fl-section-body">
<p data-lang="en">Has your income or the other parent's income changed significantly?
Florida allows modification when the new guideline amount differs by 15% AND $50 (FL 61.30(1)(b)).</p>
<p data-lang="es" style="display:none;">¿Han cambiado significativamente sus ingresos o los del otro padre?
Florida permite la modificación cuando el nuevo monto de las pautas difiere en un 15% Y $50.</p>
<a href="/family-law/intake/start?case_type=modification" class="btn btn-sm btn-outline-primary">
<span data-lang="en">Start Modification Case</span>
<span data-lang="es" style="display:none;">Iniciar Caso de Modificación</span>
</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="fl-section-card h-100">
<div class="fl-section-header">Paternity</div>
<div class="fl-section-body">
<p data-lang="en">Establish legal paternity, parenting rights, and child support for unmarried parents in Miami-Dade County.</p>
<p data-lang="es" style="display:none;">Establecer paternidad legal, derechos de crianza y manutención infantil para padres no casados en el Condado de Miami-Dade.</p>
<a href="/family-law/intake/start?case_type=paternity" class="btn btn-sm btn-outline-primary">
<span data-lang="en">Start Paternity Case</span>
<span data-lang="es" style="display:none;">Iniciar Caso de Paternidad</span>
</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="fl-section-card h-100">
<div class="fl-section-header">Dissolution of Marriage</div>
<div class="fl-section-body">
<p data-lang="en">File for divorce in Florida. Residency requirement: 6 months in Florida before filing (FL 61.021).</p>
<p data-lang="es" style="display:none;">Presentar una solicitud de divorcio en Florida. Requisito de residencia: 6 meses en Florida antes de presentar (FL 61.021).</p>
<a href="/family-law/intake/start?case_type=dissolution" class="btn btn-sm btn-outline-primary">
<span data-lang="en">Start Dissolution Case</span>
<span data-lang="es" style="display:none;">Iniciar Caso de Disolución</span>
</a>
</div>
</div>
</div>
</div>
<!-- Resources -->
<div class="fl-section-card mb-4">
<div class="fl-section-header" data-lang="en">Free Legal Resources — Miami-Dade</div>
<div class="fl-section-header" data-lang="es" style="display:none;">Recursos Legales Gratuitos — Miami-Dade</div>
<div class="fl-section-body">
<div class="row g-3" style="font-size:0.9rem;">
<div class="col-md-6">
<strong>FL Volunteer Lawyers Project:</strong>
<a href="https://www.flvlp.org" target="_blank">flvlp.org</a><br/>
<strong>Legal Services of Greater Miami:</strong>
<a href="https://www.lsgmi.org" target="_blank">lsgmi.org</a><br/>
<strong>Miami-Dade Clerk Self-Help Center:</strong>
<a href="https://www.miamidadeclerk.gov/family" target="_blank">miamidadeclerk.gov</a>
</div>
<div class="col-md-6">
<strong>FL Courts Self-Help:</strong>
<a href="https://www.flcourts.gov/Resources-Services/Family-Courts" target="_blank">flcourts.gov</a><br/>
<strong>FL DOR Child Support eServices:</strong>
<a href="https://floridarevenue.com/childsupport" target="_blank">floridarevenue.com/childsupport</a><br/>
<strong>DV Hotline (24/7):</strong> 1-800-799-7233
</div>
</div>
</div>
</div>
<!-- Disclaimer -->
<div class="text-center text-muted" style="font-size:0.8rem;">
<span data-lang="en">ActiveBlue Family Law is a case management tool — NOT a law firm.
Nothing on this website constitutes legal advice. Consult a licensed Florida attorney for legal guidance.</span>
<span data-lang="es" style="display:none;">ActiveBlue Family Law es una herramienta de gestión de casos, NO una firma de abogados.
Nada en este sitio web constituye asesoramiento legal. Consulte a un abogado con licencia de Florida.</span>
</div>
</div>
</t>
</template>
<!-- ══ Multi-step intake form ═══════════════════════════════════════ -->
<template id="website_intake_form" name="FL Intake — Case Setup Form">
<t t-call="website.layout">
<div class="container mt-4 mb-5">
<!-- Language toggle -->
<div class="text-end mb-3">
<button class="btn btn-outline-secondary btn-sm fl-lang-btn btn-primary" data-lang="en">EN</button>
<button class="btn btn-outline-secondary btn-sm fl-lang-btn" data-lang="es">ES</button>
</div>
<div class="row justify-content-center">
<div class="col-md-8">
<!-- Header -->
<h2 style="color:#003366;" class="mb-1" data-lang="en">Start Your Case</h2>
<h2 style="color:#003366;" class="mb-1" data-lang="es" style="display:none;">Iniciar Su Caso</h2>
<p class="text-muted mb-4" data-lang="en">Fill in the information below. We'll help you generate the right court forms and track your deadlines.</p>
<p class="text-muted mb-4" data-lang="es" style="display:none;">Complete la información a continuación. Le ayudaremos a generar los formularios judiciales correctos y rastrear sus fechas límite.</p>
<!-- Progress bar -->
<div class="fl-intake-progress mb-4">
<div class="fl-intake-progress-step active"></div>
<div class="fl-intake-progress-step"></div>
<div class="fl-intake-progress-step"></div>
<div class="fl-intake-progress-step"></div>
</div>
<div class="text-muted mb-3 small fl-step-counter">Step 1 of 4</div>
<!-- DV safety check (always first) -->
<div class="alert alert-warning mb-4" style="font-size:0.88rem;">
<span data-lang="en"><strong>Safety Check:</strong> If you are in danger, call 911.
DV Hotline: 1-800-799-7233. Miami-Dade Safe Space: (305) 758-2546.
<a href="https://www.google.com" target="_blank" class="btn btn-sm btn-danger ms-2">Quick Exit</a></span>
<span data-lang="es" style="display:none;"><strong>Verificación de Seguridad:</strong> Si está en peligro, llame al 911.
Línea de violencia doméstica: 1-800-799-7233.
<a href="https://www.google.com" target="_blank" class="btn btn-sm btn-danger ms-2">Salida Rápida</a></span>
</div>
<form method="post" action="/family-law/intake/submit" class="fl-intake-form"
t-att-data-case-type="case_type or ''">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<input type="hidden" name="case_type" t-att-value="case_type or 'child_support'" id="intake_case_type"/>
<!-- STEP 1: Case Type & Basic Info -->
<div class="fl-intake-step active" id="step-1">
<h5 class="mb-3" data-lang="en">Step 1 — Case Type &amp; Your Information</h5>
<h5 class="mb-3" data-lang="es" style="display:none;">Paso 1 — Tipo de Caso e Información Personal</h5>
<div class="mb-3">
<label class="form-label fw-semibold" data-lang="en">Type of Case</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">Tipo de Caso</label>
<select name="case_type_select" id="case_type_select" class="form-select"
onchange="document.getElementById('intake_case_type').value=this.value">
<option value="modification" t-att-selected="case_type == 'modification' and 'selected' or None">Child Support Modification (FL 61.14)</option>
<option value="paternity" t-att-selected="case_type == 'paternity' and 'selected' or None">Establish Paternity (FL 742.10)</option>
<option value="child_support" t-att-selected="(not case_type or case_type == 'child_support') and 'selected' or None">Child Support (New Order)</option>
<option value="dissolution" t-att-selected="case_type == 'dissolution' and 'selected' or None">Dissolution of Marriage (FL 61.052)</option>
</select>
</div>
<div class="mb-3">
<label class="form-label fw-semibold" data-lang="en">Your Full Legal Name</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">Su Nombre Legal Completo</label>
<input type="text" name="petitioner_name" class="form-control"
placeholder="First Middle Last" required="required"/>
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label fw-semibold" data-lang="en">Your Email</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">Su Correo Electrónico</label>
<input type="email" name="petitioner_email" class="form-control"
placeholder="you@email.com" required="required"/>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold" data-lang="en">Your Phone</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">Su Teléfono</label>
<input type="tel" name="petitioner_phone" class="form-control" placeholder="(305) 555-0000"/>
</div>
</div>
<div class="mb-3 mt-3">
<label class="form-label fw-semibold" data-lang="en">Your Address (Miami-Dade County)</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">Su Dirección (Condado de Miami-Dade)</label>
<input type="text" name="petitioner_address" class="form-control" placeholder="Street Address"/>
<input type="text" name="petitioner_city" class="form-control mt-2" placeholder="City"/>
</div>
<div class="mb-3">
<label class="form-label fw-semibold" data-lang="en">Florida Resident Since</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">Residente de Florida Desde</label>
<input type="date" name="fl_resident_since" class="form-control"/>
<div class="form-text" data-lang="en">Must be at least 6 months before filing date for dissolution (FL 61.021).</div>
<div class="form-text" data-lang="es" style="display:none;">Debe ser al menos 6 meses antes de la fecha de presentación para la disolución (FL 61.021).</div>
</div>
<div class="d-flex justify-content-end">
<button type="button" class="btn btn-primary fl-next-btn">
<span data-lang="en">Next →</span>
<span data-lang="es" style="display:none;">Siguiente →</span>
</button>
</div>
</div>
<!-- STEP 2: Other Party & Children -->
<div class="fl-intake-step" id="step-2">
<h5 class="mb-3" data-lang="en">Step 2 — Other Party &amp; Children</h5>
<h5 class="mb-3" data-lang="es" style="display:none;">Paso 2 — Otra Parte e Hijos</h5>
<div class="mb-3">
<label class="form-label fw-semibold" data-lang="en">Other Party's Full Name (Respondent)</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">Nombre Completo de la Otra Parte (Respondente)</label>
<input type="text" name="respondent_name" class="form-control"
placeholder="First Middle Last" required="required"/>
</div>
<div class="mb-3">
<label class="form-label fw-semibold" data-lang="en">Does the other party have an attorney?</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">¿La otra parte tiene un abogado?</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="respondent_has_counsel" value="yes" id="counsel_yes"/>
<label class="form-check-label" for="counsel_yes" data-lang="en">Yes</label>
<label class="form-check-label" for="counsel_yes" data-lang="es" style="display:none;"></label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="respondent_has_counsel" value="no" id="counsel_no" checked="checked"/>
<label class="form-check-label" for="counsel_no" data-lang="en">No</label>
<label class="form-check-label" for="counsel_no" data-lang="es" style="display:none;">No</label>
</div>
</div>
<div class="form-text text-danger" data-lang="en">If the other party has an attorney, we strongly recommend you consult one too.</div>
<div class="form-text text-danger" data-lang="es" style="display:none;">Si la otra parte tiene un abogado, le recomendamos encarecidamente que también consulte a uno.</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold" data-lang="en">Number of Minor Children</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">Número de Hijos Menores</label>
<select name="num_children" class="form-select">
<option value="0">0 (no minor children)</option>
<option value="1" selected="selected">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5+</option>
</select>
</div>
<div class="mb-3">
<label class="form-label fw-semibold" data-lang="en">Existing Court Case Number (if modifying)</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">Número de Caso del Tribunal Existente (si modifica)</label>
<input type="text" name="court_case_number" class="form-control"
placeholder="e.g. 2023-DR-12345"/>
<div class="form-text" data-lang="en">Leave blank if this is a new case.</div>
<div class="form-text" data-lang="es" style="display:none;">Déjelo en blanco si es un caso nuevo.</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold" data-lang="en">Is there a history of domestic violence?</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">¿Existe un historial de violencia doméstica?</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="domestic_violence" value="yes" id="dv_yes"/>
<label class="form-check-label" for="dv_yes" data-lang="en">Yes</label>
<label class="form-check-label" for="dv_yes" data-lang="es" style="display:none;"></label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="domestic_violence" value="no" id="dv_no" checked="checked"/>
<label class="form-check-label" for="dv_no" data-lang="en">No</label>
<label class="form-check-label" for="dv_no" data-lang="es" style="display:none;">No</label>
</div>
</div>
</div>
<div class="d-flex justify-content-between">
<button type="button" class="btn btn-outline-secondary fl-prev-btn" data-lang="en">← Back</button>
<button type="button" class="btn btn-outline-secondary fl-prev-btn" data-lang="es" style="display:none;">← Atrás</button>
<button type="button" class="btn btn-primary fl-next-btn">
<span data-lang="en">Next →</span>
<span data-lang="es" style="display:none;">Siguiente →</span>
</button>
</div>
</div>
<!-- STEP 3: Income & Financial -->
<div class="fl-intake-step" id="step-3">
<h5 class="mb-3" data-lang="en">Step 3 — Income Information</h5>
<h5 class="mb-3" data-lang="es" style="display:none;">Paso 3 — Información de Ingresos</h5>
<div class="alert alert-info" style="font-size:0.85rem;" data-lang="en">
Use <strong>monthly net income</strong> (after taxes, FICA, and required deductions per FL 61.30(3)).
</div>
<div class="alert alert-info" style="font-size:0.85rem;" data-lang="es" style="display:none;">
Use el <strong>ingreso neto mensual</strong> (después de impuestos, FICA y deducciones requeridas por FL 61.30(3)).
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label class="form-label fw-semibold" data-lang="en">Your Monthly Net Income</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">Su Ingreso Neto Mensual</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input type="number" name="petitioner_income" class="form-control" placeholder="0.00" min="0" step="1"/>
</div>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold" data-lang="en">Other Party's Monthly Net Income (estimated)</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">Ingreso Neto Mensual de la Otra Parte (estimado)</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input type="number" name="respondent_income" class="form-control" placeholder="0.00" min="0" step="1"/>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold" data-lang="en">Do you believe the other party is hiding income or voluntarily underemployed?</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">¿Cree que la otra parte está ocultando ingresos o está voluntariamente subempleada?</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="income_imputation_concern" value="yes" id="imp_yes"/>
<label class="form-check-label" for="imp_yes">Yes</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="income_imputation_concern" value="no" id="imp_no" checked="checked"/>
<label class="form-check-label" for="imp_no">No</label>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold" data-lang="en">Current Monthly Child Support Order (if modifying)</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">Orden Actual de Manutención Mensual (si modifica)</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input type="number" name="current_order_amount" class="form-control" placeholder="0.00" min="0" step="1"/>
</div>
</div>
<div class="d-flex justify-content-between">
<button type="button" class="btn btn-outline-secondary fl-prev-btn">← Back</button>
<button type="button" class="btn btn-primary fl-next-btn">
<span data-lang="en">Next →</span>
<span data-lang="es" style="display:none;">Siguiente →</span>
</button>
</div>
</div>
<!-- STEP 4: Fee Waiver & Submit -->
<div class="fl-intake-step" id="step-4">
<h5 class="mb-3" data-lang="en">Step 4 — Fee Waiver &amp; Submit</h5>
<h5 class="mb-3" data-lang="es" style="display:none;">Paso 4 — Exención de Tarifas y Enviar</h5>
<div class="mb-3">
<label class="form-label fw-semibold" data-lang="en">Household Size (including yourself)</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">Tamaño del Hogar (incluyéndose usted)</label>
<select name="household_size" class="form-select">
<option value="1">1</option>
<option value="2">2</option>
<option value="3" selected="selected">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6+</option>
</select>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="fee_waiver_request" id="fee_waiver_check" value="yes"/>
<label class="form-check-label" for="fee_waiver_check" data-lang="en">
I would like to apply for a filing fee waiver (FL 57.082 — Civil Indigent Status).
Eligibility: household income at or below 200% of federal poverty level.
</label>
<label class="form-check-label" for="fee_waiver_check" data-lang="es" style="display:none;">
Deseo solicitar una exención de tarifas de presentación (FL 57.082 — Estado Civil Indigente).
Elegibilidad: ingresos del hogar iguales o inferiores al 200% del nivel federal de pobreza.
</label>
</div>
</div>
<div class="mb-4">
<label class="form-label fw-semibold" data-lang="en">Additional Notes (optional)</label>
<label class="form-label fw-semibold" data-lang="es" style="display:none;">Notas Adicionales (opcional)</label>
<textarea name="notes" class="form-control" rows="3"
placeholder="Any other information you'd like us to know about your case..."></textarea>
</div>
<div class="mb-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="acknowledge_disclaimer" id="acknowledge" required="required"/>
<label class="form-check-label" for="acknowledge" style="font-size:0.85rem;" data-lang="en">
I understand that ActiveBlue Family Law is a case management tool, NOT a law firm.
This service does not provide legal advice. I will verify all documents with
the court before filing. <strong>Required to submit.</strong>
</label>
<label class="form-check-label" for="acknowledge" style="font-size:0.85rem;display:none;" data-lang="es">
Entiendo que ActiveBlue Family Law es una herramienta de gestión de casos, NO una firma de abogados.
Este servicio no proporciona asesoramiento legal. Verificaré todos los documentos con el tribunal antes de presentarlos.
<strong>Requerido para enviar.</strong>
</label>
</div>
</div>
<div class="d-flex justify-content-between">
<button type="button" class="btn btn-outline-secondary fl-prev-btn">← Back</button>
<button type="submit" class="btn btn-success btn-lg">
<span data-lang="en">Submit &amp; Create My Case →</span>
<span data-lang="es" style="display:none;">Enviar y Crear Mi Caso →</span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</t>
</template>
<!-- ══ Intake confirmation page ════════════════════════════════════ -->
<template id="website_intake_confirm" name="FL Intake — Confirmation">
<t t-call="website.layout">
<div class="container mt-5 mb-5 text-center">
<div style="font-size:3rem;"></div>
<h2 style="color:#003366;" data-lang="en">Your Case Has Been Created!</h2>
<h2 style="color:#003366;" data-lang="es" style="display:none;">¡Su Caso Ha Sido Creado!</h2>
<p class="lead" data-lang="en">
Case <strong><t t-esc="case_name"/></strong> is now open.
An email confirmation has been sent to <strong><t t-esc="petitioner_email"/></strong>.
</p>
<p class="lead" data-lang="es" style="display:none;">
El caso <strong><t t-esc="case_name"/></strong> ya está abierto.
Se ha enviado una confirmación por correo electrónico a <strong><t t-esc="petitioner_email"/></strong>.
</p>
<div class="mt-4">
<a href="/my/cases" class="btn btn-primary btn-lg me-2" data-lang="en">View My Cases →</a>
<a href="/my/cases" class="btn btn-primary btn-lg me-2" data-lang="es" style="display:none;">Ver Mis Casos →</a>
<a href="/my/cases/calculator" class="btn btn-outline-secondary btn-lg" data-lang="en">Support Calculator</a>
<a href="/my/cases/calculator" class="btn btn-outline-secondary btn-lg" data-lang="es" style="display:none;">Calculadora</a>
</div>
<t t-if="fee_waiver_eligible">
<div class="alert alert-success mt-4 text-start" style="max-width:600px;margin:auto;">
<strong data-lang="en">💚 You may qualify for a filing fee waiver (FL 57.082).</strong>
<strong data-lang="es" style="display:none;">💚 Puede calificar para una exención de tarifas de presentación (FL 57.082).</strong>
<span data-lang="en"> A Fee Waiver Application has been prepared in your case documents.</span>
<span data-lang="es" style="display:none;"> Una Solicitud de Exención de Tarifas ha sido preparada en los documentos de su caso.</span>
</div>
</t>
<t t-if="attorney_referral">
<div class="fl-attorney-referral-banner mt-4 text-start" style="max-width:600px;margin:auto;">
<h5 data-lang="en">⚠ Attorney Consultation Recommended</h5>
<h5 data-lang="es" style="display:none;">⚠ Se Recomienda Consulta con Abogado</h5>
<p data-lang="en">Based on your case details (opposing counsel or DV concern), we strongly recommend
consulting with a licensed Florida attorney before proceeding.
<a href="https://www.flvlp.org" target="_blank">FL Volunteer Lawyers Project</a></p>
<p data-lang="es" style="display:none;">Según los detalles de su caso, recomendamos encarecidamente consultar con un abogado de Florida con licencia antes de continuar.</p>
</div>
</t>
</div>
</t>
</template>
</data>
</odoo>