Phase 1: core models, security, seed data, and backend views

Implements full Phase 1 of the activeblue_familylaw Odoo 18 module:
- 17 Python models (fl.case, fl.party, fl.child, fl.support.calculation,
  fl.fee.waiver, fl.income.withholding, fl.deadline, fl.hearing,
  fl.deposition, fl.discovery, fl.document, fl.caselaw, fl.analysis,
  fl.ai.engine, fl.argument, fl.statute, fl.issue.tag) + hr.expense extension
- 3 wizard stubs (intake, analysis, generate-packet)
- Security: 4 groups (admin/paralegal/portal-petitioner/portal-respondent)
  + record rules scoping portal users to their own cases
- Seed data: issue tags, FL statutes, FL DCF support schedule, ir.sequence
- 13 backend view XML files with FL 61.30 worksheet, fee waiver
  eligibility banner, DV safety resources, emancipation alerts
- Static CSS/JS stubs for Phase 6 portal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Carlos Garcia
2026-05-04 18:52:04 -04:00
commit 1d52d85a78
46 changed files with 5626 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
from . import models
from . import wizard

View File

@@ -0,0 +1,63 @@
{
'name': 'ActiveBlue Family Law',
'version': '18.0.1.0.0',
'category': 'Legal',
'summary': 'Florida Family Law Case Management — Pro Se',
'description': """
Florida family law case management for pro se litigants.
Covers child support modification, dissolution of marriage,
and paternity cases in Miami-Dade County (11th Circuit).
Includes FL 61.30 child support calculator, document generation,
deadline tracking, and AI-powered case law analysis via Ollama.
""",
'author': 'Active Blue LLC',
'website': 'https://avc.activeblue.net',
'depends': [
'base',
'mail',
'portal',
'website',
'contacts',
'calendar',
'project',
'crm',
'account',
'hr_expense',
# 'sign', # Odoo Sign — enable when confirmed installed
# 'queue_job', # OCA queue_job — install from https://github.com/OCA/queue
],
'data': [
# Security
'security/fl_security.xml',
'security/ir.model.access.csv',
# Seed data (load before views)
'data/fl_issue_tags.xml',
'data/fl_statute_data.xml',
'data/fl_support_schedule.xml',
'data/ir_sequence.xml',
# Views — backend (actions before menus so menuitem refs resolve)
'views/fl_case_views.xml',
'views/fl_party_views.xml',
'views/fl_child_views.xml',
'views/fl_support_views.xml',
'views/fl_deadline_views.xml',
'views/fl_hearing_views.xml',
'views/fl_deposition_views.xml',
'views/fl_discovery_views.xml',
'views/fl_caselaw_views.xml',
'views/fl_analysis_views.xml',
'views/fl_fee_waiver_views.xml',
'views/fl_statute_views.xml',
'views/menu_views.xml',
],
'assets': {
'web.assets_frontend': [
'activeblue_familylaw/static/src/css/familylaw_portal.css',
'activeblue_familylaw/static/src/js/fl_calculator.js',
'activeblue_familylaw/static/src/js/fl_timeline.js',
],
},
'installable': True,
'application': True,
'license': 'LGPL-3',
}

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="tag_modification_threshold" model="fl.issue.tag">
<field name="name">Modification Threshold</field>
<field name="name_es">Umbral de Modificación</field>
<field name="color">1</field>
<field name="case_type">modification</field>
</record>
<record id="tag_income_imputation" model="fl.issue.tag">
<field name="name">Income Imputation</field>
<field name="name_es">Imputación de Ingresos</field>
<field name="color">2</field>
<field name="case_type">all</field>
</record>
<record id="tag_self_employment_income" model="fl.issue.tag">
<field name="name">Self-Employment Income</field>
<field name="name_es">Ingresos por Trabajo Independiente</field>
<field name="color">3</field>
<field name="case_type">all</field>
</record>
<record id="tag_timesharing_deviation" model="fl.issue.tag">
<field name="name">Timesharing Deviation</field>
<field name="name_es">Desviación de Tiempo Compartido</field>
<field name="color">4</field>
<field name="case_type">modification</field>
</record>
<record id="tag_domestic_violence" model="fl.issue.tag">
<field name="name">Domestic Violence</field>
<field name="name_es">Violencia Doméstica</field>
<field name="color">9</field>
<field name="case_type">all</field>
</record>
<record id="tag_fee_waiver" model="fl.issue.tag">
<field name="name">Fee Waiver / Indigent Status</field>
<field name="name_es">Exención de Tarifas / Estado Indigente</field>
<field name="color">6</field>
<field name="case_type">all</field>
</record>
<record id="tag_default_judgment" model="fl.issue.tag">
<field name="name">Default Judgment</field>
<field name="name_es">Sentencia en Rebeldía</field>
<field name="color">7</field>
<field name="case_type">all</field>
</record>
<record id="tag_parenting_class" model="fl.issue.tag">
<field name="name">Parenting Class Required</field>
<field name="name_es">Clase de Crianza Requerida</field>
<field name="color">5</field>
<field name="case_type">all</field>
</record>
<record id="tag_residency" model="fl.issue.tag">
<field name="name">Residency Requirement</field>
<field name="name_es">Requisito de Residencia</field>
<field name="color">8</field>
<field name="case_type">all</field>
</record>
<record id="tag_post_order" model="fl.issue.tag">
<field name="name">Post-Order / Income Withholding</field>
<field name="name_es">Post-Orden / Retención de Ingresos</field>
<field name="color">10</field>
<field name="case_type">all</field>
</record>
<record id="tag_lifestyle_inconsistency" model="fl.issue.tag">
<field name="name">Lifestyle Inconsistency</field>
<field name="name_es">Inconsistencia de Estilo de Vida</field>
<field name="color">3</field>
<field name="case_type">all</field>
</record>
<record id="tag_emancipation" model="fl.issue.tag">
<field name="name">Child Emancipation</field>
<field name="name_es">Emancipación de Hijo</field>
<field name="color">6</field>
<field name="case_type">modification</field>
</record>
<record id="tag_substantial_change" model="fl.issue.tag">
<field name="name">Substantial Change in Circumstances</field>
<field name="name_es">Cambio Sustancial en Circunstancias</field>
<field name="color">1</field>
<field name="case_type">modification</field>
</record>
<record id="tag_alimony_reform" model="fl.issue.tag">
<field name="name">Alimony (2023 Reform)</field>
<field name="name_es">Pensión Alimenticia (Reforma 2023)</field>
<field name="color">2</field>
<field name="case_type">dissolution</field>
</record>
<record id="tag_attorney_referral" model="fl.issue.tag">
<field name="name">Attorney Referral Recommended</field>
<field name="name_es">Se Recomienda Referir a Abogado</field>
<field name="color">9</field>
<field name="case_type">all</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,242 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- ══════════════════════════════════════════════════════
CHAPTER 61 — DISSOLUTION OF MARRIAGE / SUPPORT
══════════════════════════════════════════════════════ -->
<record id="statute_fl6130" model="fl.statute">
<field name="name">FL 61.30</field>
<field name="title">Child Support Guidelines</field>
<field name="description">Establishes the guidelines for calculating child support in Florida. Provides the Basic Support Obligation schedule based on combined net monthly income and number of children. Governs adjustments for health insurance, childcare, timesharing, and extraordinary expenses.</field>
<field name="category">child_support</field>
<field name="url">http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&amp;URL=0000-0099/0061/Sections/0061.30.html</field>
</record>
<record id="statute_fl6114" model="fl.statute">
<field name="name">FL 61.14</field>
<field name="title">Modification of Support, Maintenance, Alimony, or Child Support</field>
<field name="description">Authorizes courts to modify child support and alimony orders when there is a substantial change in circumstances. For child support, requires the change to meet the 15% or $50 threshold under FL 61.30(1)(b).</field>
<field name="category">modification</field>
<field name="url">http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&amp;URL=0000-0099/0061/Sections/0061.14.html</field>
</record>
<record id="statute_fl6108" model="fl.statute">
<field name="name">FL 61.08</field>
<field name="title">Alimony</field>
<field name="description">Governs alimony awards in dissolution cases. AMENDED 2023 (HB 1409, effective July 1, 2023): Permanent alimony eliminated. Durational alimony capped at length of marriage. Pre-2023 permanent alimony orders may still be modified under new standards.</field>
<field name="category">alimony</field>
<field name="url">http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&amp;URL=0000-0099/0061/Sections/0061.08.html</field>
</record>
<record id="statute_fl6113" model="fl.statute">
<field name="name">FL 61.13</field>
<field name="title">Support of Children; Parenting and Time-Sharing</field>
<field name="description">Governs parental responsibility, timesharing schedules, and parenting plans. FL 61.13(2)(c) specifically addresses timesharing in domestic violence situations. Best interests of the child standard applies.</field>
<field name="category">timesharing</field>
<field name="url">http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&amp;URL=0000-0099/0061/Sections/0061.13.html</field>
</record>
<record id="statute_fl6121" model="fl.statute">
<field name="name">FL 61.21</field>
<field name="title">Parenting Course; Requirement</field>
<field name="description">Requires both parents to complete a parenting course when minor children are involved in a dissolution or modification proceeding. Must be completed before final hearing. Miami-Dade approved courses available through the court.</field>
<field name="category">procedure</field>
<field name="url">http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&amp;URL=0000-0099/0061/Sections/0061.21.html</field>
</record>
<record id="statute_fl61075" model="fl.statute">
<field name="name">FL 61.075</field>
<field name="title">Equitable Distribution of Marital Assets and Liabilities</field>
<field name="description">Governs the distribution of marital assets and liabilities in dissolution proceedings. Presumption of equal distribution. Applies in dissolution cases — not directly applicable to child support modification.</field>
<field name="category">dissolution</field>
<field name="url">http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&amp;URL=0000-0099/0061/Sections/0061.075.html</field>
</record>
<record id="statute_fl61052" model="fl.statute">
<field name="name">FL 61.052</field>
<field name="title">Dissolution of Marriage</field>
<field name="description">Establishes grounds for dissolution of marriage in Florida. Florida is a no-fault divorce state — irretrievable breakdown of the marriage is sufficient grounds. No showing of fault required.</field>
<field name="category">dissolution</field>
<field name="url">http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&amp;URL=0000-0099/0061/Sections/0061.052.html</field>
</record>
<record id="statute_fl61021" model="fl.statute">
<field name="name">FL 61.021</field>
<field name="title">Residence Requirement</field>
<field name="description">Requires at least one party to have been a Florida resident for at least 6 months before filing for dissolution of marriage or modification. Must be proven by driver's license, voter registration, or witness testimony.</field>
<field name="category">procedure</field>
<field name="url">http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&amp;URL=0000-0099/0061/Sections/0061.021.html</field>
</record>
<record id="statute_fl611301" model="fl.statute">
<field name="name">FL 61.1301</field>
<field name="title">Income Deduction Orders</field>
<field name="description">Mandatory income withholding order must be entered with every child support or alimony order. Employer deducts from paycheck and remits to Florida State Disbursement Unit (SDU). Exceptions only for good cause shown or written agreement between parties.</field>
<field name="category">enforcement</field>
<field name="url">http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&amp;URL=0000-0099/0061/Sections/0061.1301.html</field>
</record>
<record id="statute_fl61_30_17" model="fl.statute">
<field name="name">FL 61.30(17)</field>
<field name="title">Child Support Retroactivity</field>
<field name="description">Modified child support is retroactive only to the date the petition for modification was filed. Cannot seek retroactive modification for periods before the filing date. Critical rule often missed by pro se filers.</field>
<field name="category">modification</field>
<field name="url">http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&amp;URL=0000-0099/0061/Sections/0061.30.html</field>
</record>
<!-- ══════════════════════════════════════════════════════
CHAPTER 742 — PATERNITY
══════════════════════════════════════════════════════ -->
<record id="statute_fl74210" model="fl.statute">
<field name="name">FL 742.10</field>
<field name="title">Establishment of Paternity</field>
<field name="description">Governs the establishment of paternity in Florida. Methods include voluntary acknowledgment, administrative order, or court proceedings. Once paternity is established, child support and timesharing may be addressed.</field>
<field name="category">paternity</field>
<field name="url">http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&amp;URL=0000-0099/0742/Sections/0742.10.html</field>
</record>
<!-- ══════════════════════════════════════════════════════
CHAPTER 741 — DOMESTIC VIOLENCE
══════════════════════════════════════════════════════ -->
<record id="statute_fl74130" model="fl.statute">
<field name="name">FL 741.30</field>
<field name="title">Domestic Violence Injunctions</field>
<field name="description">Provides for injunctions for protection against domestic violence. An active injunction affects mediation requirements, timesharing, and all court proceedings. Pro se litigants with active DV injunctions should strongly consider legal representation.</field>
<field name="category">domestic_violence</field>
<field name="url">http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&amp;URL=0000-0099/0741/Sections/0741.30.html</field>
</record>
<!-- ══════════════════════════════════════════════════════
CHAPTER 44 — MEDIATION
══════════════════════════════════════════════════════ -->
<record id="statute_fl44102" model="fl.statute">
<field name="name">FL 44.102</field>
<field name="title">Referral to Mediation</field>
<field name="description">Authorizes courts to refer civil matters to mediation. In family law cases, mediation is generally required before final hearing. Miami-Dade Family Mediation Unit provides services.</field>
<field name="category">procedure</field>
<field name="url">http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&amp;URL=0000-0099/0044/Sections/0044.102.html</field>
</record>
<record id="statute_fl44108" model="fl.statute">
<field name="name">FL 44.108</field>
<field name="title">Mediation Confidentiality; Mediator Fees</field>
<field name="description">Governs mediator fees and confidentiality. Miami-Dade County provides county mediators at no cost for qualifying income levels (under 200% FPL). Parties may apply for mediator fee waiver in conjunction with civil indigent status.</field>
<field name="category">procedure</field>
<field name="url">http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&amp;URL=0000-0099/0044/Sections/0044.108.html</field>
</record>
<!-- ══════════════════════════════════════════════════════
CHAPTER 57 — CIVIL INDIGENT STATUS
══════════════════════════════════════════════════════ -->
<record id="statute_fl57082" model="fl.statute">
<field name="name">FL 57.082</field>
<field name="title">Determination of Civil Indigent Status</field>
<field name="description">Process for determining civil indigent status for fee waivers. Income threshold: less than 200% of federal poverty level. Application filed with clerk of court. If approved, filing fees and service fees are waived.</field>
<field name="category">fee_waiver</field>
<field name="url">http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&amp;URL=0000-0099/0057/Sections/0057.082.html</field>
</record>
<!-- ══════════════════════════════════════════════════════
FLORIDA RULES OF CIVIL PROCEDURE
══════════════════════════════════════════════════════ -->
<record id="statute_fl1070" model="fl.statute">
<field name="name">FL 1.070</field>
<field name="title">Process — Service of Process</field>
<field name="description">Governs service of process in civil actions. Summons must be served within 120 days of filing. If not served within 120 days, court may dismiss or extend time for good cause. Pro se tip: aim to serve within 30 days of filing.</field>
<field name="category">procedure</field>
<field name="url">http://www.floridabar.org/rules/frcp/</field>
</record>
<record id="statute_fl1140" model="fl.statute">
<field name="name">FL 1.140</field>
<field name="title">Defenses — Time to Answer</field>
<field name="description">Respondent has 20 days from date of service to file an answer or other responsive pleading. Failure to respond within 20 days may result in a default judgment being entered against the respondent.</field>
<field name="category">procedure</field>
<field name="url">http://www.floridabar.org/rules/frcp/</field>
</record>
<record id="statute_fl1280" model="fl.statute">
<field name="name">FL 1.280</field>
<field name="title">General Provisions Governing Discovery</field>
<field name="description">Governs the scope and limits of discovery in civil proceedings. Discovery may cover any matter relevant to the subject matter of the pending action. Allows discovery of information reasonably calculated to lead to admissible evidence.</field>
<field name="category">discovery</field>
<field name="url">http://www.floridabar.org/rules/frcp/</field>
</record>
<record id="statute_fl1310" model="fl.statute">
<field name="name">FL 1.310</field>
<field name="title">Depositions Upon Oral Examination</field>
<field name="description">Governs oral depositions. Minimum 10 days notice required (FL 1.310(b)). Maximum 7 hours per deponent per day (FL 1.310(d)). Video recording allowed with notice. Deponent may object to questions on privilege grounds.</field>
<field name="category">discovery</field>
<field name="url">http://www.floridabar.org/rules/frcp/</field>
</record>
<record id="statute_fl1340" model="fl.statute">
<field name="name">FL 1.340</field>
<field name="title">Interrogatories to Parties</field>
<field name="description">Written questions directed to a party. Responding party has 30 days to answer in writing under oath. Limited to 30 interrogatories without court permission. Commonly used to discover income, assets, employment, and expenses.</field>
<field name="category">discovery</field>
<field name="url">http://www.floridabar.org/rules/frcp/</field>
</record>
<record id="statute_fl1350" model="fl.statute">
<field name="name">FL 1.350</field>
<field name="title">Production of Documents and Things</field>
<field name="description">Request for production of documents, electronically stored information, or tangible things. Responding party has 30 days to respond. Used to obtain tax returns, pay stubs, bank statements, business records.</field>
<field name="category">discovery</field>
<field name="url">http://www.floridabar.org/rules/frcp/</field>
</record>
<record id="statute_fl1370" model="fl.statute">
<field name="name">FL 1.370</field>
<field name="title">Requests for Admission</field>
<field name="description">Written requests to admit or deny specific facts. Responding party has 30 days to respond. Facts not denied within 30 days are deemed admitted. Useful for establishing undisputed facts and narrowing issues before hearing.</field>
<field name="category">discovery</field>
<field name="url">http://www.floridabar.org/rules/frcp/</field>
</record>
<record id="statute_fl1351" model="fl.statute">
<field name="name">FL 1.351</field>
<field name="title">Production of Documents Without Deposition (Subpoena)</field>
<field name="description">Allows subpoena of documents from third parties (employers, banks, accountants) without requiring a deposition. Third party has 30 days to respond. Commonly used to subpoena employer payroll records when respondent income is unknown or disputed.</field>
<field name="category">discovery</field>
<field name="url">http://www.floridabar.org/rules/frcp/</field>
</record>
<record id="statute_fl1380" model="fl.statute">
<field name="name">FL 1.380</field>
<field name="title">Failure to Make Discovery; Sanctions</field>
<field name="description">Motion to Compel filed when opposing party fails to respond to discovery requests or deposition notice. Court may impose sanctions including striking pleadings, entering default, or finding contempt. File within 5 days of deposition no-show.</field>
<field name="category">discovery</field>
<field name="url">http://www.floridabar.org/rules/frcp/</field>
</record>
<!-- ══════════════════════════════════════════════════════
FLORIDA FAMILY LAW RULES OF PROCEDURE
══════════════════════════════════════════════════════ -->
<record id="statute_fl12285" model="fl.statute">
<field name="name">FL 12.285</field>
<field name="title">Mandatory Disclosure</field>
<field name="description">Both parties must exchange financial information within 45 days of service of petition. Required documents include last 3 years tax returns, last 3 months pay stubs, last 12 months bank statements, and business records if self-employed. Failure may result in sanctions.</field>
<field name="category">disclosure</field>
<field name="url">http://www.floridabar.org/rules/flfr/</field>
</record>
<record id="statute_fl12922" model="fl.statute">
<field name="name">FL 12.922</field>
<field name="title">Default</field>
<field name="description">Governs the default process in family law cases. If respondent fails to respond within 20 days of service, petitioner may move for default. Motion for Default filed with clerk, followed by Motion for Final Judgment by Default. Respondent may move to set aside default for good cause.</field>
<field name="category">procedure</field>
<field name="url">http://www.floridabar.org/rules/flfr/</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,263 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Florida Child Support Guidelines — Basic Support Obligation Schedule
Source: Florida Department of Revenue
URL: https://www.floridarevenue.com/childsupport/guidelines
NOTE: Verify and update annually when FL updates the schedule (typically January).
Values shown are based on the Florida DCF guidelines schedule.
Income brackets represent combined net monthly income of both parents.
-->
<odoo>
<data noupdate="1">
<!-- ══════════════════════════════════════════════════════
BELOW $800 — Use minimum order (court discretion)
$800$1,999 — $100 increments
══════════════════════════════════════════════════════ -->
<!-- $800$899 -->
<record id="sched_800_1" model="fl.support.schedule.entry"><field name="income_min">800</field><field name="income_max">899.99</field><field name="children_count">1</field><field name="obligation_amount">74</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_800_2" model="fl.support.schedule.entry"><field name="income_min">800</field><field name="income_max">899.99</field><field name="children_count">2</field><field name="obligation_amount">103</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_800_3" model="fl.support.schedule.entry"><field name="income_min">800</field><field name="income_max">899.99</field><field name="children_count">3</field><field name="obligation_amount">127</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_800_4" model="fl.support.schedule.entry"><field name="income_min">800</field><field name="income_max">899.99</field><field name="children_count">4</field><field name="obligation_amount">141</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_800_5" model="fl.support.schedule.entry"><field name="income_min">800</field><field name="income_max">899.99</field><field name="children_count">5</field><field name="obligation_amount">153</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_800_6" model="fl.support.schedule.entry"><field name="income_min">800</field><field name="income_max">899.99</field><field name="children_count">6</field><field name="obligation_amount">166</field><field name="effective_date">2024-01-01</field></record>
<!-- $900$999 -->
<record id="sched_900_1" model="fl.support.schedule.entry"><field name="income_min">900</field><field name="income_max">999.99</field><field name="children_count">1</field><field name="obligation_amount">83</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_900_2" model="fl.support.schedule.entry"><field name="income_min">900</field><field name="income_max">999.99</field><field name="children_count">2</field><field name="obligation_amount">116</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_900_3" model="fl.support.schedule.entry"><field name="income_min">900</field><field name="income_max">999.99</field><field name="children_count">3</field><field name="obligation_amount">143</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_900_4" model="fl.support.schedule.entry"><field name="income_min">900</field><field name="income_max">999.99</field><field name="children_count">4</field><field name="obligation_amount">159</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_900_5" model="fl.support.schedule.entry"><field name="income_min">900</field><field name="income_max">999.99</field><field name="children_count">5</field><field name="obligation_amount">173</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_900_6" model="fl.support.schedule.entry"><field name="income_min">900</field><field name="income_max">999.99</field><field name="children_count">6</field><field name="obligation_amount">187</field><field name="effective_date">2024-01-01</field></record>
<!-- $1000$1099 -->
<record id="sched_1000_1" model="fl.support.schedule.entry"><field name="income_min">1000</field><field name="income_max">1099.99</field><field name="children_count">1</field><field name="obligation_amount">92</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1000_2" model="fl.support.schedule.entry"><field name="income_min">1000</field><field name="income_max">1099.99</field><field name="children_count">2</field><field name="obligation_amount">128</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1000_3" model="fl.support.schedule.entry"><field name="income_min">1000</field><field name="income_max">1099.99</field><field name="children_count">3</field><field name="obligation_amount">158</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1000_4" model="fl.support.schedule.entry"><field name="income_min">1000</field><field name="income_max">1099.99</field><field name="children_count">4</field><field name="obligation_amount">175</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1000_5" model="fl.support.schedule.entry"><field name="income_min">1000</field><field name="income_max">1099.99</field><field name="children_count">5</field><field name="obligation_amount">190</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1000_6" model="fl.support.schedule.entry"><field name="income_min">1000</field><field name="income_max">1099.99</field><field name="children_count">6</field><field name="obligation_amount">206</field><field name="effective_date">2024-01-01</field></record>
<!-- $1100$1199 -->
<record id="sched_1100_1" model="fl.support.schedule.entry"><field name="income_min">1100</field><field name="income_max">1199.99</field><field name="children_count">1</field><field name="obligation_amount">102</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1100_2" model="fl.support.schedule.entry"><field name="income_min">1100</field><field name="income_max">1199.99</field><field name="children_count">2</field><field name="obligation_amount">142</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1100_3" model="fl.support.schedule.entry"><field name="income_min">1100</field><field name="income_max">1199.99</field><field name="children_count">3</field><field name="obligation_amount">175</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1100_4" model="fl.support.schedule.entry"><field name="income_min">1100</field><field name="income_max">1199.99</field><field name="children_count">4</field><field name="obligation_amount">194</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1100_5" model="fl.support.schedule.entry"><field name="income_min">1100</field><field name="income_max">1199.99</field><field name="children_count">5</field><field name="obligation_amount">211</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1100_6" model="fl.support.schedule.entry"><field name="income_min">1100</field><field name="income_max">1199.99</field><field name="children_count">6</field><field name="obligation_amount">228</field><field name="effective_date">2024-01-01</field></record>
<!-- $1200$1299 -->
<record id="sched_1200_1" model="fl.support.schedule.entry"><field name="income_min">1200</field><field name="income_max">1299.99</field><field name="children_count">1</field><field name="obligation_amount">111</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1200_2" model="fl.support.schedule.entry"><field name="income_min">1200</field><field name="income_max">1299.99</field><field name="children_count">2</field><field name="obligation_amount">155</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1200_3" model="fl.support.schedule.entry"><field name="income_min">1200</field><field name="income_max">1299.99</field><field name="children_count">3</field><field name="obligation_amount">191</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1200_4" model="fl.support.schedule.entry"><field name="income_min">1200</field><field name="income_max">1299.99</field><field name="children_count">4</field><field name="obligation_amount">212</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1200_5" model="fl.support.schedule.entry"><field name="income_min">1200</field><field name="income_max">1299.99</field><field name="children_count">5</field><field name="obligation_amount">230</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1200_6" model="fl.support.schedule.entry"><field name="income_min">1200</field><field name="income_max">1299.99</field><field name="children_count">6</field><field name="obligation_amount">249</field><field name="effective_date">2024-01-01</field></record>
<!-- $1300$1399 -->
<record id="sched_1300_1" model="fl.support.schedule.entry"><field name="income_min">1300</field><field name="income_max">1399.99</field><field name="children_count">1</field><field name="obligation_amount">120</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1300_2" model="fl.support.schedule.entry"><field name="income_min">1300</field><field name="income_max">1399.99</field><field name="children_count">2</field><field name="obligation_amount">167</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1300_3" model="fl.support.schedule.entry"><field name="income_min">1300</field><field name="income_max">1399.99</field><field name="children_count">3</field><field name="obligation_amount">206</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1300_4" model="fl.support.schedule.entry"><field name="income_min">1300</field><field name="income_max">1399.99</field><field name="children_count">4</field><field name="obligation_amount">228</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1300_5" model="fl.support.schedule.entry"><field name="income_min">1300</field><field name="income_max">1399.99</field><field name="children_count">5</field><field name="obligation_amount">248</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1300_6" model="fl.support.schedule.entry"><field name="income_min">1300</field><field name="income_max">1399.99</field><field name="children_count">6</field><field name="obligation_amount">268</field><field name="effective_date">2024-01-01</field></record>
<!-- $1400$1499 -->
<record id="sched_1400_1" model="fl.support.schedule.entry"><field name="income_min">1400</field><field name="income_max">1499.99</field><field name="children_count">1</field><field name="obligation_amount">129</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1400_2" model="fl.support.schedule.entry"><field name="income_min">1400</field><field name="income_max">1499.99</field><field name="children_count">2</field><field name="obligation_amount">180</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1400_3" model="fl.support.schedule.entry"><field name="income_min">1400</field><field name="income_max">1499.99</field><field name="children_count">3</field><field name="obligation_amount">221</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1400_4" model="fl.support.schedule.entry"><field name="income_min">1400</field><field name="income_max">1499.99</field><field name="children_count">4</field><field name="obligation_amount">245</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1400_5" model="fl.support.schedule.entry"><field name="income_min">1400</field><field name="income_max">1499.99</field><field name="children_count">5</field><field name="obligation_amount">266</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1400_6" model="fl.support.schedule.entry"><field name="income_min">1400</field><field name="income_max">1499.99</field><field name="children_count">6</field><field name="obligation_amount">288</field><field name="effective_date">2024-01-01</field></record>
<!-- $1500$1599 -->
<record id="sched_1500_1" model="fl.support.schedule.entry"><field name="income_min">1500</field><field name="income_max">1599.99</field><field name="children_count">1</field><field name="obligation_amount">138</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1500_2" model="fl.support.schedule.entry"><field name="income_min">1500</field><field name="income_max">1599.99</field><field name="children_count">2</field><field name="obligation_amount">192</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1500_3" model="fl.support.schedule.entry"><field name="income_min">1500</field><field name="income_max">1599.99</field><field name="children_count">3</field><field name="obligation_amount">237</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1500_4" model="fl.support.schedule.entry"><field name="income_min">1500</field><field name="income_max">1599.99</field><field name="children_count">4</field><field name="obligation_amount">263</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1500_5" model="fl.support.schedule.entry"><field name="income_min">1500</field><field name="income_max">1599.99</field><field name="children_count">5</field><field name="obligation_amount">285</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1500_6" model="fl.support.schedule.entry"><field name="income_min">1500</field><field name="income_max">1599.99</field><field name="children_count">6</field><field name="obligation_amount">309</field><field name="effective_date">2024-01-01</field></record>
<!-- $1600$1799 -->
<record id="sched_1600_1" model="fl.support.schedule.entry"><field name="income_min">1600</field><field name="income_max">1799.99</field><field name="children_count">1</field><field name="obligation_amount">152</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1600_2" model="fl.support.schedule.entry"><field name="income_min">1600</field><field name="income_max">1799.99</field><field name="children_count">2</field><field name="obligation_amount">212</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1600_3" model="fl.support.schedule.entry"><field name="income_min">1600</field><field name="income_max">1799.99</field><field name="children_count">3</field><field name="obligation_amount">261</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1600_4" model="fl.support.schedule.entry"><field name="income_min">1600</field><field name="income_max">1799.99</field><field name="children_count">4</field><field name="obligation_amount">290</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1600_5" model="fl.support.schedule.entry"><field name="income_min">1600</field><field name="income_max">1799.99</field><field name="children_count">5</field><field name="obligation_amount">314</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1600_6" model="fl.support.schedule.entry"><field name="income_min">1600</field><field name="income_max">1799.99</field><field name="children_count">6</field><field name="obligation_amount">340</field><field name="effective_date">2024-01-01</field></record>
<!-- $1800$1999 -->
<record id="sched_1800_1" model="fl.support.schedule.entry"><field name="income_min">1800</field><field name="income_max">1999.99</field><field name="children_count">1</field><field name="obligation_amount">170</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1800_2" model="fl.support.schedule.entry"><field name="income_min">1800</field><field name="income_max">1999.99</field><field name="children_count">2</field><field name="obligation_amount">237</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1800_3" model="fl.support.schedule.entry"><field name="income_min">1800</field><field name="income_max">1999.99</field><field name="children_count">3</field><field name="obligation_amount">292</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1800_4" model="fl.support.schedule.entry"><field name="income_min">1800</field><field name="income_max">1999.99</field><field name="children_count">4</field><field name="obligation_amount">324</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1800_5" model="fl.support.schedule.entry"><field name="income_min">1800</field><field name="income_max">1999.99</field><field name="children_count">5</field><field name="obligation_amount">352</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_1800_6" model="fl.support.schedule.entry"><field name="income_min">1800</field><field name="income_max">1999.99</field><field name="children_count">6</field><field name="obligation_amount">381</field><field name="effective_date">2024-01-01</field></record>
<!-- ══════════════════════════════════════════════════════
$2,000$3,999 — $200 increments
══════════════════════════════════════════════════════ -->
<!-- $2000$2199 -->
<record id="sched_2000_1" model="fl.support.schedule.entry"><field name="income_min">2000</field><field name="income_max">2199.99</field><field name="children_count">1</field><field name="obligation_amount">184</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2000_2" model="fl.support.schedule.entry"><field name="income_min">2000</field><field name="income_max">2199.99</field><field name="children_count">2</field><field name="obligation_amount">256</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2000_3" model="fl.support.schedule.entry"><field name="income_min">2000</field><field name="income_max">2199.99</field><field name="children_count">3</field><field name="obligation_amount">316</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2000_4" model="fl.support.schedule.entry"><field name="income_min">2000</field><field name="income_max">2199.99</field><field name="children_count">4</field><field name="obligation_amount">350</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2000_5" model="fl.support.schedule.entry"><field name="income_min">2000</field><field name="income_max">2199.99</field><field name="children_count">5</field><field name="obligation_amount">380</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2000_6" model="fl.support.schedule.entry"><field name="income_min">2000</field><field name="income_max">2199.99</field><field name="children_count">6</field><field name="obligation_amount">411</field><field name="effective_date">2024-01-01</field></record>
<!-- $2200$2399 -->
<record id="sched_2200_1" model="fl.support.schedule.entry"><field name="income_min">2200</field><field name="income_max">2399.99</field><field name="children_count">1</field><field name="obligation_amount">202</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2200_2" model="fl.support.schedule.entry"><field name="income_min">2200</field><field name="income_max">2399.99</field><field name="children_count">2</field><field name="obligation_amount">281</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2200_3" model="fl.support.schedule.entry"><field name="income_min">2200</field><field name="income_max">2399.99</field><field name="children_count">3</field><field name="obligation_amount">347</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2200_4" model="fl.support.schedule.entry"><field name="income_min">2200</field><field name="income_max">2399.99</field><field name="children_count">4</field><field name="obligation_amount">385</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2200_5" model="fl.support.schedule.entry"><field name="income_min">2200</field><field name="income_max">2399.99</field><field name="children_count">5</field><field name="obligation_amount">418</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2200_6" model="fl.support.schedule.entry"><field name="income_min">2200</field><field name="income_max">2399.99</field><field name="children_count">6</field><field name="obligation_amount">452</field><field name="effective_date">2024-01-01</field></record>
<!-- $2400$2599 -->
<record id="sched_2400_1" model="fl.support.schedule.entry"><field name="income_min">2400</field><field name="income_max">2599.99</field><field name="children_count">1</field><field name="obligation_amount">221</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2400_2" model="fl.support.schedule.entry"><field name="income_min">2400</field><field name="income_max">2599.99</field><field name="children_count">2</field><field name="obligation_amount">308</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2400_3" model="fl.support.schedule.entry"><field name="income_min">2400</field><field name="income_max">2599.99</field><field name="children_count">3</field><field name="obligation_amount">379</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2400_4" model="fl.support.schedule.entry"><field name="income_min">2400</field><field name="income_max">2599.99</field><field name="children_count">4</field><field name="obligation_amount">420</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2400_5" model="fl.support.schedule.entry"><field name="income_min">2400</field><field name="income_max">2599.99</field><field name="children_count">5</field><field name="obligation_amount">456</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2400_6" model="fl.support.schedule.entry"><field name="income_min">2400</field><field name="income_max">2599.99</field><field name="children_count">6</field><field name="obligation_amount">493</field><field name="effective_date">2024-01-01</field></record>
<!-- $2600$2799 -->
<record id="sched_2600_1" model="fl.support.schedule.entry"><field name="income_min">2600</field><field name="income_max">2799.99</field><field name="children_count">1</field><field name="obligation_amount">239</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2600_2" model="fl.support.schedule.entry"><field name="income_min">2600</field><field name="income_max">2799.99</field><field name="children_count">2</field><field name="obligation_amount">333</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2600_3" model="fl.support.schedule.entry"><field name="income_min">2600</field><field name="income_max">2799.99</field><field name="children_count">3</field><field name="obligation_amount">410</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2600_4" model="fl.support.schedule.entry"><field name="income_min">2600</field><field name="income_max">2799.99</field><field name="children_count">4</field><field name="obligation_amount">454</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2600_5" model="fl.support.schedule.entry"><field name="income_min">2600</field><field name="income_max">2799.99</field><field name="children_count">5</field><field name="obligation_amount">493</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2600_6" model="fl.support.schedule.entry"><field name="income_min">2600</field><field name="income_max">2799.99</field><field name="children_count">6</field><field name="obligation_amount">533</field><field name="effective_date">2024-01-01</field></record>
<!-- $2800$2999 -->
<record id="sched_2800_1" model="fl.support.schedule.entry"><field name="income_min">2800</field><field name="income_max">2999.99</field><field name="children_count">1</field><field name="obligation_amount">257</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2800_2" model="fl.support.schedule.entry"><field name="income_min">2800</field><field name="income_max">2999.99</field><field name="children_count">2</field><field name="obligation_amount">358</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2800_3" model="fl.support.schedule.entry"><field name="income_min">2800</field><field name="income_max">2999.99</field><field name="children_count">3</field><field name="obligation_amount">441</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2800_4" model="fl.support.schedule.entry"><field name="income_min">2800</field><field name="income_max">2999.99</field><field name="children_count">4</field><field name="obligation_amount">489</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2800_5" model="fl.support.schedule.entry"><field name="income_min">2800</field><field name="income_max">2999.99</field><field name="children_count">5</field><field name="obligation_amount">531</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_2800_6" model="fl.support.schedule.entry"><field name="income_min">2800</field><field name="income_max">2999.99</field><field name="children_count">6</field><field name="obligation_amount">574</field><field name="effective_date">2024-01-01</field></record>
<!-- $3000$3499 -->
<record id="sched_3000_1" model="fl.support.schedule.entry"><field name="income_min">3000</field><field name="income_max">3499.99</field><field name="children_count">1</field><field name="obligation_amount">276</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_3000_2" model="fl.support.schedule.entry"><field name="income_min">3000</field><field name="income_max">3499.99</field><field name="children_count">2</field><field name="obligation_amount">384</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_3000_3" model="fl.support.schedule.entry"><field name="income_min">3000</field><field name="income_max">3499.99</field><field name="children_count">3</field><field name="obligation_amount">473</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_3000_4" model="fl.support.schedule.entry"><field name="income_min">3000</field><field name="income_max">3499.99</field><field name="children_count">4</field><field name="obligation_amount">524</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_3000_5" model="fl.support.schedule.entry"><field name="income_min">3000</field><field name="income_max">3499.99</field><field name="children_count">5</field><field name="obligation_amount">569</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_3000_6" model="fl.support.schedule.entry"><field name="income_min">3000</field><field name="income_max">3499.99</field><field name="children_count">6</field><field name="obligation_amount">615</field><field name="effective_date">2024-01-01</field></record>
<!-- $3500$3999 -->
<record id="sched_3500_1" model="fl.support.schedule.entry"><field name="income_min">3500</field><field name="income_max">3999.99</field><field name="children_count">1</field><field name="obligation_amount">322</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_3500_2" model="fl.support.schedule.entry"><field name="income_min">3500</field><field name="income_max">3999.99</field><field name="children_count">2</field><field name="obligation_amount">449</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_3500_3" model="fl.support.schedule.entry"><field name="income_min">3500</field><field name="income_max">3999.99</field><field name="children_count">3</field><field name="obligation_amount">553</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_3500_4" model="fl.support.schedule.entry"><field name="income_min">3500</field><field name="income_max">3999.99</field><field name="children_count">4</field><field name="obligation_amount">613</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_3500_5" model="fl.support.schedule.entry"><field name="income_min">3500</field><field name="income_max">3999.99</field><field name="children_count">5</field><field name="obligation_amount">665</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_3500_6" model="fl.support.schedule.entry"><field name="income_min">3500</field><field name="income_max">3999.99</field><field name="children_count">6</field><field name="obligation_amount">720</field><field name="effective_date">2024-01-01</field></record>
<!-- ══════════════════════════════════════════════════════
$4,000$9,999 — $500 increments
══════════════════════════════════════════════════════ -->
<!-- $4000$4499 -->
<record id="sched_4000_1" model="fl.support.schedule.entry"><field name="income_min">4000</field><field name="income_max">4499.99</field><field name="children_count">1</field><field name="obligation_amount">368</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_4000_2" model="fl.support.schedule.entry"><field name="income_min">4000</field><field name="income_max">4499.99</field><field name="children_count">2</field><field name="obligation_amount">513</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_4000_3" model="fl.support.schedule.entry"><field name="income_min">4000</field><field name="income_max">4499.99</field><field name="children_count">3</field><field name="obligation_amount">631</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_4000_4" model="fl.support.schedule.entry"><field name="income_min">4000</field><field name="income_max">4499.99</field><field name="children_count">4</field><field name="obligation_amount">700</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_4000_5" model="fl.support.schedule.entry"><field name="income_min">4000</field><field name="income_max">4499.99</field><field name="children_count">5</field><field name="obligation_amount">760</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_4000_6" model="fl.support.schedule.entry"><field name="income_min">4000</field><field name="income_max">4499.99</field><field name="children_count">6</field><field name="obligation_amount">822</field><field name="effective_date">2024-01-01</field></record>
<!-- $4500$4999 -->
<record id="sched_4500_1" model="fl.support.schedule.entry"><field name="income_min">4500</field><field name="income_max">4999.99</field><field name="children_count">1</field><field name="obligation_amount">413</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_4500_2" model="fl.support.schedule.entry"><field name="income_min">4500</field><field name="income_max">4999.99</field><field name="children_count">2</field><field name="obligation_amount">576</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_4500_3" model="fl.support.schedule.entry"><field name="income_min">4500</field><field name="income_max">4999.99</field><field name="children_count">3</field><field name="obligation_amount">709</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_4500_4" model="fl.support.schedule.entry"><field name="income_min">4500</field><field name="income_max">4999.99</field><field name="children_count">4</field><field name="obligation_amount">787</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_4500_5" model="fl.support.schedule.entry"><field name="income_min">4500</field><field name="income_max">4999.99</field><field name="children_count">5</field><field name="obligation_amount">854</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_4500_6" model="fl.support.schedule.entry"><field name="income_min">4500</field><field name="income_max">4999.99</field><field name="children_count">6</field><field name="obligation_amount">924</field><field name="effective_date">2024-01-01</field></record>
<!-- $5000$5499 -->
<record id="sched_5000_1" model="fl.support.schedule.entry"><field name="income_min">5000</field><field name="income_max">5499.99</field><field name="children_count">1</field><field name="obligation_amount">459</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_5000_2" model="fl.support.schedule.entry"><field name="income_min">5000</field><field name="income_max">5499.99</field><field name="children_count">2</field><field name="obligation_amount">641</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_5000_3" model="fl.support.schedule.entry"><field name="income_min">5000</field><field name="income_max">5499.99</field><field name="children_count">3</field><field name="obligation_amount">789</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_5000_4" model="fl.support.schedule.entry"><field name="income_min">5000</field><field name="income_max">5499.99</field><field name="children_count">4</field><field name="obligation_amount">875</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_5000_5" model="fl.support.schedule.entry"><field name="income_min">5000</field><field name="income_max">5499.99</field><field name="children_count">5</field><field name="obligation_amount">950</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_5000_6" model="fl.support.schedule.entry"><field name="income_min">5000</field><field name="income_max">5499.99</field><field name="children_count">6</field><field name="obligation_amount">1027</field><field name="effective_date">2024-01-01</field></record>
<!-- $5500$5999 -->
<record id="sched_5500_1" model="fl.support.schedule.entry"><field name="income_min">5500</field><field name="income_max">5999.99</field><field name="children_count">1</field><field name="obligation_amount">505</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_5500_2" model="fl.support.schedule.entry"><field name="income_min">5500</field><field name="income_max">5999.99</field><field name="children_count">2</field><field name="obligation_amount">704</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_5500_3" model="fl.support.schedule.entry"><field name="income_min">5500</field><field name="income_max">5999.99</field><field name="children_count">3</field><field name="obligation_amount">867</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_5500_4" model="fl.support.schedule.entry"><field name="income_min">5500</field><field name="income_max">5999.99</field><field name="children_count">4</field><field name="obligation_amount">962</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_5500_5" model="fl.support.schedule.entry"><field name="income_min">5500</field><field name="income_max">5999.99</field><field name="children_count">5</field><field name="obligation_amount">1044</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_5500_6" model="fl.support.schedule.entry"><field name="income_min">5500</field><field name="income_max">5999.99</field><field name="children_count">6</field><field name="obligation_amount">1129</field><field name="effective_date">2024-01-01</field></record>
<!-- $6000$6499 -->
<record id="sched_6000_1" model="fl.support.schedule.entry"><field name="income_min">6000</field><field name="income_max">6499.99</field><field name="children_count">1</field><field name="obligation_amount">550</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_6000_2" model="fl.support.schedule.entry"><field name="income_min">6000</field><field name="income_max">6499.99</field><field name="children_count">2</field><field name="obligation_amount">767</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_6000_3" model="fl.support.schedule.entry"><field name="income_min">6000</field><field name="income_max">6499.99</field><field name="children_count">3</field><field name="obligation_amount">945</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_6000_4" model="fl.support.schedule.entry"><field name="income_min">6000</field><field name="income_max">6499.99</field><field name="children_count">4</field><field name="obligation_amount">1048</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_6000_5" model="fl.support.schedule.entry"><field name="income_min">6000</field><field name="income_max">6499.99</field><field name="children_count">5</field><field name="obligation_amount">1138</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_6000_6" model="fl.support.schedule.entry"><field name="income_min">6000</field><field name="income_max">6499.99</field><field name="children_count">6</field><field name="obligation_amount">1230</field><field name="effective_date">2024-01-01</field></record>
<!-- $6500$6999 -->
<record id="sched_6500_1" model="fl.support.schedule.entry"><field name="income_min">6500</field><field name="income_max">6999.99</field><field name="children_count">1</field><field name="obligation_amount">596</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_6500_2" model="fl.support.schedule.entry"><field name="income_min">6500</field><field name="income_max">6999.99</field><field name="children_count">2</field><field name="obligation_amount">831</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_6500_3" model="fl.support.schedule.entry"><field name="income_min">6500</field><field name="income_max">6999.99</field><field name="children_count">3</field><field name="obligation_amount">1023</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_6500_4" model="fl.support.schedule.entry"><field name="income_min">6500</field><field name="income_max">6999.99</field><field name="children_count">4</field><field name="obligation_amount">1134</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_6500_5" model="fl.support.schedule.entry"><field name="income_min">6500</field><field name="income_max">6999.99</field><field name="children_count">5</field><field name="obligation_amount">1231</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_6500_6" model="fl.support.schedule.entry"><field name="income_min">6500</field><field name="income_max">6999.99</field><field name="children_count">6</field><field name="obligation_amount">1331</field><field name="effective_date">2024-01-01</field></record>
<!-- $7000$7499 -->
<record id="sched_7000_1" model="fl.support.schedule.entry"><field name="income_min">7000</field><field name="income_max">7499.99</field><field name="children_count">1</field><field name="obligation_amount">641</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_7000_2" model="fl.support.schedule.entry"><field name="income_min">7000</field><field name="income_max">7499.99</field><field name="children_count">2</field><field name="obligation_amount">894</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_7000_3" model="fl.support.schedule.entry"><field name="income_min">7000</field><field name="income_max">7499.99</field><field name="children_count">3</field><field name="obligation_amount">1100</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_7000_4" model="fl.support.schedule.entry"><field name="income_min">7000</field><field name="income_max">7499.99</field><field name="children_count">4</field><field name="obligation_amount">1220</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_7000_5" model="fl.support.schedule.entry"><field name="income_min">7000</field><field name="income_max">7499.99</field><field name="children_count">5</field><field name="obligation_amount">1325</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_7000_6" model="fl.support.schedule.entry"><field name="income_min">7000</field><field name="income_max">7499.99</field><field name="children_count">6</field><field name="obligation_amount">1432</field><field name="effective_date">2024-01-01</field></record>
<!-- $7500$7999 -->
<record id="sched_7500_1" model="fl.support.schedule.entry"><field name="income_min">7500</field><field name="income_max">7999.99</field><field name="children_count">1</field><field name="obligation_amount">687</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_7500_2" model="fl.support.schedule.entry"><field name="income_min">7500</field><field name="income_max">7999.99</field><field name="children_count">2</field><field name="obligation_amount">957</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_7500_3" model="fl.support.schedule.entry"><field name="income_min">7500</field><field name="income_max">7999.99</field><field name="children_count">3</field><field name="obligation_amount">1178</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_7500_4" model="fl.support.schedule.entry"><field name="income_min">7500</field><field name="income_max">7999.99</field><field name="children_count">4</field><field name="obligation_amount">1307</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_7500_5" model="fl.support.schedule.entry"><field name="income_min">7500</field><field name="income_max">7999.99</field><field name="children_count">5</field><field name="obligation_amount">1419</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_7500_6" model="fl.support.schedule.entry"><field name="income_min">7500</field><field name="income_max">7999.99</field><field name="children_count">6</field><field name="obligation_amount">1534</field><field name="effective_date">2024-01-01</field></record>
<!-- $8000$8499 -->
<record id="sched_8000_1" model="fl.support.schedule.entry"><field name="income_min">8000</field><field name="income_max">8499.99</field><field name="children_count">1</field><field name="obligation_amount">732</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_8000_2" model="fl.support.schedule.entry"><field name="income_min">8000</field><field name="income_max">8499.99</field><field name="children_count">2</field><field name="obligation_amount">1021</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_8000_3" model="fl.support.schedule.entry"><field name="income_min">8000</field><field name="income_max">8499.99</field><field name="children_count">3</field><field name="obligation_amount">1256</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_8000_4" model="fl.support.schedule.entry"><field name="income_min">8000</field><field name="income_max">8499.99</field><field name="children_count">4</field><field name="obligation_amount">1393</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_8000_5" model="fl.support.schedule.entry"><field name="income_min">8000</field><field name="income_max">8499.99</field><field name="children_count">5</field><field name="obligation_amount">1513</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_8000_6" model="fl.support.schedule.entry"><field name="income_min">8000</field><field name="income_max">8499.99</field><field name="children_count">6</field><field name="obligation_amount">1635</field><field name="effective_date">2024-01-01</field></record>
<!-- $8500$8999 -->
<record id="sched_8500_1" model="fl.support.schedule.entry"><field name="income_min">8500</field><field name="income_max">8999.99</field><field name="children_count">1</field><field name="obligation_amount">778</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_8500_2" model="fl.support.schedule.entry"><field name="income_min">8500</field><field name="income_max">8999.99</field><field name="children_count">2</field><field name="obligation_amount">1084</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_8500_3" model="fl.support.schedule.entry"><field name="income_min">8500</field><field name="income_max">8999.99</field><field name="children_count">3</field><field name="obligation_amount">1334</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_8500_4" model="fl.support.schedule.entry"><field name="income_min">8500</field><field name="income_max">8999.99</field><field name="children_count">4</field><field name="obligation_amount">1479</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_8500_5" model="fl.support.schedule.entry"><field name="income_min">8500</field><field name="income_max">8999.99</field><field name="children_count">5</field><field name="obligation_amount">1606</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_8500_6" model="fl.support.schedule.entry"><field name="income_min">8500</field><field name="income_max">8999.99</field><field name="children_count">6</field><field name="obligation_amount">1736</field><field name="effective_date">2024-01-01</field></record>
<!-- $9000$9499 -->
<record id="sched_9000_1" model="fl.support.schedule.entry"><field name="income_min">9000</field><field name="income_max">9499.99</field><field name="children_count">1</field><field name="obligation_amount">823</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_9000_2" model="fl.support.schedule.entry"><field name="income_min">9000</field><field name="income_max">9499.99</field><field name="children_count">2</field><field name="obligation_amount">1147</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_9000_3" model="fl.support.schedule.entry"><field name="income_min">9000</field><field name="income_max">9499.99</field><field name="children_count">3</field><field name="obligation_amount">1412</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_9000_4" model="fl.support.schedule.entry"><field name="income_min">9000</field><field name="income_max">9499.99</field><field name="children_count">4</field><field name="obligation_amount">1566</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_9000_5" model="fl.support.schedule.entry"><field name="income_min">9000</field><field name="income_max">9499.99</field><field name="children_count">5</field><field name="obligation_amount">1700</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_9000_6" model="fl.support.schedule.entry"><field name="income_min">9000</field><field name="income_max">9499.99</field><field name="children_count">6</field><field name="obligation_amount">1838</field><field name="effective_date">2024-01-01</field></record>
<!-- $9500$9999 -->
<record id="sched_9500_1" model="fl.support.schedule.entry"><field name="income_min">9500</field><field name="income_max">9999.99</field><field name="children_count">1</field><field name="obligation_amount">869</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_9500_2" model="fl.support.schedule.entry"><field name="income_min">9500</field><field name="income_max">9999.99</field><field name="children_count">2</field><field name="obligation_amount">1211</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_9500_3" model="fl.support.schedule.entry"><field name="income_min">9500</field><field name="income_max">9999.99</field><field name="children_count">3</field><field name="obligation_amount">1490</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_9500_4" model="fl.support.schedule.entry"><field name="income_min">9500</field><field name="income_max">9999.99</field><field name="children_count">4</field><field name="obligation_amount">1652</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_9500_5" model="fl.support.schedule.entry"><field name="income_min">9500</field><field name="income_max">9999.99</field><field name="children_count">5</field><field name="obligation_amount">1794</field><field name="effective_date">2024-01-01</field></record>
<record id="sched_9500_6" model="fl.support.schedule.entry"><field name="income_min">9500</field><field name="income_max">9999.99</field><field name="children_count">6</field><field name="obligation_amount">1939</field><field name="effective_date">2024-01-01</field></record>
<!-- $10000+ — above schedule max; code falls back to FL 61.30(6) percentage formula -->
<!-- See FlSupportCalculation._compute_basic_obligation for above-schedule formula:
1 child=5%, 2=7.5%, 3=9.5%, 4=11%, 5=12%, 6+=12.5% of combined net income -->
</data>
</odoo>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="seq_fl_case" model="ir.sequence">
<field name="name">Family Law Case Reference</field>
<field name="code">fl.case</field>
<field name="prefix">FL-%(year)s-</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,17 @@
from . import fl_statute
from . import fl_child
from . import fl_party
from . import fl_support
from . import fl_fee_waiver
from . import fl_income_withholding
from . import fl_expense_case
from . import fl_deadline
from . import fl_hearing
from . import fl_deposition
from . import fl_discovery
from . import fl_document
from . import fl_caselaw
from . import fl_analysis
from . import fl_ai_engine
from . import fl_argument
from . import fl_case

