Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 22 additions & 18 deletions child_compassion/models/compassion_hold.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,24 +486,28 @@ def postpone_no_money_hold(self, additional_text=None):
}
hold.write(hold_vals)

body = (
"The no money hold for child {local_id} was expiring on "
"{old_expiration} and was extended to {new_expiration} "
"({extension_description} extension).{additional_text}"
)
hold.message_post(
body=_(body.format(**values)),
subject=_("No money hold extension"),
subtype_xmlid="mail.mt_comment",
)

else:
body = _(
"The no money hold for child {local_id} is expiring on "
"{old_expiration} and will not be extended since "
"no sponsorship exists for this child."
)
hold.message_post(body=body.format(**values))
# ----------------------------------------------------
# NOTIFICATION (Only for NO_MONEY_HOLD)
# ----------------------------------------------------
if hold.type == HoldType.NO_MONEY_HOLD.value:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This if statement checks the hold type again, which seems redundant since the code block is already within the for hold in self.filtered(lambda h: h.no_money_extension < 3) loop and if hold.type == HoldType.NO_MONEY_HOLD.value: block. Consider removing this redundant check to simplify the code.

if hold.child_id.sponsor_id:
body = (
"The no money hold for child {local_id} was expiring on "
"{old_expiration} and was extended to {new_expiration} "
"({extension_description} extension).{additional_text}"
)
hold.message_post(
body=_(body.format(**values)),
subject=_("No money hold extension"),
subtype_xmlid="mail.mt_comment",
)
else:
body = _(
"The no money hold for child {local_id} is expiring on "
"{old_expiration} and will not be extended since "
"no sponsorship exists for this child."
)
hold.message_post(body=body.format(**values))

##########################################################################
# Mapping METHOD #
Expand Down
14 changes: 14 additions & 0 deletions partner_communication/models/communication_snippet.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
from odoo import fields, models


class CommunicationSnippetCategory(models.Model):
_name = "communication.snippet.category"
_description = "Communication Snippet Category"

name = fields.Char(string="Category Name", required=True)


class CommunicationSnippet(models.Model):
_name = "communication.snippet"
_description = "Communication Snippet"

name = fields.Char(required=True, index=True)
snippet_text = fields.Html(required=True, translate=True)
description = fields.Text(string="Description")

category_id = fields.Many2one(
"communication.snippet.category",
string="Category",
help="Category of the communication snippet",
)

def action_edit_snippet(self):
self.ensure_one()
Expand Down
1 change: 1 addition & 0 deletions partner_communication/security/ir.model.access.csv
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ access_partner_communication_generate_wizard,access_partner_communication_genera
access_partner_communication_download_print_wizard,access_partner_communication_download_print_wizard,model_partner_communication_download_print_job_wizard,base.group_user,1,0,1,0
access_partner_communication_default_config,access_partner_communication_default_config,model_partner_communication_default_config,base.group_user,1,1,1,1
access_communication_snippets,Full access on communication_snippets,model_communication_snippet,base.group_user,1,1,1,1
access_comm_snippet_category_user,communication.snippet.category,model_communication_snippet_category,base.group_user,1,1,1,1
10 changes: 10 additions & 0 deletions partner_communication/views/communication_snippet_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
<group>
<field name="name" />
<field name="snippet_text" />
<field
name="category_id"
options="{'no_create_edit': False}"
/>
<field
name="description"
placeholder="Useful for global numbers..."
/>
</group>
</sheet>
</form>
Expand All @@ -20,6 +28,8 @@
<tree editable="top">
<field name="name" />
<field name="snippet_text" />
<field name="category_id" />
<field name="description" />
<button
name="action_edit_snippet"
type="object"
Expand Down
139 changes: 98 additions & 41 deletions sbc_compassion/models/correspondence_s2b_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@ class CorrespondenceS2bGenerator(models.Model):
preview_pdf = fields.Binary(readonly=True)
filename = fields.Char(compute="_compute_filename")
month = fields.Selection("_get_months")
generation_status = fields.Selection(
[
("creating_task", "creating_task"),
("apply_template", "apply_template"),
("apply_text", "apply_text"),
("apply_images", "apply_images"),
("generate_pdf", "generate_pdf"),
("done", "done"),
("failed", "failed"),
("finalizing", "finalizing"),
],
default="creating_task",
string="Generation Status",
)
generation_error_message = fields.Text(string="Generation Message")
MAX_PAGE_COUNT = 15 # Maximum number of pages allowed in a letter

def _compute_nb_letters(self):
for generator in self:
Expand Down Expand Up @@ -130,50 +146,63 @@ def onchange_month(self):

def preview(self):
"""Generate a picture for preview."""
pdf = self._get_pdf(self.sponsorship_ids[:1])[0]
if self.template_id.layout == "CH-A-3S01-1":
# Read page 2
in_pdf = PdfFileReader(BytesIO(pdf))
output_pdf = PdfFileWriter()
out_data = BytesIO()
output_pdf.addPage(in_pdf.getPage(1))
output_pdf.write(out_data)
out_data.seek(0)
pdf = out_data.read()

try:
pdf = self._get_pdf(self.sponsorship_ids[:1])[0]

if self.template_id.layout == "CH-A-3S01-1":
in_pdf = PdfFileReader(BytesIO(pdf))
output_pdf = PdfFileWriter()
output_pdf.addPage(in_pdf.getPage(1))
out_data = BytesIO()
output_pdf.write(out_data)
pdf = out_data.getvalue()

