Add FL e-Filing Portal integration (assisted submission, Phase 1)

- fl.efiling.submission: generates the 11th Circuit court filename
  ({LastName}_{CaseNumber}_{DocumentType}_{YYYYMMDD}.pdf), validates PDF/A via
  pikepdf (XMP pdfaid + OutputIntents, graceful if pikepdf missing), and tracks
  status (draft → validated → pending_manual → submitted → accepted/rejected/failed)
- Assisted flow: "Open e-Filing Portal" deep-links to eportal.flcourts.org
  (?caseNumber=… when available; base overridable via ir.config_parameter
  fl_efiling.portal_url); confirmation # capture; accepted/rejected mark the
  linked fl.document filed and log to chatter
- Phase 2 API stub (action_submit_api) reads creds/endpoint from ir.config_parameter
  and refuses to call an unconfirmed endpoint — no guessed URLs, no silent failure
- fl.efiling.wizard: pick document/attachment/filing_type, preview the filename,
  create + auto-validate the submission
- Wiring: model + wizard registered, ACL (admin/paralegal), e-filing views, Cases
  menu item, fl.case.efiling_submission_ids + Filings tab + Prepare e-Filing button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 00:42:28 +00:00
parent 70c951a7ef
commit 6f6129550e
10 changed files with 589 additions and 1 deletions

View File

@@ -58,6 +58,7 @@
'views/fl_discovery_suggest_views.xml',
'views/fl_conflict_check_views.xml',
'views/fl_timesheet_views.xml',
'views/fl_efiling_views.xml',
'views/menu_views.xml',
# Phase 4 — QWeb PDF Reports
'report/report_financial_affidavit_short.xml',

View File

@@ -19,3 +19,4 @@ from . import fl_conflict_check
from . import fl_paralegal_agent
from . import fl_attorney_agent
from . import fl_timesheet
from . import fl_efiling

View File

@@ -400,6 +400,9 @@ class FlCase(models.Model):
document_ids = fields.One2many(
'fl.document', 'case_id', string='Case Documents'
)
efiling_submission_ids = fields.One2many(
'fl.efiling.submission', 'case_id', string='e-Filing Submissions'
)
# ══════════════════════════════════════════════════════════════════════
# PROJECT / TASK INTEGRATION
@@ -1222,3 +1225,14 @@ class FlCase(models.Model):
'view_mode': 'form',
'target': 'current',
}
def action_prepare_efiling(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': 'Prepare e-Filing',
'res_model': 'fl.efiling.wizard',
'view_mode': 'form',
'target': 'new',
'context': {'default_case_id': self.id},
}

View File