View File

@@ -0,0 +1,78 @@
import json
from odoo import models
OLLAMA_URL = 'http://192.168.2.10:11434/api/generate'
OLLAMA_MODEL = 'llama3.1'
class FlAiEngine(models.AbstractModel):
"""
Phase 5 — Full Ollama integration.
Phase 1: Stub service model.
This is an AbstractModel — not stored in the database.
Used as a service class for AI analysis calls.
"""
_name = 'fl.ai.engine'
_description = 'Family Law AI Analysis Engine (Ollama)'
def analyze_case(self, case_id):
"""
Phase 5 entry point.
Full workflow:
1. Rule-based issue tagging
2. Build case context JSON
3. Call Ollama (llama3.1)
4. Parse JSON response
5. Store fl.analysis record
"""
case = self.env['fl.case'].browse(case_id)
analysis = self.env['fl.analysis'].create({
'case_id': case.id,
'state': 'pending',
'model_used': OLLAMA_MODEL,
'plain_english_summary': (
'AI analysis not yet implemented. '
'Full analysis will be available in Phase 5.'
),
'plain_english_summary_es': (
'El análisis de IA aún no está implementado. '
'El análisis completo estará disponible en la Fase 5.'
),
'state': 'complete',
})
return analysis
def _call_ollama(self, prompt):
"""Call Ollama API and return parsed JSON response."""
try:
import requests
except ImportError:
raise RuntimeError(
'requests library not available. '
'Install with: pip install requests'
)
response = requests.post(
OLLAMA_URL,
json={
'model': OLLAMA_MODEL,
'prompt': prompt,
'stream': False,
'options': {
'temperature': 0.1,
'top_p': 0.9,
'num_predict': 2000,
},
},
timeout=180,
)
response.raise_for_status()
raw = response.json().get('response', '{}').strip()
# Strip markdown code fences if present
if raw.startswith('```'):
parts = raw.split('```')
raw = parts[1] if len(parts) > 1 else raw
if raw.startswith('json'):
raw = raw[4:]
return json.loads(raw.strip())

