[IMP] account: add Vendor Bill ref. on Invoice PDF

The goal is to add a digital stamp on the Invoice PDF
stating the name of its related Vendor Bill.

Auditors may request printed copies of all (original) Invoices
as well as the possibility to relate each document
to a posted entry (Vendor Bill).

task-2755372

closes odoo/odoo#86127

X-original-commit: f0ea39791c70cc63fe39665514e5344a56ed5b71
Signed-off-by: William André (wan) <wan@odoo.com>
Signed-off-by: John Laterre <jol@odoo.com>
This commit is contained in:
John Laterre (jol)
2022-03-09 15:19:04 +00:00
parent 5a3cc5e307
commit 32dee3dd5e
2 changed files with 78 additions and 1 deletions
+12 -1
View File
@@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
from odoo import models, api, _
from odoo import models, _
from odoo.exceptions import UserError
from odoo.tools import pdf
class IrActionsReport(models.Model):
_inherit = 'ir.actions.report'
@@ -45,3 +47,12 @@ class IrActionsReport(models.Model):
raise UserError(_("Only invoices could be printed."))
return super()._render_qweb_pdf(res_ids=res_ids, data=data)
def _retrieve_stream_from_attachment(self, attachment):
# Overridden in order to add a banner in the upper right corner of the exported Vendor Bill PDF.
stream = super()._retrieve_stream_from_attachment(attachment)
vendor_bill_export = self.env.ref('account.action_account_original_vendor_bill')
if self == vendor_bill_export and attachment.mimetype == 'application/pdf':
record = self.env[attachment.res_model].browse(attachment.res_id)
return pdf.add_banner(stream, record.name, logo=True)
return stream
+66
View File
@@ -9,6 +9,11 @@ from logging import getLogger
from PyPDF2 import PdfFileWriter, PdfFileReader
from PyPDF2.generic import DictionaryObject, NameObject, ArrayObject, DecodedStreamObject, NumberObject, createStringObject, ByteStringObject
from zlib import compress, decompress
from PIL import Image
from reportlab.lib import colors
from reportlab.lib.units import cm
from reportlab.lib.utils import ImageReader
from reportlab.pdfgen import canvas
try:
from fontTools.ttLib import TTFont
@@ -78,6 +83,67 @@ def rotate_pdf(pdf):
writer.write(_buffer)
return _buffer.getvalue()
def add_banner(pdf_stream, text=None, logo=False, thickness=2 * cm):
""" Add a banner on a PDF in the upper right corner, with Odoo's logo (optionally).
:param pdf_stream (BytesIO): The PDF stream where the banner will be applied.
:param text (str): The text to be displayed.
:param logo (bool): Whether to display Odoo's logo in the banner.
:param thickness (float): The thickness of the banner in pixels.
:return (BytesIO): The modified PDF stream.
"""
old_pdf = PdfFileReader(pdf_stream, strict=False, overwriteWarnings=False)
old_pdf.getNumPages()
packet = io.BytesIO()
can = canvas.Canvas(packet)
odoo_logo = Image.open(file_open('base/static/img/main_partner-image.png', mode='rb'))
odoo_color = colors.Color(113 / 255, 75 / 255, 103 / 255, 0.8)
for p in range(old_pdf.getNumPages()):
page = old_pdf.getPage(p)
width = float(abs(page.mediaBox.getWidth()))
height = float(abs(page.mediaBox.getHeight()))
can.translate(width, height)
can.rotate(-45)
# Draw banner
path = can.beginPath()
path.moveTo(-width, -thickness)
path.lineTo(-width, -2 * thickness)
path.lineTo(width, -2 * thickness)
path.lineTo(width, -thickness)
can.setFillColor(odoo_color)
can.drawPath(path, fill=1, stroke=False)
# Insert text (and logo) inside the banner
can.setFontSize(10)
can.setFillColor(colors.white)
can.drawRightString(0.75 * thickness, -1.45 * thickness, text)
logo and can.drawImage(
ImageReader(odoo_logo), 0.25 * thickness, -2.05 * thickness, 40, 40, mask='auto', preserveAspectRatio=True)
can.showPage()
can.save()
# Merge the old pages with the watermark
watermark_pdf = PdfFileReader(packet, overwriteWarnings=False)
new_pdf = PdfFileWriter()
for p in range(old_pdf.getNumPages()):
new_page = old_pdf.getPage(p)
new_page.mergePage(watermark_pdf.getPage(p))
new_pdf.addPage(new_page)
# Write the new pdf into a new output stream
output = io.BytesIO()
new_pdf.write(output)
return output
# by default PdfFileReader will overwrite warnings.showwarning which is what
# logging.captureWarnings does, meaning it essentially reverts captureWarnings
# every time it's called which is undesirable