From e90ed66b4912a7657ba383df3c19790a832149d0 Mon Sep 17 00:00:00 2001 From: Andre Date: Tue, 2 Nov 2021 14:50:16 +0100 Subject: [PATCH 01/12] CS-593 - cannot update email or delete/archive a partner linked to a user --- partner_compassion/models/__init__.py | 1 + partner_compassion/models/res_users.py | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 partner_compassion/models/res_users.py diff --git a/partner_compassion/models/__init__.py b/partner_compassion/models/__init__.py index efda89891..46a9b158a 100644 --- a/partner_compassion/models/__init__.py +++ b/partner_compassion/models/__init__.py @@ -22,3 +22,4 @@ from . import mail_activity from . import partner_segment from . import parter_segment_affinity +from . import res_users diff --git a/partner_compassion/models/res_users.py b/partner_compassion/models/res_users.py new file mode 100644 index 000000000..aa3994104 --- /dev/null +++ b/partner_compassion/models/res_users.py @@ -0,0 +1,7 @@ +from odoo import models, fields + +class ResUsers(models.Model): + + _inherit = "res.users" + + partner_id = fields.Many2one(ondelete='cascade') From f749af919c283386c5c1aa6ce6ba0cfb71ee8c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Margueron?= Date: Thu, 11 Nov 2021 13:51:52 +0100 Subject: [PATCH 02/12] CO-3821 & CO-3822 - Refactor the code to allow search by partner - Add UI and code logic for a specific partner case generation - Fix the problem where sometimes the 1 child was generated with partner sponsoring multiple children - Add buttons to generate only certain comunication parts + a button for all of them --- .../models/partner_communication_config.py | 116 ++++++++++++------ .../views/communication_test_case_view.xml | 38 ++++-- .../communication_test_cases_wizard.py | 67 ++++++++-- 3 files changed, 155 insertions(+), 66 deletions(-) diff --git a/partner_communication_switzerland/models/partner_communication_config.py b/partner_communication_switzerland/models/partner_communication_config.py index 0c964bbda..2fc2e67f1 100644 --- a/partner_communication_switzerland/models/partner_communication_config.py +++ b/partner_communication_switzerland/models/partner_communication_config.py @@ -7,7 +7,8 @@ # The licence is in the file __manifest__.py # ############################################################################## -from random import randint +import random +import itertools from odoo import api, models @@ -16,33 +17,28 @@ class PartnerCommunication(models.Model): _inherit = "partner.communication.config" @api.multi - def generate_test_cases(self, lang="de_DE", send_mode="digital"): + def generate_test_cases_by_language_family_case(self, lang="de_DE", family_case="single", send_mode="digital"): """ - Generates example communications for our multiple cases in CH. + Generates example communications for our multiple cases in CH + depending on the language and the family case Outputs the texts in a file :param lang: :return: True """ self.ensure_one() - family = self.env.ref("partner_compassion.res_partner_title_family") - # Find a partner with > 3 sponsorships - family_pool = self.env["res.partner"].search([ - ("number_sponsorships", ">", 3), - ("lang", "=", lang), - ("title", "=", family.id) - ], order="name asc", limit=50) - single_pool = self.env["res.partner"].search([ - ("number_sponsorships", ">", 3), - ("lang", "=", lang), - ("title", "!=", family.id), - ("title.plural", "=", False) - ], order="name asc", limit=50) - comm_obj = self.env["partner.communication.job"].with_context(must_skip_send_to_printer=True) + + comm_obj = self.env["partner.communication.job"].with_context( + must_skip_send_to_printer=True) + res = [] - for case in ["single", "family"]: - pool = single_pool if case == "single" else family_pool - partner = pool[min(randint(0, 49), len(pool) - 1)] + + for number_sponsorship in [1, 3, 4]: + partner = self._find_partner(number_sponsorship, lang, family_case) + if partner is None: + continue object_ids = self._get_test_objects(partner) + object_ids = ",".join([str(id) + for id in object_ids[0:number_sponsorship]]) temp_comm = comm_obj.create({ "partner_id": partner.id, "config_id": self.id, @@ -51,28 +47,46 @@ def generate_test_cases(self, lang="de_DE", send_mode="digital"): "send_mode": send_mode, }) res.append({ - "case": f"{case}_4_children", - "subject": temp_comm.subject, - "body_html": temp_comm.body_html}) - partner = pool[min(randint(0, 49), len(pool) - 1)] - object_ids = self._get_test_objects(partner) - temp_comm.object_ids = ",".join(map(str, object_ids[:3])) - temp_comm.partner_id = partner - temp_comm.refresh_text() - res.append({ - "case": f"{case}_3_children", + "case": f"{family_case}_{number_sponsorship}_child", "subject": temp_comm.subject, - "body_html": temp_comm.body_html}) - partner = pool[min(randint(0, 49), len(pool) - 1)] - object_ids = self._get_test_objects(partner) - temp_comm.object_ids = object_ids[0] - temp_comm.partner_id = partner - temp_comm.refresh_text() - res.append({ - "case": f"{case}_1_child", - "subject": temp_comm.subject, - "body_html": temp_comm.body_html}) + "body_html": temp_comm.body_html + }) temp_comm.unlink() + + return res + + @api.multi + def generate_test_case_by_partner(self, partner=None, send_mode="digital"): + """ + Generates example communications for our multiple cases in CH + depending on partner + Outputs the texts in a file + :param partner: + :return: True + """ + self.ensure_one() + + comm_obj = self.env["partner.communication.job"].with_context( + must_skip_send_to_printer=True) + + res = [] + + object_ids = self._get_test_objects(partner) + object_ids = ",".join([str(id) for id in object_ids]) + temp_comm = comm_obj.create({ + "partner_id": partner.id, + "config_id": self.id, + "object_ids": object_ids, + "auto_send": False, + "send_mode": send_mode, + }) + res = { + "case": "partner", + "subject": temp_comm.subject, + "body_html": temp_comm.body_html + } + temp_comm.unlink() + return res def open_test_case_wizard(self): @@ -101,3 +115,25 @@ def _get_test_objects(self, partner): ("invoice_id.invoice_category", "=", "fund") ], limit=4).ids return object_ids + + def _find_partner(self, number_sponsorships, lang, family_case): + family = self.env.ref("partner_compassion.res_partner_title_family") + + query = [ + ("number_sponsorships", "=", number_sponsorships), + ("lang", "=", lang), + ] + if family_case == "single": + query += [("title", "!=", family.id), ("title.plural", "=", False)] + else: + query += [("title", "=", family.id)] + + answers = self.env["res.partner"].search(query, limit=50) + + # check that the query returned a result + if len(answers) <= 0: + return None + + # randomly select one + answer = random.choice(answers) + return answer diff --git a/partner_communication_switzerland/views/communication_test_case_view.xml b/partner_communication_switzerland/views/communication_test_case_view.xml index 557a69e1b..24827b903 100644 --- a/partner_communication_switzerland/views/communication_test_case_view.xml +++ b/partner_communication_switzerland/views/communication_test_case_view.xml @@ -5,48 +5,60 @@
-
+ + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + +
diff --git a/partner_communication_switzerland/wizards/communication_test_cases_wizard.py b/partner_communication_switzerland/wizards/communication_test_cases_wizard.py index 293243e5e..fac4baadb 100644 --- a/partner_communication_switzerland/wizards/communication_test_cases_wizard.py +++ b/partner_communication_switzerland/wizards/communication_test_cases_wizard.py @@ -7,7 +7,7 @@ # The licence is in the file __manifest__.py # ############################################################################## -from odoo import models, api, fields, _ +from odoo import models, api, fields, _, exceptions class GenerateCommunicationWizard(models.TransientModel): @@ -21,24 +21,31 @@ class GenerateCommunicationWizard(models.TransientModel): language = fields.Selection( "_get_lang", default=lambda self: self.env.lang ) + partner = fields.Many2one( + "res.partner", string="Partner", + required=False + ) send_mode = fields.Selection( [("digital", _("By e-mail")), ("physical", _("Print report"))], default="digital" ) single_1_child_subject = fields.Char(readonly=True) - single_3_children_subject = fields.Char(readonly=True) - single_4_children_subject = fields.Char(readonly=True) + single_3_child_subject = fields.Char(readonly=True) + single_4_child_subject = fields.Char(readonly=True) family_1_child_subject = fields.Char(readonly=True) - family_3_children_subject = fields.Char(readonly=True) - family_4_children_subject = fields.Char(readonly=True) + family_3_child_subject = fields.Char(readonly=True) + family_4_child_subject = fields.Char(readonly=True) single_1_child_body = fields.Html(readonly=True) - single_3_children_body = fields.Html(readonly=True) - single_4_children_body = fields.Html(readonly=True) + single_3_child_body = fields.Html(readonly=True) + single_4_child_body = fields.Html(readonly=True) family_1_child_body = fields.Html(readonly=True) - family_3_children_body = fields.Html(readonly=True) - family_4_children_body = fields.Html(readonly=True) + family_3_child_body = fields.Html(readonly=True) + family_4_child_body = fields.Html(readonly=True) + + partner_subject = fields.Html(readonly=True) + partner_body = fields.Html(readonly=True) def _compute_display_name(self): for wizard in self: @@ -48,10 +55,44 @@ def _get_lang(self): langs = self.env["res.lang"].search([]) return [(l.code, l.name) for l in langs] + + @api.multi + def generate_test_cases_single(self): + self.ensure_one() + cases = self.config_id.generate_test_cases_by_language_family_case( + self.language, "single", self.send_mode) + self._apply_cases(cases) + return True + + @api.multi + def generate_test_cases_family(self): + self.ensure_one() + cases = self.config_id.generate_test_cases_by_language_family_case( + self.language, "family", self.send_mode) + self._apply_cases(cases) + return True + @api.multi - def generate_test_cases(self): + def generate_test_cases_partner(self): self.ensure_one() - for data in self.config_id.generate_test_cases(self.language, self.send_mode): - setattr(self, data["case"] + "_subject", data["subject"]) - setattr(self, data["case"] + "_body", data["body_html"]) + if len(self.partner) < 1: + raise exceptions.UserError("No partner selected") + return False + case = self.config_id.generate_test_case_by_partner(self.partner, self.send_mode) + self._apply_cases([case]) return True + + @api.multi + def generate_test_cases_all(self): + self.ensure_one() + answer = True + answer &= self.generate_test_cases_single() + answer &= self.generate_test_cases_family() + answer &= self.generate_test_cases_partner() + return answer + + + def _apply_cases(self, cases): + for case in cases: + setattr(self, case["case"] + "_subject", case["subject"]) + setattr(self, case["case"] + "_body", case["body_html"]) From 40c5526cf2700fcc14df38662aaf8c5407053004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Margueron?= Date: Thu, 11 Nov 2021 15:51:28 +0100 Subject: [PATCH 03/12] CO-3821 & CO-3822 - Add dynamic UI to select between partner and language --- .../views/communication_test_case_view.xml | 17 +++++++------ .../communication_test_cases_wizard.py | 25 ++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/partner_communication_switzerland/views/communication_test_case_view.xml b/partner_communication_switzerland/views/communication_test_case_view.xml index 24827b903..be10457b1 100644 --- a/partner_communication_switzerland/views/communication_test_case_view.xml +++ b/partner_communication_switzerland/views/communication_test_case_view.xml @@ -1,3 +1,4 @@ + partner.communication.test.cases.wizard.form @@ -5,23 +6,23 @@
-
+ - + - + @@ -37,7 +38,7 @@ - + @@ -53,7 +54,7 @@ - + diff --git a/partner_communication_switzerland/wizards/communication_test_cases_wizard.py b/partner_communication_switzerland/wizards/communication_test_cases_wizard.py index fac4baadb..307402f54 100644 --- a/partner_communication_switzerland/wizards/communication_test_cases_wizard.py +++ b/partner_communication_switzerland/wizards/communication_test_cases_wizard.py @@ -25,6 +25,8 @@ class GenerateCommunicationWizard(models.TransientModel): "res.partner", string="Partner", required=False ) + partner_selected = fields.Boolean( + compute="_compute_partner_selected", store=True) send_mode = fields.Selection( [("digital", _("By e-mail")), ("physical", _("Print report"))], @@ -55,6 +57,14 @@ def _get_lang(self): langs = self.env["res.lang"].search([]) return [(l.code, l.name) for l in langs] + @api.depends("partner") + @api.multi + def _compute_partner_selected(self): + for line in self: + if len(line.partner) <= 0: + line.partner_selected = False + else: + line.partner_selected = line.partner.name != "" @api.multi def generate_test_cases_single(self): @@ -75,23 +85,14 @@ def generate_test_cases_family(self): @api.multi def generate_test_cases_partner(self): self.ensure_one() - if len(self.partner) < 1: + if not self.partner_selected: raise exceptions.UserError("No partner selected") return False - case = self.config_id.generate_test_case_by_partner(self.partner, self.send_mode) + case = self.config_id.generate_test_case_by_partner( + self.partner, self.send_mode) self._apply_cases([case]) return True - @api.multi - def generate_test_cases_all(self): - self.ensure_one() - answer = True - answer &= self.generate_test_cases_single() - answer &= self.generate_test_cases_family() - answer &= self.generate_test_cases_partner() - return answer - - def _apply_cases(self, cases): for case in cases: setattr(self, case["case"] + "_subject", case["subject"]) From f9bd4c8c54fb9d4063526caaa771d2091323e30b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Margueron?= Date: Fri, 12 Nov 2021 10:44:06 +0100 Subject: [PATCH 04/12] CS-608 : Christmas letters online, cannot write several letters in a row. I could only reproduce this bug on Chromium based browsers (Firefox did not have it). I fixed it by updating the function declartions and adding event listeners for the callback instead of directly affecting it. I also change the var variable declarations by let. --- .../static/src/js/write_a_letter.js | 117 ++++++++++-------- 1 file changed, 63 insertions(+), 54 deletions(-) diff --git a/website_compassion/static/src/js/write_a_letter.js b/website_compassion/static/src/js/write_a_letter.js index 7119f3d05..af9136056 100644 --- a/website_compassion/static/src/js/write_a_letter.js +++ b/website_compassion/static/src/js/write_a_letter.js @@ -1,3 +1,27 @@ +//Global variables + +// The images compressed +let images_comp = []; +// The list of images actually displayed inside the view +let images_list = []; +// The new non-duplicated images to add to the view +let new_images = []; + +let loading = false; + +// Consts +const max_size = 1000; +const hard_max_size_limit = 1e7; +const resize_limit = 2e5; + +const file_selector = document.getElementById("file_selector"); +const image_display_table = document.getElementById("image_display_table"); +const letter_content = document.getElementById("letter_content"); +const canvas = document.createElement("canvas"); + +file_selector.addEventListener("change", updateImageDisplay); + + /** * This function replaces a range in a string * @param from the starting point of the substitution @@ -6,7 +30,7 @@ * @returns {string} a new string substituted with the desired content */ String.prototype.replaceRange = function(from, to, substitute) { - var result = this.substring(0, from) + substitute; + let result = this.substring(0, from) + substitute; if (to != -1) { result += this.substring(to) } @@ -23,8 +47,8 @@ String.prototype.indexOfEnd = function(substring) { return index === -1 ? index : index + substring.length; } -downloadLetter = function() { - window.open("/my/download/labels/?child_id="+$('#child_id').text()); +function downloadLetter() { + window.open("/my/download/labels/?child_id=" + $('#child_id').text()); } /** * Selects the element given the element type and the object id. This relies @@ -32,7 +56,7 @@ downloadLetter = function() { * @param obj_id the id of the object to select * @param elem_type the type of the object to select */ -selectElement = function(obj_id, elem_type) { +function selectElement(obj_id, elem_type) { // The id in the XML must have this form precisely, for this to work. const elem_id = `${elem_type}_${obj_id}`; // Elements are selected by finding the corresponding image and setting @@ -48,7 +72,7 @@ selectElement = function(obj_id, elem_type) { // Change url to display selected child and template id const base_url = window.location.origin + window.location.pathname; - var params = window.location.search; + let params = window.location.search; const from = params.indexOfEnd(`${elem_type}_id=`); if (from === -1) { params += `&${elem_type}_id=${obj_id}`; @@ -87,11 +111,10 @@ selectElement = function(obj_id, elem_type) { $(".christmas_action").hide(); } - // We use replaceState for refreshes to work as intended + // We use replaceState for refreshes to work as intended history.replaceState({}, document.title, base_url + params); } -const max_size = 1000; /** * This function compresses images that are too big by shrinking them and if * necessary compressing using JPEG @@ -99,11 +122,9 @@ const max_size = 1000; * @returns {Promise} the image as a promised blob (to allow * asynchronous calls) */ -const compressImage = async function(image) { - var canvas = document.createElement('canvas'); - - var width = image.width; - var height = image.height; +async function compressImage(image) { + let width = image.width; + let height = image.height; // calculate the width and height, constraining the proportions const min_width = Math.min(width, max_size); @@ -113,7 +134,7 @@ const compressImage = async function(image) { // resize the canvas and draw the image data into it canvas.width = Math.floor(width * factor); canvas.height = Math.floor(height * factor); - var ctx = canvas.getContext("2d"); + let ctx = canvas.getContext("2d"); ctx.drawImage(image, 0, 0, canvas.width, canvas.height); return await new Promise(resolve => ctx.canvas.toBlob(resolve, "image/jpeg")); @@ -128,7 +149,7 @@ const compressImage = async function(image) { * @returns {number} the index if found or -1, else */ Array.prototype.indexOfFile = function(name, size, type) { - for (var i = 0 ; i < this.length ; i++) { + for (let i = 0; i < this.length; i++) { const f = this[i]; if (f.name === name && f.size === size && f.type === type) { return i; @@ -154,7 +175,7 @@ Array.prototype.containsFile = function(name, size, type) { * @param size the size of the file * @param type the type of the file */ -const removeFile = function(name, size, type) { +function removeFile(name, size, type) { if (images_list.containsFile(name, size, type)) { const index = images_list.indexOfFile(name, size, type); images_list.splice(index, 1); @@ -168,7 +189,7 @@ const removeFile = function(name, size, type) { * @param size the size of the file * @param type the type of the file */ -const removeImage = function(name, size, type) { +function removeImage(name, size, type) { removeFile(name, size, type); document.getElementById(`${name}_${size}_${type}`).remove(); } @@ -176,25 +197,20 @@ const removeImage = function(name, size, type) { /** * Display the images contained in new_images inside the HTML page */ - - const hard_max_size_limit = 1e7; - const resize_limit = 2e5; - -const displayImages = function() { - const image_display = document.getElementById("image_display_table"); +function displayImages() { // We use the images stored in the new_images array - for (var i = 0 ; i < new_images.length ; i++) { + for (let i = 0; i < new_images.length; i++) { const original_image = new_images[i]; - if (original_image.size > hard_max_size_limit){ + if (original_image.size > hard_max_size_limit) { displayAlert("image_too_large"); continue; } const reader = new FileReader(); reader.onload = function(event) { - var image = new Image(); + let image = new Image(); image.src = event.target.result; image.onload = function(event) { if (original_image.size > resize_limit || original_image.type.valueOf() != "image/jpeg") { @@ -208,7 +224,7 @@ const displayImages = function() { images_comp = images_comp.concat(original_image); } - image_display.innerHTML += ` + image_display_table.innerHTML += `
  • × @@ -224,28 +240,22 @@ const displayImages = function() { new_images = []; } -// The images compressed -var images_comp = []; -// The list of images actually displayed inside the view -var images_list = []; -// The new non-duplicated images to add to the view -var new_images = []; /** * Handle the addition of new images and ignore the duplications * @param event the event containing the file, among other things */ -document.getElementById("file_selector").onchange = function(event) { - var input_images = event.target.files; +function updateImageDisplay(event) { + let input_images = event.target.files; // TODO CI-765: remove the following block to support multiple images - for (var i = 0 ; i < images_list.length ; i++) { + for (let i = 0; i < images_list.length; i++) { const file = images_list[i]; removeImage(file.name, file.size, file.type); } // TODO CI-765: end of block - for (var i = 0 ; i < input_images.length ; i++) { + for (let i = 0; i < input_images.length; i++) { const file = input_images[i]; const is_image = file.type.startsWith("image/"); @@ -269,15 +279,14 @@ document.getElementById("file_selector").onchange = function(event) { displayImages(); } -var loading = false; /** * Starts and end the loading of the type elements * @param type the type of elements to start or stop loading */ -const startStopLoading = function(type) { +function startStopLoading(type) { loading = !loading; $("button").attr('disabled', loading); - if(loading) { + if (loading) { document.getElementById(`${type}_normal`).style.display = "none"; document.getElementById(`${type}_loading`).style.display = ""; } else { @@ -293,14 +302,14 @@ const startStopLoading = function(type) { * @returns {Promise} return the entire method as a promise so that * we can send a letter directly if the user pressed the corresponding button */ -const createLetter = async function(preview=false, with_loading=true) { +async function createLetter(preview = false, with_loading = true) { return new Promise(function(resolve) { if (with_loading) { startStopLoading("preview"); } - var form_data = new FormData(); + let form_data = new FormData(); - form_data.append("letter-copy", document.getElementById('letter_content').value); + form_data.append("letter-copy", letter_content.value); form_data.append("selected-child", $('#child_local_id').text()); form_data.append("selected-letter-id", $('#template_id').text()); form_data.append("source", "website"); @@ -310,10 +319,10 @@ const createLetter = async function(preview=false, with_loading=true) { } // TODO CI-765: end of block - var xhr = new XMLHttpRequest(); - var url = `${window.location.origin}/mobile-app-api/correspondence/get_preview`; + let xhr = new XMLHttpRequest(); + let url = `${window.location.origin}/mobile-app-api/correspondence/get_preview`; xhr.open("POST", url, true); - xhr.onreadystatechange = function () { + xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (preview) { if (with_loading) { @@ -336,26 +345,26 @@ const createLetter = async function(preview=false, with_loading=true) { * This function takes care of sending a letter when the corresponding button * is clicked */ -const sendLetter = async function() { +async function sendLetter() { startStopLoading("sending"); - await createLetter(preview=false, with_loading=false); + await createLetter(preview = false, with_loading = false); - var json_data = JSON.parse(`{ + let json_data = JSON.parse(`{ "TemplateID": "${$('#template_id').text()}", "Need": "${$('#child_id').text()}" }`); - var xhr = new XMLHttpRequest(); - var url = `${window.location.origin}/mobile-app-api/correspondence/send_letter`; + let xhr = new XMLHttpRequest(); + let url = `${window.location.origin}/mobile-app-api/correspondence/send_letter`; xhr.open("POST", url, true); xhr.setRequestHeader("Content-Type", "application/json"); - xhr.onreadystatechange = function () { + xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 200) { // Empty images and text (to avoid duplicate) - document.getElementById("letter_content").value = "" - for (var i = 0 ; i < images_list.length ; i++) { - var image = images_list[i]; + letter_content.value = "" + for (let i = 0; i < images_list.length; i++) { + let image = images_list[i]; removeImage(image.name, image.size, image.type); } From 926e6f31c401edaa0c3351bac542c15defe2ab45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Margueron?= Date: Fri, 12 Nov 2021 13:23:26 +0100 Subject: [PATCH 05/12] CS-612 : Mycompassion - download photos - In compassion.child.pictures, the url yield by ".image_url" is not correct - To fix the issue, I used for the "single" download, the same method as for the other download methods (respectively: "multiple" and "all"). This method concist to download the image with an URL which depend on the ".id" and give it to the response --- website_compassion/controllers/my_account.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/website_compassion/controllers/my_account.py b/website_compassion/controllers/my_account.py index 277ffab0c..feaef0d60 100644 --- a/website_compassion/controllers/my_account.py +++ b/website_compassion/controllers/my_account.py @@ -30,6 +30,8 @@ from ..tools.image_compression import compress_big_images +IMG_URL = "https://erp.compassion.ch/web/image/compassion.child.pictures/{id}/fullshot/" + def _map_contracts(partner, mapping_val=None, sorting_val=None, filter_fun=lambda _: True): @@ -138,7 +140,7 @@ def _create_archive(images, archive_name): filename = path.basename(full_path) # Create file, write to archive and delete it from os - img_url = f'https://erp.compassion.ch/web/image/compassion.child.pictures/{img.id}/fullshot/' + img_url = IMG_URL.format(id=img.id) urlretrieve(img_url, filename) archive.write(filename, full_path) remove(filename) @@ -185,7 +187,7 @@ def _download_image(type, child_id=None, obj_id=None): # We get the extension and the binary content from URL ext = image.image_url.split(".")[-1] - data = urlopen(image.image_url).read() + data = urlopen(IMG_URL.format(id=image.id)).read() filename = f"{child.preferred_name}_{image.date}.{ext}" return request.make_response( From 1d4ed64a7475744d0b513106cb874522084ffa7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Margueron?= Date: Wed, 17 Nov 2021 16:43:47 +0100 Subject: [PATCH 06/12] CO-3829 : number of sponsorship on Mailchimp is wrong if many partner linked - add numspon in mass_mailing_contact which is the amount of sponsored children for a email address - use the partner with the most children sponsored as the mailing recipient --- .../models/mass_mailing_contact.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/mass_mailing_switzerland/models/mass_mailing_contact.py b/mass_mailing_switzerland/models/mass_mailing_contact.py index d3cf1ac47..2614c1107 100644 --- a/mass_mailing_switzerland/models/mass_mailing_contact.py +++ b/mass_mailing_switzerland/models/mass_mailing_contact.py @@ -69,6 +69,8 @@ class MassMailingContact(models.Model): sponsored_child_your_child = fields.Char(compute="_compute_sponsored_child_fields") pending_letter_child_names = fields.Char(compute="_compute_sponsored_child_fields") + numspons = fields.Integer(compute="_compute_sponsored_child_fields") + _sql_constraints = [( "unique_email", "unique(email)", "This mailing contact already exists" )] @@ -83,9 +85,10 @@ def _compute_sponsored_child_fields(self): for contact in self: partners = contact.partner_ids.with_context(lang=contact.partner_id.lang) # Allow option to take a child given in context, otherwise take + sponsored_child_ids = partners.mapped("sponsored_child_ids") # the sponsored children. - child = self.env.context.get("mailchimp_child", - partners.mapped("sponsored_child_ids")) + child = self.env.context.get("mailchimp_child", sponsored_child_ids) + contact.numspons = len(list(child)) if country_filter_id: child = child.filtered( lambda c: c.field_office_id.id == country_filter_id) @@ -228,7 +231,10 @@ def get_partner(self, email): """Override to fetch partner directly from relation if set.""" self.ensure_one() if self.partner_ids: - return self.partner_ids[0].id + partners = self.partner_ids.with_context(lang=self.partner_id.lang) + partners_by_child_count = [(p, len(list(p.mapped("sponsored_child_ids")))) for p in partners] + partners_by_child_count.sort(key=lambda x:-x[-1]) + return partners_by_child_count[0][0].id else: return self.env["res.partner"].search([ ("email", "=ilike", email), From dca8b3d1106064b320501a1dfa910f7b8801e3e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Margueron?= Date: Thu, 18 Nov 2021 09:21:03 +0100 Subject: [PATCH 07/12] CO-3829 It seems that the MERGE field for numbers (in this case Integer) does not handle the 0. I changed it to Char and Text respectively and it correctly show the 0 now --- mass_mailing_switzerland/models/mass_mailing_contact.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mass_mailing_switzerland/models/mass_mailing_contact.py b/mass_mailing_switzerland/models/mass_mailing_contact.py index 2614c1107..8fbb18465 100644 --- a/mass_mailing_switzerland/models/mass_mailing_contact.py +++ b/mass_mailing_switzerland/models/mass_mailing_contact.py @@ -69,7 +69,7 @@ class MassMailingContact(models.Model): sponsored_child_your_child = fields.Char(compute="_compute_sponsored_child_fields") pending_letter_child_names = fields.Char(compute="_compute_sponsored_child_fields") - numspons = fields.Integer(compute="_compute_sponsored_child_fields") + numspons = fields.Char(compute="_compute_sponsored_child_fields") _sql_constraints = [( "unique_email", "unique(email)", "This mailing contact already exists" From 876f82d2aa18573fdf66c510c433c52c0c2343e0 Mon Sep 17 00:00:00 2001 From: Emanuel Cino Date: Thu, 4 Nov 2021 11:44:04 +0100 Subject: [PATCH 08/12] CO-3752 Replace BVR Reports by QR-Bills --- .../models/partner_communication.py | 25 +- report_compassion/__manifest__.py | 4 +- .../controllers/payment_slip_controller.py | 3 - .../migrations/12.0.1.1.0/pre-migration.py | 37 ++ report_compassion/models/contract.py | 6 +- report_compassion/models/contract_group.py | 70 +- .../models/partner_communication.py | 32 +- report_compassion/models/report_bvr_fund.py | 4 - .../models/report_bvr_sponsorship.py | 1 - .../models/report_bvr_sponsorship_gift.py | 1 - report_compassion/report/a4_bvr.xml | 18 - report_compassion/report/bvr_fund.xml | 59 +- report_compassion/report/bvr_gift.xml | 166 +++-- report_compassion/report/bvr_layout.xml | 326 ++++------ report_compassion/report/bvr_sponsorship.xml | 353 +++++----- .../report/communication_mailing_bvr.xml | 226 ------- .../report/compassion_layout.xml | 10 +- report_compassion/report/paperformats.xml | 200 ++---- .../report/partner_communication.xml | 605 +++++++++--------- report_compassion/report/tax_receipt.xml | 56 +- report_compassion/static/img/bvr.jpg | Bin 50254 -> 0 bytes .../static/scss/report_swissqr.scss | 132 ++++ .../views/communication_job_view.xml | 3 +- .../generate_communication_wizard_view.xml | 1 - .../views/print_bvr_fund_view.xml | 2 - .../views/print_sponsorship_bvr_view.xml | 3 - .../views/print_sponsorship_gift_bvr_view.xml | 9 - .../wizards/generate_communication_wizard.py | 5 - report_compassion/wizards/print_bvr_fund.py | 15 - .../wizards/print_sponsorship_bvr.py | 24 +- .../wizards/print_sponsorship_gift_bvr.py | 20 +- 31 files changed, 967 insertions(+), 1449 deletions(-) create mode 100644 report_compassion/migrations/12.0.1.1.0/pre-migration.py delete mode 100644 report_compassion/report/a4_bvr.xml delete mode 100644 report_compassion/report/communication_mailing_bvr.xml delete mode 100644 report_compassion/static/img/bvr.jpg create mode 100644 report_compassion/static/scss/report_swissqr.scss diff --git a/partner_communication_switzerland/models/partner_communication.py b/partner_communication_switzerland/models/partner_communication.py index 5d0cf9148..961ad3674 100644 --- a/partner_communication_switzerland/models/partner_communication.py +++ b/partner_communication_switzerland/models/partner_communication.py @@ -354,31 +354,29 @@ def get_child_picture_attachment(self): res[name] = ("partner_communication_switzerland.child_picture", pdf) return res - def get_yearly_payment_slips_2bvr(self): - return self.get_yearly_payment_slips(bv_number=2) - - def get_yearly_payment_slips(self, bv_number=3): + def get_yearly_payment_slips(self): """ Attach payment slips - :param bv_number number of BV on a page (switch between 2BV/3BV page) :return: dict {attachment_name: [report_name, pdf_data]} """ self.ensure_one() - assert bv_number in (2, 3) sponsorships = self.get_objects() payment_mode_bvr = self.env.ref("sponsorship_switzerland.payment_mode_bvr") + pm_permanent = self.env.ref( + "sponsorship_switzerland.payment_mode_permanent_order") attachments = dict() # IF payment mode is BVR and partner is paying # attach sponsorship payment slips + # Year 2022 only: we send Permanent Orders again for QR-update! pay_bvr = sponsorships.filtered( - lambda s: s.payment_mode_id == payment_mode_bvr - and s.partner_id == self.partner_id + lambda s: s.payment_mode_id in (payment_mode_bvr, pm_permanent) + and s.partner_id == self.partner_id ) if pay_bvr and pay_bvr.must_pay_next_year(): today = date.today() date_start = today.replace(today.year + 1, 1, 1) date_stop = date_start.replace(month=12, day=31) - report_name = f"report_compassion.{bv_number}bvr_sponsorship" + report_name = f"report_compassion.2bvr_sponsorship" data = { "doc_ids": pay_bvr.ids, "date_start": date_start, @@ -387,7 +385,7 @@ def get_yearly_payment_slips(self, bv_number=3): } pdf = self._get_pdf_from_data( data, self.env.ref( - f"report_compassion.report_{bv_number}bvr_sponsorship") + f"report_compassion.report_2bvr_sponsorship") ) attachments.update({_("sponsorship payment slips.pdf"): [report_name, pdf]}) # Attach gifts for correspondents @@ -396,18 +394,17 @@ def get_yearly_payment_slips(self, bv_number=3): if sponsorship.mapped(sponsorship.send_gifts_to) == self.partner_id: pays_gift += sponsorship if pays_gift: - nb_gifts = 4 if bv_number == 2 else 3 product_ids = self.env['product.product'].search([ - ('default_code', 'in', GIFT_REF[:nb_gifts]) + ('default_code', 'in', [GIFT_REF[0]] + GIFT_REF[2:4]) ]).ids - report_name = f"report_compassion.{bv_number}bvr_gift_sponsorship" + report_name = f"report_compassion.2bvr_gift_sponsorship" data = { "doc_ids": pays_gift.ids, "product_ids": product_ids } pdf = self._get_pdf_from_data( data, self.env.ref( - f"report_compassion.report_{bv_number}bvr_gift_sponsorship"), + f"report_compassion.report_2bvr_gift_sponsorship"), ) attachments.update({_("sponsorship gifts.pdf"): [report_name, pdf]}) return attachments diff --git a/report_compassion/__manifest__.py b/report_compassion/__manifest__.py index aa91ec7d0..da7263170 100644 --- a/report_compassion/__manifest__.py +++ b/report_compassion/__manifest__.py @@ -29,7 +29,7 @@ # pylint: disable=C8101 { "name": "Compassion CH PDF-Qweb Reports", - "version": "12.0.1.0.8", + "version": "12.0.1.1.0", "category": "Other", "author": "Compassion CH", "license": "AGPL-3", @@ -49,11 +49,9 @@ "report/partner_communication.xml", "report/bvr_gift.xml", "report/anniversary_card.xml", - "report/a4_bvr.xml", "report/bvr_fund.xml", "report/tax_receipt.xml", "report/ending_sponsorship_certificate.xml", - "report/communication_mailing_bvr.xml", "views/print_sponsorship_bvr_view.xml", "views/print_sponsorship_gift_bvr_view.xml", "views/print_childpack_view.xml", diff --git a/report_compassion/controllers/payment_slip_controller.py b/report_compassion/controllers/payment_slip_controller.py index 966e26446..f2ddcc71d 100644 --- a/report_compassion/controllers/payment_slip_controller.py +++ b/report_compassion/controllers/payment_slip_controller.py @@ -54,7 +54,6 @@ def get_payment_slip(self, partner_uuid, fund_id, sponsorship_id=None, raise BadRequest() wizard = request.env["print.bvr.fund"].sudo().create({ "product_id": product.id, - "draw_background": True, "pdf": True, "amount": fund_amount }).with_context(active_ids=partner.ids) @@ -65,7 +64,6 @@ def get_payment_slip(self, partner_uuid, fund_id, sponsorship_id=None, if product.categ_id == sponsorship: wizard = request.env["print.sponsorship.bvr"].sudo().create({ "paper_format": "report_compassion.bvr_sponsorship", - "draw_background": True, "pdf": True }).with_context(active_ids=sponsorship_id, active_model="recurring.contract") @@ -79,7 +77,6 @@ def get_payment_slip(self, partner_uuid, fund_id, sponsorship_id=None, "project_gift": gift_index == 3, "graduation_gift": gift_index == 4, "paper_format": "report_compassion.bvr_gift_sponsorship", - "draw_background": True, "pdf": True } wizard = request.env["print.sponsorship.gift.bvr"].sudo().create( diff --git a/report_compassion/migrations/12.0.1.1.0/pre-migration.py b/report_compassion/migrations/12.0.1.1.0/pre-migration.py new file mode 100644 index 000000000..05d9a69e0 --- /dev/null +++ b/report_compassion/migrations/12.0.1.1.0/pre-migration.py @@ -0,0 +1,37 @@ +from openupgradelib import openupgrade + + +@openupgrade.migrate() +def migrate(env, version): + if not version: + return + + # Update A4 Compassion to A4 l10n_ch + env.cr.execute(""" + UPDATE report_paperformat_parameter + SET paperformat_id = 21 + WHERE paperformat_id IN (19,9,8,7) + """) + env.cr.execute(""" + UPDATE partner_communication_job + SET config_id = 1 + WHERE config_id IN (29) + """) + openupgrade.set_xml_ids_noupdate_value( + env, "report_compassion", + ["report_bvr_fund", "bvr_fund", "report_bvr_fund_document", + "report_bvr_gift_sponsorship", "report_2bvr_gift_sponsorship", + "bvr_gift_sponsorship_2bvr", "2bvr_gift_sponsorship", + "report_bvr_sponsorship_gift_document_2bvr", "bvr_gift_sponsorship", + "report_bvr_sponsorship_gift_document", "report_sponsorship_2bvr_top_slip", + "report_sponsorship_2bvr_bottom_slip", "report_bvr_sponsorship", + "report_2bvr_sponsorship", "report_bvr_due", "bvr_sponsorship", + "report_bvr_sponsorship_document", "report_gift_document", + "bvr_sponsorship_2bvr", "2bvr_sponsorship", + "report_bvr_sponsorship_document_2bvr", "report_gift_document_2bvr", "bvr_due", + "report_bvr_due_document", "report_partner_communication_mailing_bvr", + "partner_communication_mailing_bvr", "paperformat_childpack", + "paperformat_mini_childpack", "paperformat_a4_childpack", + "paperformat_anniversary_card", "tax_receipt_report", "tax_receipt"], + False + ) diff --git a/report_compassion/models/contract.py b/report_compassion/models/contract.py index d3dc03610..84d2c43f5 100644 --- a/report_compassion/models/contract.py +++ b/report_compassion/models/contract.py @@ -97,11 +97,7 @@ def get_gift_communication(self, product): @api.multi def generate_bvr_reference(self, product): self.ensure_one() - return self.env["l10n_ch.payment_slip"]._space( - self.env["generate.gift.wizard"] - .generate_bvr_reference(self, product) - .lstrip("0") - ) + return self.env["generate.gift.wizard"].generate_bvr_reference(self, product) @api.model def get_sponsorship_gift_products(self): diff --git a/report_compassion/models/contract_group.py b/report_compassion/models/contract_group.py index 48eec7f8b..8a355060a 100644 --- a/report_compassion/models/contract_group.py +++ b/report_compassion/models/contract_group.py @@ -8,49 +8,22 @@ # ############################################################################## import logging -import math from datetime import datetime from babel.dates import format_date from odoo import api, models, fields, _ -from odoo.exceptions import Warning as odooWarning -from odoo.tools import mod10r +from odoo.exceptions import UserError logger = logging.getLogger(__name__) -COMPASSION_BVR = "01-44443-7" +COMPASSION_QRR = "CH2430808007681434347" class ContractGroup(models.Model): _inherit = ["recurring.contract.group", "translatable.model"] _name = "recurring.contract.group" - scan_line = fields.Char(compute="_compute_scan_line") - format_ref = fields.Char(compute="_compute_format_ref") - - @api.multi - def _compute_scan_line(self): - """ Generate a scan line for contract group. """ - acc_number = self.get_company_bvr_account() - for group in self.filtered("bvr_reference"): - group.scan_line = self.get_scan_line(acc_number, group.bvr_reference) - - @api.multi - def compute_scan_line(self, start, stop, sponsorships): - """ Generate a scan line for contract group. """ - self.ensure_one() - acc_number = self.get_company_bvr_account() - amount = self._get_amount(start, stop, sponsorships) - return self.get_scan_line(acc_number, self.bvr_reference, amount) - - @api.multi - def _compute_format_ref(self): - slip_obj = self.env["l10n_ch.payment_slip"] - for group in self: - ref = group.bvr_reference or group.compute_partner_bvr_ref() - group.format_ref = slip_obj._space(ref.lstrip("0")) - @api.multi def get_months(self, months, sponsorships): """ @@ -69,7 +42,7 @@ def get_months(self, months, sponsorships): if open_invoice: first_invoice_date = open_invoice.replace(day=1) else: - raise odooWarning(_("No open invoice found !")) + raise UserError(_("No open invoice found !")) for i, month in enumerate(months): if isinstance(month, str): @@ -79,7 +52,7 @@ def get_months(self, months, sponsorships): # check if first invoice is after last month if first_invoice_date > months[-1]: - raise odooWarning(_(f"First invoice is after Date Stop")) + raise UserError(_(f"First invoice is after Date Stop")) # Only keep unpaid months valid_months = [ @@ -120,7 +93,7 @@ def get_communication(self, start, stop, sponsorships): """ self.ensure_one() payment_mode = self.with_context(lang="en_US").payment_mode_id - amount = self._get_amount(start, stop, sponsorships) + amount = self.get_amount(start, stop, sponsorships) valid = sponsorships number_sponsorship = len(sponsorships) date_start = fields.Date.to_date(start) @@ -166,37 +139,12 @@ def get_communication(self, start, stop, sponsorships): ) @api.model - def get_scan_line(self, account, reference, amount=False): - """ Generate a scan line given the reference """ - if amount: - line = "01" - decimal_amount, int_amount = math.modf(amount) - str_amount = ( - str(int(int_amount)) + str(int(decimal_amount * 100)).rjust(2, "0") - ).rjust(10, "0") - line += str_amount - line = mod10r(line) - else: - line = "042" - line += ">" - line += reference.replace(" ", "").rjust(27, "0") - line += "+ " - account_components = account.split("-") - bank_identifier = ( - f"{account_components[0]}" - f"{account_components[1].rjust(6, '0')}" - f"{account_components[2]}" - ) - line += bank_identifier - line += ">" - return line - - @api.model - def get_company_bvr_account(self): + def get_company_qrr_account(self): """ Utility to find the bvr account of the company. """ - return COMPASSION_BVR + return self.env["res.partner.bank"].search([ + ('acc_number', '=', COMPASSION_QRR)]) - def _get_amount(self, start, stop, sponsorships): + def get_amount(self, start, stop, sponsorships): self.ensure_one() amount = sum(sponsorships.mapped("total_amount")) months = int(stop.split("-")[1]) - int(start.split("-")[1]) + 1 diff --git a/report_compassion/models/partner_communication.py b/report_compassion/models/partner_communication.py index 9d73ee38b..471949602 100644 --- a/report_compassion/models/partner_communication.py +++ b/report_compassion/models/partner_communication.py @@ -21,11 +21,7 @@ class PartnerCommunication(models.Model): ########################################################################## # FIELDS # ########################################################################## - product_id = fields.Many2one("product.product", "A4 + payment slip", readonly=False) - preprinted = fields.Boolean( - help="Enable if you print on a payment slip that already has company " - "information printed on it." - ) + product_id = fields.Many2one("product.product", "QR Bill for fund", readonly=False) display_pp = fields.Boolean( string="Display PP", help="If not set, the PP is not printed upper the address.", @@ -48,36 +44,27 @@ def send(self): ) bvr_both = self.filtered(lambda j: j.send_mode == "both" and j.product_id) - if bvr_both: - origin = self.env.context.get("origin") + if bvr_both and self.env.context.get("origin") == "both_email": for bvr in bvr_both: - if origin == "both_email" or ( - origin == "both_print" - and bvr.report_id != self.env.ref( - "report_compassion.report_a4_bvr") - ): - # email part - self._put_bvr_in_attachments(bvr, background=origin == "both_email") + # email part + self._put_bvr_in_attachments(bvr) super(PartnerCommunication, bvr_both).send() if bvr_to_send: for bvr in bvr_to_send: - self._put_bvr_in_attachments(bvr, background=True) + self._put_bvr_in_attachments(bvr) super(PartnerCommunication, bvr_to_send).send() if bvr_to_print: - for bvr in bvr_to_print: - if bvr.report_id.report_name != "report_compassion.a4_bvr": - self._put_bvr_in_attachments(bvr, background=False) super(PartnerCommunication, bvr_to_print).send() return super( PartnerCommunication, self - bvr_both - bvr_to_print - bvr_to_send ).send() - def _put_bvr_in_attachments(self, bvr, background): - pdf_download = self._generate_pdf_data(bvr, background) + def _put_bvr_in_attachments(self, bvr): + pdf_download = self._generate_pdf_data(bvr) return self._create_and_add_attachment(bvr, pdf_download) def _create_and_add_attachment(self, bvr, datas): @@ -95,13 +82,12 @@ def _create_and_add_attachment(self, bvr, datas): bvr.write({"attachment_ids": [(4, comm_attachment.id)]}) return bvr - def _generate_pdf_data(self, bvr, background): + def _generate_pdf_data(self, bvr): data = { "doc_ids": bvr.id, "product_id": bvr.product_id.id, - "background": background, } - report_ref = self.env.ref("report_compassion.report_a4_bvr") + report_ref = self.env.ref("report_compassion.report_compassion_qr_slip") pdf_data = report_ref.with_context( must_skip_send_to_printer=True ).render_qweb_pdf(bvr.id, data=data)[0] diff --git a/report_compassion/models/report_bvr_fund.py b/report_compassion/models/report_bvr_fund.py index f56e2f510..3ef5705bf 100644 --- a/report_compassion/models/report_bvr_fund.py +++ b/report_compassion/models/report_bvr_fund.py @@ -43,10 +43,6 @@ def _get_report_values(self, docids, data=None): _("You must give a product in data to print this report.") ) data["product_id"] = product_id - if "background" not in data: - # By default, prepare a report with background - data["background"] = True - data["preprinted"] = False if not docids and data["doc_ids"]: docids = data["doc_ids"] diff --git a/report_compassion/models/report_bvr_sponsorship.py b/report_compassion/models/report_bvr_sponsorship.py index 68b1e1e3e..7cd9aa841 100644 --- a/report_compassion/models/report_bvr_sponsorship.py +++ b/report_compassion/models/report_bvr_sponsorship.py @@ -43,7 +43,6 @@ def _get_default_data(self): return { "date_start": print_bvr_obj.default_start(), "date_stop": print_bvr_obj.default_stop(), - "preprinted": False, } @api.model diff --git a/report_compassion/models/report_bvr_sponsorship_gift.py b/report_compassion/models/report_bvr_sponsorship_gift.py index 631072daf..ce2929e26 100644 --- a/report_compassion/models/report_bvr_sponsorship_gift.py +++ b/report_compassion/models/report_bvr_sponsorship_gift.py @@ -38,7 +38,6 @@ def _get_default_data(self): "product_ids": self.env["recurring.contract"] .get_sponsorship_gift_products() .ids, - "preprinted": False, } @api.model diff --git a/report_compassion/report/a4_bvr.xml b/report_compassion/report/a4_bvr.xml deleted file mode 100644 index f45c8ccf6..000000000 --- a/report_compassion/report/a4_bvr.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - diff --git a/report_compassion/report/bvr_fund.xml b/report_compassion/report/bvr_fund.xml index f73a55258..0ace4c70d 100644 --- a/report_compassion/report/bvr_fund.xml +++ b/report_compassion/report/bvr_fund.xml @@ -1,40 +1,37 @@ - - - - - + + + + - - - - + + diff --git a/report_compassion/report/bvr_gift.xml b/report_compassion/report/bvr_gift.xml index 8e5459751..b6e43817e 100644 --- a/report_compassion/report/bvr_gift.xml +++ b/report_compassion/report/bvr_gift.xml @@ -1,114 +1,98 @@ - - - - - - + + + + + - - - - + + + + - - - - - - - - + - - - + + + diff --git a/report_compassion/report/bvr_layout.xml b/report_compassion/report/bvr_layout.xml index 6bc492b1f..7a28ab951 100644 --- a/report_compassion/report/bvr_layout.xml +++ b/report_compassion/report/bvr_layout.xml @@ -1,213 +1,133 @@ - - - - + - - - + + diff --git a/report_compassion/report/bvr_sponsorship.xml b/report_compassion/report/bvr_sponsorship.xml index 08aa8cc8e..3546f3410 100644 --- a/report_compassion/report/bvr_sponsorship.xml +++ b/report_compassion/report/bvr_sponsorship.xml @@ -1,235 +1,168 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - + - - + + + + + diff --git a/report_compassion/report/communication_mailing_bvr.xml b/report_compassion/report/communication_mailing_bvr.xml deleted file mode 100644 index 486e7c89d..000000000 --- a/report_compassion/report/communication_mailing_bvr.xml +++ /dev/null @@ -1,226 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/report_compassion/report/compassion_layout.xml b/report_compassion/report/compassion_layout.xml index 78cadd7a9..904ca71b9 100644 --- a/report_compassion/report/compassion_layout.xml +++ b/report_compassion/report/compassion_layout.xml @@ -3,11 +3,13 @@ - + /* Allow custom style for each report */ + + + +