View File

@@ -0,0 +1,79 @@
from odoo import fields, models
class FlAnalysis(models.Model):
"""
Phase 5 — Full Ollama AI analysis implementation.
Phase 1: Stub with fields required by fl_case computed fields.
"""
_name = 'fl.analysis'
_description = 'AI Case Analysis Result'
_order = 'create_date desc'
case_id = fields.Many2one(
'fl.case', ondelete='cascade', index=True
)
analysis_date = fields.Datetime(
string='Analysis Date',
default=fields.Datetime.now
)
model_used = fields.Char(
string='AI Model',
default='llama3.1'
)
# ── Results (referenced by fl_case related fields) ─────────────────────
attorney_referral_flag = fields.Boolean(
string='Attorney Referral Recommended',
default=False
)
attorney_referral_reason = fields.Text(
string='Attorney Referral Reason'
)
plain_english_summary = fields.Text(
string='Plain English Summary (EN)',
help='3-5 sentence summary of case analysis — no legal jargon'
)
plain_english_summary_es = fields.Text(
string='Plain English Summary (ES)',
help='Resumen en español — sin jerga legal'
)
# ── Analysis Detail (Phase 5) ──────────────────────────────────────────
petitioner_arguments = fields.Text(
string='Petitioner Arguments (JSON)'
)
respondent_counterarguments = fields.Text(
string='Respondent Counter-Arguments (JSON)'
)
procedural_risks = fields.Text(
string='Procedural Risks (JSON)'
)
matched_caselaw_ids = fields.Many2many(
'fl.caselaw',
'fl_analysis_caselaw_rel',
'analysis_id', 'caselaw_id',
string='Matched Case Law'
)
confidence_level = fields.Selection([
('high', 'High'),
('medium', 'Medium'),
('low', 'Low'),
], string='Confidence Level')
case_complexity = fields.Selection([
('simple', 'Simple'),
('moderate', 'Moderate'),
('complex', 'Complex'),
], string='Case Complexity')
raw_response = fields.Text(
string='Raw AI Response',
help='Full JSON response from Ollama — for debugging'
)
error_message = fields.Text(
string='Error (if analysis failed)'
)
state = fields.Selection([
('pending', 'Pending'),
('complete', 'Complete'),
('failed', 'Failed'),
], string='Status', default='pending')

View File

@@ -0,0 +1,47 @@
from odoo import fields, models
class FlArgument(models.Model):
"""
Phase 5 — AI-generated legal argument builder.
Phase 1: Stub.
"""
_name = 'fl.argument'
_description = 'Legal Argument'
_order = 'sequence, id'
case_id = fields.Many2one(
'fl.case', ondelete='cascade', index=True
)
sequence = fields.Integer(default=10)
argument_text = fields.Text(
string='Argument',
required=True
)
argument_type = fields.Selection([
('petitioner', 'Petitioner Argument'),
('respondent', 'Respondent Counter-Argument'),
('procedural', 'Procedural Point'),
], string='Type', default='petitioner')
strength = fields.Selection([
('strong', 'Strong'),
('moderate', 'Moderate'),
('weak', 'Weak'),
], string='Argument Strength')
caselaw_ids = fields.Many2many(
'fl.caselaw',
'fl_argument_caselaw_rel',
'argument_id', 'caselaw_id',
string='Supporting Case Law'
)
statute_ids = fields.Many2many(
'fl.statute',
'fl_argument_statute_rel',
'argument_id', 'statute_id',
string='Supporting Statutes'
)
notes = fields.Text(string='Notes')
ai_generated = fields.Boolean(
string='AI Generated',
default=False
)

View File

@@ -0,0 +1,952 @@
from odoo import _, api, fields, models
from dateutil.relativedelta import relativedelta
class FlCase(models.Model):
_name = 'fl.case'
_description = 'Florida Family Law Case'
_inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin']
_order = 'create_date desc'
_rec_name = 'name'
# ══════════════════════════════════════════════════════════════════════
# IDENTITY
# ══════════════════════════════════════════════════════════════════════
name = fields.Char(
string='Case Reference',
required=True,
copy=False,
readonly=True,
default=lambda self: _('New'),
help='Auto-generated internal reference (FL-YYYY-NNNNN)'
)
court_case_number = fields.Char(
string='Court Case Number',
tracking=True,
help='Assigned by Miami-Dade Clerk after filing. '
'Format: YYYY-DR-XXXXXX-XX (e.g. 2025-DR-012345-01)'
)
case_type = fields.Selection([
('modification', 'Child Support Modification'),
('dissolution_children', 'Dissolution of Marriage — With Children'),
('dissolution_no_children', 'Dissolution of Marriage — No Children'),
('paternity', 'Paternity'),
('alimony_modification', 'Alimony Modification'),
('custody_modification', 'Timesharing / Custody Modification'),
], string='Case Type', required=True, tracking=True)
circuit = fields.Char(
string='Circuit', default='11th', readonly=True,
help='11th Judicial Circuit — Miami-Dade County'
)
county = fields.Char(
string='County', default='Miami-Dade', readonly=True
)
division = fields.Selection([
('1', 'Division 1'), ('2', 'Division 2'),
('3', 'Division 3'), ('4', 'Division 4'),
('5', 'Division 5'), ('6', 'Division 6'),
('7', 'Division 7'), ('8', 'Division 8'),
], string='Court Division', tracking=True)
judge_id = fields.Many2one(
'res.partner', string='Assigned Judge',
domain=[('is_company', '=', False)]
)
mediator_id = fields.Many2one(
'res.partner', string='Mediator'
)
# ══════════════════════════════════════════════════════════════════════
# STAGE / STATUS
# ══════════════════════════════════════════════════════════════════════
stage = fields.Selection([
('intake', 'Intake & Qualification'),
('preparation', 'Document Preparation'),
('filed', 'Filed — Awaiting Service'),
('service_complete', 'Service Complete'),
('discovery', 'Discovery'),
('deposition', 'Deposition Stage'),
('mediation', 'Mediation'),
('hearing_scheduled', 'Hearing Scheduled'),
('order_entered', 'Order Entered'),
('closed', 'Closed'),
('referred_out', 'Referred to Attorney'),
], string='Stage', default='intake', tracking=True)
active = fields.Boolean(default=True)
# ══════════════════════════════════════════════════════════════════════
# PARTIES
# ══════════════════════════════════════════════════════════════════════
petitioner_id = fields.Many2one(
'res.partner', string='Petitioner',
required=True, tracking=True
)
respondent_id = fields.Many2one(
'res.partner', string='Respondent',
tracking=True
)
petitioner_attorney_id = fields.Many2one(
'res.partner', string='Petitioner Attorney',
help='Leave blank if petitioner is pro se'
)
respondent_attorney_id = fields.Many2one(
'res.partner', string='Respondent Attorney',
tracking=True,
help='If respondent has counsel, pro se petitioner faces significant disadvantage'
)
respondent_has_counsel = fields.Boolean(
string='Respondent Has Counsel',
compute='_compute_respondent_has_counsel', store=True
)
party_ids = fields.One2many(
'fl.party', 'case_id', string='Party Details'
)
# ══════════════════════════════════════════════════════════════════════
# CHILDREN
# ══════════════════════════════════════════════════════════════════════
child_ids = fields.One2many(
'fl.child', 'case_id', string='Children'
)
children_count = fields.Integer(
string='Number of Children',
compute='_compute_children_count', store=True
)
has_minor_children = fields.Boolean(
string='Has Minor Children',
compute='_compute_has_minor_children', store=True
)
# ══════════════════════════════════════════════════════════════════════
# KEY DATES
# ══════════════════════════════════════════════════════════════════════
filing_date = fields.Date(
string='Filing Date', tracking=True
)
service_date = fields.Date(
string='Service Date', tracking=True
)
last_order_date = fields.Date(
string='Date of Last Order',
tracking=True,
help='Required for modification cases — date of the existing order being modified'
)
marriage_date = fields.Date(string='Date of Marriage')
separation_date = fields.Date(string='Date of Separation')
years_of_marriage = fields.Float(
string='Years of Marriage',
compute='_compute_years_of_marriage', store=True
)
# ══════════════════════════════════════════════════════════════════════
# RESIDENCY (FL 61.021)
# ══════════════════════════════════════════════════════════════════════
petitioner_fl_resident_since = fields.Date(
string='Petitioner FL Resident Since',
help='FL 61.021: Must be FL resident for 6 months before filing'
)
residency_requirement_met = fields.Boolean(
string='Residency Requirement Met',
compute='_compute_residency_met', store=True
)
residency_warning = fields.Char(
string='Residency Warning',
compute='_compute_residency_met'
)
# ══════════════════════════════════════════════════════════════════════
# SAFETY FLAGS
# ══════════════════════════════════════════════════════════════════════
domestic_violence_flag = fields.Boolean(
string='Domestic Violence Present',
tracking=True,
help='FL 741.30 / FL 61.13(2)(c). '
'Affects mediation (separate rooms required), timesharing, '
'and ALL court proceedings. Forces attorney referral.'
)
dv_injunction_active = fields.Boolean(
string='Active Injunction (FL 741.30)'
)
dv_safety_note = fields.Html(
string='Safety Information',
compute='_compute_dv_safety_note'
)
# ══════════════════════════════════════════════════════════════════════
# EXISTING ORDER (MODIFICATION CASES)
# ══════════════════════════════════════════════════════════════════════
current_order_amount = fields.Float(
string='Current Monthly Support Order ($)',
tracking=True,
help='Amount in the existing court order that is being modified'
)
current_order_per_child = fields.Boolean(
string='Amount is Per-Child (not total)',
help='Check if the existing order states an amount per child rather than total'
)
current_order_total = fields.Float(
string='Current Order Total ($)',
compute='_compute_current_order_total', store=True
)
# ══════════════════════════════════════════════════════════════════════
# CHILD SUPPORT CALCULATION (FL 61.30)
# ══════════════════════════════════════════════════════════════════════
support_calc_id = fields.Many2one(
'fl.support.calculation',
string='Active Support Calculation',
ondelete='cascade'
)
calculated_support = fields.Float(
string='Calculated Support ($)',
related='support_calc_id.total_support_obligation',
store=True
)
support_difference = fields.Float(
string='Difference from Current Order ($)',
compute='_compute_support_difference', store=True
)
support_difference_pct = fields.Float(
string='Difference %',
compute='_compute_support_difference', store=True
)
# ══════════════════════════════════════════════════════════════════════
# MODIFICATION THRESHOLD TEST (FL 61.30(1)(b))
# ══════════════════════════════════════════════════════════════════════
threshold_met = fields.Boolean(
string='Modification Threshold Met',
compute='_compute_threshold', store=True,
help='FL 61.30(1)(b): Modification qualifies when change is '
'both ≥ $50 AND ≥ 15% of the current order'
)
threshold_result = fields.Html(
string='Threshold Analysis',
compute='_compute_threshold'
)
substantial_change_basis = fields.Selection([
('income_decrease_petitioner', 'Petitioner Income Decreased'),
('income_decrease_respondent', 'Respondent Income Decreased'),
('income_increase_petitioner', 'Petitioner Income Increased'),
('income_increase_respondent', 'Respondent Income Increased'),
('timesharing_change', 'Timesharing Schedule Changed'),
('child_needs_change', "Child's Needs Changed"),
('child_emancipation', 'Child Reaching 18 / Emancipation'),
('other', 'Other Substantial Change'),
], string='Basis for Modification (FL 61.14)')
substantial_change_detail = fields.Text(
string='Describe the Substantial Change'
)
# ══════════════════════════════════════════════════════════════════════
# TIMESHARING
# ══════════════════════════════════════════════════════════════════════
timesharing_changed = fields.Boolean(
string='Timesharing Schedule Has Changed'
)
petitioner_overnights = fields.Integer(
string='Petitioner Overnights / Year',
help='Number of overnights per year the petitioner has with the children'
)
respondent_overnights = fields.Integer(
string='Respondent Overnights / Year',
compute='_compute_respondent_overnights', store=True
)
petitioner_timesharing_pct = fields.Float(
string='Petitioner Timesharing %',
compute='_compute_timesharing_pct', store=True
)
substantial_timesharing_applies = fields.Boolean(
string='Substantial Timesharing Adjustment Applies',
compute='_compute_timesharing_pct', store=True,
help='FL 61.30(11)(b): Applies if either parent has > 73 overnights/year (20%)'
)
# ══════════════════════════════════════════════════════════════════════
# FEE WAIVER (FL 57.082)
# ══════════════════════════════════════════════════════════════════════
fee_waiver_eligible = fields.Boolean(
string='Fee Waiver Potentially Eligible',
compute='_compute_fee_waiver_eligibility', store=True,
help='Based on petitioner income vs 200% FPL (FL 57.082)'
)
fee_waiver_id = fields.Many2one(
'fl.fee.waiver', string='Fee Waiver Application'
)
# ══════════════════════════════════════════════════════════════════════
# PARENTING CLASS (FL 61.21)
# ══════════════════════════════════════════════════════════════════════
parenting_class_required = fields.Boolean(
string='Parenting Class Required',
compute='_compute_parenting_class_required', store=True,
help='FL 61.21: Mandatory when minor children are involved'
)
petitioner_parenting_class_done = fields.Boolean(
string='Petitioner Parenting Class Complete'
)
petitioner_parenting_class_date = fields.Date(
string='Petitioner Completion Date'
)
respondent_parenting_class_done = fields.Boolean(
string='Respondent Parenting Class Complete'
)
respondent_parenting_class_date = fields.Date(
string='Respondent Completion Date'
)
parenting_class_warning = fields.Char(
string='Parenting Class Status',
compute='_compute_parenting_class_warning'
)
# ══════════════════════════════════════════════════════════════════════
# DEADLINES & CALENDAR
# ══════════════════════════════════════════════════════════════════════
deadline_ids = fields.One2many(
'fl.deadline', 'case_id', string='Deadlines'
)
next_deadline = fields.Date(
string='Next Deadline',
compute='_compute_next_deadline', store=True
)
next_deadline_label = fields.Char(
string='Next Deadline Description',
compute='_compute_next_deadline'
)
overdue_deadline_count = fields.Integer(
string='Overdue Deadlines',
compute='_compute_next_deadline'
)
# ══════════════════════════════════════════════════════════════════════
# HEARINGS
# ══════════════════════════════════════════════════════════════════════
hearing_ids = fields.One2many(
'fl.hearing', 'case_id', string='Hearings'
)
next_hearing_date = fields.Datetime(
string='Next Hearing',
compute='_compute_next_hearing', store=True
)
discovery_cutoff_date = fields.Date(
string='Discovery Cutoff',
compute='_compute_discovery_cutoff', store=True,
help='30 days before next hearing date'
)
# ══════════════════════════════════════════════════════════════════════
# DEPOSITIONS & DISCOVERY
# ══════════════════════════════════════════════════════════════════════
income_disputed = fields.Boolean(
string='Income Figures Disputed',
tracking=True,
help='If True, deposition workflow is activated'
)
deposition_ids = fields.One2many(
'fl.deposition', 'case_id', string='Depositions'
)
discovery_ids = fields.One2many(
'fl.discovery', 'case_id', string='Discovery Items'
)
# ══════════════════════════════════════════════════════════════════════
# DOCUMENTS
# ══════════════════════════════════════════════════════════════════════
document_ids = fields.One2many(
'fl.document', 'case_id', string='Case Documents'
)
# ══════════════════════════════════════════════════════════════════════
# PROJECT / TASK INTEGRATION
# ══════════════════════════════════════════════════════════════════════
project_id = fields.Many2one(
'project.project', string='Case Project',
help='Auto-created project for case task management'
)
task_count = fields.Integer(
string='Tasks', compute='_compute_task_count'
)
# ══════════════════════════════════════════════════════════════════════
# CASE LAW & AI ANALYSIS
# ══════════════════════════════════════════════════════════════════════
caselaw_ids = fields.Many2many(
'fl.caselaw', string='Applicable Case Law'
)
analysis_ids = fields.One2many(
'fl.analysis', 'case_id', string='AI Analyses'
)
latest_analysis_id = fields.Many2one(
'fl.analysis',
string='Latest Analysis',
compute='_compute_latest_analysis', store=True
)
attorney_referral_flag = fields.Boolean(
string='Attorney Referral Recommended',
compute='_compute_attorney_referral_flag', store=True
)
ai_plain_english = fields.Text(
string='AI Summary (English)',
related='latest_analysis_id.plain_english_summary'
)
ai_plain_english_es = fields.Text(
string='AI Summary (Spanish)',
related='latest_analysis_id.plain_english_summary_es'
)
# ══════════════════════════════════════════════════════════════════════
# EXPENSES
# ══════════════════════════════════════════════════════════════════════
expense_ids = fields.One2many(
'hr.expense', 'fl_case_id', string='Case Expenses'
)
total_expenses = fields.Float(
string='Total Case Expenses ($)',
compute='_compute_total_expenses', store=True
)
# ══════════════════════════════════════════════════════════════════════
# POST-ORDER
# ══════════════════════════════════════════════════════════════════════
new_order_amount = fields.Float(
string='New Order Amount ($)', tracking=True
)
new_order_date = fields.Date(
string='New Order Date', tracking=True
)
income_withholding_id = fields.Many2one(
'fl.income.withholding',
string='Income Withholding Order'
)
retroactivity_date = fields.Date(
string='Retroactivity Date',
compute='_compute_retroactivity_date', store=True,
help='FL 61.30(17): Modification is retroactive to filing date ONLY. '
'Cannot seek support modification for periods before filing.'
)
respondent_answered = fields.Boolean(
string='Respondent Filed Answer',
tracking=True
)
# ══════════════════════════════════════════════════════════════════════
# COMPUTED METHODS
# ══════════════════════════════════════════════════════════════════════
@api.depends('respondent_attorney_id')
def _compute_respondent_has_counsel(self):
for rec in self:
rec.respondent_has_counsel = bool(rec.respondent_attorney_id)
@api.depends('child_ids')
def _compute_children_count(self):
for rec in self:
rec.children_count = len(rec.child_ids)
@api.depends('child_ids.emancipated', 'child_ids.date_of_birth')
def _compute_has_minor_children(self):
for rec in self:
rec.has_minor_children = any(
not c.emancipated for c in rec.child_ids
)
@api.depends('marriage_date', 'separation_date', 'filing_date')
def _compute_years_of_marriage(self):
for rec in self:
end_date = rec.separation_date or rec.filing_date
if rec.marriage_date and end_date:
delta = relativedelta(end_date, rec.marriage_date)
rec.years_of_marriage = round(
delta.years + delta.months / 12 + delta.days / 365, 2
)
else:
rec.years_of_marriage = 0.0
@api.depends('petitioner_fl_resident_since', 'filing_date')
def _compute_residency_met(self):
for rec in self:
if rec.petitioner_fl_resident_since and rec.filing_date:
required_date = rec.petitioner_fl_resident_since + relativedelta(months=6)
rec.residency_requirement_met = rec.filing_date >= required_date
if not rec.residency_requirement_met:
months_short = relativedelta(required_date, rec.filing_date)
rec.residency_warning = (
f'⚠️ FL 61.021: Petitioner must be a FL resident for 6 months '
f'before filing. Currently {months_short.months} month(s) short.'
)
else:
rec.residency_warning = '✅ Residency requirement met (FL 61.021)'
elif rec.petitioner_fl_resident_since and not rec.filing_date:
rec.residency_requirement_met = False
rec.residency_warning = 'Enter filing date to verify residency requirement'
else:
rec.residency_requirement_met = False
rec.residency_warning = '⚠️ Enter FL residency start date to verify FL 61.021'
@api.depends('domestic_violence_flag', 'dv_injunction_active')
def _compute_dv_safety_note(self):
for rec in self:
if rec.domestic_violence_flag:
injunction_note = (
'<br/><b>Active injunction on file.</b>'
if rec.dv_injunction_active else ''
)
rec.dv_safety_note = (
'<div style="background:#ffeeba;border:2px solid #e6ac00;'
'padding:12px;border-radius:4px;">'
'<h4 style="color:#856404;margin:0 0 8px 0;">'
'⚠️ DOMESTIC VIOLENCE SAFETY NOTICE</h4>'
'<p>Domestic violence has been flagged in this case. '
'This affects your rights and safety throughout this proceeding.</p>'
'<ul>'
'<li><b>Mediation:</b> Separate rooms are REQUIRED (FL 44.102)</li>'
'<li><b>Representation:</b> Pro se representation is strongly discouraged</li>'
'<li><b>Contact:</b> Do not communicate with the opposing party directly</li>'
'</ul>'
f'{injunction_note}'
'<p><b>Resources:</b><br/>'
'Legal Services of Greater Miami: <a href="tel:3055760080">(305) 576-0080</a><br/>'
'Safespace Miami: <a href="tel:3055365565">(305) 536-5565</a><br/>'
'National DV Hotline: <a href="tel:18007997233">1-800-799-7233</a></p>'
'</div>'
)
else:
rec.dv_safety_note = ''
@api.depends('current_order_amount', 'current_order_per_child', 'children_count')
def _compute_current_order_total(self):
for rec in self:
if rec.current_order_per_child and rec.children_count:
rec.current_order_total = (
rec.current_order_amount * rec.children_count
)
else:
rec.current_order_total = rec.current_order_amount
@api.depends('calculated_support', 'current_order_total')
def _compute_support_difference(self):
for rec in self:
current = rec.current_order_total or 0.0
proposed = rec.calculated_support or 0.0
rec.support_difference = proposed - current
if current > 0:
rec.support_difference_pct = (
abs(proposed - current) / current
) * 100
else:
rec.support_difference_pct = 0.0
@api.depends('support_difference', 'support_difference_pct', 'case_type')
def _compute_threshold(self):
"""
FL 61.30(1)(b): Modification qualifies when BOTH:
- |difference| >= $50 AND
- |difference| / current_amount >= 15%
"""
for rec in self:
if rec.case_type != 'modification':
rec.threshold_met = False
rec.threshold_result = ''
continue
diff = abs(rec.support_difference)
pct = rec.support_difference_pct
current = rec.current_order_total or 0.0
dollar_test = diff >= 50.0
pct_test = pct >= 15.0
if current == 0:
rec.threshold_met = False
rec.threshold_result = (
'<span style="color:orange;">⚠️ Enter current order amount '
'to run threshold test</span>'
)
continue
color_dollar = '#28a745' if dollar_test else '#dc3545'
color_pct = '#28a745' if pct_test else '#dc3545'
direction = 'increase' if rec.support_difference > 0 else 'decrease'
rec.threshold_met = dollar_test and pct_test
overall_color = '#28a745' if rec.threshold_met else '#dc3545'
overall_icon = '' if rec.threshold_met else ''
overall_label = 'QUALIFIES FOR MODIFICATION' if rec.threshold_met else 'DOES NOT QUALIFY'
rec.threshold_result = (
f'<div style="border:1px solid {overall_color};padding:10px;'
f'border-radius:4px;">'
f'<b style="color:{overall_color};">{overall_icon} {overall_label}</b>'
f'<br/><br/>'
f'Current order: <b>${current:,.2f}/mo</b><br/>'
f'Proposed: <b>${rec.calculated_support:,.2f}/mo</b> '
f'({direction} of <b>${diff:,.2f}</b>)<br/><br/>'
f'<span style="color:{color_dollar};">'
f'{"" if dollar_test else ""} $50 test: '
f'${diff:,.2f} {"" if dollar_test else "<"} $50</span><br/>'
f'<span style="color:{color_pct};">'
f'{"" if pct_test else ""} 15% test: '
f'{pct:.1f}% {"" if pct_test else "<"} 15%</span>'
f'</div>'
)
@api.depends('petitioner_overnights')
def _compute_respondent_overnights(self):
for rec in self:
if rec.petitioner_overnights:
rec.respondent_overnights = 365 - rec.petitioner_overnights
else:
rec.respondent_overnights = 0
@api.depends('petitioner_overnights')
def _compute_timesharing_pct(self):
for rec in self:
if rec.petitioner_overnights:
pct = rec.petitioner_overnights / 365
rec.petitioner_timesharing_pct = round(pct * 100, 2)
# FL 61.30(11)(b): substantial = either parent > 73 overnights (20%)
pet_substantial = rec.petitioner_overnights > 73
resp_substantial = (365 - rec.petitioner_overnights) > 73
rec.substantial_timesharing_applies = pet_substantial or resp_substantial
else:
rec.petitioner_timesharing_pct = 0.0
rec.substantial_timesharing_applies = False
@api.depends('party_ids.effective_monthly_income', 'party_ids.role')
def _compute_fee_waiver_eligibility(self):
"""
Quick eligibility check: petitioner income < 200% FPL (FL 57.082).
Full determination via fl.fee.waiver model.
2025: 1-person 200% FPL = $30,120/yr = $2,510/mo
"""
FPL_200PCT_1PERSON_MONTHLY = 2510.0
for rec in self:
petitioner_party = rec.party_ids.filtered(
lambda p: p.role == 'petitioner'
)
if petitioner_party:
income = petitioner_party[0].effective_monthly_income or 0.0
rec.fee_waiver_eligible = income <= FPL_200PCT_1PERSON_MONTHLY
else:
rec.fee_waiver_eligible = False
@api.depends('has_minor_children')
def _compute_parenting_class_required(self):
for rec in self:
rec.parenting_class_required = rec.has_minor_children
@api.depends(
'parenting_class_required',
'petitioner_parenting_class_done',
'respondent_parenting_class_done',
)
def _compute_parenting_class_warning(self):
for rec in self:
if not rec.parenting_class_required:
rec.parenting_class_warning = 'Not required'
continue
pet_done = rec.petitioner_parenting_class_done
resp_done = rec.respondent_parenting_class_done
if pet_done and resp_done:
rec.parenting_class_warning = '✅ Both parents completed'
elif pet_done:
rec.parenting_class_warning = '⚠️ Respondent parenting class pending'
elif resp_done:
rec.parenting_class_warning = '⚠️ Petitioner parenting class pending'
else:
rec.parenting_class_warning = '❌ Neither parent has completed parenting class'
@api.depends('deadline_ids.due_date', 'deadline_ids.completed', 'deadline_ids.waived')
def _compute_next_deadline(self):
today = fields.Date.today()
for rec in self:
pending = rec.deadline_ids.filtered(
lambda d: not d.completed and not d.waived
)
overdue = pending.filtered(lambda d: d.due_date < today)
upcoming = pending.filtered(
lambda d: d.due_date >= today
).sorted('due_date')
rec.overdue_deadline_count = len(overdue)
if upcoming:
next_dl = upcoming[0]
rec.next_deadline = next_dl.due_date
rec.next_deadline_label = next_dl.name
elif overdue:
oldest = overdue.sorted('due_date')[0]
rec.next_deadline = oldest.due_date
rec.next_deadline_label = f'OVERDUE: {oldest.name}'
else:
rec.next_deadline = False
rec.next_deadline_label = 'No pending deadlines'
@api.depends('hearing_ids.hearing_date')
def _compute_next_hearing(self):
now = fields.Datetime.now()
for rec in self:
future = rec.hearing_ids.filtered(
lambda h: h.hearing_date and h.hearing_date > now
and h.state not in ('cancelled',)
).sorted('hearing_date')
rec.next_hearing_date = future[0].hearing_date if future else False
@api.depends('next_hearing_date')
def _compute_discovery_cutoff(self):
for rec in self:
if rec.next_hearing_date:
rec.discovery_cutoff_date = (
rec.next_hearing_date.date() - relativedelta(days=30)
)
else:
rec.discovery_cutoff_date = False
def _compute_task_count(self):
for rec in self:
if rec.project_id:
rec.task_count = self.env['project.task'].search_count([
('project_id', '=', rec.project_id.id)
])
else:
rec.task_count = 0
@api.depends('analysis_ids')
def _compute_latest_analysis(self):
for rec in self:
latest = rec.analysis_ids.sorted('create_date', reverse=True)
rec.latest_analysis_id = latest[0] if latest else False
@api.depends('latest_analysis_id.attorney_referral_flag', 'domestic_violence_flag', 'respondent_has_counsel')
def _compute_attorney_referral_flag(self):
for rec in self:
rec.attorney_referral_flag = (
rec.domestic_violence_flag
or rec.respondent_has_counsel
or (rec.latest_analysis_id and rec.latest_analysis_id.attorney_referral_flag)
)
@api.depends('expense_ids.total_amount')
def _compute_total_expenses(self):
for rec in self:
rec.total_expenses = sum(rec.expense_ids.mapped('total_amount'))
@api.depends('filing_date')
def _compute_retroactivity_date(self):
"""
FL 61.30(17): Modification is retroactive to the filing date ONLY.
"""
for rec in self:
rec.retroactivity_date = rec.filing_date
# ══════════════════════════════════════════════════════════════════════
# CRUD OVERRIDES
# ══════════════════════════════════════════════════════════════════════
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('name', _('New')) == _('New'):
vals['name'] = self.env['ir.sequence'].next_by_code(
'fl.case'
) or _('New')
records = super().create(vals_list)
for record in records:
# 1. Create Odoo project for task management
record._create_case_project()
# 2. Generate deadlines if filing date already set
if record.filing_date:
self.env['fl.deadline'].generate_deadlines_for_case(record)
# 3. Check fee waiver eligibility (quick check)
record._check_fee_waiver_eligibility()
# 4. Handle DV flag
if record.domestic_violence_flag:
record._handle_dv_flag()
return records
def write(self, vals):
result = super().write(vals)
# Recalculate service-anchored deadlines when service_date is set/changed
if 'service_date' in vals:
for rec in self:
if rec.service_date:
self.env['fl.deadline'].recalculate_service_deadlines(rec)
# Handle DV flag being set
if vals.get('domestic_violence_flag'):
for rec in self:
if rec.domestic_violence_flag:
rec._handle_dv_flag()
# Generate income withholding order when new_order_amount is set
if vals.get('new_order_amount') and vals.get('new_order_date'):
for rec in self:
rec._check_generate_income_withholding()
return result
# ══════════════════════════════════════════════════════════════════════
# WORKFLOW METHODS
# ══════════════════════════════════════════════════════════════════════
def _create_case_project(self):
"""Create a linked Odoo project for case task management."""
project = self.env['project.project'].create({
'name': f'Case: {self.name}{self.petitioner_id.name}',
'partner_id': self.petitioner_id.id,
'description': (
f'Family law case project for {self.petitioner_id.name}. '
f'Case type: {dict(self._fields["case_type"].selection).get(self.case_type, "")}'
),
})
self.project_id = project
def _check_fee_waiver_eligibility(self):
"""Post a chatter note if petitioner appears to qualify for fee waiver."""
if self.fee_waiver_eligible and not self.fee_waiver_id:
self.message_post(
body=(
'💡 <b>Fee Waiver Opportunity:</b> Based on the income information '
'provided, this petitioner may qualify for a civil indigent fee '
'waiver (FL 57.082). This would waive filing fees, service fees, '
'and potentially mediator fees. '
'Consider creating a Fee Waiver application from the case form.'
),
subtype_xmlid='mail.mt_note',
)
def _handle_dv_flag(self):
"""
Handle domestic violence flag being set on a case.
Per spec: Force attorney referral, post safety resources,
require separate mediation rooms, disable direct contact.
"""
self.message_post(
body=(
'<div style="background:#f8d7da;border:2px solid #dc3545;'
'padding:12px;border-radius:4px;">'
'<h4 style="color:#721c24;margin:0 0 8px 0;">'
'🚨 SAFETY ALERT — DOMESTIC VIOLENCE FLAGGED</h4>'
'<ul>'
'<li>Separate mediation rooms are <b>REQUIRED</b> (FL 44.102)</li>'
'<li>Pro se representation in DV cases is <b>STRONGLY DISCOURAGED</b></li>'
'<li>Attorney referral has been flagged on this case</li>'
'<li>Do NOT allow direct portal contact between parties</li>'
'</ul>'
'<b>Resources:</b><br/>'
'Legal Services of Greater Miami: (305) 576-0080<br/>'
'Safespace Miami: (305) 536-5565<br/>'
'National DV Hotline: 1-800-799-7233<br/>'
'TTY: 1-800-787-3224'
'</div>'
),
subtype_xmlid='mail.mt_note',
)
def _check_generate_income_withholding(self):
"""
FL 61.1301: Auto-generate income withholding order when new order amount is set.
Mandatory unless good cause or written agreement.
"""
if self.income_withholding_id:
return # Already exists
iwo = self.env['fl.income.withholding'].create({
'case_id': self.id,
'obligor_id': self.respondent_id.id,
'obligee_id': self.petitioner_id.id,
'monthly_support_amount': self.new_order_amount,
})
self.income_withholding_id = iwo
self.message_post(
body=(
f'⚖️ <b>Income Withholding Order created</b> (FL 61.1301).<br/>'
f'Amount: <b>${self.new_order_amount:,.2f}/mo</b>.<br/>'
f'Income withholding is MANDATORY under FL 61.1301 unless good cause '
f'is shown or both parties agree in writing to an alternative payment method.'
),
subtype_xmlid='mail.mt_note',
)
def trigger_ai_analysis(self):
"""
Trigger AI case analysis via Ollama.
Stub for Phase 1 — full implementation in Phase 5.
"""
self.message_post(
body='🤖 AI analysis queued. Full AI analysis will be available in Phase 5.',
subtype_xmlid='mail.mt_note',
)
# ══════════════════════════════════════════════════════════════════════
# ACTIONS
# ══════════════════════════════════════════════════════════════════════
def action_view_tasks(self):
return {
'type': 'ir.actions.act_window',
'name': 'Case Tasks',
'res_model': 'project.task',
'view_mode': 'tree,form',
'domain': [('project_id', '=', self.project_id.id)],
'context': {'default_project_id': self.project_id.id},
}
def action_open_support_calculator(self):
if not self.support_calc_id:
calc = self.env['fl.support.calculation'].create({
'case_id': self.id,
'calculation_type': 'proposed',
})
self.support_calc_id = calc
return {
'type': 'ir.actions.act_window',
'name': 'FL 61.30 Support Calculator',
'res_model': 'fl.support.calculation',
'res_id': self.support_calc_id.id,
'view_mode': 'form',
'target': 'current',
}
def action_create_fee_waiver(self):
waiver = self.env['fl.fee.waiver'].create({
'case_id': self.id,
'party_id': self.petitioner_id.id,
})
self.fee_waiver_id = waiver
return {
'type': 'ir.actions.act_window',
'name': 'Fee Waiver Application',
'res_model': 'fl.fee.waiver',
'res_id': waiver.id,
'view_mode': 'form',
'target': 'current',
}
def action_run_ai_analysis(self):
return {
'type': 'ir.actions.act_window',
'name': 'Run AI Analysis',
'res_model': 'fl.analysis.wizard',
'view_mode': 'form',
'target': 'new',
'context': {'default_case_id': self.id},
}