@@ -0,0 +1,280 @@
import base64
import io
import logging
import re
from urllib.parse import quote
from odoo import _, api, fields, models
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
DEFAULT_PORTAL_URL = 'https://eportal.flcourts.org'
# filing_type → CamelCase token used in the 11th Circuit filename convention.
FILING_TYPE_TOKEN = {
'petition': 'Petition',
'supplemental_petition': 'SupplementalPetition',
'financial_affidavit': 'FinancialAffidavit',
'support_worksheet': 'ChildSupportWorksheet',
'motion_to_modify': 'MotionToModify',
'motion_to_compel': 'MotionToCompel',
'motion_default': 'MotionForDefault',
'notice_deposition': 'NoticeOfDeposition',
'parenting_plan': 'ParentingPlan',
'mandatory_disclosure': 'MandatoryDisclosure',
'fee_waiver': 'CivilIndigentStatus',
'income_withholding': 'IncomeWithholding',
'notice_ssn': 'NoticeOfSSN',
'other': 'Document',
}
# Best-effort map from fl.document.document_type → filing_type.
DOC_TYPE_TO_FILING_TYPE = {
'financial_affidavit_short': 'financial_affidavit',
'financial_affidavit_long': 'financial_affidavit',
'support_worksheet': 'support_worksheet',
'motion_to_modify': 'motion_to_modify',
'notice_deposition': 'notice_deposition',
'motion_to_compel': 'motion_to_compel',
'motion_default': 'motion_default',
'income_withholding': 'income_withholding',
'parenting_plan': 'parenting_plan',
'fee_waiver': 'fee_waiver',
'notice_ssn': 'notice_ssn',
'mandatory_disclosure': 'mandatory_disclosure',
}
class FlEfilingSubmission(models.Model):
_name = 'fl.efiling.submission'
_description = 'FL e-Filing Portal Submission'
_inherit = ['mail.thread']
_order = 'create_date desc'
_rec_name = 'court_filename'
case_id = fields.Many2one(
'fl.case', string='Case', required=True,
ondelete='cascade', index=True, tracking=True
)
document_id = fields.Many2one(
'fl.document', string='Case Document',
domain="[('case_id', '=', case_id)]"
)
attachment_id = fields.Many2one(
'ir.attachment', string='PDF to File',
help='The PDF that will be filed (use the signed PDF where applicable).'
)
filing_type = fields.Selection([
('petition', 'Petition'),
('supplemental_petition', 'Supplemental Petition'),
('financial_affidavit', 'Financial Affidavit'),
('support_worksheet', 'Child Support Worksheet'),
('motion_to_modify', 'Motion to Modify'),
('motion_to_compel', 'Motion to Compel'),
('motion_default', 'Motion for Default'),
('notice_deposition', 'Notice of Deposition'),
('parenting_plan', 'Parenting Plan'),
('mandatory_disclosure', 'Mandatory Disclosure'),
('fee_waiver', 'Civil Indigent Status'),
('income_withholding', 'Income Withholding'),
('notice_ssn', 'Notice of SSN'),
('other', 'Other'),
], string='Filing Type', default='other', required=True)
filing_date = fields.Date(
string='Filing Date', default=fields.Date.context_today
)
court_filename = fields.Char(
string='Court Filename', compute='_compute_court_filename', store=True,
help='11th Circuit naming convention: '
'{LastName}_{CaseNumber}_{DocumentType}_{YYYYMMDD}.pdf'
)
status = fields.Selection([
('draft', 'Draft'),
('validated', 'PDF/A Validated'),
('pending_manual', 'Pending Manual Filing'),
('submitted', 'Submitted'),
('accepted', 'Accepted by Clerk'),
('rejected', 'Rejected by Clerk'),
('failed', 'Failed'),
], string='Status', default='draft', required=True, tracking=True)
pdfa_valid = fields.Boolean(string='PDF/A Valid', readonly=True)
pdfa_message = fields.Char(string='PDF/A Check Result', readonly=True)
confirmation_number = fields.Char(string='Portal Confirmation #', tracking=True)
error_message = fields.Text(string='Error', readonly=True)
portal_url = fields.Char(string='Portal Link', compute='_compute_portal_url')
notes = fields.Text(string='Notes')
# ──────────────────────────────────────────────────────────────────────
# Computes
# ──────────────────────────────────────────────────────────────────────
@api.depends('case_id', 'case_id.court_case_number',
'case_id.petitioner_id', 'filing_type', 'filing_date')
def _compute_court_filename(self):
for rec in self:
case = rec.case_id
last = self._sanitize(self._last_name(case.petitioner_id)) or 'Party'
casenum = self._sanitize(case.court_case_number or 'NOCASE')
token = FILING_TYPE_TOKEN.get(rec.filing_type, 'Document')
datestr = (rec.filing_date or fields.Date.context_today(rec)).strftime('%Y%m%d')
rec.court_filename = f'{last}_{casenum}_{token}_{datestr}.pdf'
@api.depends('case_id', 'case_id.court_case_number')
def _compute_portal_url(self):
base = self.env['ir.config_parameter'].sudo().get_param(
'fl_efiling.portal_url', DEFAULT_PORTAL_URL)
for rec in self:
url = base
if rec.case_id.court_case_number:
url += '?caseNumber=' + quote(rec.case_id.court_case_number)
rec.portal_url = url
@staticmethod
def _last_name(partner):
if not partner or not partner.name:
return ''
return partner.name.strip().split()[-1]
@staticmethod
def _sanitize(value):
# Keep alphanumerics and dashes; drop everything else.
return re.sub(r'[^A-Za-z0-9-]', '', (value or '').replace(' ', ''))
# ──────────────────────────────────────────────────────────────────────
# Onchange
# ──────────────────────────────────────────────────────────────────────
@api.onchange('document_id')
def _onchange_document_id(self):
if not self.document_id:
return
self.filing_type = DOC_TYPE_TO_FILING_TYPE.get(
self.document_id.document_type, 'other')
if self.document_id.attachment_ids:
self.attachment_id = self.document_id.attachment_ids[:1]
# ──────────────────────────────────────────────────────────────────────
# PDF/A validation (pikepdf)
# ──────────────────────────────────────────────────────────────────────
def action_validate_pdfa(self):
self.ensure_one()
if not self.attachment_id:
raise UserError(_('Attach the PDF to file before validating.'))
valid, message = self._check_pdfa(self.attachment_id)
self.write({'pdfa_valid': valid, 'pdfa_message': message})
if valid and self.status == 'draft':
self.status = 'validated'
return True
def _check_pdfa(self, attachment):
"""Pragmatic PDF/A check via pikepdf (XMP pdfaid markers + OutputIntents)."""
try:
import pikepdf
except ImportError:
return False, 'pikepdf not installed — cannot validate PDF/A.'
if not attachment.datas:
return False, 'Attachment has no file data.'
try:
data = base64.b64decode(attachment.datas)
pdf = pikepdf.open(io.BytesIO(data))
except Exception as exc:
return False, f'Not a readable PDF: {exc}'
has_output_intent = '/OutputIntents' in pdf.Root
pdfa_part = None
try:
with pdf.open_metadata() as meta:
pdfa_part = meta.get('pdfaid:part')
except Exception:
pdfa_part = None
if pdfa_part:
suffix = (' and an OutputIntent' if has_output_intent
else ' — WARNING: no OutputIntents present')
return True, f'PDF/A-{pdfa_part} markers present{suffix}.'
return False, ('No PDF/A identification (pdfaid) found in the PDF metadata. '
'Convert the document to PDF/A before filing.')
# ──────────────────────────────────────────────────────────────────────
# Assisted submission (Phase 1)
# ──────────────────────────────────────────────────────────────────────
def action_open_portal(self):
self.ensure_one()
if self.status in ('draft', 'validated'):
self.status = 'pending_manual'
return {
'type': 'ir.actions.act_url',
'url': self.portal_url,
'target': 'new',
}
def action_record_confirmation(self):
"""Mark as submitted once the user has filed and entered a confirmation #."""
self.ensure_one()
if not (self.confirmation_number or '').strip():
raise UserError(_(
'Enter the portal confirmation number before marking submitted.'))
self.status = 'submitted'
self.case_id.message_post(
body=_('📤 e-Filing submitted: <b>%(file)s</b> — confirmation '
'<b>%(conf)s</b>.') % {
'file': self.court_filename,
'conf': self.confirmation_number,
},
subtype_xmlid='mail.mt_note',
)
return True
def action_mark_accepted(self):
self.ensure_one()
self.status = 'accepted'
if self.document_id:
self.document_id.write({
'state': 'filed',
'filed_date': fields.Date.context_today(self),
})
self.case_id.message_post(
body=_('✅ Clerk accepted filing: <b>%s</b>.') % self.court_filename,
subtype_xmlid='mail.mt_note',
)
return True
def action_mark_rejected(self):
self.ensure_one()
self.status = 'rejected'
self.case_id.message_post(
body=_('❌ Clerk rejected filing: <b>%s</b>. See notes.')
% self.court_filename,
subtype_xmlid='mail.mt_note',
)
return True
# ──────────────────────────────────────────────────────────────────────
# API submission (Phase 2 — not enabled against a confirmed endpoint)
# ──────────────────────────────────────────────────────────────────────
def action_submit_api(self):
"""
Programmatic submission to the FL e-Filing Portal API. The portal's API
base URL must be confirmed from current portal documentation before this
is enabled; we never call a guessed endpoint. Credentials live in
ir.config_parameter (fl_efiling.username / fl_efiling.password).
"""
self.ensure_one()
endpoint = self.env['ir.config_parameter'].sudo().get_param(
'fl_efiling.api_endpoint')
if not endpoint:
raise UserError(_(
'Automated API submission is not configured. Confirm the FL '
'e-Filing Portal API base URL, then set ir.config_parameter '
'fl_efiling.api_endpoint (plus fl_efiling.username / '
'fl_efiling.password). Until then use "Open e-Filing Portal" '
'for assisted manual submission.'))
raise UserError(_(
'An API endpoint is configured but Phase 2 programmatic submission '
'is not yet enabled in this build.'))

