From 16631338739c5cea1f7bc7abaea8c9a0f6f0790b Mon Sep 17 00:00:00 2001 From: Yannick Tivisse Date: Wed, 14 Mar 2018 15:11:02 +0100 Subject: [PATCH 01/40] [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 b051377410b67..be396e28b8ce9 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/40] [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 81a860ad21b08..bcd6e5d5a97e9 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/40] [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 afaf592686bbc..17a4d37fb65ad 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/40] [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 0000000000000..9690403db3798 --- /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/40] [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 3e5940d8ba16f..6c3a4f440d794 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/40] [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 450e9de178d6c..3cf861e68d511 100644 --- a/addons/website_project/models/project.py +++ b/addons/website_project/models/project.py @@ -11,7 +11,11 @@ def get_access_action(self): """ 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 @@ def get_access_action(self): """ 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 d6d8916026a01..ed5421924b5ce 100644 --- a/addons/website_project_issue/models/project_issue.py +++ b/addons/website_project_issue/models/project_issue.py @@ -13,7 +13,11 @@ def get_access_action(self): """ 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/40] [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 3cd8a03c24c0b..8906fbe6300c8 100644 --- a/addons/product/pricelist.py +++ b/addons/product/pricelist.py @@ -340,7 +340,7 @@ def _get_product_pricelist(self, cr, uid, ids, context=None): _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/40] [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 db828aa1b7576..463d1dc7df25c 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -611,6 +611,8 @@ def convert_links(self): 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 f51d213f43e69bd31537a994f45b326a6e33ecc9 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Tue, 20 Mar 2018 11:49:45 +0100 Subject: [PATCH 09/40] [FIX] mail: rendering of mail template in Chinese Bad translation was causing the rendering to fail Probably fixes #23430 --- addons/mail/i18n/zh_CN.po | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/addons/mail/i18n/zh_CN.po b/addons/mail/i18n/zh_CN.po index 0e9290e36c1f7..496a41064b67b 100644 --- a/addons/mail/i18n/zh_CN.po +++ b/addons/mail/i18n/zh_CN.po @@ -1,18 +1,18 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * mail -# +# # Translators: # Jeffery CHEN Fan , 2017 # Martin Trigaux, 2017 # Talway <9010446@qq.com>, 2017 # niulin lnc. , 2017 -# liAnGjiA , 2017 # xiaobin wu , 2017 # 卓忆科技 , 2017 # Gary Wei , 2017 # also , 2017 # Joray <13637815@qq.com>, 2017 +# liAnGjiA , 2017 # Michael Chong , 2017 # liulixia , 2017 # 厦门-许舟 , 2017 @@ -43,10 +43,10 @@ msgstr "" "PO-Revision-Date: 2017-11-30 13:11+0000\n" "Last-Translator: e2f , 2018\n" "Language-Team: Chinese (China) (https://www.transifex.com/odoo/teams/41243/zh_CN/)\n" -"Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" +"Language: zh_CN\n" "Plural-Forms: nplurals=1; plural=0;\n" #. module: mail @@ -245,10 +245,6 @@ msgid "" "object.parent_id.subject) or (object.parent_id and " "object.parent_id.record_name and 'Re: %s' % object.parent_id.record_name)}" msgstr "" -"${object.subject 或 (object.record_name 和 'Re: %s' % object.record_name) 或 " -"(object.parent_id and object.parent_id.subject 和 'Re: %s' % " -"object.parent_id.subject) 或 (object.parent_id 和 object.parent_id.record_name" -" 和 'Re: %s' % object.parent_id.record_name)}" #. module: mail #. openerp-web From c077ed8866b64db6e8f2335a4e0ed946d273b593 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Mon, 19 Mar 2018 15:48:58 +0100 Subject: [PATCH 10/40] [FIX] account: reconcile all the balanced items (when there is a pager) Error when using CTRL-Enter to reconcile opw-1816574 --- .../static/src/js/reconciliation/reconciliation_model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index d7de12aa70d47..dd5c333bef7d5 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -614,7 +614,7 @@ var StatementModel = BasicModel.extend({ handles = [handle]; } else { _.each(this.lines, function (line, handle) { - if (!line.reconciled && !line.balance.amount && line.reconciliation_proposition.length) { + if (!line.reconciled && line.balance && !line.balance.amount && line.reconciliation_proposition.length) { handles.push(handle); } }); From f50a08bc3d53a1ccb7821c9dacd89c18f1215f15 Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Thu, 15 Mar 2018 17:20:03 +0100 Subject: [PATCH 11/40] [FIX] sale: Wrong display of taxes in PRO-FORMA Steps to reproduce the bug: -Create a tax of 21% tax included but don't select the "affect base" checkbox. -Create another tax of 5.2% (no tax include) -Create a sale order with a line that has a unit price of 121 and both taxes. Notice that the total amount is: 126.2 (100 base amount + 21 from 21% tax + 5.2 from 5.2 tax) -Click on "Print the proforma invoice" Bug: The total amount was still 126.2 and base=100, however taxes were wrong, there were 21 for the 21% tax and 6.29 instead of 5.2 for the 5.2 tax. opw:1819882 --- addons/sale/models/sale.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index b71183e698ea9..d42ff3991edd9 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -580,20 +580,16 @@ def _get_tax_amount_by_group(self): self.ensure_one() res = {} for line in self.order_line: - base_tax = 0 + price_reduce = line.price_unit * (1.0 - line.discount / 100.0) + taxes = line.tax_id.compute_all(price_reduce, quantity=line.product_uom_qty, product=line.product_id, partner=self.partner_shipping_id)['taxes'] for tax in line.tax_id: group = tax.tax_group_id res.setdefault(group, {'amount': 0.0, 'base': 0.0}) # FORWARD-PORT UP TO SAAS-17 - price_reduce = line.price_unit * (1.0 - line.discount / 100.0) - taxes = tax.compute_all(price_reduce + base_tax, quantity=line.product_uom_qty, - product=line.product_id, partner=self.partner_shipping_id)['taxes'] for t in taxes: - res[group]['amount'] += t['amount'] - res[group]['base'] += t['base'] - if tax.include_base_amount: - base_tax += tax.compute_all(price_reduce + base_tax, quantity=1, product=line.product_id, - partner=self.partner_shipping_id)['taxes'][0]['amount'] + if t['id'] == tax.id: + res[group]['amount'] += t['amount'] + res[group]['base'] += t['base'] res = sorted(res.items(), key=lambda l: l[0].sequence) res = [(l[0].name, l[1]['amount'], l[1]['base'], len(res)) for l in res] return res From 45396f7966bd1f9ffa062e45a665fbf328f65bd5 Mon Sep 17 00:00:00 2001 From: jem-odoo Date: Fri, 9 Mar 2018 15:54:06 +0100 Subject: [PATCH 12/40] [FIX] sale_timesheet: default so line for subtasks When creating a task in a project generated from an SO line, we want this to be linked to that sale line too. When creating a subtask, the sale line is forced to be the one of the parent, in `create` method. But we want to see it on the form view. --- addons/sale_timesheet/models/project.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/addons/sale_timesheet/models/project.py b/addons/sale_timesheet/models/project.py index 09475410a900f..16dbf838655b1 100644 --- a/addons/sale_timesheet/models/project.py +++ b/addons/sale_timesheet/models/project.py @@ -56,7 +56,16 @@ def action_view_timesheet_plan(self): class ProjectTask(models.Model): _inherit = "project.task" - sale_line_id = fields.Many2one('sale.order.line', 'Sales Order Item', domain="[('is_service', '=', True), ('order_partner_id', '=', partner_id)]") + @api.model + def _default_sale_line_id(self): + sale_line_id = False + if self._context.get('default_parent_id'): + sale_line_id = self.env['project.task'].browse(self._context['default_parent_id']).sale_line_id.id + if not sale_line_id and self._context.get('default_project_id'): + sale_line_id = self.env['project.project'].browse(self._context['default_project_id']).sale_line_id.id + return sale_line_id + + sale_line_id = fields.Many2one('sale.order.line', 'Sales Order Item', default=_default_sale_line_id, domain="[('is_service', '=', True), ('order_partner_id', '=', partner_id)]") @api.model def create(self, values): From 84db2dc9d4833d68ea5dd5ba84772fa7d38f5d1a Mon Sep 17 00:00:00 2001 From: jem-odoo Date: Fri, 9 Mar 2018 16:13:42 +0100 Subject: [PATCH 13/40] [FIX] sale_timesheet: SO line of subtasks When changing the so line of a task, we should update the ones from its children too. --- addons/sale_timesheet/models/project.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/sale_timesheet/models/project.py b/addons/sale_timesheet/models/project.py index 16dbf838655b1..77b9b25377d68 100644 --- a/addons/sale_timesheet/models/project.py +++ b/addons/sale_timesheet/models/project.py @@ -83,6 +83,10 @@ def write(self, values): result = super(ProjectTask, self).write(values) # reassign SO line on related timesheet lines if 'sale_line_id' in values: + # subtasks should have the same SO line than their mother + self.sudo().mapped('child_ids').write({ + 'so_line': values['sale_line_id'] + }) self.sudo().mapped('timesheet_ids').write({ 'so_line': values['sale_line_id'] }) From 3f5663e995491e80ab6d4dc0fdef508726f293a5 Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Mon, 19 Mar 2018 14:11:52 +0100 Subject: [PATCH 14/40] [FIX] l10n_ch: ignore stylistic background If user choose custom report type with background, we don't want the background on the ISR report that is intended to be printed on preprinted paper. opw-1820123 closes #23743 --- addons/l10n_ch/static/src/less/report_isr.less | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/addons/l10n_ch/static/src/less/report_isr.less b/addons/l10n_ch/static/src/less/report_isr.less index 913404727d25c..83de6851409a7 100644 --- a/addons/l10n_ch/static/src/less/report_isr.less +++ b/addons/l10n_ch/static/src/less/report_isr.less @@ -19,12 +19,18 @@ body.l10n_ch_isr { } } } -} -/* content outside isr needs margins to not overlap header */ -body.l10n_ch_isr #content_outside_isr { - padding: 15px; - padding-top: 150px; + /* content outside isr needs margins to not overlap header */ + #content_outside_isr { + padding: 15px; + padding-top: 150px; + } + + /* ISR is intended for pre-printed paper, we don't want stylistic background */ + .o_report_layout_background { + background: none; + min-height: 0; + } } body.l10n_ch_isr #isr { From 6bd3bae04e030f4d69457e121d4137361d2b557a Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 3 Jul 2017 13:53:26 +0200 Subject: [PATCH 15/40] [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 c59f12da0e6ba..154acd47bd85e 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -836,9 +836,12 @@ def _process_mass_mailing_queue(self, cr, uid, context=None): 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 16/40] [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 a074c094de876..fe1cc32955e6c 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 17/40] [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 566a353f8aeb6..67969cb97ee46 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 279dd758711c9..63f2f20208b8e 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 b6e0f1ad59e8a6b1b5a2b3a899466fafd8c339dc Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Tue, 20 Mar 2018 15:28:24 +0100 Subject: [PATCH 18/40] [FIX] sale,sale_payment: remove implicit dependency of `sale` on `payment` module This worked by accident only because `payment` module is auto-installed when `account` is installed. opw-1829812 --- addons/sale/views/res_config_settings_views.xml | 3 --- addons/sale_payment/__manifest__.py | 1 + addons/sale_payment/views/settings.xml | 15 +++++++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 addons/sale_payment/views/settings.xml diff --git a/addons/sale/views/res_config_settings_views.xml b/addons/sale/views/res_config_settings_views.xml index 651c9cf590926..feac083505669 100644 --- a/addons/sale/views/res_config_settings_views.xml +++ b/addons/sale/views/res_config_settings_views.xml @@ -169,9 +169,6 @@
-
-
diff --git a/addons/sale_payment/__manifest__.py b/addons/sale_payment/__manifest__.py index 02916e3cbf39e..bb53a6f1e8206 100644 --- a/addons/sale_payment/__manifest__.py +++ b/addons/sale_payment/__manifest__.py @@ -20,6 +20,7 @@ 'views/crm_team_views.xml', 'views/sale_order_views.xml', 'views/sale_portal_templates.xml', + 'views/settings.xml', ], 'installable': True, 'auto_install': False, diff --git a/addons/sale_payment/views/settings.xml b/addons/sale_payment/views/settings.xml new file mode 100644 index 0000000000000..a0379056a6f45 --- /dev/null +++ b/addons/sale_payment/views/settings.xml @@ -0,0 +1,15 @@ + + + + res.config.settings.view.form.inherit.sale_payment + res.config.settings + + + +
+
+
+
+
+
From cbd395ec365f5f2f484d7137559a62a135bda5fa Mon Sep 17 00:00:00 2001 From: Nikunj Ladava Date: Tue, 20 Mar 2018 11:36:35 +0530 Subject: [PATCH 19/40] [IMP] payment, sale_payment: set invoice id in payment transaction form after payment being done from sale order. Until now invoice_id is filled on the transaction only when we make payments through invoice. However if we make payments through sale order then invoice_id is not filled. This commit fixes that issue. As there is no common point between sale_payment and account_payment except payment, a void method is added in payment and an override done in account_payment. That was when both modules are installed code is correctly triggered. This commit is related to task ID 1813602. Closes #22746 . --- addons/account_payment/models/payment.py | 7 +++++++ addons/payment/models/payment_acquirer.py | 4 ++++ addons/sale_payment/models/payment.py | 1 + 3 files changed, 12 insertions(+) diff --git a/addons/account_payment/models/payment.py b/addons/account_payment/models/payment.py index 0ad11d39788a8..389cfa206e1d2 100644 --- a/addons/account_payment/models/payment.py +++ b/addons/account_payment/models/payment.py @@ -175,3 +175,10 @@ def _check_or_create_invoice_tx(self, invoice, acquirer, payment_token=None, tx_ }) return tx + + def _post_process_after_done(self, **kwargs): + # set invoice id in payment transaction when payment being done from sale order + res = super(PaymentTransaction, self)._post_process_after_done() + if kwargs.get('invoice_id'): + self.account_invoice_id = kwargs['invoice_id'] + return res diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py index 343e7ebd11671..691de43807222 100644 --- a/addons/payment/models/payment_acquirer.py +++ b/addons/payment/models/payment_acquirer.py @@ -684,6 +684,10 @@ def form_feedback(self, data, acquirer_name): return True + @api.multi + def _post_process_after_done(self, **kwargs): + return True + # -------------------------------------------------- # SERVER2SERVER RELATED METHODS # -------------------------------------------------- diff --git a/addons/sale_payment/models/payment.py b/addons/sale_payment/models/payment.py index 857ff857dfb21..8076addab0968 100644 --- a/addons/sale_payment/models/payment.py +++ b/addons/sale_payment/models/payment.py @@ -103,6 +103,7 @@ def _generate_and_pay_invoice(self): created_invoice.with_context(default_currency_id=self.currency_id.id).pay_and_reconcile(self.acquirer_id.journal_id, pay_amount=created_invoice.amount_total) if created_invoice.payment_ids: created_invoice.payment_ids[0].payment_transaction_id = self + self._post_process_after_done(invoice_id=created_invoice) else: _logger.warning('<%s> transaction completed, could not auto-generate invoice for %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id) From 5bc29744c9e605729632c6d2561156b886772058 Mon Sep 17 00:00:00 2001 From: Adrien Dieudonne Date: Fri, 9 Mar 2018 13:55:07 +0100 Subject: [PATCH 20/40] [FIX] barcodes: skip undefined keys in barcode value This fixes an issue occurring with Firefox. Some events was buffered with an undefined value and it wasn't possible to build the barcode value from these events. See 'handle_buffered_keys' function. So now, we simply skip this kind of event. We consider this as 'special keys'. --- addons/barcodes/static/src/js/barcode_events.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/barcodes/static/src/js/barcode_events.js b/addons/barcodes/static/src/js/barcode_events.js index e02d3aafdb7cc..3c5c1f51ddfa5 100644 --- a/addons/barcodes/static/src/js/barcode_events.js +++ b/addons/barcodes/static/src/js/barcode_events.js @@ -109,7 +109,7 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Escape" || e.key === "Tab" || e.key === "Backspace" || e.key === "Delete" || - /F\d\d?/.test(e.key)) { + e.key === "Unidentified" || /F\d\d?/.test(e.key)) { return true; } else { return false; From 3a74afd21eb12ca79a2f609b58f271278cae57cf Mon Sep 17 00:00:00 2001 From: Adrien Dieudonne Date: Wed, 14 Mar 2018 09:33:35 +0100 Subject: [PATCH 21/40] [FIX] barcodes: barcode scanning on Chrome mobile Before this commit, the barcode value wasn't triggered on some Android devices with Google Chrome and using the Odoo app. In fact, the keypress event may not trigger with some devices. This is probably due to the fact that this event is marked as 'Legacy'. See: https://www.w3.org/TR/uievents/#legacy-keyboardevent-event-types To fix this, we can use 'keydown' event but we have to handle an other issue: there is no way to know which key is typed. The auto-suggest feature on Android invalidate all the following properties: Keycode, charCode, key, which, keyIdentifier, code. This is a well-known issue: https://bugs.chromium.org/p/chromium/issues/detail?id=118639 For more infos, please read this blog: https://www.outsystems.com/blog/javascript-events-unmasked-how-to-create-input-mask-for-mobile.html As a work around, we create a temporary input field that stores the barcode value. The focus is set on this input when a keydown is detected. Note that when an input has the focus, the android virtual keyboard will be opened. We can't avoid this behavior. The only thing we can do is to automatically close it after 800 ms. See: https://bugs.chromium.org/p/chromium/issues/detail?id=662386 As this fix is specific for Chrome only, it's not possible to test it easily. Some tests will be added in master. --- .../barcodes/static/src/js/barcode_events.js | 108 +++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/addons/barcodes/static/src/js/barcode_events.js b/addons/barcodes/static/src/js/barcode_events.js index 3c5c1f51ddfa5..71e07030ffd34 100644 --- a/addons/barcodes/static/src/js/barcode_events.js +++ b/addons/barcodes/static/src/js/barcode_events.js @@ -28,6 +28,12 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { // Keys from a barcode scanner are usually processed as quick as possible, // but some scanners can use an intercharacter delay (we support <= 50 ms) max_time_between_keys_in_ms: session.max_time_between_keys_in_ms || 55, + // To be able to receive the barcode value, an input must be focused. + // On mobile devices, this causes the virtual keyboard to open. + // Unfortunately it is not possible to avoid this behavior... + // To avoid keyboard flickering at each detection of a barcode value, + // we want to keep it open for a while (800 ms). + inputTimeOut: 800, init: function() { mixins.PropertiesMixin.init.call(this); @@ -38,6 +44,30 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { // Bind event handler once the DOM is loaded // TODO: find a way to be active only when there are listeners on the bus $(_.bind(this.start, this, false)); + + // Mobile device detection + var isMobile = navigator.userAgent.match(/Android/i) || + navigator.userAgent.match(/webOS/i) || + navigator.userAgent.match(/iPhone/i) || + navigator.userAgent.match(/iPad/i) || + navigator.userAgent.match(/iPod/i) || + navigator.userAgent.match(/BlackBerry/i) || + navigator.userAgent.match(/Windows Phone/i); + this.isChromeMobile = isMobile && window.chrome; + + // Creates an input who will receive the barcode scanner value. + if (this.isChromeMobile) { + this.$barcodeInput = $('', { + name: 'barcode', + type: 'text', + css: { + 'position': 'absolute', + 'opacity': 0, + }, + }); + } + + this.__removeBarcodeField = _.debounce(this._removeBarcodeField, this.inputTimeOut); }, handle_buffered_keys: function() { @@ -167,8 +197,84 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { } }, + /** + * Try to detect the barcode value by listening all keydown events: + * Checks if a dom element who may contains text value has the focus. + * If not, it's probably because these events are triggered by a barcode scanner. + * To be able to handle this value, a focused input will be created. + * + * This function also has the responsibility to detect the end of the barcode value. + * (1) In most of cases, an optional key (tab or enter) is sent to mark the end of the value. + * So, we direclty handle the value. + * (2) If no end key is configured, we have to calculate the delay between each keydowns. + * 'max_time_between_keys_in_ms' depends of the device and may be configured. + * Exceeded this timeout, we consider that the barcode value is entirely sent. + * + * @private + * @param {jQuery.Event} e keydown event + */ + _listenBarcodeScanner: function (e) { + if (!$('input:text:focus, textarea:focus, [contenteditable]:focus').length) { + $('body').append(this.$barcodeInput); + this.$barcodeInput.focus(); + } + if (this.$barcodeInput.is(":focus")) { + // Handle buffered keys immediately if the keypress marks the end + // of a barcode or after x milliseconds without a new keypress. + clearTimeout(this.timeout); + // On chrome mobile, e.which only works for some special characters like ENTER or TAB. + if (String.fromCharCode(e.which).match(this.suffix)) { + this._handleBarcodeValue(e); + } else { + this.timeout = setTimeout(this._handleBarcodeValue.bind(this, e), + this.max_time_between_keys_in_ms); + } + // if the barcode input doesn't receive keydown for a while, remove it. + this.__removeBarcodeField(); + } + }, + + /** + * Retrieves the barcode value from the temporary input element. + * This checks this value and trigger it on the bus. + * + * @private + * @param {jQuery.Event} keydown event + */ + _handleBarcodeValue: function (e) { + var barcodeValue = this.$barcodeInput.val(); + if (barcodeValue.match(this.regexp)) { + core.bus.trigger('barcode_scanned', barcodeValue, $(e.target).parent()[0]); + this.$barcodeInput.val(''); + } + }, + + /** + * Remove the temporary input created to store the barcode value. + * If nothing happens, this input will be removed, so the focus will be lost + * and the virtual keyboard on mobile devices will be closed. + * + * @private + */ + _removeBarcodeField: function () { + if (this.$barcodeInput) { + // Reset the value and remove from the DOM. + this.$barcodeInput.val('').remove(); + } + }, + start: function(prevent_key_repeat){ - $('body').bind("keypress", this.__handler); + // Chrome Mobile isn't triggering keypress event. + // This is marked as Legacy in the DOM-Level-3 Standard. + // See: https://www.w3.org/TR/uievents/#legacy-keyboardevent-event-types + // This fix is only applied for Google Chrome Mobile but it should work for + // all other cases. + // In master, we could remove the behavior with keypress and only use keydown. + if (this.isChromeMobile) { + $('body').on("keydown", this._listenBarcodeScanner.bind(this)); + } else { + $('body').bind("keypress", this.__handler); + } if (prevent_key_repeat === true) { $('body').bind("keydown", this.__keydown_handler); $('body').bind('keyup', this.__keyup_handler); From 6d195ee603503242a6b120889ac63d990b357ec3 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Tue, 20 Mar 2018 11:13:58 +0100 Subject: [PATCH 22/40] [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 61f1a6de41bb5..0ef2ae1501604 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -1062,6 +1062,11 @@ def remove_move_reconcile(self): 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 7d202628fbaf0..8616fbb746f42 100644 --- a/addons/account/tests/test_reconciliation.py +++ b/addons/account/tests/test_reconciliation.py @@ -620,3 +620,39 @@ def test_partial_reconcile_currencies(self): # 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 60793a8a48bddb59eb50b562fbff8646e82f0d3a Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Tue, 20 Mar 2018 16:40:33 +0100 Subject: [PATCH 23/40] [FIX] stock_account: Cannot validate a Customer Credit Steps to reproduce the bug: - Create a customer credit note - Add a new item - Don't input Product, input Description and Unit of Measure - Click on Validate Bug: A traceback was raised because no product was set on the invoice line. opw:1825972 --- addons/stock_account/models/account_invoice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/stock_account/models/account_invoice.py b/addons/stock_account/models/account_invoice.py index 8614fde2f6fba..5f9263dbbc170 100644 --- a/addons/stock_account/models/account_invoice.py +++ b/addons/stock_account/models/account_invoice.py @@ -44,6 +44,8 @@ class AccountInvoiceLine(models.Model): def _get_anglo_saxon_price_unit(self): self.ensure_one() + if not self.product_id: + return self.price_unit return self.product_id._get_anglo_saxon_price_unit(uom=self.uom_id) def _get_price(self, company_currency, price_unit): From 57e457c524b9d624e2a34a1c1cb36472f2e68b00 Mon Sep 17 00:00:00 2001 From: Rohan Patel Date: Fri, 9 Feb 2018 17:40:53 +0530 Subject: [PATCH 24/40] [IMP] hr: check employees only alias restriction independently of record existence Commit 78ac6de52d690d278fd0cde5476bbeaec86cb124 refactored methods checking alias security on the routing found for a given destination address. Indeed if a routing is found linked to an alias a security check is performed according to the restriction defined on the alias itself. HR module adds the 'employees only' restriction. A bug has been introduced in the mentioned commit concerning employees-based aliases. Indeed a condition on having a recordset has been added (self.ids, changed to record.ids at 20d8025036eed9dc30365e3b5ccc5cd1b4b01c77). This condition is actually not necessary as checking the email author is linked to an existing employee has nothing to do with the alias being linked to a record or creating new record. This may causes issues notably using employees-restricted aliases in expense application. Indeed you could use aliases to create new expenses for employees and you could have issues with this condition. This commit is linked to task ID 1829860 and ID 35093. Closes #22960 . --- addons/hr/models/mail_alias.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/hr/models/mail_alias.py b/addons/hr/models/mail_alias.py index f98fce32f5ae9..c224d21df074b 100644 --- a/addons/hr/models/mail_alias.py +++ b/addons/hr/models/mail_alias.py @@ -14,7 +14,7 @@ class MailAlias(models.AbstractModel): _inherit = 'mail.alias.mixin' def _alias_check_contact_on_record(self, record, message, message_dict, alias): - if alias.alias_contact == 'employees' and record.ids: + if alias.alias_contact == 'employees': email_from = tools.decode_message_header(message, 'From') email_address = tools.email_split(email_from)[0] employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1) From b200f020ea017cee4c818dca1ffa25d44b9c0196 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 14 Mar 2018 11:27:52 +0100 Subject: [PATCH 25/40] [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 42b756acc5071..1eafa0541b282 100644 --- a/addons/crm/views/crm_lead_views.xml +++ b/addons/crm/views/crm_lead_views.xml @@ -204,8 +204,6 @@ - -