View File

@@ -0,0 +1,95 @@
import urllib.parse
from odoo import api, fields, models
class FlCaselaw(models.Model):
"""
Phase 5 — Full caselaw library with AI integration.
Phase 1: Full model definition (seed data loaded in Phase 5).
"""
_name = 'fl.caselaw'
_description = 'Florida Family Law Case'
_inherit = ['mail.thread']
_order = 'year desc, short_name'
_rec_name = 'short_name'
citation = fields.Char(
string='Full Citation',
required=True,
help='e.g. Smith v. Jones, 123 So.3d 456 (Fla. 3d DCA 2019)'
)
short_name = fields.Char(
string='Short Name',
required=True,
help='e.g. Smith v. Jones'
)
court = fields.Selection([
('fl_supreme', 'Florida Supreme Court'),
('1st_dca', '1st District Court of Appeal'),
('2nd_dca', '2nd District Court of Appeal'),
('3rd_dca', '3rd DCA (Miami-Dade / Monroe)'),
('4th_dca', '4th District Court of Appeal'),
('5th_dca', '5th District Court of Appeal'),
('11th_circuit', '11th Circuit Court (Trial — Miami-Dade)'),
('other', 'Other'),
], string='Court', required=True)
year = fields.Integer(string='Year', required=True)
statute_ref_ids = fields.Many2many(
'fl.statute',
'fl_caselaw_statute_rel',
'caselaw_id', 'statute_id',
string='FL Statutes'
)
issue_tag_ids = fields.Many2many(
'fl.issue.tag',
'fl_caselaw_tag_rel',
'caselaw_id', 'tag_id',
string='Issue Tags'
)
holding = fields.Text(string='Holding', required=True)
facts_summary = fields.Text(string='Facts Summary')
relevance = fields.Text(string='Why It Matters for Pro Se')
favorable_to = fields.Selection([
('petitioner', 'Petitioner / Moving Party'),
('respondent', 'Respondent'),
('neutral', 'Neutral / Procedural'),
('depends', 'Depends on Facts'),
], string='Generally Favorable To', required=True)
full_text_url = fields.Char(string='Full Opinion URL')
google_scholar_url = fields.Char(
string='Google Scholar URL',
compute='_compute_google_scholar_url'
)
case_ids = fields.Many2many(
'fl.case',
'fl_case_caselaw_rel',
'caselaw_id', 'case_id',
string='Cases Citing This'
)
active = fields.Boolean(default=True)
# Plain English summaries (AI-generated, human-reviewed)
plain_english = fields.Text(
string='Plain English Explanation (EN)',
help='What this case means for a pro se litigant — no legal jargon'
)
plain_english_es = fields.Text(
string='Plain English Explanation (ES)',
help='Explicación en español para litigantes pro se'
)
# Post-2023 flag for alimony cases
superseded_by_2023_reform = fields.Boolean(
string='Superseded by 2023 Alimony Reform (HB 1409)',
help='Pre-2023 permanent alimony cases are superseded by FL 61.08 amendment'
)
@api.depends('citation')
def _compute_google_scholar_url(self):
base = 'https://scholar.google.com/scholar?q='
for rec in self:
if rec.citation:
rec.google_scholar_url = base + urllib.parse.quote(rec.citation)
else:
rec.google_scholar_url = ''

View File

@@ -0,0 +1,166 @@
from odoo import api, fields, models
from dateutil.relativedelta import relativedelta
class FlChild(models.Model):
_name = 'fl.child'
_description = 'Child on Case'
_order = 'date_of_birth'
case_id = fields.Many2one(
'fl.case', string='Case',
required=True, ondelete='cascade', index=True
)
name = fields.Char(string='Full Name', required=True)
date_of_birth = fields.Date(string='Date of Birth', required=True)
age = fields.Integer(
string='Age', compute='_compute_age', store=True
)
gender = fields.Selection([
('m', 'Male'),
('f', 'Female'),
('x', 'Non-binary / Other'),
], string='Gender')
school = fields.Char(string='School')
medical_provider = fields.Char(string='Primary Care Provider')
# ── Support Per Child ──────────────────────────────────────────────────
support_amount = fields.Float(
string='Support Amount This Child ($)',
help='Per-child support amount from the calculation'
)
health_insurance_premium = fields.Float(
string='Health Insurance Premium ($)',
help='Monthly cost of health insurance for this child only'
)
childcare_cost = fields.Float(
string='Work-Related Childcare ($)',
help='FL 61.30(7): Only work or job-search related childcare costs qualify'
)
extraordinary_expenses = fields.Float(
string='Extraordinary Expenses ($)',
help='FL 61.30(9): Medical or educational expenses beyond ordinary child-rearing costs'
)
# ── Emancipation Tracking ──────────────────────────────────────────────
emancipation_date = fields.Date(
string='Emancipation Date',
compute='_compute_emancipation',
store=True,
help='18th birthday (or high school graduation, whichever is later). '
'Support terminates on this date.'
)
approaching_emancipation = fields.Boolean(
string='Approaching Emancipation',
compute='_compute_approaching_emancipation',
store=True,
help='True when emancipation is within 90 days'
)
emancipation_alert_sent = fields.Boolean(
string='Emancipation Alert Sent',
default=False
)
emancipated = fields.Boolean(
string='Emancipated',
compute='_compute_emancipated',
store=True
)
days_until_emancipation = fields.Integer(
string='Days Until Emancipation',
compute='_compute_approaching_emancipation'
)
# ── Early Emancipation Factors ─────────────────────────────────────────
married = fields.Boolean(
string='Child is Married',
help='Marriage constitutes emancipation under FL law'
)
active_military = fields.Boolean(
string='Active Military Service',
help='Active duty military service constitutes emancipation under FL law'
)
declared_emancipated = fields.Boolean(
string='Court-Declared Emancipated',
help='Court has entered an order declaring this child emancipated'
)
emancipation_notes = fields.Text(
string='Emancipation Notes',
help='Document basis for any early emancipation determination'
)
# ── Computed ──────────────────────────────────────────────────────────
@api.depends('date_of_birth')
def _compute_age(self):
today = fields.Date.today()
for rec in self:
if rec.date_of_birth:
delta = relativedelta(today, rec.date_of_birth)
rec.age = delta.years
else:
rec.age = 0
@api.depends('date_of_birth')
def _compute_emancipation(self):
for rec in self:
if rec.date_of_birth:
rec.emancipation_date = rec.date_of_birth + relativedelta(years=18)
else:
rec.emancipation_date = False
@api.depends('emancipation_date', 'married', 'active_military', 'declared_emancipated')
def _compute_approaching_emancipation(self):
today = fields.Date.today()
for rec in self:
# Early emancipation factors
if rec.married or rec.active_military or rec.declared_emancipated:
rec.approaching_emancipation = False
rec.days_until_emancipation = 0
continue
if rec.emancipation_date:
days_remaining = (rec.emancipation_date - today).days
rec.days_until_emancipation = max(days_remaining, 0)
rec.approaching_emancipation = 0 < days_remaining <= 90
else:
rec.approaching_emancipation = False
rec.days_until_emancipation = 0
@api.depends('emancipation_date', 'married', 'active_military', 'declared_emancipated')
def _compute_emancipated(self):
today = fields.Date.today()
for rec in self:
if rec.married or rec.active_military or rec.declared_emancipated:
rec.emancipated = True
elif rec.emancipation_date:
rec.emancipated = today >= rec.emancipation_date
else:
rec.emancipated = False
# ── Cron ──────────────────────────────────────────────────────────────
def _cron_emancipation_alerts(self):
"""
Run daily via ir.cron.
Sends chatter alert on cases where a child is approaching emancipation
and no alert has been sent yet.
"""
approaching = self.search([
('approaching_emancipation', '=', True),
('emancipation_alert_sent', '=', False),
])
for child in approaching:
days = child.days_until_emancipation
child.case_id.message_post(
body=(
f'<strong>⚠️ EMANCIPATION ALERT</strong><br/>'
f'<b>{child.name}</b> will turn 18 on '
f'<b>{child.emancipation_date}</b> '
f'({days} days from today).<br/>'
f'Child support for this child terminates on that date.<br/>'
f'<b>Action required:</b> A Motion to Modify should be filed '
f'at least 60 days before emancipation to address the change '
f'in support obligation.'
),
subtype_xmlid='mail.mt_note',
)
child.emancipation_alert_sent = True

View File

@@ -0,0 +1,248 @@
from datetime import date, datetime, time
from odoo import api, fields, models
from dateutil.relativedelta import relativedelta
class FlDeadline(models.Model):
"""
Phase 2 — Full implementation with calendar integration and cron alerts.
Phase 1: Core fields and generate_deadlines_for_case stub.
"""
_name = 'fl.deadline'
_description = 'Case Deadline'
_inherit = ['mail.thread']
_order = 'due_date asc'
case_id = fields.Many2one(
'fl.case', required=True, ondelete='cascade', index=True
)
name = fields.Char(string='Deadline', required=True)
deadline_type = fields.Selection([
('filing', 'Filing'),
('service', 'Service of Process'),
('response', 'Response / Answer'),
('discovery_open', 'Discovery Opens'),
('financial_disclosure', 'Financial Disclosure Exchange'),
('deposition_notice', 'Deposition Notice Deadline'),
('deposition', 'Deposition'),
('discovery_cutoff', 'Discovery Cutoff'),
('parenting_class', 'Parenting Class Completion'),
('mediation', 'Mediation'),
('hearing', 'Hearing'),
('compliance', 'Compliance Deadline'),
('emancipation', 'Emancipation'),
('default_motion', 'Motion for Default'),
('custom', 'Custom'),
], required=True)
statute_reference = fields.Char(
string='Statute / Rule',
help='e.g. FL 1.140 — 20 days to answer'
)
due_date = fields.Date(string='Due Date', required=True, tracking=True)
anchor_date = fields.Date(
string='Anchor Date',
help='Date this deadline is calculated from'
)
offset_days = fields.Integer(
string='Offset Days',
help='Days from anchor date to due date'
)
completed = fields.Boolean(string='Completed', tracking=True)
completed_date = fields.Date(string='Completion Date')
waived = fields.Boolean(string='Waived')
notes = fields.Text(string='Notes')
# ── Calendar Integration (Phase 2) ────────────────────────────────────
calendar_event_id = fields.Many2one(
'calendar.event', string='Calendar Event'
)
# ── Alerts ────────────────────────────────────────────────────────────
alert_7day_sent = fields.Boolean(default=False)
alert_3day_sent = fields.Boolean(default=False)
alert_1day_sent = fields.Boolean(default=False)
overdue_alert_sent = fields.Boolean(default=False)
is_overdue = fields.Boolean(
string='Overdue',
compute='_compute_is_overdue', store=True
)
days_until_due = fields.Integer(
string='Days Until Due',
compute='_compute_days_until_due'
)
@api.depends('due_date', 'completed', 'waived')
def _compute_is_overdue(self):
today = fields.Date.today()
for rec in self:
rec.is_overdue = (
not rec.completed
and not rec.waived
and bool(rec.due_date)
and rec.due_date < today
)
@api.depends('due_date')
def _compute_days_until_due(self):
today = fields.Date.today()
for rec in self:
if rec.due_date:
rec.days_until_due = (rec.due_date - today).days
else:
rec.days_until_due = 0
def action_mark_complete(self):
self.completed = True
self.completed_date = fields.Date.today()
# ── Generation Engine ─────────────────────────────────────────────────
@api.model
def generate_deadlines_for_case(self, case):
"""
Auto-generate procedural deadlines from filing_date.
Phase 1: Core deadlines only.
Phase 2: Full calendar events + alert setup.
"""
if not case.filing_date:
return
# Avoid duplicates on re-run
existing_types = case.deadline_ids.mapped('deadline_type')
rules = [
{
'name': 'Serve Respondent (Target)',
'deadline_type': 'service',
'offset_days': 30,
'anchor': case.filing_date,
'statute_reference': 'FL 1.070 — 120-day maximum',
},
]
if case.has_minor_children:
rules.append({
'name': 'Parenting Class — Petitioner (FL 61.21)',
'deadline_type': 'parenting_class',
'offset_days': 45,
'anchor': case.filing_date,
'statute_reference': 'FL 61.21',
})
for rule in rules:
if rule['deadline_type'] in existing_types:
continue
due = rule['anchor'] + relativedelta(days=rule['offset_days'])
self.create({
'case_id': case.id,
'name': rule['name'],
'deadline_type': rule['deadline_type'],
'due_date': due,
'anchor_date': rule['anchor'],
'offset_days': rule['offset_days'],
'statute_reference': rule.get('statute_reference', ''),
})
@api.model
def recalculate_service_deadlines(self, case):
"""
Recalculate deadlines anchored to service_date when it is set.
Called from fl_case.write when service_date changes.
"""
if not case.service_date:
return
service_anchored = [
{
'name': 'Respondent Answer Deadline',
'deadline_type': 'response',
'offset_days': 20,
'statute_reference': 'FL 1.140 — 20 days to answer',
},
{
'name': 'Mandatory Financial Disclosure Exchange',
'deadline_type': 'financial_disclosure',
'offset_days': 45,
'statute_reference': 'FL 12.285 — 45 days',
},
{
'name': 'Discovery Opens (Case at Issue)',
'deadline_type': 'discovery_open',
'offset_days': 20,
'statute_reference': 'FL 12.280',
},
]
if case.has_minor_children:
service_anchored.append({
'name': 'Parenting Class — Respondent (FL 61.21)',
'deadline_type': 'parenting_class',
'offset_days': 60,
'statute_reference': 'FL 61.21',
})
existing = {d.deadline_type: d for d in case.deadline_ids}
for rule in service_anchored:
due = case.service_date + relativedelta(days=rule['offset_days'])
dl_type = rule['deadline_type']
if dl_type in existing:
existing[dl_type].write({
'due_date': due,
'anchor_date': case.service_date,
})
else:
self.create({
'case_id': case.id,
'name': rule['name'],
'deadline_type': dl_type,
'due_date': due,
'anchor_date': case.service_date,
'offset_days': rule['offset_days'],
'statute_reference': rule.get('statute_reference', ''),
})
def _cron_deadline_alerts(self):
"""Run daily — send deadline alerts at 7, 3, 1 days and overdue."""
today = fields.Date.today()
upcoming = self.search([
('completed', '=', False),
('waived', '=', False),
('due_date', '>=', today),
])
for dl in upcoming:
days = (dl.due_date - today).days
if days == 7 and not dl.alert_7day_sent:
dl._send_deadline_alert('7 days')
dl.alert_7day_sent = True
elif days == 3 and not dl.alert_3day_sent:
dl._send_deadline_alert('3 days')
dl.alert_3day_sent = True
elif days == 1 and not dl.alert_1day_sent:
dl._send_deadline_alert('1 day')
dl.alert_1day_sent = True
overdue = self.search([
('completed', '=', False),
('waived', '=', False),
('due_date', '<', today),
('overdue_alert_sent', '=', False),
])
for dl in overdue:
dl._send_deadline_alert('OVERDUE')
dl.overdue_alert_sent = True
def _send_deadline_alert(self, timing):
icon = '🔴' if timing == 'OVERDUE' else ''
self.case_id.message_post(
body=(
f'{icon} <b>Deadline Alert — {timing}</b><br/>'
f'<b>{self.name}</b> is due on <b>{self.due_date}</b>.<br/>'
f'Statute: {self.statute_reference or "N/A"}'
),
subtype_xmlid='mail.mt_note',
)

View File

@@ -0,0 +1,55 @@
from odoo import api, fields, models
class FlDeposition(models.Model):
"""
Phase 3 — Full implementation with notice validation, duces tecum, no-show workflow.
Phase 1: Stub with core fields.
"""
_name = 'fl.deposition'
_description = 'Deposition Record'
_inherit = ['mail.thread']
_order = 'scheduled_date asc'
case_id = fields.Many2one(
'fl.case', required=True, ondelete='cascade', index=True
)
deponent_id = fields.Many2one(
'res.partner', string='Deponent', required=True
)
deponent_type = fields.Selection([
('opposing_party', 'Opposing Party'),
('employer', 'Employer'),
('accountant_cpa', 'Accountant / CPA'),
('business_partner', 'Business Partner'),
('vocational_expert', 'Vocational Expert'),
('witness', 'Witness'),
('other', 'Other'),
], string='Deponent Type', required=True)
notice_date = fields.Date(
string='Notice Served Date',
help='FL 1.310(b): Minimum 10 days notice required before deposition'
)
scheduled_date = fields.Datetime(string='Deposition Date / Time')
location = fields.Char(string='Location / Zoom Link')
state = fields.Selection([
('draft', 'Drafting Notice'),
('noticed', 'Notice Served'),
('confirmed', 'Confirmed'),
('completed', 'Completed'),
('no_show', 'Deponent No-Show'),
('cancelled', 'Cancelled'),
('rescheduled', 'Rescheduled'),
], string='Status', default='draft', tracking=True)
duces_tecum = fields.Boolean(
string='Duces Tecum (Document Production)',
help='Deponent is required to bring documents'
)
max_duration_hours = fields.Float(
default=7.0,
help='FL 1.310(d): Maximum 7 hours per deponent per day'
)
income_verified = fields.Boolean(string='Income Figures Verified')
income_verified_amount = fields.Float(string='Verified Income Amount ($)')
key_findings = fields.Text(string='Key Findings')
notes = fields.Text(string='Notes')

View File

@@ -0,0 +1,70 @@
from odoo import api, fields, models
from dateutil.relativedelta import relativedelta
class FlDiscovery(models.Model):
"""
Phase 3 — Full implementation.
Phase 1: Stub with core fields.
Covers all FL discovery methods:
- Interrogatories (FL 1.340) — 30 days to respond
- Request for Production (FL 1.350) — 30 days to respond
- Request for Admissions (FL 1.370) — 30 days to respond
- Subpoena — third party documents (FL 1.351)
- Depositions — tracked in fl.deposition
"""
_name = 'fl.discovery'
_description = 'Discovery Item'
_inherit = ['mail.thread']
_order = 'served_date asc'
case_id = fields.Many2one(
'fl.case', required=True, ondelete='cascade', index=True
)
discovery_type = fields.Selection([
('interrogatories', 'Interrogatories (FL 1.340)'),
('production', 'Request for Production (FL 1.350)'),
('admissions', 'Request for Admissions (FL 1.370)'),
('subpoena', 'Subpoena — Third Party (FL 1.351)'),
('deposition', 'Deposition (FL 1.310)'),
], string='Discovery Type', required=True)
directed_to = fields.Selection([
('petitioner', 'Petitioner'),
('respondent', 'Respondent'),
('third_party', 'Third Party'),
], string='Directed To', required=True)
third_party_id = fields.Many2one(
'res.partner', string='Third Party'
)
description = fields.Char(string='Description / Subject')
served_date = fields.Date(string='Served Date')
response_due_date = fields.Date(
string='Response Due Date',
compute='_compute_response_due', store=True,
help='FL 1.340/1.350/1.370: 30 days to respond'
)
response_received_date = fields.Date(string='Response Received Date')
response_complete = fields.Boolean(string='Response Complete')
objections_raised = fields.Boolean(string='Objections Raised')
objection_detail = fields.Text(string='Objection Details')
deficiency_notice_sent = fields.Boolean(string='Deficiency Notice Sent')
state = fields.Selection([
('draft', 'Drafting'),
('served', 'Served'),
('responded', 'Response Received'),
('deficient', 'Response Deficient'),
('compelled', 'Motion to Compel Filed'),
('complete', 'Complete'),
], string='Status', default='draft', tracking=True)
notes = fields.Text(string='Notes')
@api.depends('served_date', 'discovery_type')
def _compute_response_due(self):
for rec in self:
if rec.served_date and rec.discovery_type != 'deposition':
rec.response_due_date = (
rec.served_date + relativedelta(days=30)
)
else:
rec.response_due_date = False