View File

@@ -87,6 +87,12 @@ access_fl_timesheet_paralegal,fl.timesheet paralegal,model_fl_timesheet,group_pa
# ── account.analytic.line (timesheet wraps it — ensure non-admins can post) ───
access_account_analytic_line_fl_admin,account.analytic.line fl admin,analytic.model_account_analytic_line,group_admin,1,1,1,1
access_account_analytic_line_fl_paralegal,account.analytic.line fl paralegal,analytic.model_account_analytic_line,group_paralegal,1,1,1,0
# ── fl.efiling.submission ────────────────────────────────────────────────────
access_fl_efiling_submission_admin,fl.efiling.submission admin,model_fl_efiling_submission,group_admin,1,1,1,1
access_fl_efiling_submission_paralegal,fl.efiling.submission paralegal,model_fl_efiling_submission,group_paralegal,1,1,1,0
# ── fl.efiling.wizard ────────────────────────────────────────────────────────
access_fl_efiling_wizard_admin,fl.efiling.wizard admin,model_fl_efiling_wizard,group_admin,1,1,1,1
access_fl_efiling_wizard_paralegal,fl.efiling.wizard paralegal,model_fl_efiling_wizard,group_paralegal,1,1,1,1
# ── 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
1 id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
87 # ── account.analytic.line (timesheet wraps it — ensure non-admins can post) ───
88 access_account_analytic_line_fl_admin,account.analytic.line fl admin,analytic.model_account_analytic_line,group_admin,1,1,1,1
89 access_account_analytic_line_fl_paralegal,account.analytic.line fl paralegal,analytic.model_account_analytic_line,group_paralegal,1,1,1,0
90 # ── fl.efiling.submission ────────────────────────────────────────────────────
91 access_fl_efiling_submission_admin,fl.efiling.submission admin,model_fl_efiling_submission,group_admin,1,1,1,1
92 access_fl_efiling_submission_paralegal,fl.efiling.submission paralegal,model_fl_efiling_submission,group_paralegal,1,1,1,0
93 # ── fl.efiling.wizard ────────────────────────────────────────────────────────
94 access_fl_efiling_wizard_admin,fl.efiling.wizard admin,model_fl_efiling_wizard,group_admin,1,1,1,1
95 access_fl_efiling_wizard_paralegal,fl.efiling.wizard paralegal,model_fl_efiling_wizard,group_paralegal,1,1,1,1
96 # ── fl.intake.wizard ─────────────────────────────────────────────────────────
97 access_fl_intake_wizard_admin,fl.intake.wizard admin,model_fl_intake_wizard,group_admin,1,1,1,1
98 access_fl_intake_wizard_paralegal,fl.intake.wizard paralegal,model_fl_intake_wizard,group_paralegal,1,1,1,1

