summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRémi Rahir <rar@odoo.com>2018-08-03 12:21:28 +0200
committerRémi Rahir <rar@odoo.com>2018-08-10 18:41:00 +0200
commit53cdd68e7cc9e1494a9a99dcd2fff0cc835eab50 (patch)
tree34b9c7837603a3e929b52271381a3a6e5bd17018
parenta07902ffa81443d242167bd8291f1902b43beb2e (diff)
[IMP] account,snailmail: support batch calls
- reworked the multi.compose.message model to manage several documents simultaneously - Added a letter model for asynchronous processing - usability improvement of the modal
-rw-r--r--addons/account/__manifest__.py2
-rw-r--r--addons/account/models/__init__.py1
-rw-r--r--addons/account/models/account_invoice.py9
-rw-r--r--addons/account/models/company.py3
-rw-r--r--addons/account/models/res_company.py11
-rw-r--r--addons/account/models/res_config_settings.py2
-rw-r--r--addons/account/views/res_config_settings_views.xml21
-rw-r--r--addons/account/wizard/__init__.py2
-rw-r--r--addons/account/wizard/account_invoice_send.py64
-rw-r--r--addons/account/wizard/account_invoice_send_views.xml93
-rw-r--r--addons/account/wizard/multi_compose_message.py79
-rw-r--r--addons/account/wizard/multi_compose_message_views.xml66
-rw-r--r--addons/snailmail/__init__.py1
-rw-r--r--addons/snailmail/__manifest__.py10
-rw-r--r--addons/snailmail/data/mail_activity_data.xml10
-rw-r--r--addons/snailmail/data/snailmail_data.xml21
-rw-r--r--addons/snailmail/models/__init__.py1
-rw-r--r--addons/snailmail/models/res_company.py5
-rw-r--r--addons/snailmail/models/res_config_settings.py13
-rw-r--r--addons/snailmail/models/snailmail_letter.py302
-rw-r--r--addons/snailmail/security/ir.model.access.csv5
-rw-r--r--addons/snailmail/views/res_config_settings_views.xml70
-rw-r--r--addons/snailmail/views/snailmail_views.xml56
-rw-r--r--addons/snailmail/wizard/__init__.py3
-rw-r--r--addons/snailmail/wizard/multi_compose_message.py244
-rw-r--r--addons/snailmail/wizard/multi_compose_message_views.xml39
-rw-r--r--addons/snailmail_account/__init__.py4
-rw-r--r--addons/snailmail_account/__manifest__.py16
-rw-r--r--addons/snailmail_account/models/__init__.py4
-rw-r--r--addons/snailmail_account/models/res_company.py10
-rw-r--r--addons/snailmail_account/models/res_config_settings.py10
-rw-r--r--addons/snailmail_account/views/res_config_settings_views.xml32
-rw-r--r--addons/snailmail_account/wizard/__init__.py3
-rw-r--r--addons/snailmail_account/wizard/account_invoice_send.py76
-rw-r--r--addons/snailmail_account/wizard/account_invoice_send_views.xml44
35 files changed, 822 insertions, 510 deletions
diff --git a/addons/account/__manifest__.py b/addons/account/__manifest__.py
index c99baf6f130..e79c388f6f3 100644
--- a/addons/account/__manifest__.py
+++ b/addons/account/__manifest__.py
@@ -72,11 +72,11 @@ You could use this simplified accounting in case you work with an (external) acc
'views/res_users_views.xml',
'data/service_cron_reverse.xml',
'views/account_fiscal_year_view.xml',
- 'wizard/multi_compose_message_views.xml',
'views/account_incoterms_view.xml',
'data/account_incoterms_data.xml',
'views/res_company_view.xml',
'views/digest_views.xml',
+ 'wizard/account_invoice_send_views.xml',
],
'demo': [
'demo/account_demo.xml',
diff --git a/addons/account/models/__init__.py b/addons/account/models/__init__.py
index 144bc6bc2ba..50b2b4adcf0 100644
--- a/addons/account/models/__init__.py
+++ b/addons/account/models/__init__.py
@@ -16,6 +16,5 @@ from . import res_config_settings
from . import account_cash_rounding
from . import reconciliation_widget
from . import res_users
-from . import res_company
from . import account_incoterms
from . import digest
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 785159896fd..56f1e9c910e 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -574,8 +574,7 @@ class AccountInvoice(models.Model):
""" Print the invoice and mark it as sent, so that we can see more
easily the next step of the workflow
"""
- self.ensure_one()
- self.sent = True
+ self.write({'sent': True})
if self.user_has_groups('account.group_account_invoice'):
return self.env.ref('account.account_invoices').report_action(self)
else:
@@ -588,7 +587,7 @@ class AccountInvoice(models.Model):
"""
self.ensure_one()
template = self.env.ref('account.email_template_edi_invoice', False)
- compose_form = self.env.ref('account.multi_compose_message_wizard_form', False)
+ compose_form = self.env.ref('account.account_invoice_send_wizard_form', False)
ctx = dict(
default_model='account.invoice',
default_res_id=self.id,
@@ -600,11 +599,11 @@ class AccountInvoice(models.Model):
force_email=True
)
return {
- 'name': _('Compose Email'),
+ 'name': _('Send Invoice'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
- 'res_model': 'multi.compose.message',
+ 'res_model': 'account.invoice.send',
'views': [(compose_form.id, 'form')],
'view_id': compose_form.id,
'target': 'new',
diff --git a/addons/account/models/company.py b/addons/account/models/company.py
index 9929b942aa9..c9a898d626f 100644
--- a/addons/account/models/company.py
+++ b/addons/account/models/company.py
@@ -71,6 +71,9 @@ Best Regards,'''))
qr_code_payment_journal_id = fields.Many2one('account.journal', string='SEPA QR Code Bank Journal account')
qr_code_valid = fields.Boolean(string='Has all required arguments', related="qr_code_payment_journal_id.bank_account_id.qr_code_valid")
+ invoice_is_email = fields.Boolean('Email by default', default=True)
+ invoice_is_print = fields.Boolean('Print by default', default=True)
+
#Fields of the setup step for opening move
account_opening_move_id = fields.Many2one(string='Opening Journal Entry', comodel_name='account.move', help="The journal entry containing the initial balance of all this company's accounts.")
account_opening_journal_id = fields.Many2one(string='Opening Journal', comodel_name='account.journal', related='account_opening_move_id.journal_id', help="Journal where the opening entry of this company's accounting has been posted.")
diff --git a/addons/account/models/res_company.py b/addons/account/models/res_company.py
deleted file mode 100644
index 522bd956fc3..00000000000
--- a/addons/account/models/res_company.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import fields, models
-
-
-class ResCompany(models.Model):
- _inherit = 'res.company'
-
- is_email = fields.Boolean('Email by default', default=True)
- is_print = fields.Boolean('Print by default', default=True)
diff --git a/addons/account/models/res_config_settings.py b/addons/account/models/res_config_settings.py
index 739507e5dc1..438393ca020 100644
--- a/addons/account/models/res_config_settings.py
+++ b/addons/account/models/res_config_settings.py
@@ -86,6 +86,8 @@ class ResConfigSettings(models.TransientModel):
qr_code = fields.Boolean(string='Display SEPA QR code', related='company_id.qr_code')
qr_code_payment_journal_id = fields.Many2one('account.journal', related='company_id.qr_code_payment_journal_id', string="Payment Journal", domain="['&',('type', '=', 'bank'), ('currency_id.name','=','EUR')]")
qr_code_valid = fields.Boolean(string='Has all required arguments', related="qr_code_payment_journal_id.bank_account_id.qr_code_valid")
+ invoice_is_print = fields.Boolean(string='Print', related='company_id.invoice_is_print')
+ invoice_is_email = fields.Boolean(string='Send Email', related='company_id.invoice_is_email')
@api.multi
def set_values(self):
diff --git a/addons/account/views/res_config_settings_views.xml b/addons/account/views/res_config_settings_views.xml
index daf25279f83..6158e088d3f 100644
--- a/addons/account/views/res_config_settings_views.xml
+++ b/addons/account/views/res_config_settings_views.xml
@@ -450,6 +450,27 @@
</div>
</div>
</div>
+ <h2>Default Sending Options</h2>
+ <div class="row mt16 o_settings_container" id="send_default">
+ <div class="col-12 col-lg-6 o_setting_box">
+ <div class="o_setting_left_pane">
+ <field name="invoice_is_print"/>
+ </div>
+ <div class="o_setting_right_pane">
+ <label for="invoice_is_print"/>
+ <span class="fa fa-lg fa-building-o" title="Values set here are company-specific."/>
+ </div>
+ </div>
+ <div class="col-12 col-lg-6 o_setting_box">
+ <div class="o_setting_left_pane">
+ <field name="invoice_is_email"/>
+ </div>
+ <div class="o_setting_right_pane">
+ <label for="invoice_is_email"/>
+ <span class="fa fa-lg fa-building-o" title="Values set here are company-specific."/>
+ </div>
+ </div>
+ </div>
<h2>Invoices</h2>
<div class="row mt16 o_settings_container" id="invoicing_settings">
<div class="col-12 col-lg-6 o_setting_box">
diff --git a/addons/account/wizard/__init__.py b/addons/account/wizard/__init__.py
index b7374520988..d8440f9e57c 100644
--- a/addons/account/wizard/__init__.py
+++ b/addons/account/wizard/__init__.py
@@ -21,4 +21,4 @@ from . import account_report_aged_partner_balance
from . import setup_wizards
from . import wizard_tax_adjustments
from . import account_report_tax
-from . import multi_compose_message
+from . import account_invoice_send
diff --git a/addons/account/wizard/account_invoice_send.py b/addons/account/wizard/account_invoice_send.py
new file mode 100644
index 00000000000..2bb3c6b8750
--- /dev/null
+++ b/addons/account/wizard/account_invoice_send.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+from odoo.exceptions import UserError
+
+
+class AccountInvoiceSend(models.TransientModel):
+ _name = 'account.invoice.send'
+ _inherits = {'mail.compose.message':'composer_id'}
+
+ is_email = fields.Boolean('Email', default=lambda self: self.env.user.company_id.invoice_is_email)
+ is_print = fields.Boolean('Print', default=lambda self: self.env.user.company_id.invoice_is_print)
+ printed = fields.Boolean('Is Printed', default=False)
+ invoice_ids = fields.Many2many('account.invoice', 'account_invoice_account_invoice_send_rel', string='Invoices')
+ composer_id = fields.Many2one('mail.compose.message', string='Composer', required=True, ondelete='cascade')
+ template_id = fields.Many2one(
+ 'mail.template', 'Use template', index=True,
+ domain="[('model', '=', 'account.invoice')]"
+ )
+
+ @api.model
+ def default_get(self, fields):
+ res = super(AccountInvoiceSend, self).default_get(fields)
+ res_ids = self._context.get('active_ids')
+ composer = self.env['mail.compose.message'].create({
+ 'composition_mode': 'comment' if len(res_ids) == 1 else 'mass_mail',
+ })
+ res.update({
+ 'invoice_ids': res_ids,
+ 'composer_id': composer.id,
+ })
+ return res
+
+ @api.multi
+ @api.onchange('invoice_ids')
+ def _compute_composition_mode(self):
+ for wizard in self:
+ wizard.composition_mode = 'comment' if len(wizard.invoice_ids) == 1 else 'mass_mail'
+
+ @api.onchange('template_id')
+ def onchange_template_id(self):
+ if self.composer_id:
+ self.composer_id.template_id = self.template_id.id
+ self.composer_id.onchange_template_id_wrapper()
+
+ @api.multi
+ def _send_email(self):
+ if self.is_email:
+ self.composer_id.send_mail()
+
+ @api.multi
+ def _print_document(self):
+ """ to override for each type of models that will use this composer."""
+ self.ensure_one()
+ return self.invoice_ids.invoice_print()
+
+ @api.multi
+ def send_and_print_action(self):
+ self.ensure_one()
+ self._send_email()
+ if self.is_print:
+ return self._print_document()
+ return {'type': 'ir.actions.act_window_close'}
diff --git a/addons/account/wizard/account_invoice_send_views.xml b/addons/account/wizard/account_invoice_send_views.xml
new file mode 100644
index 00000000000..c48e7b4dc02
--- /dev/null
+++ b/addons/account/wizard/account_invoice_send_views.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data>
+
+ <record model="ir.ui.view" id="account_invoice_send_wizard_form">
+ <field name="name">account.invoice.send.form</field>
+ <field name="model">account.invoice.send</field>
+ <field name="groups_id" eval="[(4,ref('base.group_user'))]"/>
+ <field name="arch" type="xml">
+ <form string="Invoice send &amp; Print">
+ <!-- truly invisible fields for control and options -->
+ <field name="composition_mode" invisible="1"/>
+ <field name="composer_id" invisible="1"/>
+ <field name="printed" invisible="1"/>
+ <field name="invoice_ids" invisible="1"/>
+ <div name="option_print">
+ <field name="is_print" />
+ <b><label for="is_print"/></b>
+ <div name="info_form" attrs="{'invisible': ['|', ('is_print', '=', False), ('composition_mode', '=', 'mass_mail')]}" class="text-center text-muted d-inline-block">
+ Preview as a PDF
+ </div>
+ </div>
+ <div name="option_email">
+ <field name="is_email" />
+ <b><label for="is_email"/></b>
+ </div>
+ <div name="mail_form" attrs="{'invisible': [('is_email', '=', False)]}">
+ <!-- visible wizard -->
+ <div attrs="{'invisible': [('composition_mode', '=', 'mass_mail')]}">
+ <group>
+ <label for="partner_ids" string="Recipients" groups="base.group_user"/>
+ <div groups="base.group_user">
+ <span attrs="{'invisible': [('composition_mode', '!=', 'mass_mail')]}">
+ <strong>Email mass mailing</strong> on
+ <span>the selected records</span>
+ </span>
+ <span>Followers of the document and</span>
+ <field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to notify..."
+ context="{'force_email':True, 'show_email':True}" attrs="{'invisible': [('composition_mode', '=', 'mass_mail')]}"/>
+ </div>
+ <field name="subject" placeholder="Subject..." attrs="{'required': [('is_email', '=', True), ('composition_mode', '=', 'comment')]}"/>
+ </group>
+ <field name="body" style="border:none;" options="{'style-inline': true}"/>
+ </div>
+ <group>
+ <group attrs="{'invisible': [('composition_mode', '=', 'mass_mail')]}">
+ <field name="attachment_ids" widget="many2many_binary" string="Attach a file" nolabel="1" colspan="2" attrs="{'invisible': [('composition_mode', '=', 'mass_mail')]}"/>
+ </group>
+ <group>
+ <field name="template_id" options="{'no_create': True, 'no_edit': True}"
+ context="{'default_model': 'account.invoice'}"/>
+ </group>
+ </group>
+ </div>
+
+ <footer>
+ <button string="Send &amp; Print"
+ attrs="{'invisible': ['|', '|', ('printed', '=', True), '|', ('is_email', '=', False), ('is_print', '=', False)]}"
+ name="send_and_print_action" type="object" class="send_and_print btn-primary o_mail_send"/>
+ <button string="Send"
+ attrs="{'invisible': ['|', ('is_print', '=', True), ('is_email', '=', False)]}"
+ name="send_and_print_action" type="object" class="send btn-primary o_mail_send"/>
+ <button string="Print"
+ attrs="{'invisible': ['|', '|', ('printed', '=', True), ('is_print', '=', False), ('is_email', '=', True)]}"
+ name="send_and_print_action" type="object" class="print btn-primary o_mail_send"/>
+ <button string="Done" attrs="{'invisible': [('printed', '=', False)]}" special="cancel" class="btn-primary"/>
+ <button string="Cancel" class="btn-secondary" special="cancel" />
+ <button icon="fa-lg fa-save" type="object" name="save_as_template" string="Save as new template"
+ attrs="{'invisible': ['|', '|', '|', ('composition_mode', '=', 'mass_mail'), ('printed', '=', True), ('is_email', '=', False)]}"
+ class="pull-right btn-secondary" help="Save as a new template" />
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <act_window
+ name="Send"
+ id="invoice_send"
+ multi="True"
+ key2="client_action_multi"
+ res_model="account.invoice.send"
+ src_model="account.invoice"
+ view_mode="form"
+ target="new"
+ view_type="form"
+ context="{
+ 'default_template_id': ref('account.email_template_edi_invoice'),
+ 'default_is_print': False}"
+ groups="account.group_account_invoice"/>
+
+ </data>
+
+</odoo>
diff --git a/addons/account/wizard/multi_compose_message.py b/addons/account/wizard/multi_compose_message.py
deleted file mode 100644
index cdbae7610f2..00000000000
--- a/addons/account/wizard/multi_compose_message.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import api, fields, models
-
-
-class MultiComposer(models.TransientModel):
-
- _name = 'multi.compose.message'
- _inherit = 'mail.compose.message'
-
- company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True,
- default=lambda self: self.env.user.company_id.id)
- is_email = fields.Boolean('Email', default=lambda self: self.env.user.company_id.is_email)
- is_print = fields.Boolean('Print', default=lambda self: self.env.user.company_id.is_print)
- printed = fields.Boolean('Is Printed', default=False)
- partner_ids = fields.Many2many(
- 'res.partner', 'account_compose_message_res_partner_rel',
- 'wizard_id', 'partner_id', 'Additional Contacts')
- attachment_ids = fields.Many2many(
- 'ir.attachment', 'account_compose_message_ir_attachments_rel',
- 'wizard_id', 'attachment_id', 'Attachments')
- print_attachment_id = fields.Many2one('ir.attachment', string='Print Attachment')
-
- @api.multi
- def _update_company(self):
- for rec in self:
- values = {}
- for field in ['is_print', 'is_email']:
- if rec.company_id[field] != rec[field]:
- values.update({field: rec[field]})
- if values:
- rec.company_id.write(values)
-
- @api.multi
- def write(self, vals):
- res = super(MultiComposer, self).write(vals)
- self._update_company()
- return res
-
- @api.multi
- @api.onchange('template_id')
- def onchange_template_id_wrapper(self):
- self.ensure_one()
- super(MultiComposer,self).onchange_template_id_wrapper()
- self.print_attachment_id = self.attachment_ids[:1]
-
- def _send_email(self):
- if self.is_email:
- self.send_mail()
-
- @api.multi
- def _print_document(self):
- """ to override for each type of models that will use this composer."""
- self.ensure_one()
- action = {
- 'type': 'ir.actions.act_url',
- 'url': '/web/content/%s?download=true' % (self.print_attachment_id.id),
- 'target': 'new',
- }
- self.printed = True
- return action
-
- @api.multi
- def send_and_print_action(self):
- self.ensure_one()
- self._send_email()
- return self._print_document()
-
- @api.multi
- def send_action(self):
- for wizard in self:
- wizard._send_email()
- return {'type': 'ir.actions.act_window_close'}
-
- @api.multi
- def print_action(self):
- self.ensure_one()
- return self._print_document()
diff --git a/addons/account/wizard/multi_compose_message_views.xml b/addons/account/wizard/multi_compose_message_views.xml
deleted file mode 100644
index bed5b6939e8..00000000000
--- a/addons/account/wizard/multi_compose_message_views.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
- <data>
- <record model="ir.ui.view" id="multi_compose_message_wizard_form">
- <field name="name">multi.compose.message.form</field>
- <field name="model">multi.compose.message</field>
- <field name="groups_id" eval="[(4,ref('base.group_user'))]"/>
- <field name="arch" type="xml">
- <form string="Compose Email &amp; Print">
- <field name="print_attachment_id" invisible="1"/>
- <div name="option_print" class="col-md-12 mb16">
- <field name="is_print" widget="boolean_toggle"/>
- <b><label for="is_print"/></b>
- </div>
- <div class="col-md-12 mb16" style="border-bottom: 1px solid #e5e5e5;"></div>
- <div name="option_email" class="col-md-12 mb16">
- <field name="is_email" widget="boolean_toggle"/>
- <b><label for="is_email"/></b>
- </div>
- <div name="mail_form" attrs="{'invisible': [('is_email', '=', False)]}">
- <group>
- <!-- truly invisible fields for control and options -->
- <field name="printed" invisible="1"/>
- <field name="model" invisible="1"/>
- <field name="res_id" invisible="1"/>
- <field name="is_log" invisible="1"/>
- <field name="company_id" invisible="1"/>
- <!-- visible wizard -->
- <label for="partner_ids" string="Recipients" groups="base.group_user"/>
- <div groups="base.group_user">
- <span attrs="{'invisible':['|', ('model', '=', False)]}">Followers of the document and</span>
- <field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to notify..."
- context="{'force_email':True, 'show_email':True}"/>
- </div>
- <field name="subject" placeholder="Subject..." required="True"/>
- </group>
- <field name="body" options="{'style-inline': true}"/>
- <group col="4">
- <field name="attachment_ids" widget="many2many_binary" string="Attach a file" nolabel="1" colspan="2"/>
- <field name="template_id" options="{'no_create': True}"
- context="{'default_model': model, 'default_body_html': body, 'default_subject': subject}"/>
- </group>
- </div>
- <footer>
- <button string="Send &amp; Print"
- attrs="{'invisible': ['|', '|', ('is_log', '=', True), ('printed', '=', True), '|', ('is_email', '=', False), ('is_print', '=', False)]}"
- name="send_and_print_action" type="object" class="btn-primary o_mail_send"/>
- <button string="Send"
- attrs="{'invisible': ['|', ('is_print', '=', True), ('is_email', '=', False)]}"
- name="send_action" type="object" class="btn-primary o_mail_send"/>
- <button string="Print"
- attrs="{'invisible': ['|', '|', '|', ('is_log', '=', True), ('printed', '=', True), ('is_print', '=', False), ('is_email', '=', True)]}"
- name="print_action" type="object" class="btn-primary o_mail_send"/>
- <button string="Done" attrs="{'invisible': [('printed', '=', False)]}" special="cancel" class="btn-primary"/>
- <button string="Cancel" class="btn-secondary" special="cancel" />
- <button icon="fa-lg fa-save" type="object" name="save_as_template" string="Save as new template"
- attrs="{'invisible': ['|', '|', '|', ('is_log', '=', True), ('printed', '=', True), ('is_email', '=', False)]}"
- class="float-right btn-secondary" help="Save as a new template" />
- </footer>
- </form>
- </field>
- </record>
-
- </data>
-
-</odoo>
diff --git a/addons/snailmail/__init__.py b/addons/snailmail/__init__.py
index 35e7c9600c5..cde864bae21 100644
--- a/addons/snailmail/__init__.py
+++ b/addons/snailmail/__init__.py
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from . import models
-from . import wizard
diff --git a/addons/snailmail/__manifest__.py b/addons/snailmail/__manifest__.py
index 2a83ff96e5e..eb40e985b9c 100644
--- a/addons/snailmail/__manifest__.py
+++ b/addons/snailmail/__manifest__.py
@@ -2,15 +2,17 @@
{
'name': "Snail Mail",
'description': """
-Allows users to send invoices by post
+Allows users to send documents by post
=====================================================
""",
'category': 'Tools',
'version': '0.1',
- 'depends': ['account', 'iap'],
+ 'depends': ['iap', 'base_setup'],
'data': [
- 'data/mail_activity_data.xml',
- 'wizard/multi_compose_message_views.xml',
+ 'data/snailmail_data.xml',
'views/res_config_settings_views.xml',
+ 'views/snailmail_views.xml',
+ 'security/ir.model.access.csv',
],
+ 'auto_install': True,
}
diff --git a/addons/snailmail/data/mail_activity_data.xml b/addons/snailmail/data/mail_activity_data.xml
deleted file mode 100644
index 191fb9bc03a..00000000000
--- a/addons/snailmail/data/mail_activity_data.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
- <data>
- <record id="mail_activity_data_snailmail" model="mail.activity.type">
- <field name="name">Snailmail</field>
- <field name="icon">fa-address-card</field>
- <field name="sequence">15</field>
- </record>
- </data>
-</odoo>
diff --git a/addons/snailmail/data/snailmail_data.xml b/addons/snailmail/data/snailmail_data.xml
new file mode 100644
index 00000000000..354c30aaf53
--- /dev/null
+++ b/addons/snailmail/data/snailmail_data.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data>
+ <record model="ir.cron" id="snailmail_print">
+ <field name="name">Snaimail: process letters queue</field>
+ <field name="model_id" ref="model_snailmail_letter"/>
+ <field name="state">code</field>
+ <field name="code">model._snailmail_cron()</field>
+ <field name="interval_number">1</field>
+ <field name="interval_type">hours</field>
+ <field name="numbercall">-1</field>
+ <field name="doall" eval="False"/>
+ </record>
+
+ <record id="mail_activity_data_snailmail" model="mail.activity.type">
+ <field name="name">Snailmail</field>
+ <field name="icon">fa-address-card</field>
+ <field name="sequence">15</field>
+ </record>
+ </data>
+</odoo>
diff --git a/addons/snailmail/models/__init__.py b/addons/snailmail/models/__init__.py
index 883205a7200..ee0d4f4704c 100644
--- a/addons/snailmail/models/__init__.py
+++ b/addons/snailmail/models/__init__.py
@@ -2,3 +2,4 @@
from . import res_company
from . import res_config_settings
+from . import snailmail_letter
diff --git a/addons/snailmail/models/res_company.py b/addons/snailmail/models/res_company.py
index c4875d754ac..b51147b2ef1 100644
--- a/addons/snailmail/models/res_company.py
+++ b/addons/snailmail/models/res_company.py
@@ -1,13 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import api, fields, models, tools, _
-from odoo.exceptions import ValidationError
-
+from odoo import fields, models
class Company(models.Model):
_inherit = "res.company"
snailmail_color = fields.Boolean(string='Color', default=False)
snailmail_duplex = fields.Boolean(string='Both sides', default=False)
- snailmail_send_by_letter = fields.Boolean(string='Send by Letter by default', default=True)
diff --git a/addons/snailmail/models/res_config_settings.py b/addons/snailmail/models/res_config_settings.py
index 52468b7a0f5..7b916ce0e1f 100644
--- a/addons/snailmail/models/res_config_settings.py
+++ b/addons/snailmail/models/res_config_settings.py
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import fields, models, api
-
+
+from odoo import fields, models
+
+
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
-
- snailmail_color = fields.Boolean(string='color', related='company_id.snailmail_color')
- snailmail_duplex = fields.Boolean(string='Both sides', related='company_id.snailmail_duplex')
+
+ snailmail_color = fields.Boolean(string='Print In Color', related='company_id.snailmail_color')
+ snailmail_duplex = fields.Boolean(string='Print Both sides', related='company_id.snailmail_duplex')
diff --git a/addons/snailmail/models/snailmail_letter.py b/addons/snailmail/models/snailmail_letter.py
new file mode 100644
index 00000000000..5b09681bead
--- /dev/null
+++ b/addons/snailmail/models/snailmail_letter.py
@@ -0,0 +1,302 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import re
+import base64
+import datetime
+
+from odoo import fields, models, api, _, tools
+from odoo.addons.iap import jsonrpc
+from odoo.exceptions import UserError
+from odoo.tools.safe_eval import safe_eval
+
+DEFAULT_ENDPOINT = 'https://iap-snailmail.odoo.com'
+ESTIMATE_ENDPOINT = '/iap/snailmail/1/estimate'
+PRINT_ENDPOINT = '/iap/snailmail/1/print'
+
+
+class SnailmailLetter(models.Model):
+ _name = 'snailmail.letter'
+
+ user_id = fields.Many2one('res.users', 'User sending the letter')
+ model = fields.Char('Model', required=True)
+ res_id = fields.Integer('Document ID', required=True)
+ partner_id = fields.Many2one('res.partner', string='Recipient', required=True)
+ company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True,
+ default=lambda self: self.env.user.company_id.id)
+ report_template = fields.Many2one('ir.actions.report', 'Optional report to print and attach')
+
+ attachment_id = fields.Many2one('ir.attachment', string='Attachment', ondelete='cascade')
+ color = fields.Boolean(string='Color', default=lambda self: self.env.user.company_id.snailmail_color)
+ duplex = fields.Boolean(string='Both side', default=lambda self: self.env.user.company_id.snailmail_duplex)
+ state = fields.Selection([
+ ('draft', 'Draft'),
+ ('pending', 'In Queue'),
+ ('sent', 'Sent'),
+ ('error', 'Error'),
+ ('canceled', 'Canceled')
+ ], 'Status', readonly=True, copy=False, default='draft',
+ help="When a letter is created, the status is 'Draft'.\n"
+ "If the letter is correctly sent, the status goes in 'Sent',\n"
+ "If not, it will got in state 'Error' and the error message will be displayed in the field 'Error Message'.")
+ info_msg = fields.Char('Information')
+
+ @api.multi
+ def _fetch_attachment(self):
+ """
+ This method will check if we have any existent attachement matching the model
+ and res_ids and create them if not found.
+ """
+ self.ensure_one()
+ obj = self.env[self.model].browse(self.res_id)
+ if not self.attachment_id:
+ report = self.report_template
+ if not report:
+ report_name = self.env.context.get('report_name')
+ report = self.env['ir.actions.report']._get_report_from_name(report_name)
+ if not report:
+ return False
+ else:
+ self.write({'report_template': report.id})
+ # report = self.env.ref('account.account_invoices')
+ if report.print_report_name:
+ report_name = safe_eval(report.print_report_name, {'object': obj})
+ elif report.attachment:
+ report_name = safe_eval(report.attachment, {'object': obj})
+ else:
+ report_name = 'Document'
+ filename = "%s.%s" % (report_name, "pdf")
+ pdf_bin, _ = report.render_qweb_pdf(self.res_id)
+ attachment = self.env['ir.attachment'].create({
+ 'name': filename,
+ 'datas': base64.b64encode(pdf_bin),
+ 'datas_fname': filename,
+ 'res_model': 'snailmail.letter',
+ 'res_id': self.id,
+ 'type': 'binary', # override default_type from context, possibly meant for another model!
+ })
+ self.write({'attachment_id': attachment.id})
+
+ return self.attachment_id
+
+ def _count_pages_pdf(self, bin_pdf):
+ """ Count the number of pages of the given pdf file.
+ :param bin_pdf : binary content of the pdf file
+ """
+ pages = 0
+ for match in re.compile(b"/Count\s+(\d+)").finditer(bin_pdf):
+ pages = int(match.group(1))
+ return pages
+
+ @api.multi
+ def _snailmail_create(self, route):
+ """
+ Create a dictionnary object to send to snailmail server.
+
+ :return: Dict in the form:
+ {
+ account_token: string, //IAP Account token of the user
+ documents: [{
+ pages: int,
+ pdf_bin: pdf file
+ res_id: int (client-side res_id),
+ res_model: char (client-side res_model),
+ address: {
+ name: char,
+ street: char,
+ street2: char (OPTIONAL),
+ zip: int,
+ city: char,
+ state: char (state code (OPTIONAL)),
+ country_code: char (country code)
+ }
+ return_address: {
+ name: char,
+ street: char,
+ street2: char (OPTIONAL),
+ zip: int,
+ city: char,at
+ state: char (state code (OPTIONAL)),
+ country_code: char (country code)
+ }
+ }],
+ options: {
+ color: boolean (true if color, false if black-white),
+ duplex: boolean (true if duplex, false otherwise),
+ }
+ }
+ """
+ account_token = self.env['iap.account'].get('snailmail').account_token
+ documents = []
+
+ batch = len(self) > 1
+ for letter in self:
+ document = {
+ # generic informations to send
+ 'letter_id': letter.id,
+ 'res_model': letter.model,
+ 'res_id': letter.res_id,
+ 'address': {
+ 'name': letter.partner_id.name,
+ 'street': letter.partner_id.street,
+ 'street2': letter.partner_id.street2,
+ 'zip': letter.partner_id.zip,
+ 'state': letter.partner_id.state_id.code if letter.partner_id.state_id else False,
+ 'city': letter.partner_id.city,
+ 'country_code': letter.partner_id.country_id.code
+ },
+ 'return_address': {
+ 'name': letter.company_id.partner_id.name,
+ 'street': letter.company_id.partner_id.street,
+ 'street2': letter.company_id.partner_id.street2,
+ 'zip': letter.company_id.partner_id.zip,
+ 'state': letter.company_id.partner_id.state_id.code if letter.company_id.partner_id.state_id else False,
+ 'city': letter.company_id.partner_id.city,
+ 'country_code': letter.company_id.partner_id.country_id.code,
+ }
+ }
+ # Specific to each case:
+ # If we are estimating the price: batch: 1 obj = 1 page else: we generate the attachment to have the exact price.
+ # If we are printing -> attach the pdf
+ if route == 'estimate' and batch:
+ document.update(pages=1)
+ else:
+ attachment = letter._fetch_attachment()
+ if attachment:
+ document.update({
+ 'pdf_bin': route == 'print' and attachment.datas,
+ 'pages': route == 'estimate' and self._count_pages_pdf(base64.b64decode(attachment.datas)),
+ })
+ else:
+ letter.write({
+ 'info_msg': 'The attachment could not be generated.',
+ 'state': 'error',
+ })
+ continue
+ documents.append(document)
+
+ return {
+ 'account_token': account_token,
+ 'documents': documents,
+ 'options': {
+ 'color': self and self[0].color,
+ 'duplex': self and self[0].duplex,
+ },
+ # this will not raise the InsufficientCreditError which is the behaviour we want for now
+ 'batch': True,
+ }
+
+ def _get_error_message(self, error):
+ if error == 'CREDIT_ERROR':
+ link = self.env['iap.account'].get_credits_url(service_name='snailmail')
+ return _('You don\'t have enough credits to perform this operation.<br>Please go to your <a href=%s target="new">iap account</a>.' % link)
+ if error == 'NO_PRICE_AVAILABLE':
+ return _('The country of the partner is not covered by Snailmail.')
+ if error == 'MISSING_REQUIRED_FIELDS':
+ return _('One or more required fields are empty.')
+ if error == 'SOMETHING_IS_WRONG':
+ return _('An unknown error happened. Please contact the support.')
+ return error
+
+ @api.multi
+ def _snailmail_print(self):
+ """
+ get response
+ {
+ 'request_code': RESPONSE_OK, # because we receive 200 if good or fail
+ 'total_cost': total_cost,
+ 'credit_error': credit_error,
+ 'request': {
+ 'documents': documents,
+ 'options': options
+ }
+ }
+ }
+ """
+ self.write({'state': 'pending'})
+ endpoint = self.env['ir.config_parameter'].sudo().get_param('snailmail.endpoint', DEFAULT_ENDPOINT)
+ params = self._snailmail_create('print')
+ response = jsonrpc(endpoint + PRINT_ENDPOINT, params=params)
+ for doc in response['request']['documents']:
+ letter = self.browse(doc['letter_id'])
+ record = self.env[doc['res_model']].browse(doc['res_id'])
+ if doc.get('sent') and response['request_code'] == 200:
+ if hasattr(record, '_message_log'):
+ message = _('The document was correctly sent by post.<br>The tracking id is %s' % doc['send_id'])
+ record._message_log(body=message)
+ letter.write({'info_msg': message, 'state': 'sent'})
+ else:
+ note = _('An error occured when sending the document by post.<br>Error: %s' % \
+ self._get_error_message(doc['error'] if response['request_code'] == 200 else response['reason']))
+ activity_data = {
+ 'res_id': letter.res_id,
+ 'res_model_id': self.env['ir.model']._get(letter.model).id,
+ 'activity_type_id': self.env.ref('snailmail.mail_activity_data_snailmail').id,
+ 'summary': _('Post letter: an error occured.'),
+ 'note': note,
+ 'user_id': letter.user_id.id,
+ }
+ self.env['mail.activity'].create(activity_data)
+ letter.write({'info_msg': note})
+
+ self.env.cr.commit()
+
+ @api.multi
+ def snailmail_print(self):
+ self._snailmail_print()
+
+ @api.multi
+ def cancel(self):
+ self.write({'state': 'canceled'})
+
+ @api.multi
+ def _snailmail_estimate(self):
+ """
+ Send a request to estimate the cost of sending all the documents with
+ the differents options.
+
+ The JSON object sent is the one generated from self._snailmail_create()
+
+ arguments sent:
+ {
+ "documents":[{
+ pages: int,
+ res_id: int (client_side, optional),
+ res_model: int (client_side, optional),
+ address: {
+ country_code: char (country name)
+ }
+ }],
+ 'color': # color on the letter,
+ 'duplex': # one/two side printing,
+ }
+
+ The answer of the server is the same JSON object with some additionnal fields:
+ {
+ "total_cost": integer, //The cost of sending ALL the documents
+ body: JSON object (same as body param except new param 'cost' in each documents)
+ }
+ """
+ endpoint = self.env['ir.config_parameter'].sudo().get_param('snailmail.endpoint', DEFAULT_ENDPOINT)
+ params = self._snailmail_create('estimate')
+ req = jsonrpc(endpoint + '/iap/snailmail/1/estimate', params=params)
+ # The cost is sent in hunderth of eurocents, we change it to euros and then convert it to the company currency
+ cost = int(req['total_cost'])/10000.0
+ currency_eur = self.env.ref('base.EUR')
+
+ return currency_eur._convert(cost, self[0].company_id.currency_id, self[0].company_id, fields.Datetime.now())
+
+ @api.model
+ def _snailmail_cron(self):
+ letters_send = self.search([('state', 'in', ['pending', 'error'])])
+ if letters_send:
+ letters_send._snailmail_print()
+ limit_date = datetime.datetime.utcnow() - datetime.timedelta(days=1)
+ limit_date_str = datetime.datetime.strftime(limit_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
+ letters_canceled = self.search([
+ '|',
+ ('state', '=', 'canceled'),
+ '&',
+ ('state' ,'=', 'draft'),
+ ('write_date', '<', limit_date_str),
+ ])
+ letters_canceled.unlink()
diff --git a/addons/snailmail/security/ir.model.access.csv b/addons/snailmail/security/ir.model.access.csv
new file mode 100644
index 00000000000..3c111854063
--- /dev/null
+++ b/addons/snailmail/security/ir.model.access.csv
@@ -0,0 +1,5 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_snailmail_letter_all,snailmail.letter.all,model_snailmail_letter,,1,0,0,0
+access_snailmail_letter_portal,snailmail.letter.portal,model_snailmail_letter,base.group_portal,1,0,1,0
+access_snailmail_letter_user,snailmail.letter.user,model_snailmail_letter,base.group_user,1,1,1,0
+access_snailmail_letter_system,snailmail.letter.system,model_snailmail_letter,base.group_system,1,1,1,1 \ No newline at end of file
diff --git a/addons/snailmail/views/res_config_settings_views.xml b/addons/snailmail/views/res_config_settings_views.xml
index cd5577c8cde..1e8b864d928 100644
--- a/addons/snailmail/views/res_config_settings_views.xml
+++ b/addons/snailmail/views/res_config_settings_views.xml
@@ -1,41 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
-<odoo>
- <record id="res_config_settings_view_form" model="ir.ui.view">
+<odoo>
+ <record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.snailmail</field>
- <field name="model">res.config.settings</field>
- <field name="priority" eval="100"/>
- <field name="inherit_id" ref="base.res_config_settings_view_form"/>
- <field name="arch" type="xml">
- <xpath expr="//div[@id='multi_company']" position="after">
- <h2>Postal Printings</h2>
- <div class="row mt16 o_settings_container">
+ <field name="model">res.config.settings</field>
+ <field name="priority" eval="100"/>
+ <field name="inherit_id" ref="base.res_config_settings_view_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//div[@id='multi_company']" position="after">
+ <h2>Postal Printings</h2>
+ <div class="row mt16 o_settings_container">
<div class="col-12 col-lg-6 o_setting_box">
- <div class="o_setting_left_pane">
- <field name="snailmail_duplex"/>
- </div>
- <div class="o_setting_right_pane">
- <label for="snailmail_duplex"/>
- <span class="fa fa-lg fa-building-o" title="Values set here are company-specific." groups="base.group_multi_company"/>
- <div class="text-muted">
- Choose the layout to print documents
- </div>
- </div>
- </div>
+ <div class="o_setting_left_pane">
+ <field name="snailmail_duplex"/>
+ </div>
+ <div class="o_setting_right_pane">
+ <label for="snailmail_duplex"/>
+ <span class="fa fa-lg fa-building-o" title="Values set here are company-specific."/>
+ <div class="text-muted">
+ Choose the layout to print documents
+ </div>
+ </div>
+ </div>
<div class="col-12 col-lg-6 o_setting_box">
- <div class="o_setting_left_pane">
- <field name="snailmail_color"/>
- </div>
- <div class="o_setting_right_pane">
- <label for="snailmail_color"/>
- <span class="fa fa-lg fa-building-o" title="Values set here are company-specific." groups="base.group_multi_company"/>
- <div class="text-muted">
- Choose the ink used to print documents
- </div>
- </div>
- </div>
- </div>
- </xpath>
- </field>
- </record>
+ <div class="o_setting_left_pane">
+ <field name="snailmail_color"/>
+ </div>
+ <div class="o_setting_right_pane">
+ <label for="snailmail_color"/>
+ <span class="fa fa-lg fa-building-o" title="Values set here are company-specific."/>
+ <div class="text-muted">
+ Choose the ink used to print documents
+ </div>
+ </div>
+ </div>
+ </div>
+ </xpath>
+ </field>
+ </record>
</odoo>
diff --git a/addons/snailmail/views/snailmail_views.xml b/addons/snailmail/views/snailmail_views.xml
new file mode 100644
index 00000000000..7a230c2f706
--- /dev/null
+++ b/addons/snailmail/views/snailmail_views.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <record model="ir.ui.view" id="snailmail_letter_list">
+ <field name="name">snailmail.letter.tree</field>
+ <field name="model">snailmail.letter</field>
+ <field name="arch" type="xml">
+ <tree decoration-danger="state=='error'" decoration-muted="state=='sent'" string="Letters">
+ <field name="attachment_id"/>
+ <field name="model"/>
+ <field name="res_id"/>
+ <field name="company_id"/>
+ <field name="state"/>
+ <field name="info_msg" widget="html"/>
+ </tree>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="snailmail_letter_form">
+ <field name="name">snailmail.letter.form</field>
+ <field name="model">snailmail.letter</field>
+ <field name="arch" type="xml">
+ <form>
+ <header>
+ <button name="snailmail_print" string="Send Now" type="object" states="pending,error" class="oe_highlight"/>
+ <button name="cancel" string="Cancel" type="object" states="pending,error"/>
+ <field name="state" widget="statusbar" statusbar_visible="pending,sent,cancel"/>
+ </header>
+ <sheet>
+ <group>
+ <field name="attachment_id"/>
+ <field name="partner_id"/>
+ <field name="user_id"/>
+ <field name="model"/>
+ <field name="res_id"/>
+ <field name="color"/>
+ <field name="duplex"/>
+ <field name="info_msg" widget="html" readonly="1"/>
+ <field name="company_id"/>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record model="ir.actions.act_window" id="action_mail_letters">
+ <field name="name">Letters</field>
+ <field name="res_model">snailmail.letter</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">form,tree</field>
+ <field name="domain">[('state', '!=', 'draft')]</field>
+ <field name="view_id" ref="snailmail_letter_list" />
+ </record>
+
+ <menuitem id="menu_snailmail_letters" parent="base.menu_email" action="action_mail_letters"
+ sequence="20"/>
+</odoo>
diff --git a/addons/snailmail/wizard/__init__.py b/addons/snailmail/wizard/__init__.py
deleted file mode 100644
index eebbf14ea53..00000000000
--- a/addons/snailmail/wizard/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from . import multi_compose_message
diff --git a/addons/snailmail/wizard/multi_compose_message.py b/addons/snailmail/wizard/multi_compose_message.py
deleted file mode 100644
index 14df5467e7e..00000000000
--- a/addons/snailmail/wizard/multi_compose_message.py
+++ /dev/null
@@ -1,244 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import re
-
-from odoo import api, fields, models, _
-from odoo.addons.iap import jsonrpc
-
-DEFAULT_ENDPOINT = 'https://iap-services.odoo.com'
-ESTIMATE_ENDPOINT = '/iap/snailmail/1/estimate'
-PRINT_ENDPOINT = '/iap/snailmail/1/print'
-
-
-class MultiComposer(models.TransientModel):
-
- _name = 'multi.compose.message'
- _inherit = 'multi.compose.message'
-
- partner_id = fields.Many2one('res.partner', string='Partner')
- snailmail_color = fields.Boolean(string='Color', default=lambda self: self.env.user.company_id.snailmail_color)
- snailmail_duplex = fields.Boolean(string='Both side', default=lambda self: self.env.user.company_id.snailmail_duplex)
- snailmail_send_by_letter = fields.Boolean('Post', default=lambda self: self.env.user.company_id.snailmail_send_by_letter)
-
- snailmail_cost = fields.Float(string='Credits', compute='_snailmail_estimate', store=True, readonly=True)
- currency_id = fields.Many2one('res.currency', default=lambda self: self.env.user.company_id.currency_id.id, string="Currency")
-
- @api.multi
- def _update_company(self):
- super(MultiComposer,self)._update_company()
- for rec in self:
- if rec.company_id.snailmail_send_by_letter != rec.snailmail_send_by_letter:
- rec.company_id.write({
- 'snailmail_send_by_letter': rec.snailmail_send_by_letter,
- })
-
- @api.multi
- def _snailmail_create(self, route, batch=False):
- """
- Create a dictionnary object to send to snailmail server.
-
- :return: Dict in the form:
- {
- account_token: string, //IAP Account token of the user
- documents: [{
- pages: int,
- pdf_bin: pdf file
- res_id: int (client-side res_id),
- res_model: char (client-side res_model),
- address: {
- name: char,
- street: char,
- street2: char (OPTIONAL),
- zip: int,
- city: char,
- state: char (state code (OPTIONAL)),
- country_code: char (country code)
- }
- return_address: {
- name: char,
- street: char,
- street2: char (OPTIONAL),
- zip: int,
- city: char,
- state: char (state code (OPTIONAL)),
- country_code: char (country code)
- }
- }],
- options: {
- color: boolean (true if color, false if black-white),
- duplex: boolean (true if duplex, false otherwise),
- }
- }
- """
- account_token = self.env['iap.account'].get('snailmail').account_token
- documents = []
- pdf = self.print_attachment_id.datas
- if pdf:
- documents.append({
- 'pdf_bin': route == 'print' and pdf,
- 'pages': route == 'estimate' and self._count_pages_pdf(pdf),
- 'address': {
- 'name': self.partner_id.name,
- 'street': self.partner_id.street,
- 'street2': self.partner_id.street2,
- 'zip': self.partner_id.zip,
- 'state': self.partner_id.state_id.code if self.partner_id.state_id else False,
- 'city': self.partner_id.city,
- 'country_code': self.partner_id.country_id.code
- },
- 'return_address': {
- 'name': self.company_id.partner_id.name,
- 'street': self.company_id.partner_id.street,
- 'street2': self.company_id.partner_id.street2,
- 'zip': self.company_id.partner_id.zip,
- 'state': self.company_id.partner_id.state_id.code if self.company_id.partner_id.state_id else False,
- 'city': self.company_id.partner_id.city,
- 'country_code': self.company_id.partner_id.country_id.code,
- }
- })
- return {
- 'account_token': account_token,
- 'documents': documents,
- 'options': {
- 'color': self.snailmail_color,
- 'duplex': self.snailmail_duplex,
- },
- 'batch': batch,
- }
-
- @api.multi
- def action_snailmail_print(self):
- """
- get response
- {
- 'request_code': RESPONSE_OK, # 200 if good or fail
- 'total_cost': total_cost,
- 'credit_error': credit_error,
- 'request': {
- 'documents': documents,
- 'options': options
- }
- }
- }
- """
- self.ensure_one()
- endpoint = self.env['ir.config_parameter'].sudo().get_param('snailmail.endpoint', DEFAULT_ENDPOINT)
- params = self._snailmail_create('print', batch=True)
- response = jsonrpc(endpoint + PRINT_ENDPOINT, params=params)
-
- error = False
- doc = response['request']['documents'][0]
- record = self.env[self.model].browse(self.res_id)
- if doc.get('sent') and response['request_code'] == 200:
- if hasattr(record, '_message_log'):
- message = _('The document was correctly sent by post.<br>The tracking id is %s' % doc['send_id'])
- record._message_log(body=message)
- else:
- activity_data = {
- 'res_id': self.res_id,
- 'res_model_id': self.env['ir.model']._get(self.model).id,
- 'activity_type_id': self.env.ref('snailmail.mail_activity_data_snailmail').id,
- 'summary': _('Post letter: an error occured.'),
- 'note': _('An error occured when sending the document by post.<br>Error: %s' % \
- self._get_error_message(doc['error'] if response['request_code'] == 200 else response['reason'])),
- 'user_id': self.env.user.id,
- }
- self.env['mail.activity'].create(activity_data)
- error = True
- if not error:
- notification = _('Post letter: document sent.')
- else:
- notification = _('Post letter: an error occured. Please check the document for more information.')
- # as we interact with the exterior world, we commit
- self.env.cr.commit()
- self._update_company()
- return {
- 'error': error,
- 'message': notification,
- }
-
- @api.multi
- @api.depends('print_attachment_id')
- @api.onchange('snailmail_send_by_letter')
- def _snailmail_estimate(self):
- """
- Send a request to estimate the cost of sending all the documents with
- the differents options.
-
- The JSON object sent is the one generated from self._snailmail_create()
-
- arguments sent:
- {
- "documents":[{
- pages: int,
- res_id: int (client_side, optional),
- res_model: int (client_side, optional),
- address: {
- country_code: char (country name)
- }
- }],
- 'color': # color on the letter,
- 'duplex': # one/two side printing,
- }
-
- The answer of the server is the same JSON object with some additionnal fields:
- {
- "total_cost": integer, //The cost of sending ALL the documents
- body: JSON object (same as body param except new param 'cost' in each documents)
- }
- """
- for composer in self:
- if not composer.partner_id:
- composer.partner_id = self.env[self.model].browse(self.res_id).partner_id
-
- if composer.snailmail_send_by_letter and composer.print_attachment_id :# and not composer.is_estimated:
- endpoint = self.env['ir.config_parameter'].sudo().get_param('snailmail.endpoint', DEFAULT_ENDPOINT)
-
- params = composer._snailmail_create('estimate')
- req = jsonrpc(endpoint + '/iap/snailmail/1/estimate', params=params)
- composer.is_estimated = True
-
- # The cost is sent in eurocents, we change it to euros and then convert it to the company currency
- cost = int(req['total_cost'])/100.0
- currency_eur = self.env.ref('base.EUR')
-
- composer.snailmail_cost = currency_eur._convert(cost, self.currency_id, self.company_id, fields.Datetime.now())
-
- # all the print stuff will go to the cron onsnailail print orders
-
- def _count_pages_pdf(self, bin_pdf):
- """ Count the number of pages of the given pdf file.
- :param bin_pdf : binary content of the pdf file
- """
- pages = 0
- for match in re.compile(b"/Count\s+(\d+)").finditer(bin_pdf):
- pages = int(match.group(1))
- return pages
-
- def _get_error_message(self, error):
- if error == 'CREDIT_ERROR':
- link = self.env['iap.account'].get_credits_url(service_name='snailmail')
- return _('You don\'t have enough credits to perform this operation.<br>Please go to your <a href=%s target="new">iap account</a>.' % link)
- if error == 'NO_PRICE_AVAILABLE':
- return _('The country of the partner is not covered by Snailmail.')
- if error == 'MISSING_REQUIRED_FIELDS':
- return _('One or more required fields are empty.')
- if error == 'SOMETHING_IS_WRONG':
- return _('An unknown error happened. Please contact the support.')
- return error
-
-
- @api.multi
- def send_and_print_action(self):
- res = super(MultiComposer,self).send_and_print_action()
- if self.snailmail_send_by_letter:
- self.action_snailmail_print()
- return res
-
- @api.multi
- def send_action(self):
- res = super(MultiComposer,self).send_action()
- for wizard in self:
- if wizard.snailmail_send_by_letter:
- wizard.action_snailmail_print()
- return res
diff --git a/addons/snailmail/wizard/multi_compose_message_views.xml b/addons/snailmail/wizard/multi_compose_message_views.xml
deleted file mode 100644
index 47d595e0685..00000000000
--- a/addons/snailmail/wizard/multi_compose_message_views.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
- <data>
- <record model="ir.ui.view" id="multi_compose_message_inherit_account_wizard_form">
- <field name="name">multi.compose.message.form.inherited.snailmail</field>
- <field name="model">multi.compose.message</field>
- <field name="inherit_id" ref="account.multi_compose_message_wizard_form"/>
- <field name="arch" type="xml">
- <xpath expr="//div[@name='option_email']" position='before'>
- <div name="option_letter" class="col-md-12 mb16">
- <div name="option" class="float-left">
- <field name="snailmail_send_by_letter" widget="boolean_toggle"/>
- <b><label for="snailmail_send_by_letter"/></b>
- </div>
- <div class="float-right" name='cost' attrs="{'invisible': [('snailmail_send_by_letter','=',False)]}">
- <field name="currency_id" invisible="1"/>
- <b><field name="snailmail_cost" widget='monetary' options="{'currency_field': 'currency_id'}"/></b>
- </div>
- </div>
- <div name="address_form" attrs="{'invisible': [('snailmail_send_by_letter', '=', False)]}">
- <group>
- <field string="Delivery Address" name="partner_id" readonly="1" force_save="1" context="{'show_address': 1}" options="{'always_reload': True, 'no_quick_create': True}" />
- </group>
- </div>
- <div class="col-md-12 mb16" style="border-bottom: 1px solid #e5e5e5;"></div>
- </xpath>
- <xpath expr="//footer/button[@name='send_and_print_action']" position='attributes'>
- <attribute name="attrs">{'invisible': ['|', '|', ('is_log', '=', True), ('printed', '=', True), '|', ('is_print', '=', False), '&amp;', '&amp;', ('is_print', '=', True), ('snailmail_send_by_letter', '=', False), ('is_email', '=', False)]}</attribute>
- </xpath>
- <xpath expr="//footer/button[@name='send_action']" position='attributes'>
- <attribute name="attrs">{'invisible': ['|' , ('is_log', '=', True), '|', ('is_print', '=', True), '&amp;', '&amp;', ('is_print', '=', False), ('snailmail_send_by_letter', '=', False), ('is_email', '=', False)]}</attribute>
- </xpath>
- <xpath expr="//footer/button[@name='print_action']" position='attributes'>
- <attribute name="attrs">{'invisible': ['|', '|', '|', '|', ('printed', '=', True), ('is_log', '=', True), ('is_print', '=', False), ('snailmail_send_by_letter', '=', True), ('is_email', '=', True)]}</attribute>
- </xpath>
- </field>
- </record>
- </data>
-</odoo>
diff --git a/addons/snailmail_account/__init__.py b/addons/snailmail_account/__init__.py
new file mode 100644
index 00000000000..35e7c9600c5
--- /dev/null
+++ b/addons/snailmail_account/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from . import models
+from . import wizard
diff --git a/addons/snailmail_account/__manifest__.py b/addons/snailmail_account/__manifest__.py
new file mode 100644
index 00000000000..5f71dd9eacf
--- /dev/null
+++ b/addons/snailmail_account/__manifest__.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+{
+ 'name': "snailmail_account",
+ 'description': """
+Allows users to send invoices by post
+=====================================================
+ """,
+ 'category': 'Tools',
+ 'version': '0.1',
+ 'depends': ['account', 'snailmail'],
+ 'data': [
+ 'views/res_config_settings_views.xml',
+ 'wizard/account_invoice_send_views.xml',
+ ],
+ 'auto_install': True,
+}
diff --git a/addons/snailmail_account/models/__init__.py b/addons/snailmail_account/models/__init__.py
new file mode 100644
index 00000000000..883205a7200
--- /dev/null
+++ b/addons/snailmail_account/models/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from . import res_company
+from . import res_config_settings
diff --git a/addons/snailmail_account/models/res_company.py b/addons/snailmail_account/models/res_company.py
new file mode 100644
index 00000000000..1ac0da4e0fb
--- /dev/null
+++ b/addons/snailmail_account/models/res_company.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class Company(models.Model):
+ _inherit = "res.company"
+
+ invoice_is_snailmail = fields.Boolean(string='Send by Letter by default', default=True)
diff --git a/addons/snailmail_account/models/res_config_settings.py b/addons/snailmail_account/models/res_config_settings.py
new file mode 100644
index 00000000000..06a1eb98373
--- /dev/null
+++ b/addons/snailmail_account/models/res_config_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class ResConfigSettings(models.TransientModel):
+ _inherit = 'res.config.settings'
+
+ invoice_is_snailmail = fields.Boolean(string='Send by Post', related='company_id.invoice_is_snailmail')
diff --git a/addons/snailmail_account/views/res_config_settings_views.xml b/addons/snailmail_account/views/res_config_settings_views.xml
new file mode 100644
index 00000000000..13df8ce7e2e
--- /dev/null
+++ b/addons/snailmail_account/views/res_config_settings_views.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <record id="res_config_settings_view_form" model="ir.ui.view">
+ <field name="name">res.config.settings.view.form.inherit.snailmail.account</field>
+ <field name="model">res.config.settings</field>
+ <field name="priority" eval="100"/>
+ <field name="inherit_id" ref="base.res_config_settings_view_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//div[@id='send_default']" position="inside">
+ <div class="col-12 col-lg-6 o_setting_box">
+ <div class="o_setting_left_pane">
+ <field name="invoice_is_snailmail"/>
+ </div>
+ <div class="o_setting_right_pane">
+ <label for="invoice_is_snailmail"/>
+ <span class="fa fa-lg fa-building-o" title="Values set here are company-specific."/>
+ <div class="content-group" attrs="{'invisible': [('invoice_is_snailmail' ,'=', False)]}">
+ <div class="mt16">
+ <field name="snailmail_duplex" class="oe_inline"/>
+ <label string="Print on Both Sides" class="o_light_label" for="snailmail_duplex"/>
+ </div>
+ <div>
+ <field name="snailmail_color" class="oe_inline"/>
+ <label string="Print In Color" class="o_light_label" for="snailmail_color"/>
+ </div>
+ </div>
+ </div>
+ </div>
+ </xpath>
+ </field>
+ </record>
+</odoo>
diff --git a/addons/snailmail_account/wizard/__init__.py b/addons/snailmail_account/wizard/__init__.py
new file mode 100644
index 00000000000..1a67eb15ca1
--- /dev/null
+++ b/addons/snailmail_account/wizard/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import account_invoice_send
diff --git a/addons/snailmail_account/wizard/account_invoice_send.py b/addons/snailmail_account/wizard/account_invoice_send.py
new file mode 100644
index 00000000000..e6c08e1e535
--- /dev/null
+++ b/addons/snailmail_account/wizard/account_invoice_send.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+
+class AccountInvoiceSend(models.TransientModel):
+ _name = 'account.invoice.send'
+ _inherit = 'account.invoice.send'
+
+ partner_id = fields.Many2one('res.partner', compute='_get_partner', string='Partner')
+ snailmail_is_letter = fields.Boolean('Send by Post', help='Allows to send the document by snail mail (coventional posting delivery service)', default=lambda self: self.env.user.company_id.snailmail_is_letter)
+ snailmail_cost = fields.Float(string='Credits', compute='_snailmail_estimate', store=True, readonly=True)
+ currency_id = fields.Many2one('res.currency', compute='_get_invoice_currency', string="Currency")
+ letter_ids = fields.Many2many('snailmail.letter', 'snailmail_letter_account_invoice_send_rel', ondelete='cascade')
+
+ @api.multi
+ @api.onchange('invoice_ids')
+ def _get_invoice_currency(self):
+ for wizard in self:
+ if wizard.invoice_ids:
+ wizard.currency_id = wizard.invoice_ids.mapped('currency_id')[0].id
+
+ @api.multi
+ @api.onchange('invoice_ids')
+ def _get_partner(self):
+ for wizard in self:
+ if wizard.invoice_ids and len(wizard.invoice_ids) == 1:
+ wizard.partner_id = wizard.invoice_ids.partner_id.id
+
+ @api.multi
+ def unlink(self):
+ for agc in self:
+ agc.letter_ids.filtered(lambda l: l.state == 'draft').unlink()
+ return super(AccountInvoiceSend, self).unlink()
+
+ @api.multi
+ def _fetch_letters(self):
+ self.ensure_one()
+ if not self.letter_ids:
+
+ letters = self.env['snailmail.letter']
+ for invoice in self.invoice_ids:
+ letter = letters.create({
+ 'partner_id': invoice.partner_id.id,
+ 'model': 'account.invoice',
+ 'res_id': invoice.id,
+ 'user_id': self.env.user.id,
+ 'company_id': invoice.company_id.id,
+ 'report_template': self.env.ref('account.account_invoices').id
+ })
+ letters |= letter
+ self.letter_ids = [(4, l.id) for l in letters]
+ return self.letter_ids
+
+ @api.onchange('snailmail_is_letter')
+ def _snailmail_estimate(self):
+ for wizard in self:
+ if wizard.snailmail_is_letter:
+ letters = wizard._fetch_letters()
+ wizard.snailmail_cost = letters._snailmail_estimate()
+
+ @api.multi
+ def snailmail_print_action(self):
+ for wizard in self:
+ letters = wizard._fetch_letters()
+ letters.write({'state': 'pending'})
+ if len(letters) == 1:
+ letters._snailmail_print()
+
+ @api.multi
+ def send_and_print_action(self):
+ res = super(AccountInvoiceSend, self).send_and_print_action()
+ if self.snailmail_is_letter:
+ self.snailmail_print_action()
+ return res
diff --git a/addons/snailmail_account/wizard/account_invoice_send_views.xml b/addons/snailmail_account/wizard/account_invoice_send_views.xml
new file mode 100644
index 00000000000..275f8ea202f
--- /dev/null
+++ b/addons/snailmail_account/wizard/account_invoice_send_views.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data>
+ <record model="ir.ui.view" id="account_invoice_send_inherit_account_wizard_form">
+ <field name="name">account.invoice.send.form.inherited.snailmail</field>
+ <field name="model">account.invoice.send</field>
+ <field name="inherit_id" ref="account.account_invoice_send_wizard_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//div[@name='option_email']" position='before'>
+ <div name="option_letter">
+ <field name="letter_ids" invisible="1"/>
+ <field name="currency_id" invisible="1"/>
+ <div name="option" class="text-left d-inline-block">
+ <field name="snailmail_is_letter" />
+ <b><label for="snailmail_is_letter"/></b>
+ </div>
+ <div class="text-left d-inline-block mr8" name='cost' attrs="{'invisible': [('snailmail_is_letter','=', False)]}">
+ <b>(
+ <field name="snailmail_cost" class="mr4" widget='monetary' options="{'currency_field': 'currency_id'}"/>
+ <i class="fa fa-info-circle" role="img" aria-label="Warning" title="
+ The letter will be sent using the IAP service from Odoo.&#10;Make sure you have enough credits on your account or proceed to a recharge.&#10;In case of multiple sending, the price is estimated based on 1 page per document.
+ "/>
+ )</b>
+ </div>
+ <div class="text-right text-muted d-inline-block" name="address" attrs="{'invisible': ['|', ('composition_mode', '=', 'mass_mail'), ('is_print','=', False)]}">
+ <span> to: </span>
+ <field name="partner_id" readonly="1" force_save="1" context="{'show_address': 1, 'address_inline': 1}" options="{'always_reload': True, 'no_quick_create': True}"/>
+ </div>
+ </div>
+ </xpath>
+ <xpath expr="//footer/button[hasclass('send_and_print')]" position='attributes'>
+ <attribute name="attrs">{'invisible': ['|', ('printed', '=', True), '|', ('is_print', '=', False), '&amp;', '&amp;', ('is_print', '=', True), ('snailmail_is_letter', '=', False), ('is_email', '=', False)]}</attribute>
+ </xpath>
+ <xpath expr="//footer/button[hasclass('send')]" position='attributes'>
+ <attribute name="attrs">{'invisible': ['|', ('is_print', '=', True), '&amp;', '&amp;', ('is_print', '=', False), ('snailmail_is_letter', '=', False), ('is_email', '=', False)]}</attribute>
+ </xpath>
+ <xpath expr="//footer/button[hasclass('print')]" position='attributes'>
+ <attribute name="attrs">{'invisible': ['|', '|', '|', ('printed', '=', True), ('is_print', '=', False), ('snailmail_is_letter', '=', True), ('is_email', '=', True)]}</attribute>
+ </xpath>
+ </field>
+ </record>
+
+ </data>
+</odoo>