View File

@@ -0,0 +1,52 @@
from odoo import fields, models
class FlDocument(models.Model):
"""
Phase 4 — Full implementation with QWeb report generation.
Phase 1: Stub with core fields.
"""
_name = 'fl.document'
_description = 'Case Document'
_inherit = ['mail.thread']
_order = 'create_date desc'
case_id = fields.Many2one(
'fl.case', required=True, ondelete='cascade', index=True
)
name = fields.Char(string='Document Name', required=True)
document_type = fields.Selection([
('financial_affidavit_short', 'Financial Affidavit Short (FL-12.902(b))'),
('financial_affidavit_long', 'Financial Affidavit Long (FL-12.902(c))'),
('support_worksheet', 'Child Support Worksheet (FL-12.902(e))'),
('motion_to_modify', 'Motion to Modify Child Support'),
('notice_deposition', 'Notice of Taking Deposition'),
('motion_to_compel', 'Motion to Compel (FL 1.380)'),
('motion_default', 'Motion for Default (FL 12.922)'),
('income_withholding', 'Income Withholding Order (FL 61.1301)'),
('parenting_plan', 'Parenting Plan (FL-12.995(a))'),
('fee_waiver', 'Application for Civil Indigent Status (FL 57.082)'),
('notice_ssn', 'Notice of SSN (FL-12.930(a))'),
('mandatory_disclosure', 'Certificate of Mandatory Disclosure (FL-12.932)'),
('duces_tecum', 'Subpoena Duces Tecum'),
('other', 'Other Document'),
], string='Document Type')
state = fields.Selection([
('draft', 'Draft'),
('generated', 'Generated'),
('signed', 'Signed'),
('filed', 'Filed with Court'),
], string='Status', default='draft', tracking=True)
attachment_ids = fields.Many2many(
'ir.attachment',
'fl_document_attachment_rel',
'document_id', 'attachment_id',
string='Files'
)
notes = fields.Text(string='Notes')
requires_notarization = fields.Boolean(
string='Requires Notarization',
help='Financial affidavits must be notarized before filing'
)
notarized = fields.Boolean(string='Notarized')
filed_date = fields.Date(string='Filed with Court Date')

View File

@@ -0,0 +1,27 @@
from odoo import fields, models
class HrExpenseFLCase(models.Model):
"""
Extend hr.expense to link expenses to a family law case.
Enables cost-tracking per case (filing fees, process server, court reporter, etc.)
"""
_inherit = 'hr.expense'
fl_case_id = fields.Many2one(
'fl.case',
string='Family Law Case',
index=True,
help='Link this expense to a specific family law case for cost tracking'
)
expense_category = fields.Selection([
('filing_fee', 'Court Filing Fee'),
('service_fee', 'Service of Process Fee'),
('court_reporter', 'Court Reporter'),
('mediation', 'Mediation Fee'),
('copying', 'Document Copying / Printing'),
('postage', 'Postage / Certified Mail'),
('translation', 'Translation / Interpretation'),
('expert', 'Expert Witness'),
('other', 'Other Case Expense'),
], string='Case Expense Type')

View File

@@ -0,0 +1,162 @@
from odoo import api, fields, models
class FlFeeWaiver(models.Model):
"""
FL Statute 57.082 — Determination of Civil Indigent Status.
Income threshold: < 200% of federal poverty level.
2025 Federal Poverty Level (HHS):
Base (1 person): $15,060/year
Per additional person: $5,380/year
Update FPL_BASE and FPL_PER_PERSON constants annually.
Source: https://aspe.hhs.gov/topics/poverty-economic-mobility/poverty-guidelines
"""
_name = 'fl.fee.waiver'
_description = 'Fee Waiver / Civil Indigent Status (FL 57.082)'
_inherit = ['mail.thread']
_order = 'create_date desc'
# 2025 FPL constants — UPDATE ANNUALLY
FPL_BASE_2025 = 15060 # 1-person household
FPL_PER_PERSON_2025 = 5380 # Each additional person
case_id = fields.Many2one(
'fl.case', string='Case',
ondelete='cascade', index=True
)
party_id = fields.Many2one(
'res.partner', string='Applicant',
required=True
)
# ── Income & Household ─────────────────────────────────────────────────
household_size = fields.Integer(
string='Household Size',
required=True,
default=1,
help='Include all persons in the household (applicant + dependents + spouse/partner)'
)
monthly_gross_income = fields.Float(
string='Monthly Gross Income ($)',
required=True,
help='Total gross monthly income from all sources'
)
annual_gross_income = fields.Float(
string='Annual Gross Income ($)',
compute='_compute_annual', store=True
)
# ── FPL Threshold Calculation ──────────────────────────────────────────
fpl_annual = fields.Float(
string='Federal Poverty Level — Annual ($)',
compute='_compute_threshold', store=True,
help='100% FPL for this household size'
)
fpl_200pct_threshold = fields.Float(
string='200% FPL Threshold ($)',
compute='_compute_threshold', store=True,
help='200% FPL — income must be below this to qualify'
)
eligible = fields.Boolean(
string='Fee Waiver Eligible',
compute='_compute_eligibility', store=True
)
eligibility_note = fields.Char(
string='Eligibility Result',
compute='_compute_eligibility'
)
# ── Miami-Dade Mediator Fee Waiver (FL 44.108) ─────────────────────────
mediator_fee_waiver_eligible = fields.Boolean(
string='Mediator Fee Waiver Eligible (FL 44.108)',
compute='_compute_mediator_waiver', store=True,
help='Miami-Dade County provides free mediators for qualifying income. '
'Same income threshold as civil indigent status (200% FPL).'
)
# ── Status ────────────────────────────────────────────────────────────
state = fields.Selection([
('draft', 'Draft'),
('submitted', 'Submitted to Clerk'),
('approved', 'Approved'),
('denied', 'Denied'),
], string='Status', default='draft', tracking=True)
application_date = fields.Date(
string='Application Date',
default=fields.Date.today
)
approval_date = fields.Date(string='Approval Date')
denial_reason = fields.Text(string='Denial Reason')
expiration_date = fields.Date(
string='Expiration Date',
help='Fee waiver approvals typically expire — check with clerk'
)
clerk_stamp_attachment_ids = fields.Many2many(
'ir.attachment',
'fl_fee_waiver_attachment_rel',
'waiver_id', 'attachment_id',
string='Clerk-Stamped Copy'
)
notes = fields.Text(string='Notes')
# ── Computed ──────────────────────────────────────────────────────────
@api.depends('monthly_gross_income')
def _compute_annual(self):
for rec in self:
rec.annual_gross_income = (rec.monthly_gross_income or 0.0) * 12
@api.depends('household_size')
def _compute_threshold(self):
for rec in self:
size = max(rec.household_size or 1, 1)
fpl = self.FPL_BASE_2025 + (size - 1) * self.FPL_PER_PERSON_2025
rec.fpl_annual = fpl
rec.fpl_200pct_threshold = fpl * 2
@api.depends('annual_gross_income', 'fpl_200pct_threshold')
def _compute_eligibility(self):
for rec in self:
annual = rec.annual_gross_income or 0.0
threshold = rec.fpl_200pct_threshold or 0.0
if annual and threshold:
rec.eligible = annual <= threshold
if rec.eligible:
rec.eligibility_note = (
f'✅ ELIGIBLE — Annual income ${annual:,.0f} '
f'is below 200% FPL (${threshold:,.0f})'
)
else:
excess = annual - threshold
rec.eligibility_note = (
f'❌ NOT ELIGIBLE — Annual income ${annual:,.0f} '
f'exceeds 200% FPL (${threshold:,.0f}) '
f'by ${excess:,.0f}'
)
else:
rec.eligible = False
rec.eligibility_note = 'Enter income and household size to check eligibility'
@api.depends('eligible')
def _compute_mediator_waiver(self):
# Miami-Dade FL 44.108 — same threshold as civil indigent status
for rec in self:
rec.mediator_fee_waiver_eligible = rec.eligible
def action_submit(self):
self.state = 'submitted'
def action_approve(self):
self.state = 'approved'
self.approval_date = fields.Date.today()
def action_deny(self):
self.state = 'denied'

View File

@@ -0,0 +1,47 @@
from odoo import api, fields, models
class FlHearing(models.Model):
"""
Phase 2 — Full implementation.
Phase 1: Stub with fields needed by fl_case computed fields.
"""
_name = 'fl.hearing'
_description = 'Case Hearing'
_inherit = ['mail.thread']
_order = 'hearing_date asc'
case_id = fields.Many2one(
'fl.case', required=True, ondelete='cascade', index=True
)
name = fields.Char(string='Hearing Description', required=True)
hearing_date = fields.Datetime(
string='Hearing Date / Time', tracking=True
)
location = fields.Char(
string='Location',
default='Lawson E. Thomas Courthouse Center, 175 NW 1st Ave, Miami, FL 33128'
)
courtroom = fields.Char(string='Courtroom')
judge_id = fields.Many2one(
'res.partner', string='Judge',
related='case_id.judge_id', store=True
)
hearing_type = fields.Selection([
('status_conference', 'Status Conference'),
('motion', 'Motion Hearing'),
('temporary_relief', 'Temporary Relief Hearing'),
('mediation', 'Mediation'),
('final', 'Final Hearing'),
('contempt', 'Contempt Hearing'),
('other', 'Other'),
], string='Hearing Type', default='final')
state = fields.Selection([
('scheduled', 'Scheduled'),
('completed', 'Completed'),
('cancelled', 'Cancelled'),
('continued', 'Continued'),
], string='Status', default='scheduled', tracking=True)
notes = fields.Text(string='Notes')
outcome = fields.Text(string='Outcome / Result')
order_entered = fields.Boolean(string='Order Entered')

View File

@@ -0,0 +1,165 @@
from odoo import api, fields, models
class FlIncomeWithholding(models.Model):
"""
FL 61.1301 — Income Deduction (Withholding) Order.
MANDATORY after every child support or alimony order unless:
- Good cause shown, OR
- Written agreement between parties for alternative payment method.
All payments route through Florida State Disbursement Unit (SDU).
Employer receives copy and deducts from obligor's paycheck.
"""
_name = 'fl.income.withholding'
_description = 'Income Withholding Order (FL 61.1301)'
_inherit = ['mail.thread']
_order = 'issued_date desc'
case_id = fields.Many2one(
'fl.case', string='Case',
ondelete='cascade', index=True
)
name = fields.Char(
string='Reference',
compute='_compute_name', store=True
)
# ── Parties ────────────────────────────────────────────────────────────
obligor_id = fields.Many2one(
'res.partner',
string='Obligor (Person Paying)',
required=True
)
obligee_id = fields.Many2one(
'res.partner',
string='Obligee (Person Receiving)',
required=True
)
employer_id = fields.Many2one(
'res.partner',
string="Obligor's Employer",
help='Employer who will receive and execute the withholding order'
)
employer_contact = fields.Char(
string='Employer HR / Payroll Contact'
)
# ── Amounts ────────────────────────────────────────────────────────────
monthly_support_amount = fields.Float(
string='Monthly Support Amount ($)',
required=True,
tracking=True
)
arrears_amount = fields.Float(
string='Arrears Amount ($)',
help='Total unpaid support owed prior to this order'
)
arrears_monthly_payment = fields.Float(
string='Monthly Arrears Payment ($)',
help='Additional monthly amount toward arrears payoff'
)
total_monthly_withholding = fields.Float(
string='Total Monthly Withholding ($)',
compute='_compute_total', store=True,
help='Support + arrears payment per month'
)
# ── Florida State Disbursement Unit ────────────────────────────────────
# ALL payments must go through FL SDU — never directly to the other party
fl_sdu_case_number = fields.Char(
string='FL SDU Case Number',
help='Florida State Disbursement Unit case number. '
'Obtain from DOR: https://www.floridarevenue.com/childsupport'
)
fl_sdu_address = fields.Char(
string='FL SDU Payment Address',
default='Florida State Disbursement Unit, PO Box 8500, Tallahassee, FL 32314-8500',
readonly=True
)
fl_sdu_website = fields.Char(
default='https://www.floridarevenue.com/childsupport',
readonly=True
)
# ── Dates & Status ────────────────────────────────────────────────────
issued_date = fields.Date(
string='Order Issued Date',
tracking=True
)
served_on_employer_date = fields.Date(
string='Served on Employer Date'
)
first_withholding_date = fields.Date(
string='First Withholding Payroll Date',
help='Date of first paycheck with withholding applied'
)
state = fields.Selection([
('draft', 'Draft'),
('issued', 'Issued by Court'),
('served', 'Served on Employer'),
('active', 'Active — Withholding'),
('modified', 'Modified'),
('terminated', 'Terminated'),
], string='Status', default='draft', tracking=True)
# ── Exceptions (rare) ─────────────────────────────────────────────────
good_cause_exception = fields.Boolean(
string='Good Cause Exception',
help='FL 61.1301(1)(a): Court may waive withholding for good cause shown. '
'Must be documented in court order. RARE.'
)
written_agreement = fields.Boolean(
string='Written Agreement Between Parties',
help='Parties may agree in writing to an alternative payment method. '
'Agreement must be filed with the court.'
)
exception_reason = fields.Text(
string='Exception Reason / Agreement Details'
)
# ── Notes ─────────────────────────────────────────────────────────────
notes = fields.Text(string='Notes')
# ── Computed ──────────────────────────────────────────────────────────
@api.depends('case_id', 'issued_date')
def _compute_name(self):
for rec in self:
case_ref = rec.case_id.name if rec.case_id else 'NEW'
date_str = str(rec.issued_date) if rec.issued_date else 'Draft'
rec.name = f'IWO — {case_ref}{date_str}'
@api.depends('monthly_support_amount', 'arrears_monthly_payment')
def _compute_total(self):
for rec in self:
rec.total_monthly_withholding = (
(rec.monthly_support_amount or 0.0)
+ (rec.arrears_monthly_payment or 0.0)
)
# ── State Actions ─────────────────────────────────────────────────────
def action_issue(self):
self.state = 'issued'
self.issued_date = self.issued_date or fields.Date.today()
def action_serve(self):
self.state = 'served'
self.served_on_employer_date = (
self.served_on_employer_date or fields.Date.today()
)
def action_activate(self):
self.state = 'active'
def action_terminate(self):
self.state = 'terminated'

View File

@@ -0,0 +1,318 @@
from odoo import api, fields, models
class FlIncomeSource(models.Model):
_name = 'fl.income.source'
_description = 'Party Income Source'
_order = 'monthly_amount desc'
party_id = fields.Many2one(
'fl.party', string='Party',
required=True, ondelete='cascade', index=True
)
source_type = fields.Selection([
('wages', 'Wages / Salary (W-2)'),
('self_employment', 'Self-Employment / Business Income'),
('rental', 'Rental Income'),
('investment', 'Investment / Dividend Income'),
('pension', 'Pension / Retirement'),
('social_security', 'Social Security'),
('disability', 'Disability Benefits (SSI/SSDI)'),
('unemployment', 'Unemployment Compensation'),
('workers_comp', "Workers' Compensation"),
('alimony_received', 'Alimony Received'),
('child_support_received', 'Child Support Received (other case)'),
('overtime', 'Overtime / Bonuses'),
('tips', 'Tips / Gratuities'),
('commission', 'Commissions'),
('trust', 'Trust / Estate Income'),
('other', 'Other Income'),
], string='Source Type', required=True)
description = fields.Char(string='Description / Payer')
monthly_amount = fields.Float(string='Monthly Amount ($)', required=True)
annual_amount = fields.Float(
string='Annual Amount ($)',
compute='_compute_annual', store=True
)
verified = fields.Boolean(
string='Verified',
help='Income verified via documentation (pay stub, tax return, bank statement)'
)
verification_document = fields.Char(
string='Verification Document',
help='e.g. "2023 W-2", "Last 3 pay stubs", "2023 1040 Schedule C"'
)
@api.depends('monthly_amount')
def _compute_annual(self):
for rec in self:
rec.annual_amount = rec.monthly_amount * 12
class FlParty(models.Model):
_name = 'fl.party'
_description = 'Case Party Details'
_inherit = ['mail.thread']
_order = 'role, id'
case_id = fields.Many2one(
'fl.case', string='Case',
required=True, ondelete='cascade', index=True
)
partner_id = fields.Many2one(
'res.partner', string='Contact',
required=True
)
role = fields.Selection([
('petitioner', 'Petitioner'),
('respondent', 'Respondent'),
], string='Role', required=True)
display_name_computed = fields.Char(
string='Display Name',
compute='_compute_display_name_field', store=True
)
# ── Employment ─────────────────────────────────────────────────────────
employment_type = fields.Selection([
('employed', 'W-2 Employed'),
('self_employed', 'Self-Employed / Business Owner'),
('unemployed', 'Unemployed'),
('underemployed', 'Voluntarily Underemployed'),
('disabled', 'Disabled — Receiving Benefits'),
('retired', 'Retired'),
('student', 'Full-Time Student'),
('unknown', 'Unknown / To Be Discovered'),
], string='Employment Type', required=True, default='employed', tracking=True)
employer_name = fields.Char(string='Employer Name')
employer_address = fields.Char(string='Employer Address')
employer_phone = fields.Char(
string='Employer Phone',
help='Used for deposition notice and employer subpoena'
)
# ── Income (FL 61.30(2)) ───────────────────────────────────────────────
gross_monthly_income = fields.Float(
string='Gross Monthly Income ($)',
tracking=True
)
income_source_ids = fields.One2many(
'fl.income.source', 'party_id',
string='Income Sources'
)
income_sources_total = fields.Float(
string='Income Sources Total ($)',
compute='_compute_income_sources_total', store=True,
help='Sum of all income source monthly amounts'
)
# Statutory deductions to reach Net Monthly Income (FL 61.30(3))
fed_tax_monthly = fields.Float(
string='Federal Income Tax Withholding ($)',
help='Actual federal tax withheld per month (from pay stub)'
)
fica_ss_monthly = fields.Float(
string='Social Security Tax (6.2%) ($)',
compute='_compute_fica', store=True
)
fica_medicare_monthly = fields.Float(
string='Medicare Tax (1.45%) ($)',
compute='_compute_fica', store=True
)
mandatory_retirement = fields.Float(
string='Mandatory Retirement Contribution ($)',
help='Only mandatory contributions qualify — not voluntary 401k'
)
mandatory_union_dues = fields.Float(string='Mandatory Union Dues ($)')
health_insurance_self = fields.Float(
string='Health Insurance — Self Only ($)',
help='FL 61.30(3)(e): Self-only portion of health insurance premium. '
'Do NOT include the child portion here.'
)
other_court_ordered_support = fields.Float(
string='Other Court-Ordered Child Support ($)',
help='Court-ordered support for children from other relationships (FL 61.30(3)(g))'
)
net_monthly_income = fields.Float(
string='Net Monthly Income ($)',
compute='_compute_net_income', store=True,
tracking=True,
help='Gross income minus all allowable FL 61.30(3) deductions'
)
# ── Income Imputation (FL 61.30(2)(b)) ───────────────────────────────
income_imputed = fields.Boolean(
string='Income Imputed',
help='Check if this party is voluntarily unemployed/underemployed '
'and income should be imputed'
)
imputed_amount = fields.Float(
string='Imputed Monthly Income ($)',
help='FL 61.30(2)(b): Requires showing BOTH ability AND availability of work. '
'Default to FL minimum wage if voluntarily unemployed.'
)
imputation_basis = fields.Text(
string='Basis for Imputation',
help='FL 61.30(2)(b): Document ability (education, skills, health) '
'AND availability (local job market, prior work history). '
'Cannot impute income to disabled party without medical evidence.'
)
fl_minimum_wage_hourly = fields.Float(
string='FL Minimum Wage ($/hr)',
default=13.00,
help='Update annually per Florida minimum wage schedule. '
'2025: $13.00/hr. Full-time = $13.00 × 40hr × 52wk / 12 = $2,253.33/mo'
)
fl_minimum_wage_monthly = fields.Float(
string='FL Minimum Wage ($/mo)',
compute='_compute_fl_min_wage_monthly', store=True
)
effective_monthly_income = fields.Float(
string='Effective Monthly Income ($)',
compute='_compute_effective_income', store=True,
help='Net monthly income used in support calculation — '
'uses imputed amount if income_imputed = True'
)
# ── Lifestyle Analysis (Barner v. Barner) ────────────────────────────
lifestyle_inconsistency_flag = fields.Boolean(
string='Income/Lifestyle Inconsistency Flagged',
help='Flag when reported income appears inconsistent with '
'observed lifestyle (Barner v. Barner). '
'Document indicators: vehicles, vacations, residence, spending.'
)
lifestyle_notes = fields.Text(
string='Lifestyle Analysis Notes',
help='Document specific lifestyle inconsistencies: '
'e.g., drives a 2023 BMW but reports $800/mo income; '
'took 3 vacations in past year; lives in $3,000/mo apartment.'
)
# ── Portal Access ──────────────────────────────────────────────────────
portal_user_id = fields.Many2one(
'res.users', string='Portal User Account'
)
portal_access_granted = fields.Boolean(
string='Portal Access Granted'
)
portal_invite_sent = fields.Boolean(
string='Portal Invitation Sent'
)
preferred_language = fields.Selection([
('en_US', 'English'),
('es_MX', 'Spanish / Español'),
], string='Preferred Language', default='en_US')
# ── SSN (FL-12.930(a)) ────────────────────────────────────────────────
# SECURITY: Store last 4 digits only.
# Full SSN is NOT stored in this system.
# User handwrites full SSN on the physical FL-12.930(a) form before filing.
ssn_last4 = fields.Char(
string='SSN Last 4 Digits',
size=4,
help='Last 4 digits only. Full SSN is not stored — '
'user handwrites SSN on physical FL-12.930(a) form.'
)
ssn_notice_filed = fields.Boolean(
string='Notice of Social Security Number Filed (FL-12.930(a))',
help='Required to be filed with the court'
)
ssn_handwritten_confirmed = fields.Boolean(
string='I have handwritten my SSN on the FL-12.930(a) form',
help='Checkbox confirms user has added SSN to physical form before filing'
)
# ── Service of Process ────────────────────────────────────────────────
service_address = fields.Text(
string='Address for Service of Process'
)
service_method = fields.Selection([
('personal', 'Personal Service (Process Server)'),
('certified_mail', 'Certified Mail (if agreed by both parties)'),
('publication', 'Service by Publication (last resort — respondent location unknown)'),
], string='Service Method')
process_server_id = fields.Many2one(
'res.partner', string='Process Server'
)
service_by_publication_warning = fields.Boolean(
compute='_compute_service_publication_warning'
)
# ── Computed ──────────────────────────────────────────────────────────
@api.depends('partner_id', 'role')
def _compute_display_name_field(self):
for rec in self:
role_label = dict(rec._fields['role'].selection).get(rec.role, '')
name = rec.partner_id.name or ''
rec.display_name_computed = f'{name} ({role_label})' if role_label else name
@api.depends('income_source_ids.monthly_amount')
def _compute_income_sources_total(self):
for rec in self:
rec.income_sources_total = sum(
rec.income_source_ids.mapped('monthly_amount')
)
@api.depends('gross_monthly_income')
def _compute_fica(self):
for rec in self:
gross = rec.gross_monthly_income or 0.0
rec.fica_ss_monthly = round(gross * 0.062, 2)
rec.fica_medicare_monthly = round(gross * 0.0145, 2)
@api.depends(
'gross_monthly_income',
'fed_tax_monthly',
'fica_ss_monthly',
'fica_medicare_monthly',
'mandatory_retirement',
'mandatory_union_dues',
'health_insurance_self',
'other_court_ordered_support',
)
def _compute_net_income(self):
for rec in self:
gross = rec.gross_monthly_income or 0.0
deductions = (
(rec.fed_tax_monthly or 0.0)
+ (rec.fica_ss_monthly or 0.0)
+ (rec.fica_medicare_monthly or 0.0)
+ (rec.mandatory_retirement or 0.0)
+ (rec.mandatory_union_dues or 0.0)
+ (rec.health_insurance_self or 0.0)
+ (rec.other_court_ordered_support or 0.0)
)
rec.net_monthly_income = max(gross - deductions, 0.0)
@api.depends('fl_minimum_wage_hourly')
def _compute_fl_min_wage_monthly(self):
for rec in self:
# Full-time: 40hr/wk × 52wk / 12 months
rec.fl_minimum_wage_monthly = round(
rec.fl_minimum_wage_hourly * 40 * 52 / 12, 2
)
@api.depends('net_monthly_income', 'income_imputed', 'imputed_amount')
def _compute_effective_income(self):
for rec in self:
if rec.income_imputed and rec.imputed_amount:
rec.effective_monthly_income = rec.imputed_amount
else:
rec.effective_monthly_income = rec.net_monthly_income
@api.depends('service_method')
def _compute_service_publication_warning(self):
for rec in self:
rec.service_by_publication_warning = (
rec.service_method == 'publication'
)

View File

@@ -0,0 +1,56 @@
from odoo import fields, models
class FlStatute(models.Model):
_name = 'fl.statute'
_description = 'Florida Statute Reference'
_order = 'name'
name = fields.Char(
string='Statute',
required=True,
help='e.g. FL 61.30'
)
title = fields.Char(string='Title', required=True)
description = fields.Text(string='Summary')
category = fields.Selection([
('child_support', 'Child Support'),
('modification', 'Modification'),
('alimony', 'Alimony'),
('timesharing', 'Timesharing / Parental Responsibility'),
('dissolution', 'Dissolution of Marriage'),
('paternity', 'Paternity'),
('domestic_violence', 'Domestic Violence'),
('enforcement', 'Enforcement'),
('disclosure', 'Mandatory Disclosure'),
('fee_waiver', 'Fee Waiver / Indigent Status'),
('procedure', 'Procedure'),
('discovery', 'Discovery'),
('other', 'Other'),
], string='Category')
url = fields.Char(string='Official URL')
active = fields.Boolean(default=True)
_sql_constraints = [
('name_uniq', 'unique(name)', 'Statute reference must be unique.'),
]
class FlIssueTag(models.Model):
_name = 'fl.issue.tag'
_description = 'Legal Issue Tag'
_order = 'name'
name = fields.Char(string='Tag', required=True)
name_es = fields.Char(string='Tag (Spanish)')
color = fields.Integer(string='Color Index', default=0)
case_type = fields.Selection([
('modification', 'Modification'),
('dissolution', 'Dissolution'),
('paternity', 'Paternity'),
('all', 'All Cases'),
], string='Applies To', default='all')
_sql_constraints = [
('name_uniq', 'unique(name)', 'Issue tag name must be unique.'),
]

View File