View File

@@ -24,6 +24,9 @@
<button name="action_run_paralegal" string="Paralegal Review"
type="object"
groups="activeblue_familylaw.group_admin,activeblue_familylaw.group_paralegal"/>
<button name="action_prepare_efiling" string="Prepare e-Filing"
type="object"
groups="activeblue_familylaw.group_admin,activeblue_familylaw.group_paralegal"/>
</header>
<!-- Attorney Referral Banner -->
@@ -310,7 +313,37 @@
<field name="total_expenses" readonly="1"/>
</page>
<!-- TAB 9: Time & Billing -->
<!-- TAB 9: Court Filings -->
<page string="Filings" name="filings">
<button name="action_prepare_efiling" string="Prepare e-Filing"
type="object" class="btn-primary"
groups="activeblue_familylaw.group_admin,activeblue_familylaw.group_paralegal"/>
<separator string="Case Documents"/>
<field name="document_ids">
<tree string="Documents">
<field name="name"/>
<field name="document_type"/>
<field name="state"/>
<field name="filed_date"/>
</tree>
</field>
<separator string="e-Filing Submissions"/>
<field name="efiling_submission_ids">
<tree string="e-Filing Submissions"
decoration-success="status == 'accepted'"
decoration-danger="status in ('rejected','failed')"
decoration-info="status == 'submitted'">
<field name="court_filename"/>
<field name="filing_type"/>
<field name="filing_date"/>
<field name="pdfa_valid"/>
<field name="confirmation_number"/>
<field name="status"/>
</tree>
</field>
</page>
<!-- TAB 10: Time & Billing -->
<page string="Time &amp; Billing" name="timesheets">
<group>
<group>

View File