n_pages = PdfFileReader(BytesIO(pdf)).getNumPages()
if n_pages > self.MAX_PAGE_COUNT:
msg = _("Oops your letter has %d pages. The limit is %d.") % (
n_pages,
self.MAX_PAGE_COUNT,
)

raise UserError(msg)
with Image(blob=pdf, resolution=96) as pdf_image:
preview = base64.b64encode(pdf_image.make_blob(format="jpeg"))
except PolicyError as error:
_logger.error(
"ImageMagick policy error. Please add following line to "
"/etc/Image-Magick-<version>/policy.xml: "
'<policy domain="coder" rights="read|write" '
'pattern="PDF" />',
)
raise UserError(
_(
"Please allow ImageMagick to write PDF files."
" Ask an IT admin for help."

return self.isolated_write(
{
"state": "preview",
"generation_status": "done",
"generation_error_message": False,
"preview_image": preview,
"preview_pdf": base64.b64encode(pdf),
}
)
) from error
except TypeError as error:
raise UserError(

except (PolicyError, TypeError, UserError, Exception) as error:
error_message = (
_(
"Unfortunately the server cannot generate PDF documents "
"at the moment. Our IT team is informed and will fix this issue "
"as soon as possible."
)
if isinstance(error, PolicyError)
else str(error)
if isinstance(error, UserError)
else _(
"There was an error while generating the PDF of the letter. "
"Please check FPDF logs for more information."
)
) from error

pdf_image = base64.b64encode(pdf)

return self.write(
{
"state": "preview",
"preview_image": preview,
"preview_pdf": pdf_image,
}
)
)
_logger.error("Unable to generate PDF", exc_info=True)
if self.env.context.get("raise_error"):
raise UserError(error_message) from error
self.env.cr.rollback()
self.isolated_write(
{
"generation_status": "failed",
"generation_error_message": error_message,
}
)

def edit(self):
"""Generate a picture for preview."""
Expand All @@ -184,6 +213,7 @@ def generate_letters(self):
Launch S2B Creation job
:return: True
"""
self.generation_status = "finalizing"
self.with_delay(
identity_key="s2b_generator." + str(self.ids)
).generate_letters_job()
Expand Down Expand Up @@ -217,7 +247,7 @@ def generate_letters_job(self):
"res_model": letters._name,
},
)
for atchmt in self.image_ids
for atchmt in self.image_ids.sorted(reverse=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Sorting attachments in reverse order might not be the desired behavior. It's important to ensure that the order of attachments is correct for the intended use case. Confirm that reversing the order is indeed what's needed.

]
letters += letters.create(vals)

Expand All @@ -227,11 +257,26 @@ def generate_letters_job(self):
# If the operation succeeds, notify the user
message = "Letters have been successfully generated."
self.env.user.notify_success(message=message)
return self.write({"state": "done", "date": fields.Datetime.now()})

return self.isolated_write(
{
"state": "done",
"date": fields.Datetime.now(),
"generation_status": "done",
"generation_error_message": False,
}
)

except Exception as error:
# If the operation fails, notify the user with the error message
self.env.cr.rollback()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Rolling back the database transaction in the except block is crucial for maintaining data integrity. However, it's also important to ensure that the user is properly notified about the failure. Consider adding a more specific error message or logging the exception details for debugging purposes.

error_message = str(error)
self.isolated_write(
{
"generation_status": "failed",
"generation_error_message": error_message,
}
)
_logger.error(error_message, exc_info=True)
self.env.user.notify_danger(message=error_message)

Expand All @@ -257,8 +302,8 @@ def _get_text(self, sponsorship):
keywords = {
"%child%": child.preferred_name,
"%age%": str(child.age),
"%firstname%": sponsor.firstname or sponsor.name,
"%lastname%": sponsor.firstname and sponsor.lastname or "",
"%firstname%": sponsor.preferred_name or sponsor.firstname or sponsor.name,
"%lastname%": sponsor.lastname or "",
Comment on lines +305 to +306
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider using sponsor.name as a fallback only if sponsor.preferred_name and sponsor.firstname are empty to ensure a more robust solution.

            "%age%": str(child.age),
            "%firstname%": sponsor.preferred_name or sponsor.firstname or sponsor.name,
            "%lastname%": sponsor.lastname or "",

}
text = self.body
for keyword, replacement in list(keywords.items()):
Expand All @@ -285,7 +330,19 @@ def _get_pdf(self, sponsorship):
sponsorship.display_name,
(header, ""), # Headers (front/back)
{"Original": [text]}, # Text
self.mapped("image_ids.datas"), # Images
self.mapped("image_ids").sorted(reverse=True).mapped("datas"), # Images
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Sorting the images in reverse order might not be the desired behavior. It's important to ensure that the order of images is correct for the intended use case. Confirm that reversing the order is indeed what's needed.

s2b_generator=self,
),
text,
)

def isolated_write(self, vals):
"""Use a separate transaction to update the letter_generator."""
if len(self) != 1:
return False

with self.env.registry.cursor() as new_cr:
new_env = self.env(cr=new_cr)
new_s2b_generator = new_env[self._name].browse(self.id)
new_s2b_generator.write(vals)
new_cr.commit()
Comment on lines +339 to +348
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The isolated_write method is used to update the letter generator in a separate transaction. It's important to ensure that this method is robust and handles potential errors gracefully. Consider adding error handling to catch exceptions that might occur during the write operation or commit.

Loading