@@ -0,0 +1,426 @@
from odoo import api, fields, models
class FlSupportScheduleEntry(models.Model):
"""
FL Department of Revenue — Basic Support Obligation Schedule.
Loaded from data/fl_support_schedule.xml.
MUST be updated annually when FL updates the schedule.
Source: https://www.floridarevenue.com/childsupport/guidelines
"""
_name = 'fl.support.schedule.entry'
_description = 'FL Support Obligation Schedule Entry'
_order = 'income_min, children_count'
income_min = fields.Float(
string='Combined Income — Min ($/mo)',
required=True
)
income_max = fields.Float(
string='Combined Income — Max ($/mo)',
required=True
)
children_count = fields.Integer(
string='Number of Children',
required=True
)
obligation_amount = fields.Float(
string='Basic Obligation ($)',
required=True
)
effective_date = fields.Date(
string='Schedule Effective Date',
required=True
)
active = fields.Boolean(default=True)
def name_get(self):
result = []
for rec in self:
result.append((
rec.id,
f'${rec.income_min:,.0f}${rec.income_max:,.0f} / '
f'{rec.children_count} child(ren) = ${rec.obligation_amount:,.0f}'
))
return result
class FlSupportCalculation(models.Model):
"""
FL 61.30 Child Support Guidelines Worksheet.
One calculation record per case (current vs. proposed vs. historical).
"""
_name = 'fl.support.calculation'
_description = 'FL 61.30 Child Support Calculation'
_inherit = ['mail.thread']
_order = 'calculation_date desc'
case_id = fields.Many2one(
'fl.case', string='Case',
ondelete='cascade', index=True
)
calculation_date = fields.Date(
string='Calculation Date',
default=fields.Date.today
)
calculation_type = fields.Selection([
('current', 'Current Order (Baseline)'),
('proposed', 'Proposed Modification'),
('historical', 'Historical Reference'),
], string='Type', default='proposed', required=True)
notes = fields.Text(string='Notes')
# ── Party Net Incomes ──────────────────────────────────────────────────
petitioner_net_income = fields.Float(
string='Petitioner Net Monthly Income ($)',
tracking=True
)
respondent_net_income = fields.Float(
string='Respondent Net Monthly Income ($)',
tracking=True
)
combined_net_income = fields.Float(
string='Combined Net Monthly Income ($)',
compute='_compute_combined', store=True
)
petitioner_income_pct = fields.Float(
string='Petitioner Income %',
compute='_compute_income_pcts', store=True
)
respondent_income_pct = fields.Float(
string='Respondent Income %',
compute='_compute_income_pcts', store=True
)
# ── Basic Support Obligation ───────────────────────────────────────────
number_of_children = fields.Integer(
string='Number of Children',
related='case_id.children_count', store=True
)
basic_support_obligation = fields.Float(
string='Basic Support Obligation ($)',
compute='_compute_basic_obligation', store=True,
help='Looked up from FL DCF Schedule based on combined income and number of children'
)
support_schedule_id = fields.Many2one(
'fl.support.schedule.entry',
string='Schedule Entry Used',
compute='_compute_basic_obligation', store=True
)
above_schedule = fields.Boolean(
string='Above Schedule Maximum',
compute='_compute_basic_obligation', store=True,
help='If True, FL 61.30(6) percentage formula was applied'
)
# ── Adjustments (FL 61.30(7),(8),(9)) ─────────────────────────────────
# Health insurance for children (FL 61.30(8))
child_health_insurance_total = fields.Float(
string='Child Health Insurance Premium Total ($)',
help='Total monthly premium for children only — not self-only portion'
)
health_insurance_by_petitioner = fields.Float(
string=' Paid by Petitioner ($)'
)
health_insurance_by_respondent = fields.Float(
string=' Paid by Respondent ($)'
)
# Work-related childcare (FL 61.30(7))
childcare_total = fields.Float(
string='Work-Related Childcare Total ($)',
help='FL 61.30(7): Only work or job-search related childcare qualifies'
)
childcare_by_petitioner = fields.Float(
string=' Paid by Petitioner ($)'
)
childcare_by_respondent = fields.Float(
string=' Paid by Respondent ($)'
)
# Extraordinary expenses (FL 61.30(9))
extraordinary_expenses = fields.Float(
string='Extraordinary Medical / Educational Expenses ($)',
help='FL 61.30(9): Expenses beyond ordinary child-rearing costs'
)
# ── Adjusted Support Obligation ────────────────────────────────────────
adjusted_support_obligation = fields.Float(
string='Adjusted Support Obligation ($)',
compute='_compute_adjusted_obligation', store=True,
help='Basic Obligation + Health Insurance + Childcare + Extraordinary'
)
# ── Timesharing Adjustment (FL 61.30(11)(b)) ──────────────────────────
petitioner_overnights = fields.Integer(
string='Petitioner Overnights / Year',
related='case_id.petitioner_overnights', store=True
)
respondent_overnights = fields.Integer(
string='Respondent Overnights / Year',
related='case_id.respondent_overnights', store=True
)
substantial_timesharing = fields.Boolean(
string='Substantial Timesharing Applies',
related='case_id.substantial_timesharing_applies', store=True,
help='FL 61.30(11)(b): Applies if either parent has > 73 overnights/year (20%)'
)
timesharing_adjustment = fields.Float(
string='Timesharing Adjustment ($)',
compute='_compute_timesharing_adjustment', store=True
)
# ── Final Obligations ──────────────────────────────────────────────────
total_support_obligation = fields.Float(
string='Total Support Obligation ($)',
compute='_compute_total_obligation', store=True
)
petitioner_obligation = fields.Float(
string='Petitioner Share ($)',
compute='_compute_party_obligations', store=True
)
respondent_obligation = fields.Float(
string='Respondent Share ($)',
compute='_compute_party_obligations', store=True
)
net_payment_amount = fields.Float(
string='Net Payment Amount ($)',
compute='_compute_net_payment', store=True,
help='Net amount actually exchanged between parties after credits'
)
payment_direction = fields.Selection([
('petitioner_pays', 'Petitioner Pays Respondent'),
('respondent_pays', 'Respondent Pays Petitioner'),
('no_payment', 'No Net Payment'),
], string='Payment Direction',
compute='_compute_net_payment', store=True
)
# ── Deviation (FL 61.30(1)(a)) ────────────────────────────────────────
deviation_requested = fields.Boolean(
string='Deviation Requested',
help='FL 61.30(1)(a): Court may deviate from guidelines with written findings'
)
deviation_reason = fields.Text(
string='Deviation Reason',
help='Valid reasons: extraordinary medical needs, independent child income, '
'seasonal income variations, shared physical custody arrangements'
)
deviation_amount = fields.Float(
string='Deviation Amount ($)',
help='Positive = above guidelines; Negative = below guidelines'
)
final_amount_with_deviation = fields.Float(
string='Final Amount With Deviation ($)',
compute='_compute_final_with_deviation', store=True
)
# ── Summary ───────────────────────────────────────────────────────────
calculation_summary = fields.Text(
string='Calculation Summary',
compute='_compute_summary'
)
# ══════════════════════════════════════════════════════════════════════
# COMPUTED METHODS
# ══════════════════════════════════════════════════════════════════════
@api.depends('petitioner_net_income', 'respondent_net_income')
def _compute_combined(self):
for rec in self:
rec.combined_net_income = (
(rec.petitioner_net_income or 0.0)
+ (rec.respondent_net_income or 0.0)
)
@api.depends('combined_net_income', 'petitioner_net_income', 'respondent_net_income')
def _compute_income_pcts(self):
for rec in self:
combined = rec.combined_net_income or 0.0
if combined > 0:
rec.petitioner_income_pct = (
rec.petitioner_net_income or 0.0
) / combined
rec.respondent_income_pct = (
rec.respondent_net_income or 0.0
) / combined
else:
rec.petitioner_income_pct = 0.5
rec.respondent_income_pct = 0.5
@api.depends('combined_net_income', 'number_of_children')
def _compute_basic_obligation(self):
"""
Look up Basic Support Obligation from FL DCF Schedule.
If combined income exceeds schedule maximum, apply FL 61.30(6) percentages:
1 child = 5%
2 children = 7.5%
3 children = 9.5%
4 children = 11%
5 children = 12%
6+ children = 12.5%
"""
PCT_MAP = {1: 0.05, 2: 0.075, 3: 0.095, 4: 0.11, 5: 0.12, 6: 0.125}
for rec in self:
if not rec.combined_net_income or not rec.number_of_children:
rec.basic_support_obligation = 0.0
rec.support_schedule_id = False
rec.above_schedule = False
continue
children = min(rec.number_of_children, 6)
entry = self.env['fl.support.schedule.entry'].search([
('income_min', '<=', rec.combined_net_income),
('income_max', '>=', rec.combined_net_income),
('children_count', '=', children),
('active', '=', True),
], order='effective_date desc', limit=1)
if entry:
rec.basic_support_obligation = entry.obligation_amount
rec.support_schedule_id = entry
rec.above_schedule = False
else:
# Above schedule maximum — FL 61.30(6) formula
pct = PCT_MAP.get(children, 0.125)
rec.basic_support_obligation = round(
rec.combined_net_income * pct, 2
)
rec.support_schedule_id = False
rec.above_schedule = True
@api.depends(
'basic_support_obligation',
'child_health_insurance_total',
'childcare_total',
'extraordinary_expenses',
)
def _compute_adjusted_obligation(self):
for rec in self:
rec.adjusted_support_obligation = (
(rec.basic_support_obligation or 0.0)
+ (rec.child_health_insurance_total or 0.0)
+ (rec.childcare_total or 0.0)
+ (rec.extraordinary_expenses or 0.0)
)
@api.depends(
'adjusted_support_obligation',
'petitioner_overnights',
'substantial_timesharing',
'petitioner_income_pct',
'respondent_income_pct',
)
def _compute_timesharing_adjustment(self):
"""
FL 61.30(11)(b) Substantial Timesharing Adjustment.
Only applies if either parent has > 73 overnights/year (20%).
Formula:
1. Each parent's share = adjusted_obligation × parent's income %
2. Multiply each share × 1.5 (cross-credit factor)
3. Multiply each result × other parent's timesharing %
4. Net payment = |pet_adjusted - resp_adjusted|
"""
for rec in self:
if not rec.substantial_timesharing:
rec.timesharing_adjustment = 0.0
continue
pet_nights = rec.petitioner_overnights or 0
resp_nights = 365 - pet_nights
pet_time_pct = pet_nights / 365
resp_time_pct = resp_nights / 365
adj = rec.adjusted_support_obligation or 0.0
pet_income_pct = rec.petitioner_income_pct or 0.5
resp_income_pct = rec.respondent_income_pct or 0.5
pet_share = adj * pet_income_pct
resp_share = adj * resp_income_pct
pet_adjusted = pet_share * 1.5 * resp_time_pct
resp_adjusted = resp_share * 1.5 * pet_time_pct
rec.timesharing_adjustment = round(abs(pet_adjusted - resp_adjusted), 2)
@api.depends('adjusted_support_obligation', 'timesharing_adjustment', 'substantial_timesharing')
def _compute_total_obligation(self):
for rec in self:
if rec.substantial_timesharing:
rec.total_support_obligation = rec.timesharing_adjustment
else:
rec.total_support_obligation = rec.adjusted_support_obligation
@api.depends('total_support_obligation', 'petitioner_income_pct', 'respondent_income_pct')
def _compute_party_obligations(self):
for rec in self:
total = rec.total_support_obligation or 0.0
rec.petitioner_obligation = round(total * (rec.petitioner_income_pct or 0.5), 2)
rec.respondent_obligation = round(total * (rec.respondent_income_pct or 0.5), 2)
@api.depends(
'petitioner_obligation', 'respondent_obligation',
'health_insurance_by_petitioner', 'health_insurance_by_respondent',
'childcare_by_petitioner', 'childcare_by_respondent',
)
def _compute_net_payment(self):
for rec in self:
# Credit parties for amounts they directly pay
pet_credits = (
(rec.health_insurance_by_petitioner or 0.0)
+ (rec.childcare_by_petitioner or 0.0)
)
resp_credits = (
(rec.health_insurance_by_respondent or 0.0)
+ (rec.childcare_by_respondent or 0.0)
)
pet_net = (rec.petitioner_obligation or 0.0) - pet_credits
resp_net = (rec.respondent_obligation or 0.0) - resp_credits
net = resp_net - pet_net
rec.net_payment_amount = abs(net)
if net > 0.01:
rec.payment_direction = 'petitioner_pays'
elif net < -0.01:
rec.payment_direction = 'respondent_pays'
else:
rec.payment_direction = 'no_payment'
@api.depends('total_support_obligation', 'deviation_requested', 'deviation_amount')
def _compute_final_with_deviation(self):
for rec in self:
if rec.deviation_requested and rec.deviation_amount:
rec.final_amount_with_deviation = (
rec.total_support_obligation + rec.deviation_amount
)
else:
rec.final_amount_with_deviation = rec.total_support_obligation
@api.depends(
'combined_net_income', 'basic_support_obligation',
'adjusted_support_obligation', 'net_payment_amount', 'payment_direction'
)
def _compute_summary(self):
direction_map = {
'petitioner_pays': 'Petitioner → Respondent',
'respondent_pays': 'Respondent → Petitioner',
'no_payment': 'No net payment',
}
for rec in self:
direction = direction_map.get(rec.payment_direction or '', '')
rec.calculation_summary = (
f'Combined income: ${rec.combined_net_income:,.2f}/mo | '
f'Basic obligation: ${rec.basic_support_obligation:,.2f} | '
f'Adjusted: ${rec.adjusted_support_obligation:,.2f} | '
f'Net payment: ${rec.net_payment_amount:,.2f}{direction}'
)

View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- ══════════════════════════════════════════════════════
SECURITY CATEGORIES
══════════════════════════════════════════════════════ -->
<record id="module_category_familylaw" model="ir.module.category">
<field name="name">Family Law</field>
<field name="description">Florida Family Law Case Management</field>
<field name="sequence">100</field>
</record>
<!-- ══════════════════════════════════════════════════════
USER GROUPS
══════════════════════════════════════════════════════ -->
<!-- Admin — Full access (Carlos / Active Blue staff) -->
<record id="group_admin" model="res.groups">
<field name="name">Family Law / Administrator</field>
<field name="category_id" ref="module_category_familylaw"/>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
<field name="comment">Full access to all family law models. Can configure AI engine, delete records, manage security.</field>
</record>
<!-- Paralegal — Read/write, no delete, no AI config -->
<record id="group_paralegal" model="res.groups">
<field name="name">Family Law / Paralegal</field>
<field name="category_id" ref="module_category_familylaw"/>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
<field name="comment">Can read/write cases and generate documents. Cannot delete records or access AI engine config.</field>
</record>
<!-- Portal Petitioner — Sees own cases only -->
<record id="group_portal_petitioner" model="res.groups">
<field name="name">Family Law / Portal Petitioner</field>
<field name="category_id" ref="module_category_familylaw"/>
<field name="implied_ids" eval="[(4, ref('base.group_portal'))]"/>
<field name="comment">Portal user — petitioner. Sees own cases only. Cannot see respondent financial data until mandatory disclosure complete.</field>
</record>
<!-- Portal Respondent — Sees own case info -->
<record id="group_portal_respondent" model="res.groups">
<field name="name">Family Law / Portal Respondent</field>
<field name="category_id" ref="module_category_familylaw"/>
<field name="implied_ids" eval="[(4, ref('base.group_portal'))]"/>
<field name="comment">Portal user — respondent. Sees own case info. Cannot see petitioner private notes.</field>
</record>
<!-- ══════════════════════════════════════════════════════
RECORD RULES
══════════════════════════════════════════════════════ -->
<!-- fl.case — Petitioner portal sees only their cases -->
<record id="rule_fl_case_petitioner_portal" model="ir.rule">
<field name="name">FL Case: Petitioner Portal Access</field>
<field name="model_id" ref="model_fl_case"/>
<field name="domain_force">[('petitioner_id.user_ids', 'in', [user.id])]</field>
<field name="groups" eval="[(4, ref('group_portal_petitioner'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<!-- fl.case — Respondent portal sees only their cases -->
<record id="rule_fl_case_respondent_portal" model="ir.rule">
<field name="name">FL Case: Respondent Portal Access</field>
<field name="model_id" ref="model_fl_case"/>
<field name="domain_force">[('respondent_id.user_ids', 'in', [user.id])]</field>
<field name="groups" eval="[(4, ref('group_portal_respondent'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<!-- fl.party — Petitioner portal sees only their party records -->
<record id="rule_fl_party_petitioner_portal" model="ir.rule">
<field name="name">FL Party: Petitioner Portal Access</field>
<field name="model_id" ref="model_fl_party"/>
<field name="domain_force">[('case_id.petitioner_id.user_ids', 'in', [user.id])]</field>
<field name="groups" eval="[(4, ref('group_portal_petitioner'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<!-- fl.party — Respondent portal sees only their party record -->
<record id="rule_fl_party_respondent_portal" model="ir.rule">
<field name="name">FL Party: Respondent Portal Access</field>
<field name="model_id" ref="model_fl_party"/>
<field name="domain_force">[('case_id.respondent_id.user_ids', 'in', [user.id])]</field>
<field name="groups" eval="[(4, ref('group_portal_respondent'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,84 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
# ── fl.case ──────────────────────────────────────────────────────────────────
access_fl_case_admin,fl.case admin,model_fl_case,group_admin,1,1,1,1
access_fl_case_paralegal,fl.case paralegal,model_fl_case,group_paralegal,1,1,1,0
access_fl_case_petitioner,fl.case petitioner portal,model_fl_case,group_portal_petitioner,1,1,0,0
access_fl_case_respondent,fl.case respondent portal,model_fl_case,group_portal_respondent,1,0,0,0
# ── fl.party ─────────────────────────────────────────────────────────────────
access_fl_party_admin,fl.party admin,model_fl_party,group_admin,1,1,1,1
access_fl_party_paralegal,fl.party paralegal,model_fl_party,group_paralegal,1,1,1,0
access_fl_party_petitioner,fl.party petitioner portal,model_fl_party,group_portal_petitioner,1,1,0,0
access_fl_party_respondent,fl.party respondent portal,model_fl_party,group_portal_respondent,1,1,0,0
# ── fl.income.source ─────────────────────────────────────────────────────────
access_fl_income_source_admin,fl.income.source admin,model_fl_income_source,group_admin,1,1,1,1
access_fl_income_source_paralegal,fl.income.source paralegal,model_fl_income_source,group_paralegal,1,1,1,0
access_fl_income_source_petitioner,fl.income.source petitioner,model_fl_income_source,group_portal_petitioner,1,1,0,0
# ── fl.child ─────────────────────────────────────────────────────────────────
access_fl_child_admin,fl.child admin,model_fl_child,group_admin,1,1,1,1
access_fl_child_paralegal,fl.child paralegal,model_fl_child,group_paralegal,1,1,1,0
access_fl_child_petitioner,fl.child petitioner portal,model_fl_child,group_portal_petitioner,1,1,0,0
access_fl_child_respondent,fl.child respondent portal,model_fl_child,group_portal_respondent,1,0,0,0
# ── fl.support.calculation ───────────────────────────────────────────────────
access_fl_support_calc_admin,fl.support.calculation admin,model_fl_support_calculation,group_admin,1,1,1,1
access_fl_support_calc_paralegal,fl.support.calculation paralegal,model_fl_support_calculation,group_paralegal,1,1,1,0
access_fl_support_calc_petitioner,fl.support.calculation petitioner,model_fl_support_calculation,group_portal_petitioner,1,1,1,0
access_fl_support_calc_respondent,fl.support.calculation respondent,model_fl_support_calculation,group_portal_respondent,1,0,0,0
# ── fl.support.schedule.entry ────────────────────────────────────────────────
access_fl_support_schedule_admin,fl.support.schedule.entry admin,model_fl_support_schedule_entry,group_admin,1,1,1,1
access_fl_support_schedule_paralegal,fl.support.schedule.entry paralegal,model_fl_support_schedule_entry,group_paralegal,1,0,0,0
access_fl_support_schedule_petitioner,fl.support.schedule.entry petitioner,model_fl_support_schedule_entry,group_portal_petitioner,1,0,0,0
# ── fl.statute ───────────────────────────────────────────────────────────────
access_fl_statute_admin,fl.statute admin,model_fl_statute,group_admin,1,1,1,1
access_fl_statute_paralegal,fl.statute paralegal,model_fl_statute,group_paralegal,1,0,0,0
access_fl_statute_petitioner,fl.statute petitioner,model_fl_statute,group_portal_petitioner,1,0,0,0
# ── fl.issue.tag ─────────────────────────────────────────────────────────────
access_fl_issue_tag_admin,fl.issue.tag admin,model_fl_issue_tag,group_admin,1,1,1,1
access_fl_issue_tag_paralegal,fl.issue.tag paralegal,model_fl_issue_tag,group_paralegal,1,0,0,0
access_fl_issue_tag_petitioner,fl.issue.tag petitioner,model_fl_issue_tag,group_portal_petitioner,1,0,0,0
# ── fl.deadline ──────────────────────────────────────────────────────────────
access_fl_deadline_admin,fl.deadline admin,model_fl_deadline,group_admin,1,1,1,1
access_fl_deadline_paralegal,fl.deadline paralegal,model_fl_deadline,group_paralegal,1,1,1,0
access_fl_deadline_petitioner,fl.deadline petitioner,model_fl_deadline,group_portal_petitioner,1,0,0,0
# ── fl.hearing ───────────────────────────────────────────────────────────────
access_fl_hearing_admin,fl.hearing admin,model_fl_hearing,group_admin,1,1,1,1
access_fl_hearing_paralegal,fl.hearing paralegal,model_fl_hearing,group_paralegal,1,1,1,0
access_fl_hearing_petitioner,fl.hearing petitioner,model_fl_hearing,group_portal_petitioner,1,0,0,0
# ── fl.deposition ────────────────────────────────────────────────────────────
access_fl_deposition_admin,fl.deposition admin,model_fl_deposition,group_admin,1,1,1,1
access_fl_deposition_paralegal,fl.deposition paralegal,model_fl_deposition,group_paralegal,1,1,1,0
access_fl_deposition_petitioner,fl.deposition petitioner,model_fl_deposition,group_portal_petitioner,1,0,0,0
# ── fl.discovery ─────────────────────────────────────────────────────────────
access_fl_discovery_admin,fl.discovery admin,model_fl_discovery,group_admin,1,1,1,1
access_fl_discovery_paralegal,fl.discovery paralegal,model_fl_discovery,group_paralegal,1,1,1,0
access_fl_discovery_petitioner,fl.discovery petitioner,model_fl_discovery,group_portal_petitioner,1,0,0,0
# ── fl.document ──────────────────────────────────────────────────────────────
access_fl_document_admin,fl.document admin,model_fl_document,group_admin,1,1,1,1
access_fl_document_paralegal,fl.document paralegal,model_fl_document,group_paralegal,1,1,1,0
access_fl_document_petitioner,fl.document petitioner,model_fl_document,group_portal_petitioner,1,0,0,0
# ── fl.caselaw ───────────────────────────────────────────────────────────────
access_fl_caselaw_admin,fl.caselaw admin,model_fl_caselaw,group_admin,1,1,1,1
access_fl_caselaw_paralegal,fl.caselaw paralegal,model_fl_caselaw,group_paralegal,1,1,0,0
access_fl_caselaw_petitioner,fl.caselaw petitioner,model_fl_caselaw,group_portal_petitioner,1,0,0,0
# ── fl.analysis ──────────────────────────────────────────────────────────────
access_fl_analysis_admin,fl.analysis admin,model_fl_analysis,group_admin,1,1,1,1
access_fl_analysis_paralegal,fl.analysis paralegal,model_fl_analysis,group_paralegal,1,0,0,0
access_fl_analysis_petitioner,fl.analysis petitioner,model_fl_analysis,group_portal_petitioner,1,0,0,0
# ── fl.argument ──────────────────────────────────────────────────────────────
access_fl_argument_admin,fl.argument admin,model_fl_argument,group_admin,1,1,1,1
access_fl_argument_paralegal,fl.argument paralegal,model_fl_argument,group_paralegal,1,1,0,0
# ── fl.fee.waiver ────────────────────────────────────────────────────────────
access_fl_fee_waiver_admin,fl.fee.waiver admin,model_fl_fee_waiver,group_admin,1,1,1,1
access_fl_fee_waiver_paralegal,fl.fee.waiver paralegal,model_fl_fee_waiver,group_paralegal,1,1,1,0
access_fl_fee_waiver_petitioner,fl.fee.waiver petitioner,model_fl_fee_waiver,group_portal_petitioner,1,1,1,0
# ── fl.income.withholding ─────────────────────────────────────────────────────
access_fl_income_withholding_admin,fl.income.withholding admin,model_fl_income_withholding,group_admin,1,1,1,1
access_fl_income_withholding_paralegal,fl.income.withholding paralegal,model_fl_income_withholding,group_paralegal,1,1,1,0
access_fl_income_withholding_petitioner,fl.income.withholding petitioner,model_fl_income_withholding,group_portal_petitioner,1,0,0,0
# ── fl.intake.wizard ─────────────────────────────────────────────────────────
access_fl_intake_wizard_admin,fl.intake.wizard admin,model_fl_intake_wizard,group_admin,1,1,1,1
access_fl_intake_wizard_paralegal,fl.intake.wizard paralegal,model_fl_intake_wizard,group_paralegal,1,1,1,1
# ── fl.analysis.wizard ───────────────────────────────────────────────────────
access_fl_analysis_wizard_admin,fl.analysis.wizard admin,model_fl_analysis_wizard,group_admin,1,1,1,1
# ── fl.generate.packet.wizard ────────────────────────────────────────────────
access_fl_generate_packet_wizard_admin,fl.generate.packet.wizard admin,model_fl_generate_packet_wizard,group_admin,1,1,1,1
access_fl_generate_packet_wizard_paralegal,fl.generate.packet.wizard paralegal,model_fl_generate_packet_wizard,group_paralegal,1,1,1,1
1 id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2 # ── fl.case ──────────────────────────────────────────────────────────────────
3 access_fl_case_admin,fl.case admin,model_fl_case,group_admin,1,1,1,1
4 access_fl_case_paralegal,fl.case paralegal,model_fl_case,group_paralegal,1,1,1,0
5 access_fl_case_petitioner,fl.case petitioner portal,model_fl_case,group_portal_petitioner,1,1,0,0
6 access_fl_case_respondent,fl.case respondent portal,model_fl_case,group_portal_respondent,1,0,0,0
7 # ── fl.party ─────────────────────────────────────────────────────────────────
8 access_fl_party_admin,fl.party admin,model_fl_party,group_admin,1,1,1,1
9 access_fl_party_paralegal,fl.party paralegal,model_fl_party,group_paralegal,1,1,1,0
10 access_fl_party_petitioner,fl.party petitioner portal,model_fl_party,group_portal_petitioner,1,1,0,0
11 access_fl_party_respondent,fl.party respondent portal,model_fl_party,group_portal_respondent,1,1,0,0
12 # ── fl.income.source ─────────────────────────────────────────────────────────
13 access_fl_income_source_admin,fl.income.source admin,model_fl_income_source,group_admin,1,1,1,1
14 access_fl_income_source_paralegal,fl.income.source paralegal,model_fl_income_source,group_paralegal,1,1,1,0
15 access_fl_income_source_petitioner,fl.income.source petitioner,model_fl_income_source,group_portal_petitioner,1,1,0,0
16 # ── fl.child ─────────────────────────────────────────────────────────────────
17 access_fl_child_admin,fl.child admin,model_fl_child,group_admin,1,1,1,1
18 access_fl_child_paralegal,fl.child paralegal,model_fl_child,group_paralegal,1,1,1,0
19 access_fl_child_petitioner,fl.child petitioner portal,model_fl_child,group_portal_petitioner,1,1,0,0
20 access_fl_child_respondent,fl.child respondent portal,model_fl_child,group_portal_respondent,1,0,0,0
21 # ── fl.support.calculation ───────────────────────────────────────────────────
22 access_fl_support_calc_admin,fl.support.calculation admin,model_fl_support_calculation,group_admin,1,1,1,1
23 access_fl_support_calc_paralegal,fl.support.calculation paralegal,model_fl_support_calculation,group_paralegal,1,1,1,0
24 access_fl_support_calc_petitioner,fl.support.calculation petitioner,model_fl_support_calculation,group_portal_petitioner,1,1,1,0
25 access_fl_support_calc_respondent,fl.support.calculation respondent,model_fl_support_calculation,group_portal_respondent,1,0,0,0
26 # ── fl.support.schedule.entry ────────────────────────────────────────────────
27 access_fl_support_schedule_admin,fl.support.schedule.entry admin,model_fl_support_schedule_entry,group_admin,1,1,1,1
28 access_fl_support_schedule_paralegal,fl.support.schedule.entry paralegal,model_fl_support_schedule_entry,group_paralegal,1,0,0,0
29 access_fl_support_schedule_petitioner,fl.support.schedule.entry petitioner,model_fl_support_schedule_entry,group_portal_petitioner,1,0,0,0
30 # ── fl.statute ───────────────────────────────────────────────────────────────
31 access_fl_statute_admin,fl.statute admin,model_fl_statute,group_admin,1,1,1,1
32 access_fl_statute_paralegal,fl.statute paralegal,model_fl_statute,group_paralegal,1,0,0,0
33 access_fl_statute_petitioner,fl.statute petitioner,model_fl_statute,group_portal_petitioner,1,0,0,0
34 # ── fl.issue.tag ─────────────────────────────────────────────────────────────
35 access_fl_issue_tag_admin,fl.issue.tag admin,model_fl_issue_tag,group_admin,1,1,1,1
36 access_fl_issue_tag_paralegal,fl.issue.tag paralegal,model_fl_issue_tag,group_paralegal,1,0,0,0
37 access_fl_issue_tag_petitioner,fl.issue.tag petitioner,model_fl_issue_tag,group_portal_petitioner,1,0,0,0
38 # ── fl.deadline ──────────────────────────────────────────────────────────────
39 access_fl_deadline_admin,fl.deadline admin,model_fl_deadline,group_admin,1,1,1,1
40 access_fl_deadline_paralegal,fl.deadline paralegal,model_fl_deadline,group_paralegal,1,1,1,0
41 access_fl_deadline_petitioner,fl.deadline petitioner,model_fl_deadline,group_portal_petitioner,1,0,0,0
42 # ── fl.hearing ───────────────────────────────────────────────────────────────
43 access_fl_hearing_admin,fl.hearing admin,model_fl_hearing,group_admin,1,1,1,1
44 access_fl_hearing_paralegal,fl.hearing paralegal,model_fl_hearing,group_paralegal,1,1,1,0
45 access_fl_hearing_petitioner,fl.hearing petitioner,model_fl_hearing,group_portal_petitioner,1,0,0,0
46 # ── fl.deposition ────────────────────────────────────────────────────────────
47 access_fl_deposition_admin,fl.deposition admin,model_fl_deposition,group_admin,1,1,1,1
48 access_fl_deposition_paralegal,fl.deposition paralegal,model_fl_deposition,group_paralegal,1,1,1,0
49 access_fl_deposition_petitioner,fl.deposition petitioner,model_fl_deposition,group_portal_petitioner,1,0,0,0
50 # ── fl.discovery ─────────────────────────────────────────────────────────────
51 access_fl_discovery_admin,fl.discovery admin,model_fl_discovery,group_admin,1,1,1,1
52 access_fl_discovery_paralegal,fl.discovery paralegal,model_fl_discovery,group_paralegal,1,1,1,0
53 access_fl_discovery_petitioner,fl.discovery petitioner,model_fl_discovery,group_portal_petitioner,1,0,0,0
54 # ── fl.document ──────────────────────────────────────────────────────────────
55 access_fl_document_admin,fl.document admin,model_fl_document,group_admin,1,1,1,1
56 access_fl_document_paralegal,fl.document paralegal,model_fl_document,group_paralegal,1,1,1,0
57 access_fl_document_petitioner,fl.document petitioner,model_fl_document,group_portal_petitioner,1,0,0,0
58 # ── fl.caselaw ───────────────────────────────────────────────────────────────
59 access_fl_caselaw_admin,fl.caselaw admin,model_fl_caselaw,group_admin,1,1,1,1
60 access_fl_caselaw_paralegal,fl.caselaw paralegal,model_fl_caselaw,group_paralegal,1,1,0,0
61 access_fl_caselaw_petitioner,fl.caselaw petitioner,model_fl_caselaw,group_portal_petitioner,1,0,0,0
62 # ── fl.analysis ──────────────────────────────────────────────────────────────
63 access_fl_analysis_admin,fl.analysis admin,model_fl_analysis,group_admin,1,1,1,1
64 access_fl_analysis_paralegal,fl.analysis paralegal,model_fl_analysis,group_paralegal,1,0,0,0
65 access_fl_analysis_petitioner,fl.analysis petitioner,model_fl_analysis,group_portal_petitioner,1,0,0,0
66 # ── fl.argument ──────────────────────────────────────────────────────────────
67 access_fl_argument_admin,fl.argument admin,model_fl_argument,group_admin,1,1,1,1
68 access_fl_argument_paralegal,fl.argument paralegal,model_fl_argument,group_paralegal,1,1,0,0
69 # ── fl.fee.waiver ────────────────────────────────────────────────────────────
70 access_fl_fee_waiver_admin,fl.fee.waiver admin,model_fl_fee_waiver,group_admin,1,1,1,1
71 access_fl_fee_waiver_paralegal,fl.fee.waiver paralegal,model_fl_fee_waiver,group_paralegal,1,1,1,0
72 access_fl_fee_waiver_petitioner,fl.fee.waiver petitioner,model_fl_fee_waiver,group_portal_petitioner,1,1,1,0
73 # ── fl.income.withholding ─────────────────────────────────────────────────────
74 access_fl_income_withholding_admin,fl.income.withholding admin,model_fl_income_withholding,group_admin,1,1,1,1
75 access_fl_income_withholding_paralegal,fl.income.withholding paralegal,model_fl_income_withholding,group_paralegal,1,1,1,0
76 access_fl_income_withholding_petitioner,fl.income.withholding petitioner,model_fl_income_withholding,group_portal_petitioner,1,0,0,0
77 # ── fl.intake.wizard ─────────────────────────────────────────────────────────
78 access_fl_intake_wizard_admin,fl.intake.wizard admin,model_fl_intake_wizard,group_admin,1,1,1,1
79 access_fl_intake_wizard_paralegal,fl.intake.wizard paralegal,model_fl_intake_wizard,group_paralegal,1,1,1,1
80 # ── fl.analysis.wizard ───────────────────────────────────────────────────────
81 access_fl_analysis_wizard_admin,fl.analysis.wizard admin,model_fl_analysis_wizard,group_admin,1,1,1,1
82 # ── fl.generate.packet.wizard ────────────────────────────────────────────────
83 access_fl_generate_packet_wizard_admin,fl.generate.packet.wizard admin,model_fl_generate_packet_wizard,group_admin,1,1,1,1
84 access_fl_generate_packet_wizard_paralegal,fl.generate.packet.wizard paralegal,model_fl_generate_packet_wizard,group_paralegal,1,1,1,1

