19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:30:27 +01:00
parent d1963a3c3a
commit 2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions

View file

@ -4,23 +4,6 @@ import re
import base64
import io
try:
from PyPDF2 import PdfWriter, PdfReader
# Create compatibility classes for PyPDF2 3.0+
class PdfFileWriter(PdfWriter):
def addPage(self, page):
return self.add_page(page)
def appendPagesFromReader(self, reader, after_page_append=None):
return self.append_pages_from_reader(reader, after_page_append)
class PdfFileReader(PdfReader):
def getPage(self, page_num):
return self.pages[page_num]
except ImportError:
from PyPDF2 import PdfFileWriter, PdfFileReader
from reportlab.platypus import Frame, Paragraph, KeepInFrame
from reportlab.lib.units import mm
from reportlab.lib.pagesizes import A4
@ -30,6 +13,7 @@ from reportlab.pdfgen.canvas import Canvas
from odoo import fields, models, api, _
from odoo.addons.iap.tools import iap_tools
from odoo.exceptions import AccessError, UserError
from odoo.tools.pdf import PdfFileReader, PdfFileWriter
from odoo.tools.safe_eval import safe_eval
DEFAULT_ENDPOINT = 'https://iap-snailmail.odoo.com'
@ -43,6 +27,7 @@ ERROR_CODES = [
'NO_PRICE_AVAILABLE',
'FORMAT_ERROR',
'UNKNOWN_ERROR',
'ATTACHMENT_ERROR',
]
@ -68,14 +53,13 @@ class SnailmailLetter(models.Model):
('pending', 'In Queue'),
('sent', 'Sent'),
('error', 'Error'),
('canceled', 'Canceled')
('canceled', 'Cancelled')
], 'Status', readonly=True, copy=False, default='pending', required=True,
help="When a letter is created, the status is 'Pending'.\n"
"If the letter is correctly sent, the status goes in 'Sent',\n"
"If not, it will got in state 'Error' and the error message will be displayed in the field 'Error Message'.")
error_code = fields.Selection([(err_code, err_code) for err_code in ERROR_CODES], string="Error")
info_msg = fields.Html('Information')
display_name = fields.Char('Display Name', compute="_compute_display_name")
reference = fields.Char(string='Related Record', compute='_compute_reference', readonly=True, store=False)
@ -89,11 +73,11 @@ class SnailmailLetter(models.Model):
state_id = fields.Many2one("res.country.state", string='State')
country_id = fields.Many2one('res.country', string='Country')
@api.depends('reference', 'partner_id')
@api.depends('attachment_id', 'partner_id')
def _compute_display_name(self):
for letter in self:
if letter.attachment_id:
letter.display_name = "%s - %s" % (letter.attachment_id.name, letter.partner_id.name)
letter.display_name = f"{letter.attachment_id.name} - {letter.partner_id.name}"
else:
letter.display_name = letter.partner_id.name
@ -136,22 +120,33 @@ class SnailmailLetter(models.Model):
self.env['mail.notification'].sudo().create(notification_vals)
letters.attachment_id.check('read')
letters.attachment_id.check_access('read')
return letters
def write(self, vals):
res = super().write(vals)
if 'attachment_id' in vals:
self.attachment_id.check('read')
self.attachment_id.check_access('read')
return res
def _generate_report_pdf(self, report):
obj = self.env[self.model].browse(self.res_id)
if report.print_report_name:
report_name = safe_eval(report.print_report_name, {'object': obj})
elif report.attachment:
report_name = safe_eval(report.attachment, {'object': obj})
else:
report_name = 'Document'
filename = "%s.%s" % (report_name, "pdf")
pdf_bin = self.env['ir.actions.report'].with_context(snailmail_layout=not self.cover, lang='en_US')._render_qweb_pdf(report, self.res_id)[0]
return filename, pdf_bin
def _fetch_attachment(self):
"""
This method will check if we have any existent attachement matching the model
and res_ids and create them if not found.
"""
self.ensure_one()
obj = self.env[self.model].browse(self.res_id)
if not self.attachment_id:
report = self.report_template
if not report:
@ -161,18 +156,19 @@ class SnailmailLetter(models.Model):
return False
else:
self.write({'report_template': report.id})
# report = self.env.ref('account.account_invoices')
if report.print_report_name:
report_name = safe_eval(report.print_report_name, {'object': obj})
elif report.attachment:
report_name = safe_eval(report.attachment, {'object': obj})
else:
report_name = 'Document'
filename = "%s.%s" % (report_name, "pdf")
paperformat = report.get_paperformat()
if (paperformat.format == 'custom' and paperformat.page_width != 210 and paperformat.page_height != 297) or paperformat.format != 'A4':
raise UserError(_("Please use an A4 Paper format."))
pdf_bin, unused_filetype = self.env['ir.actions.report'].with_context(snailmail_layout=not self.cover, lang='en_US')._render_qweb_pdf(report, self.res_id)
# The external_report_layout_id is changed just for the snailmail pdf generation if the layout is not supported
prev = self.company_id.external_report_layout_id
if prev in {
self.env.ref(f'web.external_layout_{layout}')
for layout in ('bubble', 'wave', 'folder')
}:
self.company_id.sudo().external_report_layout_id = self.env.ref('web.external_layout_standard')
filename, pdf_bin = self._generate_report_pdf(report)
self.company_id.sudo().external_report_layout_id = prev
pdf_bin = self._overwrite_margins(pdf_bin)
if self.cover:
pdf_bin = self._append_cover_page(pdf_bin)
@ -192,7 +188,7 @@ class SnailmailLetter(models.Model):
:param bin_pdf : binary content of the pdf file
"""
pages = 0
for match in re.compile(br"/Count\s+(\d+)").finditer(bin_pdf):
for match in re.compile(rb"/Count\s+(\d+)").finditer(bin_pdf):
pages = int(match.group(1))
return pages
@ -234,11 +230,10 @@ class SnailmailLetter(models.Model):
}
}
"""
account_token = self.env['iap.account'].get('snailmail').account_token
account_token = self.env['iap.account'].get('snailmail').sudo().account_token
dbuuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid')
documents = []
batch = len(self) > 1
for letter in self:
recipient_name = letter.partner_id.name or letter.partner_id.parent_id and letter.partner_id.parent_id.name
if not recipient_name:
@ -253,7 +248,7 @@ class SnailmailLetter(models.Model):
'letter_id': letter.id,
'res_model': letter.model,
'res_id': letter.res_id,
'contact_address': letter.partner_id.with_context(snailmail_layout=True, show_address=True).name_get()[0][1],
'contact_address': letter.partner_id.with_context(snailmail_layout=True, show_address=True).display_name,
'address': {
'name': recipient_name,
'street': letter.partner_id.street,
@ -293,7 +288,7 @@ class SnailmailLetter(models.Model):
letter.write({
'info_msg': 'The attachment could not be generated.',
'state': 'error',
'error_code': 'UNKNOWN_ERROR'
'error_code': 'ATTACHMENT_ERROR'
})
continue
if letter.company_id.external_report_layout_id == self.env.ref('l10n_de.external_layout_din5008', False):
@ -321,7 +316,7 @@ class SnailmailLetter(models.Model):
link = self.env['iap.account'].get_credits_url(service_name='snailmail')
return _('You don\'t have enough credits to perform this operation.<br>Please go to your <a href=%s target="new">iap account</a>.', link)
if error == 'TRIAL_ERROR':
link = self.env['iap.account'].get_credits_url(service_name='snailmail', trial=True)
link = self.env['iap.account'].get_credits_url(service_name='snailmail')
return _('You don\'t have an IAP account registered for this service.<br>Please go to <a href=%s target="new">iap.odoo.com</a> to claim your free credits.', link)
if error == 'NO_PRICE_AVAILABLE':
return _('The country of the partner is not covered by Snailmail.')
@ -398,9 +393,8 @@ class SnailmailLetter(models.Model):
raise ae
for doc in response['request']['documents']:
if doc.get('sent') and response['request_code'] == 200:
self.env['iap.account']._send_iap_bus_notification(
service_name='snailmail',
title=_("Snail Mails are successfully sent"))
self.env['iap.account']._send_success_notification(
message=_("Snail Mails are successfully sent"))
note = _('The document was correctly sent by post.<br>The tracking id is %s', doc['send_id'])
letter_data = {'info_msg': note, 'state': 'sent', 'error_code': False}
notification_data = {
@ -412,10 +406,9 @@ class SnailmailLetter(models.Model):
error = doc['error'] if response['request_code'] == 200 else response['reason']
if error == 'CREDIT_ERROR':
self.env['iap.account']._send_iap_bus_notification(
self.env['iap.account']._send_no_credit_notification(
service_name='snailmail',
title=_("Not enough credits for Snail Mail"),
error_type="credit")
title=_("Not enough credits for Snail Mail"))
note = _('An error occurred when sending the document by post.<br>Error: %s', self._get_error_message(error))
letter_data = {
'info_msg': note,
@ -458,7 +451,7 @@ class SnailmailLetter(models.Model):
('state', '=', 'pending'),
'&',
('state', '=', 'error'),
('error_code', 'in', ['TRIAL_ERROR', 'CREDIT_ERROR', 'MISSING_REQUIRED_FIELDS'])
('error_code', 'in', ['TRIAL_ERROR', 'CREDIT_ERROR', 'ATTACHMENT_ERROR', 'MISSING_REQUIRED_FIELDS'])
])
for letter in letters_send:
letter._snailmail_print()
@ -474,9 +467,18 @@ class SnailmailLetter(models.Model):
required_keys = ['street', 'city', 'zip', 'country_id']
return all(record[key] for key in required_keys)
def _get_cover_address_split(self):
address_split = self.partner_id.with_context(show_address=True, lang='en_US').display_name.split('\n')
if self.country_id.code == 'DE':
# Germany requires specific address formatting for Pingen
if self.street2:
address_split[1] = f'{self.street} // {self.street2}'
address_split[2] = f'{self.zip} {self.city}'
return address_split
def _append_cover_page(self, invoice_bin: bytes):
out_writer = PdfFileWriter()
address_split = self.partner_id.with_context(show_address=True, lang='en_US')._get_name().split('\n')
address_split = self._get_cover_address_split()
address_split[0] = self.partner_id.name or self.partner_id.parent_id and self.partner_id.parent_id.name or address_split[0]
address = '<br/>'.join(address_split)
address_x = 118 * mm