mirror of
https://github.com/bringout/oca-ocb-accounting.git
synced 2026-04-18 01:42:00 +02:00
fix PyPDF2 3.x page copying + rename documentation
- Added explicit page copying after cloneReaderDocumentRoot() calls
- Renamed PATCH_PDFWRITER.md to PATCH_PYPDF2_PDFWRITER.md
- Prevents 327-byte empty PDFs in PyPDF2 3.x
🤖 assisted by claude
This commit is contained in:
parent
31702e7006
commit
ae4fa097e7
3 changed files with 129 additions and 8 deletions
|
|
@ -91,6 +91,10 @@ class IrActionsReport(models.Model):
|
|||
# Post-process and embed the additional files.
|
||||
writer = OdooPdfFileWriter()
|
||||
writer.cloneReaderDocumentRoot(reader)
|
||||
# Copy all pages from the reader to the writer (required for PyPDF2 3.x)
|
||||
for page_num in range(reader.getNumPages()):
|
||||
page = reader.getPage(page_num)
|
||||
writer.addPage(page)
|
||||
|
||||
# Generate and embed Factur-X
|
||||
xml_content, _errors = self.env['account.edi.xml.cii']._export_invoice(invoice)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
from odoo.tools import cleanup_xml_node
|
||||
from odoo.tools.pdf import OdooPdfFileReader, OdooPdfFileWriter
|
||||
|
||||
from lxml import etree
|
||||
import base64
|
||||
from xml.sax.saxutils import escape, quoteattr
|
||||
import io
|
||||
|
||||
|
||||
class IrActionsReport(models.Model):
|
||||
_inherit = 'ir.actions.report'
|
||||
|
||||
def _is_invoice_report(self, report_ref):
|
||||
# EXTENDS account
|
||||
# allows to add factur-x.xml to custom PDF templates (comma separated list of template names)
|
||||
custom_templates = self.env['ir.config_parameter'].sudo().get_param('account.custom_templates_facturx_list', '')
|
||||
custom_templates = [report.strip() for report in custom_templates.split(',')]
|
||||
return super()._is_invoice_report(report_ref) or self._get_report(report_ref).report_name in custom_templates
|
||||
|
||||
def _add_pdf_into_invoice_xml(self, invoice, stream_data):
|
||||
format_codes = ['ubl_bis3', 'ubl_de', 'nlcius_1', 'efff_1']
|
||||
edi_attachments = invoice.edi_document_ids.filtered(lambda d: d.edi_format_id.code in format_codes).sudo().attachment_id
|
||||
for edi_attachment in edi_attachments:
|
||||
old_xml = base64.b64decode(edi_attachment.with_context(bin_size=False).datas, validate=True)
|
||||
tree = etree.fromstring(old_xml)
|
||||
anchor_elements = tree.xpath("//*[local-name()='AccountingSupplierParty']")
|
||||
additional_document_elements = tree.xpath("//*[local-name()='AdditionalDocumentReference']")
|
||||
# with this clause, we ensure the xml are only postprocessed once (even when the invoice is reset to
|
||||
# draft then validated again)
|
||||
if anchor_elements and not additional_document_elements:
|
||||
pdf_stream = stream_data['stream']
|
||||
pdf_content_b64 = base64.b64encode(pdf_stream.getvalue()).decode()
|
||||
pdf_name = '%s.pdf' % invoice.name.replace('/', '_')
|
||||
to_inject = '''
|
||||
<cac:AdditionalDocumentReference
|
||||
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
|
||||
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">
|
||||
<cbc:ID>%s</cbc:ID>
|
||||
<cac:Attachment>
|
||||
<cbc:EmbeddedDocumentBinaryObject mimeCode="application/pdf" filename=%s>
|
||||
%s
|
||||
</cbc:EmbeddedDocumentBinaryObject>
|
||||
</cac:Attachment>
|
||||
</cac:AdditionalDocumentReference>
|
||||
''' % (escape(pdf_name), quoteattr(pdf_name), pdf_content_b64)
|
||||
|
||||
anchor_index = tree.index(anchor_elements[0])
|
||||
tree.insert(anchor_index, etree.fromstring(to_inject))
|
||||
new_xml = etree.tostring(cleanup_xml_node(tree), xml_declaration=True, encoding='UTF-8')
|
||||
edi_attachment.sudo().write({
|
||||
'res_model': 'account.move',
|
||||
'res_id': invoice.id,
|
||||
'datas': base64.b64encode(new_xml),
|
||||
'mimetype': 'application/xml',
|
||||
})
|
||||
|
||||
def _render_qweb_pdf_prepare_streams(self, report_ref, data, res_ids=None):
|
||||
# EXTENDS base
|
||||
# Add the pdf report in the XML as base64 string.
|
||||
collected_streams = super()._render_qweb_pdf_prepare_streams(report_ref, data, res_ids=res_ids)
|
||||
|
||||
if collected_streams \
|
||||
and res_ids \
|
||||
and self._is_invoice_report(report_ref):
|
||||
for res_id, stream_data in collected_streams.items():
|
||||
invoice = self.env['account.move'].browse(res_id)
|
||||
self._add_pdf_into_invoice_xml(invoice, stream_data)
|
||||
|
||||
# If Factur-X isn't already generated, generate and embed it inside the PDF
|
||||
if len(res_ids) == 1:
|
||||
invoice = self.env['account.move'].browse(res_ids)
|
||||
edi_doc_codes = invoice.edi_document_ids.edi_format_id.mapped('code')
|
||||
# If Factur-X hasn't been generated, generate and embed it anyway
|
||||
if invoice.is_sale_document() \
|
||||
and invoice.state == 'posted' \
|
||||
and 'facturx_1_0_05' not in edi_doc_codes \
|
||||
and self.env.ref('account_edi_ubl_cii.edi_facturx_1_0_05', raise_if_not_found=False):
|
||||
# Add the attachments to the pdf file
|
||||
pdf_stream = collected_streams[invoice.id]['stream']
|
||||
|
||||
# Read pdf content.
|
||||
pdf_content = pdf_stream.getvalue()
|
||||
reader_buffer = io.BytesIO(pdf_content)
|
||||
reader = OdooPdfFileReader(reader_buffer, strict=False)
|
||||
|
||||
# Post-process and embed the additional files.
|
||||
writer = OdooPdfFileWriter()
|
||||
writer.cloneReaderDocumentRoot(reader)
|
||||
|
||||
# Generate and embed Factur-X
|
||||
xml_content, _errors = self.env['account.edi.xml.cii']._export_invoice(invoice)
|
||||
writer.addAttachment(
|
||||
name=self.env['account.edi.xml.cii']._export_invoice_filename(invoice),
|
||||
data=xml_content,
|
||||
subtype='text/xml',
|
||||
)
|
||||
|
||||
# Replace the current content.
|
||||
pdf_stream.close()
|
||||
new_pdf_stream = io.BytesIO()
|
||||
writer.write(new_pdf_stream)
|
||||
collected_streams[invoice.id]['stream'] = new_pdf_stream
|
||||
|
||||
return collected_streams
|
||||
|
|
@ -22,25 +22,33 @@ The account_edi_ubl_cii module uses PyPDF2 for:
|
|||
|
||||
## Solution
|
||||
|
||||
**This package requires NO direct patches** because it uses:
|
||||
1. `OdooPdfFileWriter` from `odoo.tools.pdf` (oca-ocb-base)
|
||||
2. `OdooPdfFileReader` from `odoo.tools.pdf` (oca-ocb-base)
|
||||
**This package includes direct fixes** for PyPDF2 3.x page copying issue:
|
||||
1. Uses `OdooPdfFileWriter` from `odoo.tools.pdf` (oca-ocb-base) for compatibility
|
||||
2. **CRITICAL FIX**: Added explicit page copying after `cloneReaderDocumentRoot()`
|
||||
|
||||
The main compatibility layer in `oca-ocb-base` handles all PyPDF2 version compatibility automatically.
|
||||
In PyPDF2 3.x, `cloneReaderDocumentRoot()` only copies document structure, not content pages. This was causing 327-byte empty PDFs. The fix includes explicit page copying:
|
||||
|
||||
```python
|
||||
writer.cloneReaderDocumentRoot(reader)
|
||||
# Copy all pages from the reader to the writer (required for PyPDF2 3.x)
|
||||
for page_num in range(reader.getNumPages()):
|
||||
page = reader.getPage(page_num)
|
||||
writer.addPage(page)
|
||||
```
|
||||
|
||||
## Files Using PyPDF2
|
||||
|
||||
### `account_edi_ubl_cii/models/ir_actions_report.py`
|
||||
- Uses `OdooPdfFileWriter` for standards-compliant PDF generation (automatically compatible)
|
||||
- Calls `writer.cloneReaderDocumentRoot(reader)`
|
||||
- **FIXED**: Added explicit page copying after `writer.cloneReaderDocumentRoot(reader)`
|
||||
- Embeds UBL/CII XML in PDF documents
|
||||
|
||||
## Implementation Details
|
||||
|
||||
**No code changes needed** in this package. Compatibility is achieved through:
|
||||
**Direct code changes applied** in this package:
|
||||
|
||||
1. **Dependency**: Requires `oca-ocb-base` with `pdfwrite` branch
|
||||
2. **Automatic compatibility**: `OdooPdfFileWriter` handles all PyPDF2 version differences
|
||||
1. **Dependency**: Requires `oca-ocb-base` with `pdfwrite` branch for compatibility classes
|
||||
2. **Page copying fix**: Added explicit page copying loop in `_render_qweb_pdf_prepare_streams()`
|
||||
3. **Standards compliance**: UBL/CII standards maintained through compatibility layer
|
||||
|
||||
## Testing
|
||||
Loading…
Add table
Add a link
Reference in a new issue