View File

@@ -0,0 +1,56 @@
/* ActiveBlue Family Law — Portal CSS
Phase 6: Full portal styling
Phase 1: Stub
*/
/* Attorney referral banner */
.fl-attorney-referral-banner {
background: #f8d7da;
border: 2px solid #dc3545;
border-radius: 6px;
padding: 16px;
margin-bottom: 20px;
color: #721c24;
font-weight: bold;
}
/* DV safety banner */
.fl-dv-safety-banner {
background: #fff3cd;
border: 2px solid #ffc107;
border-radius: 6px;
padding: 16px;
margin-bottom: 20px;
}
/* Deadline urgency colors */
.fl-deadline-overdue {
color: #dc3545;
font-weight: bold;
}
.fl-deadline-urgent {
color: #fd7e14;
font-weight: bold;
}
.fl-deadline-ok {
color: #28a745;
}
/* Support calculator */
.fl-calculator-result {
background: #d4edda;
border: 1px solid #28a745;
border-radius: 6px;
padding: 20px;
text-align: center;
font-size: 1.4em;
font-weight: bold;
color: #155724;
margin: 20px 0;
}
/* Bilingual toggle */
.fl-lang-toggle {
float: right;
margin-bottom: 10px;
}

View File

@@ -0,0 +1,9 @@
/** @odoo-module **/
/**
* ActiveBlue Family Law — FL 61.30 Interactive Calculator Widget
* Phase 6: Full interactive implementation
* Phase 1: Stub
*/
// Placeholder — full FL 61.30 interactive widget implemented in Phase 6
console.log('[ActiveBlue FamilyLaw] Calculator widget loaded (Phase 1 stub)');

View File

