import base64 import logging from odoo import fields, models, _ _logger = logging.getLogger(__name__) # Report XML IDs and their binding models # (xml_id, binding_model, label, needs_sub_record) # needs_sub_record: which related record to use as the report target _REPORT_DEFS = { # fl.case bound 'mandatory_disclosure': ( 'activeblue_familylaw.action_report_mandatory_disclosure', 'fl.case', 'Certificate of Mandatory Disclosure (FL-12.932)', None ), 'motion_to_modify': ( 'activeblue_familylaw.action_report_motion_to_modify', 'fl.case', 'Motion to Modify Child Support', None ), 'default_motion': ( 'activeblue_familylaw.action_report_default_motion', 'fl.case', 'Default Judgment Packet (FL 12.922)', None ), 'parenting_plan': ( 'activeblue_familylaw.action_report_parenting_plan', 'fl.case', 'Parenting Plan (FL-12.995(a))', None ), # fl.party bound — petitioner 'financial_affidavit_short': ( 'activeblue_familylaw.action_report_financial_affidavit_short', 'fl.party', 'Financial Affidavit — Short Form (FL-12.902(b))', 'petitioner' ), 'financial_affidavit_long': ( 'activeblue_familylaw.action_report_financial_affidavit_long', 'fl.party', 'Financial Affidavit — Long Form (FL-12.902(c))', 'petitioner' ), 'notice_ssn': ( 'activeblue_familylaw.action_report_notice_ssn', 'fl.party', 'Notice of Social Security Number (FL-12.930(a))', 'petitioner' ), # fl.support.calculation bound 'support_worksheet': ( 'activeblue_familylaw.action_report_child_support_worksheet', 'fl.support.calculation', 'Child Support Worksheet (FL-12.902(e))', 'support_calc' ), # fl.fee.waiver bound 'fee_waiver': ( 'activeblue_familylaw.action_report_fee_waiver', 'fl.fee.waiver', 'Fee Waiver Application (FL 57.082)', 'fee_waiver' ), # fl.income.withholding bound 'income_withholding': ( 'activeblue_familylaw.action_report_income_withholding', 'fl.income.withholding', 'Income Withholding Order (FL 61.1301)', 'income_withholding' ), } class FlGeneratePacketWizard(models.TransientModel): """ Phase 7 — Generate a filing packet of selected PDF reports for a case. Renders each selected report, creates ir.attachment records on the case, posts a chatter summary, and returns to the case form. """ _name = 'fl.generate.packet.wizard' _description = 'Generate Filing Packet Wizard' case_id = fields.Many2one( 'fl.case', string='Case', required=True, default=lambda self: self.env.context.get('active_id'), ) # ── Document selection checkboxes ──────────────────────────────────────── include_mandatory_disclosure = fields.Boolean( string='Certificate of Mandatory Disclosure (FL-12.932)', default=True ) include_financial_affidavit_short = fields.Boolean( string='Financial Affidavit — Short Form (FL-12.902(b))', default=False ) include_financial_affidavit_long = fields.Boolean( string='Financial Affidavit — Long Form (FL-12.902(c))', 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_fee_waiver = fields.Boolean( string='Fee Waiver Application (FL 57.082)', default=False ) include_income_withholding = fields.Boolean( string='Income Withholding Order (FL 61.1301)', default=False ) include_default_motion = fields.Boolean( string='Default Judgment Packet (FL 12.922)', default=False ) include_parenting_plan = fields.Boolean( string='Parenting Plan (FL-12.995(a))', default=False ) # ── Options ────────────────────────────────────────────────────────────── attach_to_case = fields.Boolean( string='Attach generated PDFs to case chatter', default=True ) # ── Helpers ────────────────────────────────────────────────────────────── def _get_selected_keys(self): """Return list of _REPORT_DEFS keys that the user has selected.""" mapping = { 'mandatory_disclosure': self.include_mandatory_disclosure, 'financial_affidavit_short': self.include_financial_affidavit_short, 'financial_affidavit_long': self.include_financial_affidavit_long, 'support_worksheet': self.include_support_worksheet, 'motion_to_modify': self.include_motion_to_modify, 'notice_ssn': self.include_notice_ssn, 'fee_waiver': self.include_fee_waiver, 'income_withholding': self.include_income_withholding, 'default_motion': self.include_default_motion, 'parenting_plan': self.include_parenting_plan, } return [k for k, selected in mapping.items() if selected] def _resolve_record_id(self, key, case): """ Return the (record_id, model) tuple to pass to _render_qweb_pdf. Returns (None, None) if the required sub-record doesn't exist. """ xml_id, model, label, sub = _REPORT_DEFS[key] if sub is None: return case.id, model if sub == 'petitioner': if not case.petitioner_id: _logger.warning('Packet: no petitioner_id on case %s', case.id) return None, None return case.petitioner_id.id, model if sub == 'support_calc': calc = case.support_calc_ids[:1] if case.support_calc_ids else None if not calc: _logger.warning('Packet: no support calculation on case %s', case.id) return None, None return calc.id, model if sub == 'fee_waiver': fwv = case.fee_waiver_id if not fwv: _logger.warning('Packet: no fee_waiver_id on case %s', case.id) return None, None return fwv.id, model if sub == 'income_withholding': iwo = self.env['fl.income.withholding'].search( [('case_id', '=', case.id)], limit=1 ) if not iwo: _logger.warning('Packet: no income withholding on case %s', case.id) return None, None return iwo.id, model return None, None # ── Main action ────────────────────────────────────────────────────────── def action_generate(self): """ Render each selected report PDF, create attachments on the case, post a chatter summary, and return to the case form. """ self.ensure_one() case = self.case_id selected_keys = self._get_selected_keys() if not selected_keys: return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': _('Nothing selected'), 'message': _('Please select at least one document to generate.'), 'type': 'warning', 'sticky': False, }, } generated = [] failed = [] attachment_ids = [] for key in selected_keys: xml_id, model, label, sub = _REPORT_DEFS[key] record_id, binding_model = self._resolve_record_id(key, case) if record_id is None: failed.append(f'{label} — required record not found on case') continue try: report_action = self.env.ref(xml_id) pdf_content, _mime = report_action._render_qweb_pdf([record_id]) safe_name = label.replace('/', '-').replace(':', '').strip() filename = f'{case.name} — {safe_name}.pdf' attachment = self.env['ir.attachment'].create({ 'name': filename, 'type': 'binary', 'datas': base64.b64encode(pdf_content), 'res_model': 'fl.case', 'res_id': case.id, 'mimetype': 'application/pdf', }) attachment_ids.append(attachment.id) generated.append(label) _logger.info('Packet: generated %s for case %s', label, case.id) except Exception as e: _logger.error( 'Packet: failed to generate %s for case %s: %s', label, case.id, e ) failed.append(f'{label} — {e}') # Post chatter summary if self.attach_to_case and (generated or failed): lines = [] if generated: lines.append('✅ Documents generated: