diff --git a/donation_certificate/__init__.py b/donation_certificate/__init__.py new file mode 100644 index 000000000..082fa1746 --- /dev/null +++ b/donation_certificate/__init__.py @@ -0,0 +1,4 @@ +# Copyright +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import models +from . import wizard diff --git a/donation_certificate/__manifest__.py b/donation_certificate/__manifest__.py new file mode 100644 index 000000000..c2b69ca99 --- /dev/null +++ b/donation_certificate/__manifest__.py @@ -0,0 +1,27 @@ +# Copyright +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Donation Certificate", + "summary": "Donation Certificate", + "version": "16.0.1.0.0", + "category": "Custom", + "website": "https://www.qubiq.es", + "author": "Brenda Fernández Alayo", + "license": "AGPL-3", + "application": True, + "installable": True, + "depends": ['report_py3o', 'donation', 'donation_base', 'mail'], + "data": [ + "security/ir.model.access.csv", + "data/mail_template.xml", + "data/donation_sequence.xml", + 'data/py3o_certificate_template.xml', + 'views/donation_certificate_send_views.xml', + 'views/tax_receipt_donation.xml', + 'views/donation_send_cert_button.xml', + 'views/py3o_certificate_report.xml', + 'wizard/tax_receipt_annual_create_view.xml', + 'wizard/tax_receipt_print_view.xml' + ], +} diff --git a/donation_certificate/data/donation_sequence.xml b/donation_certificate/data/donation_sequence.xml new file mode 100644 index 000000000..2b76b9590 --- /dev/null +++ b/donation_certificate/data/donation_sequence.xml @@ -0,0 +1,12 @@ + + + + + Donation + donation.donation + DON-%(range_year)s- + 4 + + + + \ No newline at end of file diff --git a/donation_certificate/data/mail_template.xml b/donation_certificate/data/mail_template.xml new file mode 100644 index 000000000..43ac78c09 --- /dev/null +++ b/donation_certificate/data/mail_template.xml @@ -0,0 +1,44 @@ + + + + + + Donation: Certificate + + + {{ object.company_id.email }} + {{ object.partner_id.email }} + Donation Certificate + Donation Certificate + +
+

+ Dear + Donor name, +
+
+ We want to express our sincere gratitude for your generous donation of + $ 143,750.00 + to our organization. This contribution is a testament to your commitment and trust in our work. +
+
+ With this collaboration you help us move forward with our mission. We sincerely appreciate your support. +
+
+ Cordially, +
+
+ + -- +
+ Association name +
+

+
+
+ + {{ object.partner_id.lang }} + Donation Certificate +
+
+
diff --git a/donation_certificate/data/py3o_certificate_template.xml b/donation_certificate/data/py3o_certificate_template.xml new file mode 100644 index 000000000..b257f0cb4 --- /dev/null +++ b/donation_certificate/data/py3o_certificate_template.xml @@ -0,0 +1,28 @@ + + + + + Donation Certificate ES + odt + report/PDA34_Certificat_de_donacions_ES.odt + + + + Donation Certificate CAT + odt + report/PDA34_Certificat_de_donacions_CAT.odt + + + + Donation Certificate FR + odt + report/PDA34_Certificat_de_donacions_FR.odt + + + + Donation Certificate EN + odt + report/PDA34_Certificat_de_donacions_EN.odt + + + diff --git a/donation_certificate/models/__init__.py b/donation_certificate/models/__init__.py new file mode 100644 index 000000000..bf2ad5dc1 --- /dev/null +++ b/donation_certificate/models/__init__.py @@ -0,0 +1,4 @@ +from . import py3o_report +from . import py3o_template +from . import donation +from . import donation_tax_receipt diff --git a/donation_certificate/models/donation.py b/donation_certificate/models/donation.py new file mode 100644 index 000000000..db341dac5 --- /dev/null +++ b/donation_certificate/models/donation.py @@ -0,0 +1,70 @@ +from odoo import _, fields, models +from odoo.exceptions import ValidationError +from odoo.tools import get_lang + + +class Donation(models.Model): + _inherit = "donation.donation" + + is_certificate_send = fields.Boolean( + help="This field automatically becomes active when " + "the thanks letter has been send.", + ) + attachment_ids = fields.One2many( + 'ir.attachment', + 'res_id', + domain=[('res_model', '=', 'donation.donation')], + string='Attachments' + ) + + def action_send_certificate(self): + self.ensure_one() + if not self.tax_receipt_id and self.tax_receipt_option == 'annual': + raise ValidationError(_("Annual receipt has not been generated yet.")) + self.write({"is_certificate_send": True}) + template = self.env.ref(self._get_mail_template(), raise_if_not_found=False) + lang = False + if template: + lang = template._render_lang(self.ids)[self.id] + if not lang: + lang = get_lang(self.env).code + compose_form = self.env.ref('donation_certificate.donation_certificate_send_wizard_form', raise_if_not_found=False) + ctx = dict( + default_model='donation.tax.receipt', + default_res_id=self.tax_receipt_id.id, + default_res_model='donation.tax.receipt', + default_use_template=bool(template), + default_template_id=template and template.id or False, + default_composition_mode='comment', + mark_invoice_as_sent=True, + default_email_layout_xmlid="mail.mail_notification_layout_with_responsible_signature", + force_email=True, + active_ids=self.tax_receipt_id.ids, + ) + report_action = { + 'name': _('Send Certificate'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'donation.certificate.send', + 'views': [(compose_form.id, 'form')], + 'view_id': compose_form.id, + 'target': 'new', + 'context': ctx, + } + return report_action + + def action_print_tax_receipt(self): + """ Print the certificate and mark it as sent, so that we can see more + easily the next step of the workflow + """ + self.filtered(lambda r: not r.is_certificate_send).write({'is_certificate_send': True}) + return self.env.ref('donation_certificate.donation_certificate_report_py3o').report_action(self) + + def _get_mail_template(self): + """ + :return: the correct mail template based on the current move type + """ + return ( + 'donation_certificate.email_template_donation_certificate' + ) diff --git a/donation_certificate/models/donation_tax_receipt.py b/donation_certificate/models/donation_tax_receipt.py new file mode 100644 index 000000000..bb0fe87f9 --- /dev/null +++ b/donation_certificate/models/donation_tax_receipt.py @@ -0,0 +1,90 @@ +from odoo import _, fields, models, api +from odoo.tools import get_lang + + +class DonationTaxReceipt(models.Model): + _inherit = "donation.tax.receipt" + + is_certificate_send = fields.Boolean( + help="This field automatically becomes active when " + "the thanks letter has been send.", + ) + attachment_ids = fields.One2many('ir.attachment', 'res_id', domain=[('res_model', '=', 'donation.tax.receipt')], string='Attachments') + amount_donation = fields.Monetary(tracking=True, compute="_compute_amounts") + amount_donation_in_kind_consu = fields.Monetary(tracking=True, compute="_compute_amounts") + amount_donation_in_kind_service = fields.Monetary(tracking=True, compute="_compute_amounts") + + @api.depends('donation_ids') + def _compute_amounts(self): + for rec in self: + amount_donation = 0 + amount_donation_in_kind_consu = 0 + amount_donation_in_kind_service = 0 + for donation in rec.donation_ids: + for line in donation.line_ids: + if line.product_id.detailed_type == 'donation': + amount_donation = amount_donation + line.amount + elif line.product_id.detailed_type == 'donation_in_kind_consu': + amount_donation_in_kind_consu = amount_donation_in_kind_consu + line.amount + else: + amount_donation_in_kind_service = amount_donation_in_kind_service + line.amount + rec.amount_donation = round(amount_donation, 2) + rec.amount_donation_in_kind_consu = round(amount_donation_in_kind_consu, 2) + rec.amount_donation_in_kind_service = round(amount_donation_in_kind_service, 2) + + def action_send_tax_receipt(self): + self.write({"is_certificate_send": True}) + template = self.env.ref(self._get_mail_template(), raise_if_not_found=False) + compose_form = self.env.ref('donation_certificate.donation_certificate_send_wizard_form', raise_if_not_found=False) + ctx = dict( + default_model='donation.tax.receipt', + default_res_model='donation.tax.receipt', + default_use_template=bool(template), + default_template_id=template and template.id or False, + default_composition_mode='comment', + mark_invoice_as_sent=True, + default_email_layout_xmlid="mail.mail_notification_layout_with_responsible_signature", + force_email=True, + active_ids=self.ids, + ) + report_action = { + 'name': _('Send Certificate'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'donation.certificate.send', + 'views': [(compose_form.id, 'form')], + 'view_id': compose_form.id, + 'target': 'new', + 'context': ctx, + } + return report_action + + def get_ref_report_name(self, lang): + report_names = { + 'es_ES': "donation_certificate.donation_certificate_report_py3o_ES", + 'ca_ES': "donation_certificate.donation_certificate_report_py3o_CAT", + 'fr_FR': "donation_certificate.donation_certificate_report_py3o_FR", + 'en_GB': "donation_certificate.donation_certificate_report_py3o_EN" + } + + return report_names.get(lang, "donation_certificate.donation_certificate_report_py3o_EN") + + def action_print_tax_receipt(self): + """ Print the certificate and mark it as sent, so that we can see more + easily the next step of the workflow + """ + today = fields.Date.context_today(self) + self.write({"print_date": today}) + self.filtered(lambda inv: not inv.is_certificate_send).write({'is_certificate_send': True}) + lang = get_lang(self.env).code + report_action_py3o = self.get_ref_report_name(lang) + return self.env.ref(report_action_py3o).report_action(self) + + def _get_mail_template(self): + """ + :return: the correct mail template based on the current move type + """ + return ( + 'donation_certificate.email_template_donation_certificate' + ) diff --git a/donation_certificate/models/py3o_report.py b/donation_certificate/models/py3o_report.py new file mode 100644 index 000000000..5af5a5a40 --- /dev/null +++ b/donation_certificate/models/py3o_report.py @@ -0,0 +1,18 @@ +# Copyright 2013 XCG Consulting (http://odoo.consulting) +# Copyright 2016 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +from odoo import models + + +class Py3oReport(models.TransientModel): + _inherit = "py3o.report" + + def _merge_results(self, reports_path): + self.ensure_one() + filetype = self.ir_actions_report_id.py3o_filetype + if not reports_path: + return False, False + if len(reports_path) == 1: + return reports_path[0], filetype + else: + return self._zip_results(reports_path), "zip" diff --git a/donation_certificate/models/py3o_template.py b/donation_certificate/models/py3o_template.py new file mode 100644 index 000000000..b94c0e01d --- /dev/null +++ b/donation_certificate/models/py3o_template.py @@ -0,0 +1,34 @@ +from odoo import fields, models, api +from base64 import b64encode +# import sys +import pkg_resources +# import os + + +class Py3oTemplate(models.Model): + _inherit = "py3o.template" + + py3o_template_fallback = fields.Char( + "Fallback", + size=128, + help=( + "If the user does not provide a template this will be used " + "it should be a relative path to root of YOUR module " + "or an absolute path on your server." + ), + ) + + @api.model_create_multi + def create(self, vals_list): + module = "donation_certificate" + vals = vals_list[0] + if vals['py3o_template_fallback']: + flbk_filename = pkg_resources.resource_filename( + "odoo.addons.%s" % module, vals['py3o_template_fallback'] + ) + with open(flbk_filename, "rb") as tmpl: + tmpl_data = b64encode(tmpl.read()) + vals['py3o_template_data'] = tmpl_data + # if not rec.py3o_template_data: + # rec.py3o_template_data = False + return super().create([vals]) diff --git a/donation_certificate/report/PDA34_Certificat_de_donacions.odt b/donation_certificate/report/PDA34_Certificat_de_donacions.odt new file mode 100644 index 000000000..a8f19eb27 Binary files /dev/null and b/donation_certificate/report/PDA34_Certificat_de_donacions.odt differ diff --git a/donation_certificate/report/PDA34_Certificat_de_donacions_CAT.odt b/donation_certificate/report/PDA34_Certificat_de_donacions_CAT.odt new file mode 100644 index 000000000..a8f19eb27 Binary files /dev/null and b/donation_certificate/report/PDA34_Certificat_de_donacions_CAT.odt differ diff --git a/donation_certificate/report/PDA34_Certificat_de_donacions_EN.odt b/donation_certificate/report/PDA34_Certificat_de_donacions_EN.odt new file mode 100644 index 000000000..90ae41029 Binary files /dev/null and b/donation_certificate/report/PDA34_Certificat_de_donacions_EN.odt differ diff --git a/donation_certificate/report/PDA34_Certificat_de_donacions_ES.odt b/donation_certificate/report/PDA34_Certificat_de_donacions_ES.odt new file mode 100644 index 000000000..2c99a2b1a Binary files /dev/null and b/donation_certificate/report/PDA34_Certificat_de_donacions_ES.odt differ diff --git a/donation_certificate/report/PDA34_Certificat_de_donacions_FR.odt b/donation_certificate/report/PDA34_Certificat_de_donacions_FR.odt new file mode 100644 index 000000000..0710ff783 Binary files /dev/null and b/donation_certificate/report/PDA34_Certificat_de_donacions_FR.odt differ diff --git a/donation_certificate/security/ir.model.access.csv b/donation_certificate/security/ir.model.access.csv new file mode 100644 index 000000000..7583f62bd --- /dev/null +++ b/donation_certificate/security/ir.model.access.csv @@ -0,0 +1,7 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_donation_certificate_send,donation.certificate.send,donation_certificate.model_donation_certificate_send,base.group_user,1,1,1,1 +access_donation_donation,donation.donation,donation.model_donation_donation,base.group_user,1,1,1,1 +access_donation_tax_receipt,donation.tax.receipt,model_donation_tax_receipt,base.group_user,1,1,1,1 +access_tax_receipt_annual_create,tax.receipt.annual.create,model_tax_receipt_annual_create,base.group_user,1,1,1,1 +access_py3o_template,py3o.template,report_py3o.model_py3o_template,base.group_user,1,1,1,1 +access_donation_tax_receipt_print,donation.tax.receipt.print,donation_base.model_donation_tax_receipt_print,base.group_user,1,1,1,1 diff --git a/donation_certificate/static/description/icon.png b/donation_certificate/static/description/icon.png new file mode 100644 index 000000000..e8f0fcf0f Binary files /dev/null and b/donation_certificate/static/description/icon.png differ diff --git a/donation_certificate/views/donation_certificate_send_views.xml b/donation_certificate/views/donation_certificate_send_views.xml new file mode 100644 index 000000000..1070813b8 --- /dev/null +++ b/donation_certificate/views/donation_certificate_send_views.xml @@ -0,0 +1,67 @@ + + + + donation.certificate.send.form + donation.certificate.send + + +
+ + + + + +
+ + + +
+ Preview as a PDF +
+
+
+ + + +
+
+ +
+
+
+ + + +
+ + + + + + + + +
+
+
+ +
+
+
diff --git a/donation_certificate/views/donation_send_cert_button.xml b/donation_certificate/views/donation_send_cert_button.xml new file mode 100644 index 000000000..c67f1f933 --- /dev/null +++ b/donation_certificate/views/donation_send_cert_button.xml @@ -0,0 +1,51 @@ + + + + + donation.form.view.form.inherit + donation.donation + + + + + + + + + + + + + + Send Certificate + + + + code + + if records: + action = records.action_send_tax_receipt() + + + + + donation.donation.tree.inherited + donation.donation + + + + + + + + diff --git a/donation_certificate/views/py3o_certificate_report.xml b/donation_certificate/views/py3o_certificate_report.xml new file mode 100644 index 000000000..69948e3ea --- /dev/null +++ b/donation_certificate/views/py3o_certificate_report.xml @@ -0,0 +1,61 @@ + + + + + Donation Tax Receipt ES + donation.tax.receipt + py3o_donation_certificate_ES + py3o + pdf + + 'Fiscal_receipt-'+(object.number or '').replace('/','') + + + + + + Donation Tax Receipt CAT + donation.tax.receipt + py3o_donation_certificate_CAT + py3o + pdf + + 'Fiscal_receipt-'+(object.number or '').replace('/','') + + + + + Donation Tax Receipt FR + donation.tax.receipt + py3o_donation_certificate_FR + py3o + pdf + + 'Fiscal_receipt-'+(object.number or '').replace('/','') + + + + + Donation Tax Receipt EN + donation.tax.receipt + py3o_donation_certificate_EN + py3o + pdf + + 'Fiscal_receipt-'+(object.number or '').replace('/','') + + + + + py3o.template.view.form.inherit + py3o.template + + + + + + + + + + diff --git a/donation_certificate/views/tax_receipt_donation.xml b/donation_certificate/views/tax_receipt_donation.xml new file mode 100644 index 000000000..0b816fd58 --- /dev/null +++ b/donation_certificate/views/tax_receipt_donation.xml @@ -0,0 +1,25 @@ + + + + + donation.tax.receipt.view.form.inherit + donation.tax.receipt + + +
+
+
+
+ + + donation.tax.receipt.view.form.inherit + donation.tax.receipt + + + + 1 + + + +
diff --git a/donation_certificate/wizard/__init__.py b/donation_certificate/wizard/__init__.py new file mode 100644 index 000000000..9dae7f968 --- /dev/null +++ b/donation_certificate/wizard/__init__.py @@ -0,0 +1,3 @@ +from . import donation_certificate_send +from . import tax_receipt_annual_create +from . import tax_receipt_print diff --git a/donation_certificate/wizard/donation_certificate_send.py b/donation_certificate/wizard/donation_certificate_send.py new file mode 100644 index 000000000..6eaad7256 --- /dev/null +++ b/donation_certificate/wizard/donation_certificate_send.py @@ -0,0 +1,164 @@ +from odoo import api, fields, models, _ +from odoo.addons.mail.wizard.mail_compose_message import _reopen +from odoo.exceptions import UserError +from odoo.tools.misc import get_lang + + +class DonationCertificateSend(models.TransientModel): + _name = 'donation.certificate.send' + _inherits = {'mail.compose.message': 'composer_id'} + _description = 'Donation Certificate Send' + + is_email = fields.Boolean('Email', default=lambda self: self.env.company.invoice_is_email) + donation_without_email = fields.Text(compute='_compute_donation_without_email', string='Donation(s) that will not be sent') + is_print = fields.Boolean('Print', default=lambda self: self.env.company.invoice_is_print) + printed = fields.Boolean('Is Printed', default=False) + tax_receipt_ids = fields.Many2many('donation.tax.receipt', 'donation_tax_receipt_donation_certificate_send_rel', string='Donations Tax Receipt') + composer_id = fields.Many2one('mail.compose.message', required=True, ondelete='cascade') + template_id = fields.Many2one('mail.template', 'Use template', domain="[('model', '=', 'donation.tax.receipt'), ]", default=lambda self: self._get_report_template()) + + def _get_report_template(self): + template = self.env.ref('donation_certificate.email_template_donation_certificate', raise_if_not_found=False) + return template.id or False + + @api.model + def default_get(self, fields): + res = super(DonationCertificateSend, self).default_get(fields) + res_ids = self._context.get('active_ids') + + tax_receipts = self.env['donation.tax.receipt'].browse(res_ids) + if not tax_receipts: + raise UserError(_("You can only send invoices.")) + + composer = self.env['mail.compose.message'].create({ + 'composition_mode': 'comment' if len(res_ids) == 1 else 'mass_mail', + }) + res.update({ + 'tax_receipt_ids': res_ids, + 'composer_id': composer.id, + }) + return res + + @api.onchange('tax_receipt_ids') + def _compute_composition_mode(self): + for wizard in self: + wizard.composer_id.composition_mode = 'comment' if len(wizard.tax_receipt_ids) == 1 else 'mass_mail' + + def get_ref_report_name(self, wizard_template_id_lang): + lang = list(wizard_template_id_lang.values())[0] + report_names = { + 'es_ES': "donation_certificate.donation_certificate_report_py3o_ES", + 'ca_ES': "donation_certificate.donation_certificate_report_py3o_CAT", + 'fr_FR': "donation_certificate.donation_certificate_report_py3o_FR", + 'en_GB': "donation_certificate.donation_certificate_report_py3o_EN" + } + report_name = report_names.get(lang, "donation_certificate.donation_certificate_report_py3o_EN") + + return self.env.ref(report_name, raise_if_not_found=False).id + + @api.onchange('template_id') + def onchange_template_id(self): + for wizard in self: + if wizard.composer_id: + # Hay que mirar porque el archivo se llama igual que el ID + # Get different report depending on the lang + lang_object = wizard.template_id.lang + render_model = wizard.template_id.render_model + res_ids = wizard.tax_receipt_ids.ids + lang = wizard.template_id._render_template(lang_object, render_model, res_ids) + + wizard.template_id.report_template = self.get_ref_report_name(lang) + wizard.composer_id.template_id = wizard.template_id.id + # Get different report depending on the lang + wizard.composer_id.template_id.report_template = self.get_ref_report_name(lang) + wizard._compute_composition_mode() + wizard.composer_id._onchange_template_id_wrapper() + + @api.onchange('is_email') + def onchange_is_email(self): + if self.is_email: + res_ids = self._context.get('active_ids') + data = { + 'composition_mode': 'comment' if len(res_ids) == 1 else 'mass_mail', + 'template_id': self.template_id.id + } + if not self.composer_id: + self.composer_id = self.env['mail.compose.message'].create(data) + else: + self.composer_id.write(data) + self._compute_composition_mode() + self.composer_id._onchange_template_id_wrapper() + + @api.onchange('is_email') + def _compute_donation_without_email(self): + for wizard in self: + if wizard.is_email and len(wizard.tax_receipt_ids) > 1: + tax_receipts = self.env['donation.tax.receipt'].search([ + ('id', 'in', self.env.context.get('active_ids')), + ('partner_id.email', '=', False) + ]) + if tax_receipts: + wizard.donation_without_email = "%s\n%s" % ( + _("The following donation(s) will not be sent by email, because the customers don't have email address."), + "\n".join([i.number for i in tax_receipts])) + else: + wizard.donation_without_email = False + else: + wizard.donation_without_email = False + + def _send_email(self): + if self.is_email: + self.composer_id.with_context(no_new_invoice=True, + mail_notify_author=self.env.user.partner_id in self.composer_id.partner_ids, + mailing_document_based=True, + )._action_send_mail() + for tax_receipt in self.tax_receipt_ids: + prioritary_attachments = tax_receipt.attachment_ids.filtered(lambda x: x.mimetype.endswith('pdf')) + if prioritary_attachments: + tax_receipt.with_context(tracking_disable=True).sudo().write({'message_main_attachment_id': prioritary_attachments[0].id}) + + def _print_certificate_document(self): + """ to override for each type of models that will use this composer.""" + self.ensure_one() + action = self.tax_receipt_ids.action_print_tax_receipt() + + action.update({'close_on_report_download': True}) + return action + + def send_and_print_action(self): + self.ensure_one() + # Send the mails in the correct language by splitting the ids per lang. + # This should ideally be fixed in mail_compose_message, so when a fix is made there this whole commit should be reverted. + # basically self.body (which could be manually edited) extracts self.template_id, + # which is then not translated for each customer. + if self.composition_mode == 'mass_mail' and self.template_id: + active_ids = self.env.context.get('active_ids', self.res_id) + active_records = self.env[self.model].browse(active_ids) + langs = set(active_records.mapped('partner_id.lang')) + for lang in langs: + active_ids_lang = active_records.filtered(lambda r: r.partner_id.lang == lang).ids + self_lang = self.with_context(active_ids=active_ids_lang, lang=get_lang(self.env, lang).code) + self_lang.onchange_template_id() + self_lang._send_email() + else: + active_record = self.env[self.model].browse(self.res_id) + lang = get_lang(self.env, active_record.partner_id.lang).code + self.with_context(lang=lang)._send_email() + if self.is_print: + return self._print_document() + return {'type': 'ir.actions.act_window_close'} + + def save_as_template(self): + self.ensure_one() + self.composer_id.action_save_as_template() + self.template_id = self.composer_id.template_id.id + action = _reopen(self, self.id, self.model, context=self._context) + action.update({'name': _('Send Certificate')}) + return action + + def _print_document(self): + """ to override for each type of models that will use this composer.""" + self.ensure_one() + action = self.tax_receipt_ids.action_print_tax_receipt() + action.update({'close_on_report_download': True}) + return action diff --git a/donation_certificate/wizard/tax_receipt_annual_create.py b/donation_certificate/wizard/tax_receipt_annual_create.py new file mode 100644 index 000000000..c328811da --- /dev/null +++ b/donation_certificate/wizard/tax_receipt_annual_create.py @@ -0,0 +1,18 @@ +from odoo import models + + +class TaxReceiptAnnualCreate(models.TransientModel): + _inherit = "tax.receipt.annual.create" + """ + Clase creada para cuando se me pida cambiar la vista o funcionamiento de + 'Crear recibos anuales', de momento no hace nada. + """ + + # @api.model + # def _prepare_annual_tax_receipt(self, partner, partner_dict): + # # wdb.set_trace() + # super()._prepare_annual_tax_receipt(partner, partner_dict) + + # def generate_annual_receipts(self): + # # wdb.set_trace() + # super().generate_annual_receipts() diff --git a/donation_certificate/wizard/tax_receipt_annual_create_view.xml b/donation_certificate/wizard/tax_receipt_annual_create_view.xml new file mode 100644 index 000000000..3c2e04257 --- /dev/null +++ b/donation_certificate/wizard/tax_receipt_annual_create_view.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/donation_certificate/wizard/tax_receipt_print.py b/donation_certificate/wizard/tax_receipt_print.py new file mode 100644 index 000000000..70a2c53ea --- /dev/null +++ b/donation_certificate/wizard/tax_receipt_print.py @@ -0,0 +1,46 @@ +# Copyright 2014-2021 Barroux Abbey (http://www.barroux.org) +# Copyright 2014-2021 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, fields, models +from odoo.exceptions import UserError +from odoo.tools import get_lang +import wdb + + +class DonationTaxReceiptPrint(models.TransientModel): + _inherit = "donation.tax.receipt.print" + _description = "Print Donation Tax Receipts" + + lang = fields.Selection( + [("es_ES", "Spanish"), ("ca_ES", "Catalan"), ("fr_FR", "French"), ("en_GB", "English")], + # readonly=True, + # copy=False, + # default="draft", + # index=True, + # tracking=True, + ) + + def print_receipts(self): + self.ensure_one() + if not self.receipt_ids: + raise UserError(_("There are no tax receipts to print.")) + today = fields.Date.context_today(self) + self.receipt_ids.write({"print_date": today}) + if self.lang: + lang = self.lang + else: + lang = get_lang(self.env).code + report_action_py3o = self.get_ref_report_name(lang) + return self.env.ref(report_action_py3o).report_action(self.receipt_ids) + + def get_ref_report_name(self, lang): + report_names = { + 'es_ES': "donation_certificate.donation_certificate_report_py3o_ES", + 'ca_ES': "donation_certificate.donation_certificate_report_py3o_CAT", + 'fr_FR': "donation_certificate.donation_certificate_report_py3o_FR", + 'en_GB': "donation_certificate.donation_certificate_report_py3o_EN" + } + + return report_names.get(lang, "donation_certificate.donation_certificate_report_py3o_EN") diff --git a/donation_certificate/wizard/tax_receipt_print_view.xml b/donation_certificate/wizard/tax_receipt_print_view.xml new file mode 100644 index 000000000..fd19b4a56 --- /dev/null +++ b/donation_certificate/wizard/tax_receipt_print_view.xml @@ -0,0 +1,40 @@ + + + + + donation_tax_receipt_print.form.inherit + donation.tax.receipt.print + + + + 1 + + + + + + + + + + + + + + + + + + + + diff --git a/donation_letter/__init__.py b/donation_letter/__init__.py new file mode 100644 index 000000000..082fa1746 --- /dev/null +++ b/donation_letter/__init__.py @@ -0,0 +1,4 @@ +# Copyright +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import models +from . import wizard diff --git a/donation_letter/__manifest__.py b/donation_letter/__manifest__.py new file mode 100644 index 000000000..07b66164f --- /dev/null +++ b/donation_letter/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Donation Thanks Letter", + "summary": "Donation Thanks Letter", + "version": "16.0.1.0.0", + "category": "Custom", + "website": "https://www.qubiq.es", + "author": "Brenda Fernández Alayo", + "license": "AGPL-3", + "installable": True, + "depends": ['donation', 'account', 'mail', 'donation_base'], + "data": [ + "security/ir.model.access.csv", + "data/mail_template.xml", + "views/donation_letter_send_views.xml", + "views/donation_send_button.xml", + ], +} diff --git a/donation_letter/data/mail_template.xml b/donation_letter/data/mail_template.xml new file mode 100644 index 000000000..8a6c9bdc4 --- /dev/null +++ b/donation_letter/data/mail_template.xml @@ -0,0 +1,49 @@ + + + + + Donation: Thanks Letter by email + + + {{ object.company_id.email }} + {{ object.partner_id.email }} + Thank you for your donation + Letter of appreciation + +
+

+ Dear + + Brandon Freeman + ( + Azure Interior + ), + + + Donor name, + +
+
+ We want to express our sincere gratitude for your generous donation of + $ 143,750.00 + to our organization. This contribution is a testament to your commitment and trust in our work. +
+
+ With this collaboration you help us move forward with our mission. We sincerely appreciate your support. +
+
+ Cordially, +
+
+ + -- +
+ Association name +
+

+
+
+ {{ object.partner_id.lang }} +
+
+
diff --git a/donation_letter/models/__init__.py b/donation_letter/models/__init__.py new file mode 100644 index 000000000..b1847a5ab --- /dev/null +++ b/donation_letter/models/__init__.py @@ -0,0 +1 @@ +from . import donation diff --git a/donation_letter/models/donation.py b/donation_letter/models/donation.py new file mode 100644 index 000000000..6505c5e15 --- /dev/null +++ b/donation_letter/models/donation.py @@ -0,0 +1,72 @@ +from odoo import _, fields, models +from odoo.tools import get_lang + + +class Donation(models.Model): + _inherit = "donation.donation" + + is_thanks_letter_send = fields.Boolean( + default=False, + help="This field automatically becomes active when " + "the thanks letter has been send.", + ) + attachment_ids = fields.One2many( + comodel_name='ir.attachment', + inverse_name='res_id', + domain=[('res_model', '=', 'donation.donation')], + string='Attachments' + ) + + def action_send_thanks(self): + template = self.env.ref(self._get_mail_template(), raise_if_not_found=False) + lang = False + + if len(self) == 1: + lang = template._render_lang(self.ids)[self.id] + + if not lang: + lang = get_lang(self.env).code + + compose_form = self.env.ref('donation_letter.donation_letter_send_wizard_form', raise_if_not_found=False) + + ctx = { + 'default_model': 'donation.donation', + 'default_res_model': 'donation.donation', + 'default_use_template': bool(template), + 'default_template_id': template and template.id or False, + 'default_composition_mode': 'comment', + 'mark_invoice_as_sent': True, + 'default_email_layout_xmlid': "mail.mail_notification_layout_with_responsible_signature", + 'force_email': True, + 'active_ids': self.ids, + } + + report_action = { + 'name': _('Send Thanks Letter'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'donation.letter.send', + 'views': [(compose_form.id, 'form')], + 'view_id': compose_form.id, + 'target': 'new', + 'context': ctx, + } + + return report_action + + + def action_donation_print(self): + """ Print the donation and mark it as sent, so that we can see more + easily the next step of the workflow + """ + self.filtered(lambda inv: not inv.is_thanks_letter_send).write({'is_thanks_letter_send': True}) + return self.env.ref('donation.report_thanks').report_action(self) + + def _get_mail_template(self): + """ + :return: the correct mail template based on the current move type + """ + return ( + 'donation_letter.email_template_thanks_letter' + ) diff --git a/donation_letter/security/ir.model.access.csv b/donation_letter/security/ir.model.access.csv new file mode 100644 index 000000000..c9a16cb5e --- /dev/null +++ b/donation_letter/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_donation_letter_send,donation.letter.send,donation_letter.model_donation_letter_send,base.group_user,1,1,1,1 +access_donation_donation,donation.donation,donation.model_donation_donation,base.group_user,1,1,1,1 diff --git a/donation_letter/static/description/icon.png b/donation_letter/static/description/icon.png new file mode 100644 index 000000000..e8f0fcf0f Binary files /dev/null and b/donation_letter/static/description/icon.png differ diff --git a/donation_letter/views/donation_letter_send_views.xml b/donation_letter/views/donation_letter_send_views.xml new file mode 100644 index 000000000..bb931ecc7 --- /dev/null +++ b/donation_letter/views/donation_letter_send_views.xml @@ -0,0 +1,67 @@ + + + + donation.letter.send.form + donation.letter.send + + +
+ + + + + +
+ + + +
+ Preview as a PDF +
+
+
+ + + +
+
+ +
+
+
+ + + +
+ + + + + + + + +
+
+
+ +
+
+
diff --git a/donation_letter/views/donation_send_button.xml b/donation_letter/views/donation_send_button.xml new file mode 100644 index 000000000..6e84f3471 --- /dev/null +++ b/donation_letter/views/donation_send_button.xml @@ -0,0 +1,35 @@ + + + + donation.form.view.form.inherit + donation.donation + + + + + + + + 1 + + + + + + Send Thanks Letter + + + code + + if records: + action = records.action_send_thanks() + + + diff --git a/donation_letter/wizard/__init__.py b/donation_letter/wizard/__init__.py new file mode 100644 index 000000000..434c36ddc --- /dev/null +++ b/donation_letter/wizard/__init__.py @@ -0,0 +1 @@ +from . import donation_letter_send diff --git a/donation_letter/wizard/donation_letter_send.py b/donation_letter/wizard/donation_letter_send.py new file mode 100644 index 000000000..844bac6d0 --- /dev/null +++ b/donation_letter/wizard/donation_letter_send.py @@ -0,0 +1,133 @@ +from odoo import api, fields, models, _ +from odoo.addons.mail.wizard.mail_compose_message import _reopen +from odoo.exceptions import UserError +from odoo.tools.misc import get_lang + + +class DonationLeterSend(models.TransientModel): + _name = 'donation.letter.send' + _inherits = {'mail.compose.message': 'composer_id'} + _description = 'Donation Thanks Letter Send' + + is_email = fields.Boolean('Email', default=lambda self: self.env.company.invoice_is_email) + donation_without_email = fields.Text(compute='_compute_donation_without_email', string='donation(s) that will not be sent') + is_print = fields.Boolean('Print', default=lambda self: self.env.company.invoice_is_print) + printed = fields.Boolean('Is Printed', default=False) + donation_ids = fields.Many2many('donation.donation', 'donation_donation_donation_letter_send_rel', string='Donations') + composer_id = fields.Many2one('mail.compose.message', string='Composer', required=True, ondelete='cascade') + template_id = fields.Many2one('mail.template', 'Use template', domain="[('model', '=', 'donation.donation')]") + + @api.model + def default_get(self, fields): + res = super(DonationLeterSend, self).default_get(fields) + res_ids = self._context.get('active_ids') + + donations = self.env['donation.donation'].browse(res_ids) + if not donations: + raise UserError(_("You can only send invoices.")) + + composer = self.env['mail.compose.message'].create({ + 'composition_mode': 'comment' if len(res_ids) == 1 else 'mass_mail', + }) + res.update({ + 'donation_ids': res_ids, + 'composer_id': composer.id, + }) + return res + + @api.onchange('donation_ids') + def _compute_composition_mode(self): + for wizard in self: + wizard.composer_id.composition_mode = 'comment' if len(wizard.donation_ids) == 1 else 'mass_mail' + + @api.onchange('template_id') + def onchange_template_id(self): + for wizard in self: + if wizard.composer_id: + wizard.composer_id.template_id = wizard.template_id.id + wizard._compute_composition_mode() + wizard.composer_id._onchange_template_id_wrapper() + + @api.onchange('is_email') + def onchange_is_email(self): + if self.is_email: + res_ids = self._context.get('active_ids') + if not self.composer_id: + self.composer_id = self.env['mail.compose.message'].create({ + 'composition_mode': 'comment' if len(res_ids) == 1 else 'mass_mail', + 'template_id': self.template_id.id + }) + else: + self.composer_id.composition_mode = 'comment' if len(res_ids) == 1 else 'mass_mail' + self.composer_id.template_id = self.template_id.id + self._compute_composition_mode() + self.composer_id._onchange_template_id_wrapper() + + @api.onchange('is_email') + def _compute_donation_without_email(self): + for wizard in self: + if wizard.is_email and len(wizard.donation_ids) > 1: + donations = self.env['donation.donation'].search([ + ('id', 'in', self.env.context.get('active_ids')), + ('partner_id.email', '=', False) + ]) + if donations: + wizard.donation_without_email = "%s\n%s" % ( + _("The following donation(s) will not be sent by email, because the customers don't have email address."), + "\n".join([i.name for i in donations])) + else: + wizard.donation_without_email = False + else: + wizard.donation_without_email = False + + def _send_email(self): + if self.is_email: + # with_context : we don't want to reimport the file we just exported. + self.composer_id.with_context(no_new_invoice=True, + mail_notify_author=self.env.user.partner_id in self.composer_id.partner_ids, + mailing_document_based=True, + )._action_send_mail() + for donation in self.donation_ids: + prioritary_attachments = donation.attachment_ids.filtered(lambda x: x.mimetype.endswith('pdf')) + if prioritary_attachments: + donation.with_context(tracking_disable=True).sudo().write({'message_main_attachment_id': prioritary_attachments[0].id}) + donation.sudo().write({"is_thanks_letter_send": True}) + + def _print_document(self): + """ to override for each type of models that will use this composer.""" + self.ensure_one() + action = self.donation_ids.action_donation_print() + + action.update({'close_on_report_download': True}) + return action + + def send_and_print_action(self): + self.ensure_one() + # Send the mails in the correct language by splitting the ids per lang. + # This should ideally be fixed in mail_compose_message, so when a fix is made there this whole commit should be reverted. + # basically self.body (which could be manually edited) extracts self.template_id, + # which is then not translated for each customer. + if self.composition_mode == 'mass_mail' and self.template_id: + active_ids = self.env.context.get('active_ids', self.res_id) + active_records = self.env[self.model].browse(active_ids) + langs = set(active_records.mapped('partner_id.lang')) + for lang in langs: + active_ids_lang = active_records.filtered(lambda r: r.partner_id.lang == lang).ids + self_lang = self.with_context(active_ids=active_ids_lang, lang=get_lang(self.env, lang).code) + self_lang.onchange_template_id() + self_lang._send_email() + else: + active_record = self.env[self.model].browse(self.res_id) + lang = get_lang(self.env, active_record.partner_id.lang).code + self.with_context(lang=lang)._send_email() + if self.is_print: + return self._print_document() + return {'type': 'ir.actions.act_window_close'} + + def save_as_template(self): + self.ensure_one() + self.composer_id.action_save_as_template() + self.template_id = self.composer_id.template_id.id + action = _reopen(self, self.id, self.model, context=self._context) + action.update({'name': _('Send Thanks Letter')}) + return action diff --git a/donation_summary/__init__.py b/donation_summary/__init__.py new file mode 100644 index 000000000..082fa1746 --- /dev/null +++ b/donation_summary/__init__.py @@ -0,0 +1,4 @@ +# Copyright +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import models +from . import wizard diff --git a/donation_summary/__manifest__.py b/donation_summary/__manifest__.py new file mode 100644 index 000000000..b9d1c7738 --- /dev/null +++ b/donation_summary/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Donation Summary", + "summary": "Make summary donations", + "version": "16.0.1.0.0", + "category": "Custom", + "website": "https://www.qubiq.es", + "author": "Brenda Fernández Alayo", + "license": "AGPL-3", + "depends": ['donation', 'donation_base', 'mail', 'donation_certificate'], + "data": [ + "security/ir.model.access.csv", + 'report/donation_summary.xml', + 'report/donation_summary_templates.xml', + 'views/donation_summary_views.xml', + 'views/donation_summary_send_views.xml', + 'data/mail_template.xml' + ], +} diff --git a/donation_summary/data/mail_template.xml b/donation_summary/data/mail_template.xml new file mode 100644 index 000000000..53081b886 --- /dev/null +++ b/donation_summary/data/mail_template.xml @@ -0,0 +1,45 @@ + + + + + Donation: Send Summary + + + {{ object.company_id.email }} + {{ object.partner_id.email }} + Summary of Your Donations + Donation Summary + +
+

Dear + Brandon Freeman,
+
We want to express our sincere gratitude for your suport and generosity towards our mission. +
We are pleased to inform you that attached you will find a detailed summary of all + the donations you have made from 01/01/2023 + to 31/12/2023. +
+
This summary includes the date and amount of each contribution, allowing you to have a + clear overview of your impact on our organization. +
+
The total amount of your donations during this period add up to $ + 143,750.00. +
+
+
If you would like more details or have any questions about your donation history, + please fell free to contact us.
+
Cordially,
+
+ + Association name +

+
+
+ {{ object.partner_id.lang }} + + RDS_{{ (object.name or '') }} +
+
+
\ No newline at end of file diff --git a/donation_summary/models/__init__.py b/donation_summary/models/__init__.py new file mode 100644 index 000000000..c450dab14 --- /dev/null +++ b/donation_summary/models/__init__.py @@ -0,0 +1,2 @@ +from . import donation +from . import donation_summary diff --git a/donation_summary/models/donation.py b/donation_summary/models/donation.py new file mode 100644 index 000000000..5165ed4a4 --- /dev/null +++ b/donation_summary/models/donation.py @@ -0,0 +1,9 @@ +from odoo import fields, models + + +class Donation(models.Model): + _inherit = "donation.donation" + + donation_summary_id = fields.Many2one( + string="Donation Summary", + comodel_name='donation.summary') diff --git a/donation_summary/models/donation_summary.py b/donation_summary/models/donation_summary.py new file mode 100644 index 000000000..7b33a4f9f --- /dev/null +++ b/donation_summary/models/donation_summary.py @@ -0,0 +1,229 @@ +from odoo import _, fields, models, api +from odoo.exceptions import UserError +from odoo.tools.misc import get_lang +import datetime + + +class DonationSummary(models.Model): + _name = "donation.summary" + _inherit = ["mail.thread", "mail.activity.mixin"] + + @api.model + def _default_end_date(self): + return datetime.date(fields.Date.context_today(self).year - 1, 12, 31) + + @api.model + def _default_start_date(self): + return datetime.date(fields.Date.context_today(self).year - 1, 1, 1) + + name = fields.Char(compute="_compute_name") + + is_summary_send = fields.Boolean( + help="This field automatically becomes active when " + "the summary has been send.", + ) + partner_id = fields.Many2one( + "res.partner", + string="Donor", + index=True, + states={"sent": [("readonly", True)]}, + tracking=True, + ondelete="restrict", + domain="[('donation_ids', '!=', False)]" + ) + start_date = fields.Date( + required=True, default=lambda self: self._default_start_date(), + states={"sent": [("readonly", True)]} + ) + end_date = fields.Date( + required=True, default=lambda self: self._default_end_date(), + states={"sent": [("readonly", True)]} + ) + + donation_ids = fields.One2many( + "donation.donation", + "donation_summary_id", + string="Related Donations", + compute="_compute_donation_ids", + required=True, + states={"sent": [("readonly", True)]}, + ) + + state = fields.Selection( + [("draft", "Draft"), ("sent", "Sent")], + readonly=True, + copy=False, + default="draft", + index=True, + tracking=True, + ) + company_id = fields.Many2one( + "res.company", + string="Company", + ondelete="cascade", + default=lambda self: self.env.company, + ) + amount_total = fields.Monetary( + tracking=True, + compute="_compute_amounts", + states={"sent": [("readonly", True)]}, + ) + currency_id = fields.Many2one( + "res.currency", + required=True, + states={"sent": [("readonly", True)]}, + tracking=True, + ondelete="restrict", + default=lambda self: self.env.company.currency_id, + ) + attachment_ids = fields.One2many( + 'ir.attachment', + 'res_id', + domain=[('res_model', '=', 'donation.summary')], + string='Attachments' + ) + + def _compute_name(self): + for rec in self: + rec.name = (rec.partner_id.name).replace(" ", "") + "_" + str(rec.start_date).replace("-", "") + "_" + str(rec.end_date).replace("-", "") if rec.partner_id else "" + + def _prepare_donation_summary(self, existing_donation_ids): + vals = { + "partner_id": existing_donation_ids.partner_id.id, + "start_date": self.start_date, + "end_date": self.end_date, + "state": 'draft', + "is_summary_send": False + } + return vals + + def massive_creation(self): + d_sum = self.env["donation.summary"] + donation_summary_ids = [] + for partner in self.donation_ids.partner_id.ids: + donations = self.donation_ids.filtered(lambda donation: donation.partner_id.id == partner) + values = self._prepare_donation_summary(donations) + donation_summary_ids.append(values) + if not len(donation_summary_ids): + raise UserError(_("No annual tax receipt to generate")) + + self.unlink() + + for new_summary_vals in donation_summary_ids: + d_sum.create(new_summary_vals) + + # Redireccionar a la vista tree + return { + 'name': _('Donations Summary'), + 'type': 'ir.actions.act_window', + 'res_model': 'donation.summary', + 'view_type': 'tree', + 'view_mode': 'tree,form', + 'target': 'main', + 'context': self.env.context, + } + + @api.depends('donation_ids') + def _compute_amounts(self): + for rec in self: + amount = 0 + for don in rec.donation_ids: + amount += don.amount_total_company_currency + rec.amount_total = amount + + @api.depends('partner_id', 'start_date', 'end_date') + def _compute_donation_ids(self): + for rec in self: + domain = [('state', '=', 'done'), ('donation_summary_id', '=', False)] + if rec.partner_id: + domain += [ + ('partner_id', '=', rec.partner_id.id) + ] + if rec.start_date and rec.end_date: + domain += [ + ('donation_date', '>=', rec.start_date), + ('donation_date', '<=', rec.end_date) + ] + donations = self.env["donation.donation"].search(domain) + if not donations: + raise UserError("There must be at least one donation in the summary.") + rec.donation_ids = donations + + def massive_send_summary(self): + template = self.env.ref(self._get_mail_template(), raise_if_not_found=False) + compose_form = self.env.ref('donation_summary.donation_summary_send_wizard_form', raise_if_not_found=False) + ctx = dict( + default_model='donation.summary', + # For the sake of consistency we need a default_res_model if + # default_res_id is set. Not renaming default_model as it can + # create many side-effects. + default_res_model='donation.summary', + default_use_template=bool(template), + default_template_id=template and template.id or False, + default_composition_mode='comment', + mark_invoice_as_sent=True, + default_email_layout_xmlid="mail.mail_notification_layout_with_responsible_signature", + force_email=True, + active_ids=self.ids, + ) + report_action = { + 'name': _('Send Donation Summary'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'donation.summary.send', + 'views': [(compose_form.id, 'form')], + 'view_id': compose_form.id, + 'target': 'new', + 'context': ctx, + } + return report_action + + def send_summary(self): + template = self.env.ref(self._get_mail_template(), raise_if_not_found=False) + lang = False + if template: + lang = template._render_lang(self.ids)[self.id] + if not lang: + lang = get_lang(self.env).code + compose_form = self.env.ref('donation_summary.donation_summary_send_wizard_form', raise_if_not_found=False) + ctx = dict( + default_model='donation.summary', + default_res_id=self.id, + default_res_model='donation.summary', + default_use_template=bool(template), + default_template_id=template and template.id or False, + default_composition_mode='comment', + mark_invoice_as_sent=True, + default_email_layout_xmlid="mail.mail_notification_layout_with_responsible_signature", + force_email=True, + active_ids=self.ids, + ) + report_action = { + 'name': _('Send Donation Summary'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'donation.summary.send', + 'views': [(compose_form.id, 'form')], + 'view_id': compose_form.id, + 'target': 'new', + 'context': ctx, + } + + return report_action + + def _get_mail_template(self): + """ + :return: the correct mail template based on the current move type + """ + return ( + 'donation_summary.email_template_donation_summary' + ) + + def action_print_donation_summary(self): + """ Print the certificate and mark it as sent, so that we can see more + easily the next step of the workflow + """ + self.filtered(lambda sum: not sum.is_summary_send).write({'is_summary_send': True}) + return self.env.ref('donation_summary.report_donation_summary').report_action(self) diff --git a/donation_summary/report/donation_summary.xml b/donation_summary/report/donation_summary.xml new file mode 100644 index 000000000..6c54cc69d --- /dev/null +++ b/donation_summary/report/donation_summary.xml @@ -0,0 +1,13 @@ + + + + Request for Donation Summary + donation.summary + qweb-pdf + donation_summary.report_donationsummary + donation_summary.report_donationsummary + 'Request for Donation Summary - %s' %(object.partner_id.name) + + report + + \ No newline at end of file diff --git a/donation_summary/report/donation_summary_templates.xml b/donation_summary/report/donation_summary_templates.xml new file mode 100644 index 000000000..cbed6a9dd --- /dev/null +++ b/donation_summary/report/donation_summary_templates.xml @@ -0,0 +1,81 @@ + + + + + + \ No newline at end of file diff --git a/donation_summary/security/ir.model.access.csv b/donation_summary/security/ir.model.access.csv new file mode 100644 index 000000000..e1e9d2149 --- /dev/null +++ b/donation_summary/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_donation_summary,donation.summary,donation_summary.model_donation_summary,base.group_user,1,1,1,1 +access_donation_donation,donation.donation,donation.model_donation_donation,base.group_user,1,1,1,1 +access_donation_summary_send,donation.summary.send,donation_summary.model_donation_summary_send,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/donation_summary/static/description/icon.png b/donation_summary/static/description/icon.png new file mode 100644 index 000000000..e8f0fcf0f Binary files /dev/null and b/donation_summary/static/description/icon.png differ diff --git a/donation_summary/views/donation_summary_send_views.xml b/donation_summary/views/donation_summary_send_views.xml new file mode 100644 index 000000000..069a90d08 --- /dev/null +++ b/donation_summary/views/donation_summary_send_views.xml @@ -0,0 +1,68 @@ + + + + donation.summary.send.form + donation.summary.send + + +
+ + + + + +
+ + + +
+ Preview as a PDF +
+
+
+ + + +
+
+ +
+
+
+ + + +
+ + + + + + + + + +
+
+
+ +
+
+
diff --git a/donation_summary/views/donation_summary_views.xml b/donation_summary/views/donation_summary_views.xml new file mode 100644 index 000000000..8802fd519 --- /dev/null +++ b/donation_summary/views/donation_summary_views.xml @@ -0,0 +1,114 @@ + + + + Donations Summary + donation.summary + tree,form + +

+ Create a new custom field in your donation summary record +

+

+ Odoo helps you easily track all custom fields related to a donation summary. +

+
+
+ + + donation.summary.view.tree + donation.summary + + + + + + + + + + + + + + + donation.summary.view.form + donation.summary + +
+
+
+ + + + + + + + + + +
+ + + +
+
+
+
+ + + donation.summary.view.tree.inherit + donation.summary + + + +
+
+
+
+
+ + donation.summary.view..search + donation.summary + + + + + + + + +
\ No newline at end of file diff --git a/donation_summary/wizard/__init__.py b/donation_summary/wizard/__init__.py new file mode 100644 index 000000000..e3da7d70a --- /dev/null +++ b/donation_summary/wizard/__init__.py @@ -0,0 +1 @@ +from . import donation_summary_send diff --git a/donation_summary/wizard/donation_summary_send.py b/donation_summary/wizard/donation_summary_send.py new file mode 100644 index 000000000..71949f648 --- /dev/null +++ b/donation_summary/wizard/donation_summary_send.py @@ -0,0 +1,211 @@ +from odoo import api, fields, models, _ +from odoo.addons.mail.wizard.mail_compose_message import _reopen +from odoo.exceptions import UserError +from odoo.tools.misc import get_lang + + +class DonationSummarySend(models.TransientModel): + _name = "donation.summary.send" + _inherits = {"mail.compose.message": "composer_id"} + _description = "Donation Summary Send" + + is_email = fields.Boolean( + "Email", default=lambda self: self.env.company.invoice_is_email + ) + summary_without_email = fields.Text( + compute="_compute_donation_without_email", + string="Summary(s) that will not be sent", + ) + is_print = fields.Boolean( + "Print", default=lambda self: self.env.company.invoice_is_print + ) + printed = fields.Boolean("Is Printed", default=False) + donation_summary_ids = fields.Many2many( + "donation.summary", + "donation_summary_donation_summary_send_rel", + string="Donations Summary", + ) + composer_id = fields.Many2one( + "mail.compose.message", required=True, ondelete="cascade" + ) + template_id = fields.Many2one( + "mail.template", + "Use template", + default=lambda self: self._get_report_template(), + ) + # template_id = fields.Many2one('mail.template', 'Use template', domain="[('model', '=', 'donation.summary'), ]", default=lambda self: self._get_report_template()) + + def _get_report_template(self): + template = self.env.ref( + "donation_summary.email_template_donation_summary", raise_if_not_found=False + ) + return template.id or False + + @api.model + def default_get(self, fields): + res = super(DonationSummarySend, self).default_get(fields) + res_ids = self._context.get("active_ids") + + donation_summary = self.env["donation.summary"].browse(res_ids) + if not donation_summary: + raise UserError(_("You can only send invoices.")) + + composer = self.env["mail.compose.message"].create( + { + "composition_mode": "comment" if len(res_ids) == 1 else "mass_mail", + } + ) + res.update( + { + "donation_summary_ids": res_ids, + "composer_id": composer.id, + } + ) + return res + + @api.onchange("donation_summary_ids") + def _compute_composition_mode(self): + for wizard in self: + wizard.composer_id.composition_mode = ( + "comment" if len(wizard.donation_summary_ids) == 1 else "mass_mail" + ) + + def get_ref_report_name(self, wizard_template_id_lang): + lang = ( + list(wizard_template_id_lang.values())[0] + if wizard_template_id_lang + else "XX" + ) + report_names = { + "es_ES": "donation_certificate.donation_certificate_report_py3o_ES", + "ca_ES": "donation_certificate.donation_certificate_report_py3o_CAT", + "fr_FR": "donation_certificate.donation_certificate_report_py3o_FR", + "en_GB": "donation_certificate.donation_certificate_report_py3o_EN", + } + report_name = report_names.get( + lang, "donation_certificate.donation_certificate_report_py3o_EN" + ) + + return self.env.ref(report_name, raise_if_not_found=False).id + + @api.onchange("template_id") + def onchange_template_id(self): + for wizard in self: + if wizard.composer_id: + # Hay que mirar porque el archivo se llama igual que el ID + # Get different report depending on the lang + # lang_object = wizard.template_id.lang + # render_model = wizard.template_id.render_model + # res_ids = wizard.donation_summary_ids.ids + # lang = wizard.template_id._render_template(lang_object, render_model, res_ids) + # wizard.template_id.report_template = self.get_ref_report_name(lang) + wizard.composer_id.template_id = wizard.template_id.id + # Get different report depending on the lang + # wizard.composer_id.template_id.report_template = self.get_ref_report_name(lang) + wizard._compute_composition_mode() + wizard.composer_id._onchange_template_id_wrapper() + + @api.onchange("is_email") + def onchange_is_email(self): + if self.is_email: + res_ids = self._context.get("active_ids") + data = { + "composition_mode": "comment" if len(res_ids) == 1 else "mass_mail", + "template_id": self.template_id.id, + } + if not self.composer_id: + self.composer_id = self.env["mail.compose.message"].create(data) + else: + self.composer_id.write(data) + self._compute_composition_mode() + self.composer_id._onchange_template_id_wrapper() + + @api.onchange("is_email") + def _compute_donation_without_email(self): + for wizard in self: + if wizard.is_email and len(wizard.donation_summary_ids) > 1: + don_summary = self.env["donation.summary"].search( + [ + ("id", "in", self.env.context.get("active_ids")), + ("partner_id.email", "=", False), + ] + ) + if don_summary: + wizard.summary_without_email = "%s\n%s" % ( + _( + "The following donation(s) will not be sent by email, because the customers don't have email address." + ), + "\n".join([i.name for i in don_summary]), + ) + else: + wizard.summary_without_email = False + else: + wizard.summary_without_email = False + + def _send_email(self): + if self.is_email: + self.composer_id.with_context( + no_new_invoice=True, + mail_notify_author=self.env.user.partner_id + in self.composer_id.partner_ids, + mailing_document_based=True, + )._action_send_mail() + for don_summary in self.donation_summary_ids: + prioritary_attachments = don_summary.attachment_ids.filtered( + lambda x: x.mimetype.endswith("pdf") + ) + if prioritary_attachments: + don_summary.with_context(tracking_disable=True).sudo().write( + {"message_main_attachment_id": prioritary_attachments[0].id} + ) + + # def _print_certificate_document(self): + # """ to override for each type of models that will use this composer.""" + # self.ensure_one() + # action = self.donation_summary_ids.action_print_tax_receipt() + + # action.update({'close_on_report_download': True}) + # return action + + def send_and_print_action(self): + self.ensure_one() + # Send the mails in the correct language by splitting the ids per lang. + # This should ideally be fixed in mail_compose_message, so when a fix is made there this whole commit should be reverted. + # basically self.body (which could be manually edited) extracts self.template_id, + # which is then not translated for each customer. + if self.composition_mode == "mass_mail" and self.template_id: + active_ids = self.env.context.get("active_ids", self.res_id) + active_records = self.env[self.model].browse(active_ids) + langs = set(active_records.mapped("partner_id.lang")) + for lang in langs: + active_ids_lang = active_records.filtered( + lambda r: r.partner_id.lang == lang + ).ids + self_lang = self.with_context( + active_ids=active_ids_lang, lang=get_lang(self.env, lang).code + ) + self_lang.onchange_template_id() + self_lang._send_email() + else: + active_record = self.env[self.model].browse(self.res_id) + lang = get_lang(self.env, active_record.partner_id.lang).code + self.with_context(lang=lang)._send_email() + if self.is_print: + return self._print_document() + self.donation_summary_ids.write({"state": "sent"}) + return {"type": "ir.actions.act_window_close"} + + def save_as_template(self): + self.ensure_one() + self.composer_id.action_save_as_template() + self.template_id = self.composer_id.template_id.id + action = _reopen(self, self.id, self.model, context=self._context) + action.update({"name": _("Send Summary")}) + return action + + def _print_document(self): + """to override for each type of models that will use this composer.""" + self.ensure_one() + action = self.donation_summary_ids.action_print_donation_summary() + action.update({"close_on_report_download": True}) + return action