@@ -0,0 +1,9 @@
/** @odoo-module **/
/**
* ActiveBlue Family Law — Visual Timeline Widget
* Phase 6: Full visual timeline
* Phase 1: Stub
*/
// Placeholder — visual deadline timeline implemented in Phase 6
console.log('[ActiveBlue FamilyLaw] Timeline widget loaded (Phase 1 stub)');

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_fl_analysis_tree" model="ir.ui.view">
<field name="name">fl.analysis.tree</field>
<field name="model">fl.analysis</field>
<field name="arch" type="xml">
<tree string="AI Analyses">
<field name="case_id"/>
<field name="analysis_date"/>
<field name="model_used"/>
<field name="attorney_referral_flag"/>
<field name="confidence_level"/>
<field name="case_complexity"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="view_fl_analysis_form" model="ir.ui.view">
<field name="name">fl.analysis.form</field>
<field name="model">fl.analysis</field>
<field name="arch" type="xml">
<form string="AI Case Analysis">
<sheet>
<group>
<group>
<field name="case_id"/>
<field name="analysis_date"/>
<field name="model_used"/>
<field name="state"/>
</group>
<group>
<field name="attorney_referral_flag"/>
<field name="confidence_level"/>
<field name="case_complexity"/>
</group>
</group>
<div class="alert alert-danger" role="alert"
attrs="{'invisible': [('attorney_referral_flag', '=', False)]}">
<strong>⚠️ ATTORNEY REFERRAL RECOMMENDED</strong><br/>
<field name="attorney_referral_reason" readonly="1" nolabel="1"/>
</div>
<separator string="Plain English Summary (English)"/>
<field name="plain_english_summary" readonly="1" nolabel="1"/>
<separator string="Plain English Summary (Español)"/>
<field name="plain_english_summary_es" readonly="1" nolabel="1"/>
<separator string="Matched Case Law"/>
<field name="matched_caselaw_ids" widget="many2many_tags" readonly="1" nolabel="1"/>
</sheet>
</form>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,388 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- ══════════════════════════════════════════════════════
FORM VIEW
══════════════════════════════════════════════════════ -->
<record id="view_fl_case_form" model="ir.ui.view">
<field name="name">fl.case.form</field>
<field name="model">fl.case</field>
<field name="arch" type="xml">
<form string="Family Law Case">
<header>
<field name="stage" widget="statusbar"
statusbar_visible="intake,preparation,filed,service_complete,discovery,mediation,hearing_scheduled,order_entered,closed"/>
<button name="action_run_ai_analysis" string="Run AI Analysis"
type="object" class="oe_highlight"
groups="activeblue_familylaw.group_admin,activeblue_familylaw.group_paralegal"/>
<button name="action_open_support_calculator" string="Open Calculator"
type="object"
attrs="{'invisible': [('case_type', 'not in', ['modification','dissolution_children','paternity','custody_modification'])]}"/>
</header>
<!-- Attorney Referral Banner -->
<div class="alert alert-danger" role="alert"
attrs="{'invisible': [('attorney_referral_flag', '=', False)]}">
<strong>⚠️ ATTORNEY REFERRAL RECOMMENDED</strong>
— This case has been flagged as complex or high-risk for pro se representation.
Strongly consider consulting with a family law attorney.
<br/>Legal Services of Greater Miami: (305) 576-0080
</div>
<!-- DV Safety Note -->
<field name="dv_safety_note" widget="html"
attrs="{'invisible': [('domestic_violence_flag', '=', False)]}"/>
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" type="object"
name="action_view_tasks" icon="fa-tasks">
<field string="Tasks" name="task_count" widget="statinfo"/>
</button>
<button class="oe_stat_button" type="object"
name="action_open_support_calculator" icon="fa-calculator"
attrs="{'invisible': [('case_type', 'not in', ['modification','dissolution_children','paternity','custody_modification'])]}">
<span class="o_stat_text">Calculator</span>
</button>
</div>
<div class="oe_title">
<h1>
<field name="name" readonly="1"/>
</h1>
<h2>
<field name="case_type" options="{'no_create': True}"/>
</h2>
</div>
<group>
<group string="Case Identity">
<field name="court_case_number"
placeholder="e.g. 2025-DR-012345-01"/>
<field name="circuit"/>
<field name="county"/>
<field name="division"/>
<field name="judge_id"/>
</group>
<group string="Key Dates">
<field name="filing_date"/>
<field name="service_date"/>
<field name="last_order_date"
attrs="{'invisible': [('case_type', 'not in', ['modification','alimony_modification','custody_modification'])]}"/>
<field name="marriage_date"
attrs="{'invisible': [('case_type', 'not in', ['dissolution_children','dissolution_no_children','alimony_modification'])]}"/>
<field name="years_of_marriage"
attrs="{'invisible': [('case_type', 'not in', ['dissolution_children','dissolution_no_children','alimony_modification'])]}"/>
<field name="retroactivity_date" readonly="1"/>
</group>
</group>
<notebook>
<!-- TAB 1: Parties & Safety -->
<page string="Parties" name="parties">
<group>
<group string="Petitioner">
<field name="petitioner_id"/>
<field name="petitioner_attorney_id"
placeholder="Leave blank if pro se"/>
<field name="petitioner_fl_resident_since"/>
<field name="residency_warning" readonly="1"
attrs="{'invisible': [('petitioner_fl_resident_since', '=', False)]}"/>
</group>
<group string="Respondent">
<field name="respondent_id"/>
<field name="respondent_attorney_id"/>
<field name="respondent_has_counsel" readonly="1"/>
<field name="respondent_answered"/>
</group>
</group>
<group string="Safety Flags">
<field name="domestic_violence_flag"/>
<field name="dv_injunction_active"
attrs="{'invisible': [('domestic_violence_flag', '=', False)]}"/>
</group>
<separator string="Party Details (Income, Employment, Service)"/>
<field name="party_ids">
<tree string="Parties" editable="bottom">
<field name="partner_id"/>
<field name="role"/>
<field name="employment_type"/>
<field name="gross_monthly_income"/>
<field name="net_monthly_income" readonly="1"/>
<field name="income_imputed"/>
</tree>
</field>
</page>
<!-- TAB 2: Children -->
<page string="Children" name="children"
attrs="{'invisible': [('case_type', 'in', ['dissolution_no_children','alimony_modification'])]}">
<group>
<field name="children_count" readonly="1"/>
<field name="has_minor_children" readonly="1"/>
</group>
<separator string="Parenting Class (FL 61.21)"
attrs="{'invisible': [('parenting_class_required', '=', False)]}"/>
<group attrs="{'invisible': [('parenting_class_required', '=', False)]}">
<group>
<field name="petitioner_parenting_class_done"/>
<field name="petitioner_parenting_class_date"
attrs="{'invisible': [('petitioner_parenting_class_done', '=', False)]}"/>
</group>
<group>
<field name="respondent_parenting_class_done"/>
<field name="respondent_parenting_class_date"
attrs="{'invisible': [('respondent_parenting_class_done', '=', False)]}"/>
</group>
</group>
<field name="parenting_class_warning" readonly="1"
attrs="{'invisible': [('parenting_class_required', '=', False)]}"/>
<separator string="Children on Case"/>
<field name="child_ids">
<tree string="Children" editable="bottom">
<field name="name"/>
<field name="date_of_birth"/>
<field name="age" readonly="1"/>
<field name="emancipation_date" readonly="1"/>
<field name="approaching_emancipation" readonly="1"/>
<field name="emancipated" readonly="1"/>
<field name="support_amount"/>
</tree>
</field>
</page>
<!-- TAB 3: Support & Threshold -->
<page string="Support Calculation" name="support"
attrs="{'invisible': [('case_type', 'not in', ['modification','dissolution_children','paternity','custody_modification'])]}">
<group string="Current Order">
<field name="current_order_amount"/>
<field name="current_order_per_child"/>
<field name="current_order_total" readonly="1"/>
</group>
<group string="FL 61.30 Calculation">
<field name="support_calc_id"/>
<field name="calculated_support" readonly="1"/>
<field name="support_difference" readonly="1"/>
<field name="support_difference_pct" readonly="1"
string="Difference %" widget="percentage"/>
</group>
<group string="Modification Threshold Test (FL 61.30(1)(b))"
attrs="{'invisible': [('case_type', '!=', 'modification')]}">
<field name="threshold_met" readonly="1"/>
<field name="substantial_change_basis"/>
<field name="substantial_change_detail"/>
</group>
<field name="threshold_result" widget="html" readonly="1"
attrs="{'invisible': [('case_type', '!=', 'modification')]}"/>
</page>
<!-- TAB 4: Timesharing -->
<page string="Timesharing" name="timesharing"
attrs="{'invisible': [('case_type', 'not in', ['modification','dissolution_children','paternity','custody_modification'])]}">
<group>
<group>
<field name="timesharing_changed"/>
<field name="petitioner_overnights"/>
<field name="respondent_overnights" readonly="1"/>
<field name="petitioner_timesharing_pct" readonly="1" string="Petitioner %"/>
<field name="substantial_timesharing_applies" readonly="1"/>
</group>
<group>
<field name="mediator_id"/>
</group>
</group>
</page>
<!-- TAB 5: Deadlines -->
<page string="Deadlines" name="deadlines">
<group>
<field name="next_deadline" readonly="1"/>
<field name="next_deadline_label" readonly="1" string="Next Deadline"/>
<field name="overdue_deadline_count" readonly="1"/>
<field name="discovery_cutoff_date" readonly="1"/>
</group>
<field name="deadline_ids">
<tree string="Deadlines" decoration-danger="is_overdue == True"
decoration-warning="days_until_due &lt;= 7 and days_until_due &gt;= 0"
decoration-muted="completed == True">
<field name="name"/>
<field name="deadline_type"/>
<field name="due_date"/>
<field name="days_until_due"/>
<field name="statute_reference"/>
<field name="completed"/>
<field name="is_overdue" readonly="1"/>
</tree>
</field>
</page>
<!-- TAB 6: Hearings, Discovery, Depositions -->
<page string="Hearings" name="hearings">
<field name="next_hearing_date" readonly="1"/>
<field name="hearing_ids">
<tree string="Hearings" editable="bottom">
<field name="name"/>
<field name="hearing_type"/>
<field name="hearing_date"/>
<field name="location"/>
<field name="state"/>
</tree>
</field>
</page>
<!-- TAB 7: AI Analysis -->
<page string="AI Analysis" name="ai">
<group>
<field name="attorney_referral_flag" readonly="1"/>
<field name="fee_waiver_eligible" readonly="1"/>
</group>
<separator string="AI Case Summary (English)"/>
<field name="ai_plain_english" readonly="1" nolabel="1"
placeholder="Run AI Analysis to generate summary"/>
<separator string="AI Case Summary (Español)"/>
<field name="ai_plain_english_es" readonly="1" nolabel="1"/>
<separator string="All Analyses"/>
<field name="analysis_ids">
<tree string="Analyses">
<field name="analysis_date"/>
<field name="model_used"/>
<field name="attorney_referral_flag"/>
<field name="confidence_level"/>
<field name="case_complexity"/>
<field name="state"/>
</tree>
</field>
</page>
<!-- TAB 8: Fee Waiver & Expenses -->
<page string="Fees &amp; Expenses" name="fees">
<group string="Fee Waiver (FL 57.082)">
<field name="fee_waiver_eligible" readonly="1"/>
<field name="fee_waiver_id"/>
<button name="action_create_fee_waiver"
string="Create Fee Waiver Application"
type="object" class="btn-secondary"
attrs="{'invisible': [('fee_waiver_id', '!=', False)]}"/>
</group>
<group string="Post-Order">
<field name="new_order_amount"/>
<field name="new_order_date"/>
<field name="income_withholding_id"/>
</group>
<separator string="Case Expenses"/>
<field name="total_expenses" readonly="1"/>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<!-- ══════════════════════════════════════════════════════
TREE VIEW
══════════════════════════════════════════════════════ -->
<record id="view_fl_case_tree" model="ir.ui.view">
<field name="name">fl.case.tree</field>
<field name="model">fl.case</field>
<field name="arch" type="xml">
<tree string="Family Law Cases"
decoration-danger="overdue_deadline_count &gt; 0"
decoration-warning="attorney_referral_flag == True">
<field name="name"/>
<field name="case_type"/>
<field name="court_case_number"/>
<field name="petitioner_id"/>
<field name="respondent_id"/>
<field name="stage"/>
<field name="filing_date"/>
<field name="next_deadline"/>
<field name="next_deadline_label" string="Next Deadline"/>
<field name="overdue_deadline_count" string="Overdue"/>
<field name="threshold_met"
attrs="{'invisible': [('case_type', '!=', 'modification')]}"/>
<field name="attorney_referral_flag" widget="boolean_toggle"/>
<field name="domestic_violence_flag" widget="boolean_toggle"/>
</tree>
</field>
</record>
<!-- ══════════════════════════════════════════════════════
SEARCH VIEW
══════════════════════════════════════════════════════ -->
<record id="view_fl_case_search" model="ir.ui.view">
<field name="name">fl.case.search</field>
<field name="model">fl.case</field>
<field name="arch" type="xml">
<search string="Search Cases">
<field name="name" string="Case Reference"/>
<field name="court_case_number"/>
<field name="petitioner_id"/>
<field name="respondent_id"/>
<separator/>
<filter string="Active Cases" name="active"
domain="[('active', '=', True)]"/>
<filter string="Intake" name="stage_intake"
domain="[('stage', '=', 'intake')]"/>
<filter string="Filed" name="stage_filed"
domain="[('stage', '=', 'filed')]"/>
<filter string="Discovery" name="stage_discovery"
domain="[('stage', '=', 'discovery')]"/>
<separator/>
<filter string="Modification Cases" name="type_modification"
domain="[('case_type', '=', 'modification')]"/>
<filter string="Dissolution" name="type_dissolution"
domain="[('case_type', 'in', ['dissolution_children','dissolution_no_children'])]"/>
<separator/>
<filter string="Has Overdue Deadlines" name="overdue"
domain="[('overdue_deadline_count', '&gt;', 0)]"/>
<filter string="Attorney Referral Flagged" name="attorney_referral"
domain="[('attorney_referral_flag', '=', True)]"/>
<filter string="Domestic Violence" name="dv"
domain="[('domestic_violence_flag', '=', True)]"/>
<filter string="Threshold Met" name="threshold_met"
domain="[('threshold_met', '=', True)]"/>
<separator/>
<group expand="0" string="Group By">
<filter string="Stage" name="group_stage" context="{'group_by': 'stage'}"/>
<filter string="Case Type" name="group_type" context="{'group_by': 'case_type'}"/>
<filter string="Filing Date" name="group_date" context="{'group_by': 'filing_date:month'}"/>
</group>
</search>
</field>
</record>
<!-- ══════════════════════════════════════════════════════
ACTIONS
══════════════════════════════════════════════════════ -->
<record id="action_fl_case_list" model="ir.actions.act_window">
<field name="name">Family Law Cases</field>
<field name="res_model">fl.case</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_fl_case_search"/>
<field name="context">{'search_default_active': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No cases yet. Create your first case!
</p>
<p>Use the intake wizard or create a case directly.</p>
</field>
</record>
<!-- Intake Wizard Action -->
<record id="action_fl_intake_wizard" model="ir.actions.act_window">
<field name="name">New Case — Intake</field>
<field name="res_model">fl.intake.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_fl_caselaw_tree" model="ir.ui.view">
<field name="name">fl.caselaw.tree</field>
<field name="model">fl.caselaw</field>
<field name="arch" type="xml">
<tree string="Case Law Library">
<field name="short_name"/>
<field name="year"/>
<field name="court"/>
<field name="favorable_to"/>
<field name="issue_tag_ids" widget="many2many_tags"/>
<field name="superseded_by_2023_reform"/>
</tree>
</field>
</record>
<record id="view_fl_caselaw_form" model="ir.ui.view">
<field name="name">fl.caselaw.form</field>
<field name="model">fl.caselaw</field>
<field name="arch" type="xml">
<form string="Case Law">
<sheet>
<div class="alert alert-warning" role="alert"
attrs="{'invisible': [('superseded_by_2023_reform', '=', False)]}">
<strong>⚠️ NOTE:</strong> This case involves permanent alimony
which was <strong>eliminated by HB 1409 (effective July 1, 2023)</strong>.
Pre-2023 permanent alimony holdings may not apply to new cases.
</div>
<group>
<group>
<field name="short_name"/>
<field name="citation"/>
<field name="court"/>
<field name="year"/>
<field name="favorable_to"/>
</group>
<group>
<field name="statute_ref_ids" widget="many2many_tags"/>
<field name="issue_tag_ids" widget="many2many_tags"/>
<field name="full_text_url" widget="url"/>
<field name="google_scholar_url" widget="url"/>
<field name="superseded_by_2023_reform"/>
<field name="active"/>
</group>
</group>
<group string="Holding">
<field name="holding" nolabel="1"/>
</group>
<group string="Facts Summary">
<field name="facts_summary" nolabel="1"/>
</group>
<group string="Why It Matters for Pro Se Litigants">
<field name="relevance" nolabel="1"/>
</group>
<group string="Plain English (EN)">
<field name="plain_english" nolabel="1"/>
</group>
<group string="Plain English (ES)">
<field name="plain_english_es" nolabel="1"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_fl_caselaw_search" model="ir.ui.view">
<field name="name">fl.caselaw.search</field>
<field name="model">fl.caselaw</field>
<field name="arch" type="xml">
<search string="Search Case Law">
<field name="short_name"/>
<field name="citation"/>
<field name="holding"/>
<field name="issue_tag_ids"/>
<separator/>
<filter string="3rd DCA (Miami-Dade)" name="3rd_dca"
domain="[('court', '=', '3rd_dca')]"/>
<filter string="FL Supreme Court" name="supreme"
domain="[('court', '=', 'fl_supreme')]"/>
<filter string="Petitioner-Favorable" name="petitioner"
domain="[('favorable_to', '=', 'petitioner')]"/>
<filter string="Not Superseded" name="current"
domain="[('superseded_by_2023_reform', '=', False)]"/>
<group expand="0" string="Group By">
<filter string="Court" name="group_court"
context="{'group_by': 'court'}"/>
<filter string="Issue" name="group_issue"
context="{'group_by': 'issue_tag_ids'}"/>
</group>
</search>
</field>
</record>
<record id="action_fl_caselaw_list" model="ir.actions.act_window">
<field name="name">Case Law Library</field>
<field name="res_model">fl.caselaw</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_fl_caselaw_search"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_fl_child_form" model="ir.ui.view">
<field name="name">fl.child.form</field>
<field name="model">fl.child</field>
<field name="arch" type="xml">
<form string="Child">
<div class="alert alert-warning" role="alert"
attrs="{'invisible': [('approaching_emancipation', '=', False)]}">
<strong>⚠️ APPROACHING EMANCIPATION:</strong>
<field name="name" readonly="1"/> turns 18 in
<field name="days_until_emancipation" readonly="1"/> days
(<field name="emancipation_date" readonly="1"/>).
File Motion to Modify 60 days before emancipation.
</div>
<sheet>
<group>
<group>
<field name="case_id"/>
<field name="name"/>
<field name="date_of_birth"/>
<field name="age" readonly="1"/>
<field name="gender"/>
<field name="school"/>
<field name="medical_provider"/>
</group>
<group string="Emancipation">
<field name="emancipation_date" readonly="1"/>
<field name="days_until_emancipation" readonly="1"/>
<field name="approaching_emancipation" readonly="1"/>
<field name="emancipated" readonly="1"/>
<field name="emancipation_alert_sent"/>
</group>
</group>
<group string="Early Emancipation Factors">
<field name="married"/>
<field name="active_military"/>
<field name="declared_emancipated"/>
<field name="emancipation_notes"/>
</group>
<group string="Support Amounts">
<field name="support_amount"/>
<field name="health_insurance_premium"/>
<field name="childcare_cost"/>
<field name="extraordinary_expenses"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_fl_child_tree" model="ir.ui.view">
<field name="name">fl.child.tree</field>
<field name="model">fl.child</field>
<field name="arch" type="xml">
<tree string="Children"
decoration-warning="approaching_emancipation == True"
decoration-muted="emancipated == True">
<field name="name"/>
<field name="date_of_birth"/>
<field name="age"/>
<field name="emancipation_date"/>
<field name="days_until_emancipation"/>
<field name="approaching_emancipation"/>
<field name="emancipated"/>
<field name="support_amount"/>
</tree>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_fl_deadline_tree" model="ir.ui.view">
<field name="name">fl.deadline.tree</field>
<field name="model">fl.deadline</field>
<field name="arch" type="xml">
<tree string="Deadlines"
decoration-danger="is_overdue == True"
decoration-warning="days_until_due &lt;= 7 and days_until_due &gt;= 0 and completed == False"
decoration-muted="completed == True or waived == True">
<field name="case_id"/>
<field name="name"/>
<field name="deadline_type"/>
<field name="due_date"/>
<field name="days_until_due"/>
<field name="statute_reference"/>
<field name="completed"/>
<field name="waived"/>
<field name="is_overdue" readonly="1"/>
</tree>
</field>
</record>
<record id="view_fl_deadline_form" model="ir.ui.view">
<field name="name">fl.deadline.form</field>
<field name="model">fl.deadline</field>
<field name="arch" type="xml">
<form string="Deadline">
<sheet>
<group>
<group>
<field name="case_id"/>
<field name="name"/>
<field name="deadline_type"/>
<field name="due_date"/>
<field name="statute_reference"/>
</group>
<group>
<field name="anchor_date"/>
<field name="offset_days"/>
<field name="days_until_due" readonly="1"/>
<field name="is_overdue" readonly="1"/>
</group>
</group>
<group>
<field name="completed"/>
<field name="completed_date"
attrs="{'invisible': [('completed', '=', False)]}"/>
<field name="waived"/>
</group>
<field name="notes"/>
</sheet>
</form>
</field>
</record>
<record id="action_fl_deadline_list" model="ir.actions.act_window">
<field name="name">Case Deadlines</field>
<field name="res_model">fl.deadline</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('completed', '=', False), ('waived', '=', False)]</field>
<field name="context">{'search_default_group_case': 1}</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_fl_deposition_tree" model="ir.ui.view">
<field name="name">fl.deposition.tree</field>
<field name="model">fl.deposition</field>
<field name="arch" type="xml">
<tree string="Depositions">
<field name="case_id"/>
<field name="deponent_id"/>
<field name="deponent_type"/>
<field name="notice_date"/>
<field name="scheduled_date"/>
<field name="state"/>
<field name="duces_tecum"/>
<field name="income_verified"/>
</tree>
</field>
</record>
<record id="view_fl_deposition_form" model="ir.ui.view">
<field name="name">fl.deposition.form</field>
<field name="model">fl.deposition</field>
<field name="arch" type="xml">
<form string="Deposition">
<header>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<div class="alert alert-info" role="alert">
<strong>FL 1.310(b):</strong> Minimum <strong>10 days notice</strong>
required before deposition.
FL 1.310(d): Maximum <strong>7 hours</strong> per deponent per day.
</div>
<group>
<group>
<field name="case_id"/>
<field name="deponent_id"/>
<field name="deponent_type"/>
<field name="notice_date"/>
<field name="scheduled_date"/>
</group>
<group>
<field name="location"/>
<field name="max_duration_hours"/>
<field name="duces_tecum"/>
</group>
</group>
<group string="Results">
<field name="income_verified"/>
<field name="income_verified_amount"
attrs="{'invisible': [('income_verified', '=', False)]}"/>
<field name="key_findings"/>
</group>
<field name="notes"/>
</sheet>
</form>
</field>
</record>
<record id="action_fl_deposition_list" model="ir.actions.act_window">
<field name="name">Depositions</field>
<field name="res_model">fl.deposition</field>
<field name="view_mode">tree,form</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_fl_discovery_tree" model="ir.ui.view">
<field name="name">fl.discovery.tree</field>
<field name="model">fl.discovery</field>
<field name="arch" type="xml">
<tree string="Discovery" decoration-danger="state == 'deficient'"
decoration-warning="state == 'served'">
<field name="case_id"/>
<field name="discovery_type"/>
<field name="directed_to"/>
<field name="third_party_id"/>
<field name="served_date"/>
<field name="response_due_date"/>
<field name="response_complete"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="view_fl_discovery_form" model="ir.ui.view">
<field name="name">fl.discovery.form</field>
<field name="model">fl.discovery</field>
<field name="arch" type="xml">
<form string="Discovery Item">
<header>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<group>
<group>
<field name="case_id"/>
<field name="discovery_type"/>
<field name="directed_to"/>
<field name="third_party_id"
attrs="{'invisible': [('directed_to', '!=', 'third_party')]}"/>
<field name="description"/>
</group>
<group>
<field name="served_date"/>
<field name="response_due_date" readonly="1"/>
<field name="response_received_date"/>
<field name="response_complete"/>
</group>
</group>
<group string="Objections / Deficiencies">
<field name="objections_raised"/>
<field name="objection_detail"
attrs="{'invisible': [('objections_raised', '=', False)]}"/>
<field name="deficiency_notice_sent"/>
</group>
<field name="notes"/>
</sheet>
</form>
</field>
</record>
<record id="action_fl_discovery_list" model="ir.actions.act_window">
<field name="name">Discovery</field>
<field name="res_model">fl.discovery</field>
<field name="view_mode">tree,form</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_fl_fee_waiver_form" model="ir.ui.view">
<field name="name">fl.fee.waiver.form</field>
<field name="model">fl.fee.waiver</field>
<field name="arch" type="xml">
<form string="Fee Waiver Application (FL 57.082)">
<header>
<button name="action_submit" string="Submit to Clerk"
type="object" class="oe_highlight"
attrs="{'invisible': [('state', '!=', 'draft')]}"/>
<button name="action_approve" string="Approved"
type="object"
attrs="{'invisible': [('state', '!=', 'submitted')]}"
groups="activeblue_familylaw.group_admin"/>
<button name="action_deny" string="Denied"
type="object"
attrs="{'invisible': [('state', '!=', 'submitted')]}"
groups="activeblue_familylaw.group_admin"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<div class="alert alert-success" role="alert"
attrs="{'invisible': [('eligible', '=', False)]}">
<strong>✅ ELIGIBLE</strong><field name="eligibility_note" readonly="1" nolabel="1"/>
<br/>This applicant may also qualify for a
<strong>free county mediator</strong> under FL 44.108.
</div>
<div class="alert alert-warning" role="alert"
attrs="{'invisible': [('eligible', '=', True)]}">
<field name="eligibility_note" readonly="1" nolabel="1"/>
</div>
<group>
<group>
<field name="case_id"/>
<field name="party_id"/>
<field name="application_date"/>
</group>
<group string="Household &amp; Income">
<field name="household_size"/>
<field name="monthly_gross_income"/>
<field name="annual_gross_income" readonly="1"/>
</group>
</group>
<group string="FPL Threshold (FL 57.082)">
<field name="fpl_annual" readonly="1" string="100% FPL"/>
<field name="fpl_200pct_threshold" readonly="1" string="200% FPL (Threshold)"/>
<field name="eligible" readonly="1"/>
<field name="mediator_fee_waiver_eligible" readonly="1"/>
</group>
<group string="Outcome"
attrs="{'invisible': [('state', 'in', ['draft', 'submitted'])]}">
<field name="approval_date"
attrs="{'invisible': [('state', '!=', 'approved')]}"/>
<field name="denial_reason"
attrs="{'invisible': [('state', '!=', 'denied')]}"/>
</group>
<field name="notes"/>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<record id="view_fl_fee_waiver_tree" model="ir.ui.view">
<field name="name">fl.fee.waiver.tree</field>
<field name="model">fl.fee.waiver</field>
<field name="arch" type="xml">
<tree string="Fee Waivers">
<field name="case_id"/>
<field name="party_id"/>
<field name="household_size"/>
<field name="monthly_gross_income"/>
<field name="fpl_200pct_threshold"/>
<field name="eligible"/>
<field name="state"/>
</tree>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_fl_hearing_tree" model="ir.ui.view">
<field name="name">fl.hearing.tree</field>
<field name="model">fl.hearing</field>
<field name="arch" type="xml">
<tree string="Hearings">
<field name="case_id"/>
<field name="name"/>
<field name="hearing_type"/>
<field name="hearing_date"/>
<field name="location"/>
<field name="courtroom"/>
<field name="state"/>
<field name="order_entered"/>
</tree>
</field>
</record>
<record id="view_fl_hearing_form" model="ir.ui.view">
<field name="name">fl.hearing.form</field>
<field name="model">fl.hearing</field>
<field name="arch" type="xml">
<form string="Hearing">
<header>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<group>
<group>
<field name="case_id"/>
<field name="name"/>
<field name="hearing_type"/>
<field name="hearing_date"/>
</group>
<group>
<field name="location"/>
<field name="courtroom"/>
<field name="judge_id"/>
</group>
</group>
<group string="Outcome">
<field name="order_entered"/>
<field name="outcome"/>
</group>
<field name="notes"/>
</sheet>
</form>
</field>
</record>
<record id="action_fl_hearing_list" model="ir.actions.act_window">
<field name="name">Hearings</field>
<field name="res_model">fl.hearing</field>
<field name="view_mode">tree,form</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_fl_party_form" model="ir.ui.view">
<field name="name">fl.party.form</field>
<field name="model">fl.party</field>
<field name="arch" type="xml">
<form string="Party Details">
<sheet>
<group>
<group>
<field name="case_id"/>
<field name="partner_id"/>
<field name="role"/>
<field name="preferred_language"/>
</group>
<group string="Employment">
<field name="employment_type"/>
<field name="employer_name"/>
<field name="employer_address"/>
<field name="employer_phone"/>
</group>
</group>
<notebook>
<page string="Income (FL 61.30)">
<group>
<group string="Gross Income">
<field name="gross_monthly_income"/>
</group>
<group string="Statutory Deductions (FL 61.30(3))">
<field name="fed_tax_monthly"/>
<field name="fica_ss_monthly" readonly="1"/>
<field name="fica_medicare_monthly" readonly="1"/>
<field name="mandatory_retirement"/>
<field name="mandatory_union_dues"/>
<field name="health_insurance_self"/>
<field name="other_court_ordered_support"/>
</group>
</group>
<group>
<field name="net_monthly_income" readonly="1"/>
<field name="effective_monthly_income" readonly="1"/>
</group>
<separator string="Income Sources"/>
<field name="income_source_ids">
<tree editable="bottom">
<field name="source_type"/>
<field name="description"/>
<field name="monthly_amount"/>
<field name="verified"/>
<field name="verification_document"/>
</tree>
</field>
</page>
<page string="Income Imputation (FL 61.30(2)(b))">
<group>
<field name="income_imputed"/>
<field name="imputed_amount"
attrs="{'invisible': [('income_imputed', '=', False)]}"/>
<field name="fl_minimum_wage_hourly"/>
<field name="fl_minimum_wage_monthly" readonly="1"/>
<field name="imputation_basis"
attrs="{'invisible': [('income_imputed', '=', False)]}"/>
</group>
<separator string="Lifestyle Analysis (Barner v. Barner)"/>
<group>
<field name="lifestyle_inconsistency_flag"/>
<field name="lifestyle_notes"
attrs="{'invisible': [('lifestyle_inconsistency_flag', '=', False)]}"/>
</group>
</page>
<page string="Service &amp; SSN">
<group>
<group string="Service of Process">
<field name="service_address"/>
<field name="service_method"/>
<field name="process_server_id"
attrs="{'invisible': [('service_method', '!=', 'personal')]}"/>
</group>
<group string="SSN (FL-12.930(a))">
<field name="ssn_last4"/>
<field name="ssn_notice_filed"/>
<field name="ssn_handwritten_confirmed"/>
</group>
</group>
<div class="alert alert-warning" role="alert"
attrs="{'invisible': [('service_method', '!=', 'publication')]}">
<strong>⚠️ Service by Publication</strong> is the most complex
service method and requires a diligent search affidavit.
Attorney representation is strongly recommended.
</div>
</page>
<page string="Portal Access">
<group>
<field name="portal_user_id"/>
<field name="portal_access_granted"/>
<field name="portal_invite_sent"/>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="view_fl_party_tree" model="ir.ui.view">
<field name="name">fl.party.tree</field>
<field name="model">fl.party</field>
<field name="arch" type="xml">
<tree string="Parties">
<field name="case_id"/>
<field name="partner_id"/>
<field name="role"/>
<field name="employment_type"/>
<field name="gross_monthly_income"/>
<field name="net_monthly_income"/>
<field name="income_imputed"/>
<field name="lifestyle_inconsistency_flag" string="Lifestyle ⚠"/>
</tree>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_fl_statute_tree" model="ir.ui.view">
<field name="name">fl.statute.tree</field>
<field name="model">fl.statute</field>
<field name="arch" type="xml">
<tree string="FL Statute Index">
<field name="name"/>
<field name="title"/>
<field name="category"/>
<field name="active"/>
</tree>
</field>
</record>
<record id="view_fl_statute_form" model="ir.ui.view">
<field name="name">fl.statute.form</field>
<field name="model">fl.statute</field>
<field name="arch" type="xml">
<form string="FL Statute">
<sheet>
<group>
<group>
<field name="name"/>
<field name="title"/>
<field name="category"/>
<field name="active"/>
</group>
<group>
<field name="url" widget="url"/>
</group>
</group>
<field name="description"/>
</sheet>
</form>
</field>
</record>
<record id="view_fl_issue_tag_tree" model="ir.ui.view">
<field name="name">fl.issue.tag.tree</field>
<field name="model">fl.issue.tag</field>
<field name="arch" type="xml">
<tree string="Issue Tags" editable="bottom">
<field name="name"/>
<field name="name_es"/>
<field name="case_type"/>
<field name="color" widget="color_picker"/>
</tree>
</field>
</record>
<record id="action_fl_statute_list" model="ir.actions.act_window">
<field name="name">FL Statute Index</field>
<field name="res_model">fl.statute</field>
<field name="view_mode">tree,form</field>
</record>
<record id="action_fl_issue_tag_list" model="ir.actions.act_window">
<field name="name">Issue Tags</field>
<field name="res_model">fl.issue.tag</field>
<field name="view_mode">tree,form</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- ══════<E29590><E29590>══════════════════════════════<E29590><E29590>════════════════
FL SUPPORT CALCULATION — FORM
══════════════════════════════════════════════<E29590><E29590><EFBFBD>═══════ -->
<record id="view_fl_support_calc_form" model="ir.ui.view">
<field name="name">fl.support.calculation.form</field>
<field name="model">fl.support.calculation</field>
<field name="arch" type="xml">
<form string="FL 61.30 Child Support Calculation">
<sheet>
<div class="oe_title">
<h1>FL 61.30 Child Support Guidelines Worksheet</h1>
</div>
<group>
<group>
<field name="case_id"/>
<field name="calculation_date"/>
<field name="calculation_type"/>
</group>
<group>
<field name="number_of_children" readonly="1"/>
<field name="notes"/>
</group>
</group>
<separator string="Step 1: Net Monthly Income (FL 61.30(3))"/>
<group>
<group string="Petitioner">
<field name="petitioner_net_income"/>
<field name="petitioner_income_pct" readonly="1" string="Income %"/>
</group>
<group string="Respondent">
<field name="respondent_net_income"/>
<field name="respondent_income_pct" readonly="1" string="Income %"/>
</group>
</group>
<group>
<field name="combined_net_income" readonly="1"/>
</group>
<separator string="Step 2: Basic Support Obligation (Schedule Lookup)"/>
<group>
<field name="basic_support_obligation" readonly="1"/>
<field name="support_schedule_id" readonly="1"/>
<field name="above_schedule" readonly="1"/>
</group>
<separator string="Step 3: Adjustments (FL 61.30(7),(8),(9))"/>
<group>
<group string="Health Insurance (FL 61.30(8))">
<field name="child_health_insurance_total"/>
<field name="health_insurance_by_petitioner"/>
<field name="health_insurance_by_respondent"/>
</group>
<group string="Childcare — Work Related (FL 61.30(7))">
<field name="childcare_total"/>
<field name="childcare_by_petitioner"/>
<field name="childcare_by_respondent"/>
</group>
</group>
<group>
<field name="extraordinary_expenses"/>
<field name="adjusted_support_obligation" readonly="1"/>
</group>
<separator string="Step 4: Timesharing Adjustment (FL 61.30(11)(b))"/>
<group>
<field name="substantial_timesharing" readonly="1"/>
<field name="petitioner_overnights" readonly="1"/>
<field name="respondent_overnights" readonly="1"/>
<field name="timesharing_adjustment" readonly="1"/>
</group>
<separator string="Step 5: Final Obligation"/>
<group>
<field name="total_support_obligation" readonly="1"/>
<field name="petitioner_obligation" readonly="1"/>
<field name="respondent_obligation" readonly="1"/>
<field name="net_payment_amount" readonly="1"/>
<field name="payment_direction" readonly="1"/>
</group>
<separator string="Deviation (FL 61.30(1)(a)) — Optional"/>
<group>
<field name="deviation_requested"/>
<field name="deviation_reason"
attrs="{'invisible': [('deviation_requested', '=', False)]}"/>
<field name="deviation_amount"
attrs="{'invisible': [('deviation_requested', '=', False)]}"/>
<field name="final_amount_with_deviation" readonly="1"
attrs="{'invisible': [('deviation_requested', '=', False)]}"/>
</group>
<separator string="Summary"/>
<field name="calculation_summary" readonly="1" nolabel="1"/>
<div class="alert alert-info" role="alert">
<strong>⚖️ Disclaimer:</strong>
This calculation is generated by computer for informational purposes.
Verify all figures before filing with the court.
FL support schedule values should be verified against the current
Florida Department of Revenue guidelines.
</div>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<!-- ════════════<E29590><E29590>═════════════════════════════════════════
FL SUPPORT CALCULATION — TREE
══════════════════════════════════════════════════════ -->
<record id="view_fl_support_calc_tree" model="ir.ui.view">
<field name="name">fl.support.calculation.tree</field>
<field name="model">fl.support.calculation</field>
<field name="arch" type="xml">
<tree string="Support Calculations">
<field name="case_id"/>
<field name="calculation_date"/>
<field name="calculation_type"/>
<field name="combined_net_income"/>
<field name="basic_support_obligation"/>
<field name="total_support_obligation"/>
<field name="net_payment_amount"/>
<field name="payment_direction"/>
</tree>
</field>
</record>
<!-- ══════════════════════<E29590><E29590><EFBFBD>═══════════════════════════════
FL SUPPORT SCHEDULE ENTRY — TREE (admin view)
═══════════════════════════════════<E29590><E29590><EFBFBD>══════════════════ -->
<record id="view_fl_support_schedule_tree" model="ir.ui.view">
<field name="name">fl.support.schedule.entry.tree</field>
<field name="model">fl.support.schedule.entry</field>
<field name="arch" type="xml">
<tree string="FL Support Schedule">
<field name="income_min"/>
<field name="income_max"/>
<field name="children_count"/>
<field name="obligation_amount"/>
<field name="effective_date"/>
<field name="active"/>
</tree>
</field>
</record>
<!-- ══════════════════<E29590><E29590><EFBFBD>══════════════════════════<E29590><E29590>════════
ACTIONS
══════════════════════════════════════════════════════ -->
<record id="action_fl_support_calc_list" model="ir.actions.act_window">
<field name="name">Support Calculations</field>
<field name="res_model">fl.support.calculation</field>
<field name="view_mode">tree,form</field>
</record>
<record id="action_fl_support_schedule_list" model="ir.actions.act_window">
<field name="name">FL DCF Support Schedule</field>
<field name="res_model">fl.support.schedule.entry</field>
<field name="view_mode">tree,form</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- ══════════════════════════════════════════════════════
TOP-LEVEL APPLICATION MENU
══════════════════════════════════════════════════════ -->
<menuitem
id="menu_familylaw_root"
name="Family Law"
sequence="100"
web_icon="activeblue_familylaw,static/src/img/icon.png"
groups="activeblue_familylaw.group_admin,activeblue_familylaw.group_paralegal"/>
<!-- ══════════════════════════════════════════════════════
MAIN SECTIONS
══════════════════════════════════════════════════════ -->
<menuitem
id="menu_fl_cases"
name="Cases"
parent="menu_familylaw_root"
sequence="10"/>
<menuitem
id="menu_fl_support"
name="Support Calculator"
parent="menu_familylaw_root"
sequence="20"/>
<menuitem
id="menu_fl_caselaw"
name="Case Law Library"
parent="menu_familylaw_root"
sequence="30"/>
<menuitem
id="menu_fl_config"
name="Configuration"
parent="menu_familylaw_root"
sequence="90"
groups="activeblue_familylaw.group_admin"/>
<!-- ══════════════════════════════════════════════════════
CASES SUB-MENU
══════════════════════════════════════════════════════ -->
<menuitem
id="menu_fl_cases_list"
name="All Cases"
parent="menu_fl_cases"
action="action_fl_case_list"
sequence="10"/>
<menuitem
id="menu_fl_case_new"
name="New Case (Intake Wizard)"
parent="menu_fl_cases"
action="action_fl_intake_wizard"
sequence="20"/>
<menuitem
id="menu_fl_deadlines"
name="Deadlines"
parent="menu_fl_cases"
action="action_fl_deadline_list"
sequence="30"/>
<menuitem
id="menu_fl_hearings"
name="Hearings"
parent="menu_fl_cases"
action="action_fl_hearing_list"
sequence="40"/>
<menuitem
id="menu_fl_depositions"
name="Depositions"
parent="menu_fl_cases"
action="action_fl_deposition_list"
sequence="50"/>
<menuitem
id="menu_fl_discovery"
name="Discovery"
parent="menu_fl_cases"
action="action_fl_discovery_list"
sequence="60"/>
<!-- ══════════════════════════════════════════════════════
SUPPORT CALCULATOR SUB-MENU
══════════════════════════════════════════════════════ -->
<menuitem
id="menu_fl_support_calcs"
name="Calculations"
parent="menu_fl_support"
action="action_fl_support_calc_list"
sequence="10"/>
<menuitem
id="menu_fl_support_schedule"
name="FL DCF Schedule"
parent="menu_fl_support"
action="action_fl_support_schedule_list"
sequence="20"
groups="activeblue_familylaw.group_admin"/>
<!-- ══════════════════════════════════════════════════════
CONFIGURATION SUB-MENU
══════════════════════════════════════════════════════ -->
<menuitem
id="menu_fl_statutes"
name="FL Statute Index"
parent="menu_fl_config"
action="action_fl_statute_list"
sequence="10"/>
<menuitem
id="menu_fl_issue_tags"
name="Issue Tags"
parent="menu_fl_config"
action="action_fl_issue_tag_list"
sequence="20"/>
</data>
</odoo>

View File

@@ -0,0 +1,3 @@
from . import fl_intake_wizard
from . import fl_analysis_wizard
from . import fl_generate_packet_wizard

View File

@@ -0,0 +1,30 @@
from odoo import fields, models
class FlAnalysisWizard(models.TransientModel):
"""
Trigger AI analysis on a case.
Phase 7 — full implementation.
Phase 1: Stub.
"""
_name = 'fl.analysis.wizard'
_description = 'Trigger AI Analysis Wizard'
case_id = fields.Many2one(
'fl.case', string='Case', required=True
)
force_reanalysis = fields.Boolean(
string='Force Re-analysis',
help='Run a new analysis even if a recent one exists'
)
def action_run_analysis(self):
self.env['fl.ai.engine'].analyze_case(self.case_id.id)
return {
'type': 'ir.actions.act_window',
'name': 'Case',
'res_model': 'fl.case',
'res_id': self.case_id.id,
'view_mode': 'form',
'target': 'current',
}

View File

@@ -0,0 +1,56 @@
from odoo import fields, models
class FlGeneratePacketWizard(models.TransientModel):
"""
Generate full filing packet for a case.
Phase 7 — full implementation with batch PDF generation.
Phase 1: Stub.
"""
_name = 'fl.generate.packet.wizard'
_description = 'Generate Filing Packet Wizard'
case_id = fields.Many2one(
'fl.case', string='Case', required=True
)
include_financial_affidavit = fields.Boolean(
string='Financial Affidavit', default=True
)
include_support_worksheet = fields.Boolean(
string='Child Support Worksheet (FL-12.902(e))', default=True
)
include_motion_to_modify = fields.Boolean(
string='Motion to Modify Child Support', default=True
)
include_notice_ssn = fields.Boolean(
string='Notice of Social Security Number (FL-12.930(a))', default=True
)
include_mandatory_disclosure = fields.Boolean(
string='Certificate of Mandatory Disclosure (FL-12.932)', default=True
)
include_fee_waiver = fields.Boolean(
string='Fee Waiver Application (if eligible)', default=False
)
language = fields.Selection([
('en', 'English'),
('es', 'Spanish / Español'),
('both', 'Bilingual'),
], string='Document Language', default='en')
def action_generate(self):
"""
Phase 7 — generate all selected documents as PDFs.
Phase 1: Stub — posts a note and returns to case.
"""
self.case_id.message_post(
body='📄 Filing packet generation will be available in Phase 4 (Document Templates).',
subtype_xmlid='mail.mt_note',
)
return {
'type': 'ir.actions.act_window',
'name': 'Case',
'res_model': 'fl.case',
'res_id': self.case_id.id,
'view_mode': 'form',
'target': 'current',
}

View File

@@ -0,0 +1,56 @@
from odoo import api, fields, models
class FlIntakeWizard(models.TransientModel):
"""
Guided case creation wizard.
Phase 7 — full multi-step intake form.
Phase 1: Stub with basic fields.
"""
_name = 'fl.intake.wizard'
_description = 'Family Law Case Intake Wizard'
case_type = fields.Selection([
('modification', 'Child Support Modification'),
('dissolution_children', 'Dissolution of Marriage — With Children'),
('dissolution_no_children', 'Dissolution of Marriage — No Children'),
('paternity', 'Paternity'),
('alimony_modification', 'Alimony Modification'),
('custody_modification', 'Timesharing / Custody Modification'),
], string='Case Type', required=True)
petitioner_id = fields.Many2one(
'res.partner', string='Petitioner', required=True
)
respondent_id = fields.Many2one(
'res.partner', string='Respondent'
)
domestic_violence_flag = fields.Boolean(
string='Is there a history of domestic violence?',
help='Your answer affects mediation and safety procedures'
)
has_minor_children = fields.Boolean(
string='Are there minor children involved?'
)
petitioner_fl_resident_since = fields.Date(
string='When did the petitioner become a FL resident?',
help='FL 61.021: Must be 6 months before filing'
)
def action_create_case(self):
"""Create the fl.case record from intake data."""
case = self.env['fl.case'].create({
'case_type': self.case_type,
'petitioner_id': self.petitioner_id.id,
'respondent_id': self.respondent_id.id if self.respondent_id else False,
'domestic_violence_flag': self.domestic_violence_flag,
'petitioner_fl_resident_since': self.petitioner_fl_resident_since,
})
return {
'type': 'ir.actions.act_window',
'name': 'New Case',
'res_model': 'fl.case',
'res_id': case.id,
'view_mode': 'form',
'target': 'current',
}