@@ -0,0 +1,163 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- ══════════════════════════════════════════════════════
SUBMISSION FORM
══════════════════════════════════════════════════════ -->
<record id="view_fl_efiling_submission_form" model="ir.ui.view">
<field name="name">fl.efiling.submission.form</field>
<field name="model">fl.efiling.submission</field>
<field name="arch" type="xml">
<form string="e-Filing Submission">
<header>
<button name="action_validate_pdfa" string="Validate PDF/A"
type="object" class="oe_highlight"
attrs="{'invisible': [('status', 'not in', ['draft','validated'])]}"/>
<button name="action_open_portal" string="Open e-Filing Portal"
type="object"
attrs="{'invisible': [('status', 'in', ['accepted','rejected'])]}"/>
<button name="action_record_confirmation" string="Mark Submitted"
type="object"
attrs="{'invisible': [('status', 'not in', ['pending_manual','validated','failed'])]}"/>
<button name="action_mark_accepted" string="Clerk Accepted"
type="object" class="oe_highlight"
attrs="{'invisible': [('status', '!=', 'submitted')]}"/>
<button name="action_mark_rejected" string="Clerk Rejected"
type="object"
attrs="{'invisible': [('status', '!=', 'submitted')]}"/>
<button name="action_submit_api" string="Submit via API"
type="object"
groups="activeblue_familylaw.group_admin"
attrs="{'invisible': [('status', 'in', ['accepted','rejected','submitted'])]}"/>
<field name="status" widget="statusbar"
statusbar_visible="draft,validated,pending_manual,submitted,accepted"/>
</header>
<sheet>
<div class="alert alert-success" role="alert"
attrs="{'invisible': [('pdfa_valid', '=', False)]}">
<strong>✅ PDF/A:</strong>
<field name="pdfa_message" readonly="1" nolabel="1" class="oe_inline"/>
</div>
<div class="alert alert-warning" role="alert"
attrs="{'invisible': ['|', ('pdfa_valid', '=', True), ('pdfa_message', '=', False)]}">
<strong>⚠️ PDF/A:</strong>
<field name="pdfa_message" readonly="1" nolabel="1" class="oe_inline"/>
</div>
<div class="oe_title">
<h1><field name="court_filename" readonly="1"/></h1>
</div>
<group>
<group>
<field name="case_id"/>
<field name="document_id"/>
<field name="attachment_id"/>
<field name="filing_type"/>
<field name="filing_date"/>
</group>
<group>
<field name="pdfa_valid" readonly="1"/>
<field name="confirmation_number"/>
<field name="portal_url" widget="url" readonly="1"/>
</group>
</group>
<field name="error_message" readonly="1"
attrs="{'invisible': [('error_message', '=', False)]}"/>
<separator string="Notes"/>
<field name="notes" nolabel="1"
placeholder="Clerk rejection reasons, filing remarks, etc."/>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<!-- ══════════════════════════════════════════════════════
SUBMISSION TREE / SEARCH
══════════════════════════════════════════════════════ -->
<record id="view_fl_efiling_submission_tree" model="ir.ui.view">
<field name="name">fl.efiling.submission.tree</field>
<field name="model">fl.efiling.submission</field>
<field name="arch" type="xml">
<tree string="e-Filing Submissions"
decoration-success="status == 'accepted'"
decoration-danger="status in ('rejected','failed')"
decoration-info="status == 'submitted'">
<field name="court_filename"/>
<field name="case_id"/>
<field name="filing_type"/>
<field name="filing_date"/>
<field name="pdfa_valid"/>
<field name="confirmation_number"/>
<field name="status"/>
</tree>
</field>
</record>
<record id="view_fl_efiling_submission_search" model="ir.ui.view">
<field name="name">fl.efiling.submission.search</field>
<field name="model">fl.efiling.submission</field>
<field name="arch" type="xml">
<search string="Search e-Filings">
<field name="case_id"/>
<field name="court_filename"/>
<field name="confirmation_number"/>
<filter string="Pending Manual" name="pending"
domain="[('status', '=', 'pending_manual')]"/>
<filter string="Submitted" name="submitted"
domain="[('status', '=', 'submitted')]"/>
<filter string="Accepted" name="accepted"
domain="[('status', '=', 'accepted')]"/>
<filter string="Rejected / Failed" name="problem"
domain="[('status', 'in', ['rejected','failed'])]"/>
<group expand="0" string="Group By">
<filter string="Status" name="group_status"
context="{'group_by': 'status'}"/>
<filter string="Case" name="group_case"
context="{'group_by': 'case_id'}"/>
</group>
</search>
</field>
</record>
<record id="action_fl_efiling_list" model="ir.actions.act_window">
<field name="name">e-Filings</field>
<field name="res_model">fl.efiling.submission</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_fl_efiling_submission_search"/>
</record>
<!-- ══════════════════════════════════════════════════════
WIZARD
══════════════════════════════════════════════════════ -->
<record id="view_fl_efiling_wizard_form" model="ir.ui.view">
<field name="name">fl.efiling.wizard.form</field>
<field name="model">fl.efiling.wizard</field>
<field name="arch" type="xml">
<form string="Prepare e-Filing">
<p class="text-muted">
Select the document and PDF to file. A court-compliant filename
will be generated; the submission record lets you validate PDF/A
and open the FL e-Filing Portal.
</p>
<group>
<field name="case_id" invisible="1"/>
<field name="document_id"/>
<field name="attachment_id"/>
<field name="filing_type"/>
<field name="court_filename_preview" readonly="1"/>
</group>
<footer>
<button name="action_create_submission" string="Create &amp; Prepare"
type="object" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</data>
</odoo>

