From 16631338739c5cea1f7bc7abaea8c9a0f6f0790b Mon Sep 17 00:00:00 2001 From: Yannick Tivisse Date: Wed, 14 Mar 2018 15:11:02 +0100 Subject: [PATCH 01/21] [FIX] purchase: Don't auto subscribe recipients on mail composers Purpose ======= During the refactoring of the purchase module something went wrong when overriding the 'send_mail' method. The 'mail_post_autofollow' key should have been set into the context only for the purchase orders model. See: https://github.com/odoo/odoo/commit/cb01be2#diff-fe8587929089e5fdb6c75687011b1c7eR822 --- addons/purchase/purchase.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index b051377410b..be396e28b8c 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -1047,7 +1047,8 @@ class MailComposeMessage(models.Model): @api.multi def send_mail(self, auto_commit=False): if self._context.get('default_model') == 'purchase.order' and self._context.get('default_res_id'): + self = self.with_context(mail_post_autofollow=True) order = self.env['purchase.order'].browse([self._context['default_res_id']]) if order.state == 'draft': order.state = 'sent' - return super(MailComposeMessage, self.with_context(mail_post_autofollow=True)).send_mail(auto_commit=auto_commit) + return super(MailComposeMessage, self).send_mail(auto_commit=auto_commit) From 2b5bb95d430a8cfb3f6abb982f7402c33cdcc313 Mon Sep 17 00:00:00 2001 From: Romain Derie Date: Thu, 15 Mar 2018 14:38:59 +0100 Subject: [PATCH 02/21] [FIX] website: make 'Optimize SEO' working again (for empty fields) This commit https://github.com/odoo/odoo/commit/965f49201812cc3cca319a3b5b93331272dd2288 removed empty meta-description and meta-keywords from the DOM for SEO audit reasons (these tags when empty are considered as duplicates at a SEO point of view). By doing so it prevent the 'Optimize SEO' to work correctly since it can't retrieve the DOM elements that are used to store the user modifications until he saves. Now, even when these meta-tags are empty, we display them to people having the rights to 'Optimize SEO'. This closes #23659 --- addons/website/views/website_templates.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 81a860ad21b..bcd6e5d5a97 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -110,8 +110,8 @@ and main_object.website_meta_description or website_meta_description"/> - - + + From 9fd5df43055057fdbe442635da2f9f0f9079a0fb Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Thu, 15 Mar 2018 14:11:22 +0100 Subject: [PATCH 03/21] [FIX] survey: add missing groups Survey Users have a correct record rule but portal and employee had no rule, being able to read more records than a Survey User Closes #23675 --- addons/survey/security/survey_security.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/survey/security/survey_security.xml b/addons/survey/security/survey_security.xml index afaf592686b..17a4d37fb65 100644 --- a/addons/survey/security/survey_security.xml +++ b/addons/survey/security/survey_security.xml @@ -42,7 +42,7 @@ Public access to user_input [('create_uid', '=', user.id)] - + From 45faa90732db8962df4521f739b0a24d0d33f730 Mon Sep 17 00:00:00 2001 From: Andreas Perhab Date: Sun, 18 Mar 2018 16:26:19 +0100 Subject: [PATCH 04/21] [CLA] signature for bigbear3001 Done at #23733 --- doc/cla/individual/bigbear3001.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 doc/cla/individual/bigbear3001.md diff --git a/doc/cla/individual/bigbear3001.md b/doc/cla/individual/bigbear3001.md new file mode 100644 index 00000000000..9690403db37 --- /dev/null +++ b/doc/cla/individual/bigbear3001.md @@ -0,0 +1,11 @@ +Austria, 18th of March 2018 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Andreas Perhab bigbear.ap@gmx.at https://github.com/bigbear3001 From 444e2a4ed08dcf312f505e7013e1a7b2702a39ca Mon Sep 17 00:00:00 2001 From: Andreas Perhab Date: Sun, 18 Mar 2018 16:20:40 +0100 Subject: [PATCH 05/21] [FIX] doc: correct link to nodejs download Closes #23733 --- doc/setup/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/setup/install.rst b/doc/setup/install.rst index 3e5940d8ba1..6c3a4f440d7 100644 --- a/doc/setup/install.rst +++ b/doc/setup/install.rst @@ -474,7 +474,7 @@ Source installation requires manually installing dependencies: $ sudo npm install -g less - - on Windows, `install nodejs `_, reboot (to + - on Windows, `install nodejs `_, reboot (to update the :envvar:`PATH`) and install less: .. code-block:: ps1 From 4c692f1bb31766840ca3eca6a8e0faebd34cda1a Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Tue, 20 Mar 2018 08:45:52 +0100 Subject: [PATCH 06/21] [FIX] website_project{_issue}: redirect from email - Create a portal user - Set an email address - Create a project with tasks / issues - Create a task / issue, set the portal user as follower => he should have access to the task from URL `/my/task/`. - On an email received (e.g. when the user is set as follower), click on 'View Task' The user is redirected to the chatter instead of `/my/task/` or `/my/issue/`. In method, `_redirect_to_record`, the record is browsed as `sudo`. Therefore, in `get_access_action`, `self.env.user` is the admin. We check the context to get the original user. opw-1814577 --- addons/website_project/models/project.py | 12 ++++++++++-- addons/website_project_issue/models/project_issue.py | 6 +++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/addons/website_project/models/project.py b/addons/website_project/models/project.py index 450e9de178d..3cf861e68d5 100644 --- a/addons/website_project/models/project.py +++ b/addons/website_project/models/project.py @@ -11,7 +11,11 @@ class Project(models.Model): """ Instead of the classic form view, redirect to website for portal users that can read the project. """ self.ensure_one() - if self.env.user.share: + if self.env.context.get('uid'): + user = self.env['res.users'].browse(self.env.context['uid']) + else: + user = self.env.user + if user.share: try: self.check_access_rule('read') except exceptions.AccessError: @@ -43,7 +47,11 @@ class Task(models.Model): """ Instead of the classic form view, redirect to website for portal users that can read the task. """ self.ensure_one() - if self.env.user.share: + if self.env.context.get('uid'): + user = self.env['res.users'].browse(self.env.context['uid']) + else: + user = self.env.user + if user.share: try: self.check_access_rule('read') except exceptions.AccessError: diff --git a/addons/website_project_issue/models/project_issue.py b/addons/website_project_issue/models/project_issue.py index d6d8916026a..ed5421924b5 100644 --- a/addons/website_project_issue/models/project_issue.py +++ b/addons/website_project_issue/models/project_issue.py @@ -13,7 +13,11 @@ class Issue(models.Model): """ Instead of the classic form view, redirect to website for portal users that can read the issue. """ self.ensure_one() - if self.env.user.share: + if self.env.context.get('uid'): + user = self.env['res.users'].browse(self.env.context['uid']) + else: + user = self.env.user + if user.share: try: self.check_access_rule('read') except exceptions.AccessError: From 407b85e329476699e9bda4348e6cb756bbd8a132 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Tue, 20 Mar 2018 09:24:18 +0100 Subject: [PATCH 07/21] [FIX] product: default quantity on pricelist item - Create a new pricelist for a new currency - Set the 'Sale Price' option to 'A single sale price per product', so that the user cannot see the pricelist details - Sell 0.5 unit of the product with the new pricelist The pricelist is not correctly applied. This is because the default minimum quantity is 1. Once the user is selling less than 1 unit, the pricelist is not applied. opw-1825353 --- addons/product/pricelist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/product/pricelist.py b/addons/product/pricelist.py index 3cd8a03c24c..8906fbe6300 100644 --- a/addons/product/pricelist.py +++ b/addons/product/pricelist.py @@ -340,7 +340,7 @@ class product_pricelist_item(osv.osv): _defaults = { 'base': 'list_price', - 'min_quantity': 1, + 'min_quantity': 0, 'sequence': 5, 'price_discount': 0, 'applied_on': '3_global', From c3b49b3a5fec204a62c4a6741c6e7bf71c1bee55 Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Mon, 19 Mar 2018 15:23:47 +0100 Subject: [PATCH 08/21] [FIX] mass_mailing: domain translation As Admin: - Set up 2 languages for the installation, e.g. English and French - Admin language is English, Demo language is French - Create a `res.partner.category` named 'Test English' - Assign the category to a newly created partner - Create a French translation for 'Test English', e.g. 'Test Francais' As Demo: - Create a Mass Mailing - Select a custom domain: ('category_id', '=', 'Test Francais') => it should match one partner - Save and validate Now either: - manually run the scheduled action "Process Mass Mailing Queue" as Admin - wait for the cron to process the mass mailing No mail will be sent since the domain will be evaluated in the Admin language, i.e. English. Indeed, the domain `('category_id', '=', 'Test Francais')` does not match any record in English. The solution/workaround is to use the language of the last user who wrote ont the mass mailing. Closes #20906, Closes #15467, Closes #15445 opw-778922 --- addons/mass_mailing/models/mass_mailing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index db828aa1b75..463d1dc7df2 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -611,6 +611,8 @@ class MassMailing(models.Model): def _process_mass_mailing_queue(self): mass_mailings = self.search([('state', 'in', ('in_queue', 'sending')), '|', ('schedule_date', '<', fields.Datetime.now()), ('schedule_date', '=', False)]) for mass_mailing in mass_mailings: + user = mass_mailing.write_uid or self.env.user + mass_mailing = mass_mailing.with_context(**user.sudo(user=user).context_get()) if len(mass_mailing.get_remaining_recipients()) > 0: mass_mailing.state = 'sending' mass_mailing.send_mail() From 6bd3bae04e030f4d69457e121d4137361d2b557a Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 3 Jul 2017 13:53:26 +0200 Subject: [PATCH 09/21] [FIX] mass_mailing: domain translation As Admin: - Set up 2 languages for the installation, e.g. English and French - Admin language is English, Demo language is French - Create a `res.partner.category` named 'Test English' - Assign the category to a newly created partner - Create a French translation for 'Test English', e.g. 'Test Francais' As Demo: - Create a Mass Mailing - Select a custom domain: ('category_id', '=', 'Test Francais') => it should match one partner - Save and validate Now either: - manually run the scheduled action "Process Mass Mailing Queue" as Admin - wait for the cron to process the mass mailing No mail will be sent since the domain will be evaluated in the Admin language, i.e. English. Indeed, the domain `('category_id', '=', 'Test Francais')` does not match any record in English. The solution/workaround is to use the language of the last user who wrote on the mass mailing. Closes #20906, Closes #15467, Closes #15445 opw-778922 --- addons/mass_mailing/models/mass_mailing.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index c59f12da0e6..154acd47bd8 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -836,9 +836,12 @@ class MassMailing(osv.Model): for mass_mailing_id in mass_mailing_ids: mass_mailing_record = self.browse(cr, uid, mass_mailing_id, context=context) + _uid = mass_mailing_record.write_uid.id or uid + _context = self.pool.get("res.users").context_get(cr, _uid, context=context) + _context = dict(context or {}, **_context) - if len(self.get_remaining_recipients(cr, uid, mass_mailing_record, context=context)) > 0: - self.write(cr, uid, [mass_mailing_id], {'state': 'sending'}, context=context) - self.send_mail(cr, uid, [mass_mailing_id], context=context) + if len(self.get_remaining_recipients(cr, uid, mass_mailing_record, context=_context)) > 0: + self.write(cr, uid, [mass_mailing_id], {'state': 'sending'}, context=_context) + self.send_mail(cr, uid, [mass_mailing_id], context=_context) else: - self.write(cr, uid, [mass_mailing_id], {'state': 'done'}, context=context) + self.write(cr, uid, [mass_mailing_id], {'state': 'done'}, context=_context) From 75d9a2b4db9fbad00114832186b7d4e3983124ba Mon Sep 17 00:00:00 2001 From: Martin Geubelle Date: Tue, 20 Mar 2018 11:48:06 +0100 Subject: [PATCH 10/21] [FIX] web: correctly display statusbar Some status were shown in the statusbar even though they were not specified in the `statusbar_visible` attribute. This happened when the value was a substring of one specified value. Example: ``` hello = fields.Selection([('hello', 'Hello'), ('hello_world', 'Hello World')]) ``` In this case, both status were shown ('hello' is a substring of 'hello_world'). Fixes #20034 --- addons/web/static/src/js/views/form_widgets.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/addons/web/static/src/js/views/form_widgets.js b/addons/web/static/src/js/views/form_widgets.js index a074c094de8..fe1cc32955e 100644 --- a/addons/web/static/src/js/views/form_widgets.js +++ b/addons/web/static/src/js/views/form_widgets.js @@ -1372,13 +1372,11 @@ var FieldStatus = common.AbstractField.extend({ // For field type selection filter values according to // statusbar_visible attribute of the field. For example: // statusbar_visible="draft,open". - var select = this.field.selection; - for(var i=0; i < select.length; i++) { - var key = select[i][0]; - if(key === this.get('value') || !this.options.visible || this.options.visible.indexOf(key) !== -1) { - selection_unfolded.push(select[i]); - } - } + var restriction = _.isString(this.options.visible) ? this.options.visible.split(',') : []; + selection_unfolded = _.filter(this.field.selection, function (val) { + return val[0] === self.get('value') || !self.options.visible || _.contains(restriction, val[0]); + }); + return $.when(); } }, this); From b77ff40e931b18011f8ae5ee4f22d9706377eb2b Mon Sep 17 00:00:00 2001 From: Romain Derie Date: Mon, 19 Mar 2018 15:39:07 +0100 Subject: [PATCH 11/21] [FIX] website_sale(_delivery): show prices according to B2B/B2C mode Before this commit: 1. The delivery price on checkout would always be shown without the taxes. This would make the cart total unclear since it is not indicated that this price is tax excluded. You could only assume that this was the price the delivery would cost you (and not just the untaxed delivery cost which you can't even find the full price anyway since taxes in subtotal is the sum of all products taxes). 2. The sale order line in payment step of checkout are always displayed with taxes. Now: We ensure that both 1. and 2. show the delivery price with or without taxes according to which mode (B2C or B2B) is enabled. This fixes #12872, closes #13592 and closes #23746 --- addons/website_sale/views/templates.xml | 3 ++- addons/website_sale_delivery/models/sale_order.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml index 566a353f8ae..67969cb97ee 100644 --- a/addons/website_sale/views/templates.xml +++ b/addons/website_sale/views/templates.xml @@ -1321,7 +1321,8 @@
- + + diff --git a/addons/website_sale_delivery/models/sale_order.py b/addons/website_sale_delivery/models/sale_order.py index 279dd758711..63f2f20208b 100644 --- a/addons/website_sale_delivery/models/sale_order.py +++ b/addons/website_sale_delivery/models/sale_order.py @@ -27,7 +27,10 @@ class SaleOrder(models.Model): @api.depends('order_line.price_unit', 'order_line.tax_id', 'order_line.discount', 'order_line.product_uom_qty') def _compute_amount_delivery(self): for order in self: - order.amount_delivery = sum(order.order_line.filtered('is_delivery').mapped('price_subtotal')) + if self.env.user.has_group('sale.group_show_price_subtotal'): + order.amount_delivery = sum(order.order_line.filtered('is_delivery').mapped('price_subtotal')) + else: + order.amount_delivery = sum(order.order_line.filtered('is_delivery').mapped('price_total')) @api.depends('order_line.is_delivery') def _compute_has_delivery(self): From 6d195ee603503242a6b120889ac63d990b357ec3 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Tue, 20 Mar 2018 11:13:58 +0100 Subject: [PATCH 12/21] [FIX] account: unreconcile payment - Create a SO for a customer of 100 $ - Create Invoice for the Sales Order and Register the payment - Create a Refund Invoice, Validate it and keep it in "Open" state - Create a second SO for 5 $ - Create an invoice for this order and validate it - It will show you outstanding payments - Apply the credit and the invoice will be fulfilled => Now you have a credit left for 95 $ - Create a third Sales order for the same customer for 5 $ - Create an Invoice for it - It will show you outstanding credit (95 $) - Apply the credits - Now 90 $ are left - In the same invoice, the credit which you applied, Unreconcile it - It should have shown you 95 $, but it shows you 100 $ which means it removed the credit which we applied to second SO The unreconcile process does not filter the partial reconciliations of the selected invoice. All partial reconciliations on the AML are removed instead. opw-1819602 --- addons/account/models/account_move.py | 5 +++ addons/account/tests/test_reconciliation.py | 36 +++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 61f1a6de41b..0ef2ae15016 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -1062,6 +1062,11 @@ class AccountMoveLine(models.Model): account_move_line.payment_id.write({'invoice_ids': [(3, invoice.id, None)]}) rec_move_ids += account_move_line.matched_debit_ids rec_move_ids += account_move_line.matched_credit_ids + if self.env.context.get('invoice_id'): + current_invoice = self.env['account.invoice'].browse(self.env.context['invoice_id']) + rec_move_ids = rec_move_ids.filtered( + lambda r: (r.debit_move_id + r.credit_move_id) & current_invoice.move_id.line_ids + ) return rec_move_ids.unlink() #################################################### diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py index 7d202628fba..8616fbb746f 100644 --- a/addons/account/tests/test_reconciliation.py +++ b/addons/account/tests/test_reconciliation.py @@ -620,3 +620,39 @@ class TestReconciliation(AccountingTestCase): # Checking if the direction of the move is correct full_rec_payable = full_rec_move.line_ids.filtered(lambda l: l.account_id == self.account_rsa) self.assertEqual(full_rec_payable.balance, 18.75) + + def test_unreconcile(self): + # Use case: + # 2 invoices paid with a single payment. Unreconcile the payment with one invoice, the + # other invoice should remain reconciled. + inv1 = self.create_invoice(invoice_amount=10, currency_id=self.currency_usd_id) + inv2 = self.create_invoice(invoice_amount=20, currency_id=self.currency_usd_id) + payment = self.env['account.payment'].create({ + 'payment_type': 'inbound', + 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id, + 'partner_type': 'customer', + 'partner_id': self.partner_agrolait_id, + 'amount': 100, + 'currency_id': self.currency_usd_id, + 'journal_id': self.bank_journal_usd.id, + }) + payment.post() + credit_aml = payment.move_line_ids.filtered('credit') + + # Check residual before assignation + self.assertAlmostEquals(inv1.residual, 10) + self.assertAlmostEquals(inv2.residual, 20) + + # Assign credit and residual + inv1.assign_outstanding_credit(credit_aml.id) + inv2.assign_outstanding_credit(credit_aml.id) + self.assertAlmostEquals(inv1.residual, 0) + self.assertAlmostEquals(inv2.residual, 0) + + # Unreconcile one invoice at a time and check residual + credit_aml.with_context(invoice_id=inv1.id).remove_move_reconcile() + self.assertAlmostEquals(inv1.residual, 10) + self.assertAlmostEquals(inv2.residual, 0) + credit_aml.with_context(invoice_id=inv2.id).remove_move_reconcile() + self.assertAlmostEquals(inv1.residual, 10) + self.assertAlmostEquals(inv2.residual, 20) From b200f020ea017cee4c818dca1ffa25d44b9c0196 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 14 Mar 2018 11:27:52 +0100 Subject: [PATCH 13/21] [FIX] crm: create opportunity from kanban - Create a customer, set an address - Create an opportunity from the kanban view The address fields are not filled in in the opportunity. We add all fields filled in by `_onchange_partner_id_values` Complement of commit e2bbee6df52191be opw-1816438 opw-1826859 --- addons/crm/views/crm_lead_views.xml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml index 42b756acc50..1eafa0541b2 100644 --- a/addons/crm/views/crm_lead_views.xml +++ b/addons/crm/views/crm_lead_views.xml @@ -204,8 +204,6 @@ - -