View File

@@ -101,6 +101,13 @@
action="action_fl_timesheet_list"
sequence="80"/>
<menuitem
id="menu_fl_efilings"
name="e-Filings"
parent="menu_fl_cases"
action="action_fl_efiling_list"
sequence="90"/>
<!-- ══════════════════════════════════════════════════════
SUPPORT CALCULATOR SUB-MENU
══════════════════════════════════════════════════════ -->

View File

@@ -2,3 +2,4 @@ from . import fl_intake_wizard
from . import fl_analysis_wizard
from . import fl_generate_packet_wizard
from . import fl_discovery_suggest_wizard
from . import fl_efiling_wizard

View File

@@ -0,0 +1,82 @@
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.addons.activeblue_familylaw.models.fl_efiling import (
DOC_TYPE_TO_FILING_TYPE, FILING_TYPE_TOKEN,
)
class FlEfilingWizard(models.TransientModel):
_name = 'fl.efiling.wizard'
_description = 'Assisted e-Filing Preparation Wizard'
case_id = fields.Many2one('fl.case', string='Case', required=True)
document_id = fields.Many2one(
'fl.document', string='Case Document',
domain="[('case_id', '=', case_id)]"
)
attachment_id = fields.Many2one(
'ir.attachment', string='PDF to File',
help='The PDF to file — use the signed PDF where applicable.'
)
filing_type = fields.Selection([
('petition', 'Petition'),
('supplemental_petition', 'Supplemental Petition'),
('financial_affidavit', 'Financial Affidavit'),
('support_worksheet', 'Child Support Worksheet'),
('motion_to_modify', 'Motion to Modify'),
('motion_to_compel', 'Motion to Compel'),
('motion_default', 'Motion for Default'),
('notice_deposition', 'Notice of Deposition'),
('parenting_plan', 'Parenting Plan'),
('mandatory_disclosure', 'Mandatory Disclosure'),
('fee_waiver', 'Civil Indigent Status'),
('income_withholding', 'Income Withholding'),
('notice_ssn', 'Notice of SSN'),
('other', 'Other'),
], string='Filing Type', default='other', required=True)
court_filename_preview = fields.Char(
string='Court Filename', compute='_compute_filename_preview'
)
@api.depends('case_id', 'filing_type')
def _compute_filename_preview(self):
Submission = self.env['fl.efiling.submission']
for rec in self:
case = rec.case_id
last = Submission._sanitize(
Submission._last_name(case.petitioner_id)) or 'Party'
casenum = Submission._sanitize(case.court_case_number or 'NOCASE')
token = FILING_TYPE_TOKEN.get(rec.filing_type, 'Document')
datestr = fields.Date.context_today(rec).strftime('%Y%m%d')
rec.court_filename_preview = f'{last}_{casenum}_{token}_{datestr}.pdf'
@api.onchange('document_id')
def _onchange_document_id(self):
if not self.document_id:
return
self.filing_type = DOC_TYPE_TO_FILING_TYPE.get(
self.document_id.document_type, 'other')
if self.document_id.attachment_ids:
self.attachment_id = self.document_id.attachment_ids[:1]
def action_create_submission(self):
self.ensure_one()
if not self.attachment_id:
raise UserError(_(
'Select the PDF to file before creating the submission.'))
submission = self.env['fl.efiling.submission'].create({
'case_id': self.case_id.id,
'document_id': self.document_id.id,
'attachment_id': self.attachment_id.id,
'filing_type': self.filing_type,
})
submission.action_validate_pdfa()
return {
'type': 'ir.actions.act_window',
'name': 'e-Filing Submission',
'res_model': 'fl.efiling.submission',
'res_id': submission.id,
'view_mode': 'form',
'target': 'current',
}