From cd5c10a47f27b77b39cd102012979bbeece2a4ea Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Tue, 10 Jul 2018 11:18:26 +0200 Subject: [PATCH 1/8] [FIX] website_sale_delivery: we should be able to pay if the order only contains services Two use cases must work: Should hide payment AND display errors when order contains deliverable product Should show payment AND not display errors when order contains only services (event tickets) Linked OPW 1864744 Linked PR v11.0 #25679 closes #25692 --- addons/website_sale_delivery/models/sale_order.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/website_sale_delivery/models/sale_order.py b/addons/website_sale_delivery/models/sale_order.py index 63f2f20208b..43aa2f63d86 100644 --- a/addons/website_sale_delivery/models/sale_order.py +++ b/addons/website_sale_delivery/models/sale_order.py @@ -95,8 +95,10 @@ class SaleOrder(models.Model): @api.model def _get_errors(self, order): + # Do not Forward port in v11.0 as the API was refactored by f9a8c53e485445bc8e3474eb3978b0f83d5645c7 + has_stockable_products = any(order.order_line.filtered(lambda line: line.product_id.type in ['consu', 'product'])) errors = super(SaleOrder, self)._get_errors(order) - if not order._get_delivery_methods(): + if not order._get_delivery_methods() and has_stockable_products: errors.append( (_('Sorry, we are unable to ship your order'), _('No shipping method is available for your current order and shipping address. ' From ddb68eb1bb0be699e86ba65d906c40a5ccf9d43c Mon Sep 17 00:00:00 2001 From: len-odoo Date: Mon, 9 Jul 2018 16:06:22 +0200 Subject: [PATCH 2/8] [FIX] crm: handle selection fields correctly while merging leads When merging two or more leads, a traceback was generated when attempting to merge some selection field, if any. closes https://github.com/odoo/odoo/issues/25644 opw 1864870 --- addons/crm/models/crm_lead.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index 411aa08a9fe..226632b144a 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -504,7 +504,8 @@ class Lead(FormatAddress, models.Model): for field in fields: value = getattr(self, field.name, False) if field.ttype == 'selection': - value = dict(field.get_values(self.env)).get(value, value) + selections = self.fields_get()[field.name]['selection'] + value = next((v[1] for v in selections if v[0] == value), value) elif field.ttype == 'many2one': if value: value = value.sudo().name_get()[0][1] From 00a55f4428bf72f5e9e2ff4b4028090b6321cf4a Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Mon, 9 Jul 2018 14:59:11 +0200 Subject: [PATCH 3/8] [FIX] service: properly invalidate session of deteled users If a user A deletes the res.users record of user B while B is connected, the verification of the session token fails with a comparison of a boolean and bytes values. While the check should obviously fail, this patch gracefully inform the user B its session has expired and redirect him to the login page. Without the patch, the session is never invalidated in the user browser, redirecting to a forbidden error page as long as the session has not been manually cleared from the browser. Fixes #25530 Closes #25654 Closes #25682 Cherry-Pick of 96f01c08f8cd15cd572333fafed4456e4f6ef88a --- openerp/service/security.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openerp/service/security.py b/openerp/service/security.py index 28c85788a0a..babdd88f30e 100644 --- a/openerp/service/security.py +++ b/openerp/service/security.py @@ -18,7 +18,8 @@ def compute_session_token(session, env): def check_session(session, env): self = env['res.users'].browse(session.uid) - if openerp.tools.misc.consteq(self._compute_session_token(session.sid), session.session_token): + expected = self._compute_session_token(session.sid) + if expected and openerp.tools.misc.consteq(expected, session.session_token): return True self._invalidate_session_cache() return False From 2d0621da05437901b88e23c8b7c20668bdfcb435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Gonz=C3=A1lez?= Date: Sun, 8 Jul 2018 18:12:29 +0000 Subject: [PATCH 4/8] [CLA] fix date format of Vauxoo's agreement According to the CLA signing instructions, the date of signature must be expressed in the following form: > ``: current date in the form `YYYY-MM-DD` However, the date was wrongly expressed as "9-2-2015". That date not only was following an incorrect format, but also it was ambiguous, because it was not clear if the format was following M-D-YYYY or D-M-YYYY (the latter one being the case). This fixes the format, so it now fits the required one. Closes #25665 --- doc/cla/corporate/vauxoo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/cla/corporate/vauxoo.md b/doc/cla/corporate/vauxoo.md index e334925ca6d..0daaf9782b1 100644 --- a/doc/cla/corporate/vauxoo.md +++ b/doc/cla/corporate/vauxoo.md @@ -1,4 +1,4 @@ -México, 9-2-2015 +México, 2015-02-09 Vauxoo agrees to the terms of the Odoo Corporate Contributor License Agreement v1.0. From 6e8f1fcc6397365678d1030281134a9ea5998856 Mon Sep 17 00:00:00 2001 From: Alexandre Moens <37290841+mao-odoo@users.noreply.github.com> Date: Wed, 11 Jul 2018 14:43:33 +0200 Subject: [PATCH 5/8] [FIX] Stock : deliveryslip report - prioritize partner_id (#25709) Display the address of the partner_id when available rather than using the partner id of the movelines. Important when the partner is changed after the movelines are created. support ticket : 1857582 --- addons/stock/report/report_deliveryslip.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/stock/report/report_deliveryslip.xml b/addons/stock/report/report_deliveryslip.xml index ccbbb9bcca1..164e62700c4 100644 --- a/addons/stock/report/report_deliveryslip.xml +++ b/addons/stock/report/report_deliveryslip.xml @@ -11,11 +11,11 @@
Customer Address:
-
+
-
+
From 189e0088e08109f0374d4b2518f8928b7010611c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul=20=28ACSONE=29?= Date: Wed, 11 Jul 2018 16:56:59 +0200 Subject: [PATCH 6/8] [FIX] account: preserve deactivated taxes on move lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Similar to cd3f52ba1064000483f1a4cd781cf576767b4e01 that was made for invoices. Was PR #25655. Courtesy of Stéphane Bidoul (ACSONE) --- addons/account/models/account_move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index e59d6c78101..0271807a235 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -413,7 +413,7 @@ class AccountMoveLine(models.Model): help="This field is used for payable and receivable journal entries. You can put the limit date for the payment of this line.") date = fields.Date(related='move_id.date', string='Date', required=True, index=True, default=fields.Date.context_today, store=True, copy=False) analytic_line_ids = fields.One2many('account.analytic.line', 'move_id', string='Analytic lines', oldname="analytic_lines") - tax_ids = fields.Many2many('account.tax', string='Taxes') + tax_ids = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)]) tax_line_id = fields.Many2one('account.tax', string='Originator tax', ondelete='restrict') analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account') company_id = fields.Many2one('res.company', related='account_id.company_id', string='Company', store=True) From dad19a769a1123538d9717ff9b9134d97d3ff194 Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Tue, 10 Jul 2018 15:33:14 +0200 Subject: [PATCH 7/8] [FIX] sale: Price on SO not working with different uom Steps to reproduce the bug: - configuration on sale: - Unit of measure: Some products may be sold/purchased in different units of measure (Advanced) - Sale Price: Advaced pricing based on formulas (discounts, margins, rounding) - Discount: Allow discounts on sale order lines - kg as the Reference UOM for weight - create an uom 10 kg - Category: Weight - Type: Bigger... - Bigger ratio: 10.0 - create a product P - Sale price: 100 - Unit of measure: kg - Purchase UOM: kg - create a pricelist "My pricelist" - Discount policy: Show public price & discount to the customer - Items : All products - 10% discount - create a SO with "My pricelist" - create a SO line with P, the unit price is automatically 90 with 10 % of discount - change the uom of the line with 10kg Bug: The unit price of the line didn't change and the discount on the line disappeared. In function product_uom_change, the uom of the SO line has been set in the context of the product(like in function product_id_change). So in funciton _get_display_price, the function _get_real_price_currency must be called with the uom of product in the context. In this way, the lst_price will be fetch on product with the right uom without any bad conversion (because uom.id == product_uom with uom from the parameters and product_uom from the context). But with function get_product_price_rule, no need to call this function with the uom in context as it is already set in the product(check "if uom_id: ... else ..." in this function). The same logic has been applied in function _onchange_discount opw:186385 --- addons/sale/models/sale.py | 27 ++-- addons/sale/tests/test_product_id_change.py | 155 ++++++++++++++++++++ 2 files changed, 173 insertions(+), 9 deletions(-) diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 190b7535b4f..633a2d0ff41 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -865,11 +865,11 @@ class SaleOrderLine(models.Model): # TO DO: move me in master/saas-16 on sale.order if self.order_id.pricelist_id.discount_policy == 'with_discount': return product.with_context(pricelist=self.order_id.pricelist_id.id).price - final_price, rule_id = self.order_id.pricelist_id.get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id) - context_partner = dict(self.env.context, partner_id=self.order_id.partner_id.id, date=self.order_id.date_order) - base_price, currency_id = self.with_context(context_partner)._get_real_price_currency(self.product_id, rule_id, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id) + product_context = dict(self.env.context, partner_id=self.order_id.partner_id.id, date=self.order_id.date_order, uom=self.product_uom.id) + final_price, rule_id = self.order_id.pricelist_id.with_context(product_context).get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id) + base_price, currency_id = self.with_context(product_context)._get_real_price_currency(product, rule_id, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id) if currency_id != self.order_id.pricelist_id.currency_id.id: - base_price = self.env['res.currency'].browse(currency_id).with_context(context_partner).compute(base_price, self.order_id.pricelist_id.currency_id) + base_price = self.env['res.currency'].browse(currency_id).with_context(product_context).compute(base_price, self.order_id.pricelist_id.currency_id) # negative discounts (= surcharge) are included in the display price return max(base_price, final_price) @@ -1014,16 +1014,25 @@ class SaleOrderLine(models.Model): self.env.user.has_group('sale.group_discount_per_so_line')): return - context_partner = dict(self.env.context, partner_id=self.order_id.partner_id.id, date=self.order_id.date_order) - pricelist_context = dict(context_partner, uom=self.product_uom.id) + product = self.product_id.with_context( + lang=self.order_id.partner_id.lang, + partner=self.order_id.partner_id.id, + quantity=self.product_uom_qty, + date=self.order_id.date_order, + pricelist=self.order_id.pricelist_id.id, + uom=self.product_uom.id, + fiscal_position=self.env.context.get('fiscal_position') + ) - price, rule_id = self.order_id.pricelist_id.with_context(pricelist_context).get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id) - new_list_price, currency_id = self.with_context(context_partner)._get_real_price_currency(self.product_id, rule_id, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id) + product_context = dict(self.env.context, partner_id=self.order_id.partner_id.id, date=self.order_id.date_order, uom=self.product_uom.id) + + price, rule_id = self.order_id.pricelist_id.with_context(product_context).get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id) + new_list_price, currency_id = self.with_context(product_context)._get_real_price_currency(product, rule_id, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id) if new_list_price != 0: if self.order_id.pricelist_id.currency_id.id != currency_id: # we need new_list_price in the same currency as price, which is in the SO's pricelist's currency - new_list_price = self.env['res.currency'].browse(currency_id).with_context(context_partner).compute(new_list_price, self.order_id.pricelist_id.currency_id) + new_list_price = self.env['res.currency'].browse(currency_id).with_context(product_context).compute(new_list_price, self.order_id.pricelist_id.currency_id) discount = (new_list_price - price) / new_list_price * 100 if discount > 0: self.discount = discount diff --git a/addons/sale/tests/test_product_id_change.py b/addons/sale/tests/test_product_id_change.py index bf61c8d644f..7d440c4c709 100644 --- a/addons/sale/tests/test_product_id_change.py +++ b/addons/sale/tests/test_product_id_change.py @@ -116,3 +116,158 @@ class TestOnchangeProductId(TransactionCase): so.date_order = '2017-12-30' order_line.product_id_change() self.assertEqual(order_line.price_unit, 50, "Second date pricelist rule not applied") + + def test_pricelist_uom_discount(self): + """ Test prices and discounts are correctly applied based on date and uom""" + computer_case = self.env.ref('product.product_product_16') + computer_case.list_price = 100 + partner = self.res_partner_model.create(dict(name="George")) + categ_unit_id = self.ref('product.product_uom_unit') + goup_discount_id = self.ref('sale.group_discount_per_so_line') + self.env.user.write({'groups_id': [(4, goup_discount_id, 0)]}) + new_uom = self.env['product.uom'].create({ + 'name': '10 units', + 'factor_inv': 10, + 'uom_type': 'bigger', + 'rounding': 1.0, + 'category_id': categ_unit_id + }) + christmas_pricelist = self.env['product.pricelist'].create({ + 'name': 'Christmas pricelist', + 'discount_policy': 'without_discount', + 'item_ids': [(0, 0, { + 'date_start': "2017-12-01", + 'date_end': "2017-12-30", + 'compute_price': 'percentage', + 'base': 'list_price', + 'percent_price': 10, + 'applied_on': '3_global', + 'name': 'Christmas discount' + })] + }) + + so = self.env['sale.order'].create({ + 'partner_id': partner.id, + 'date_order': '2017-12-20', + 'pricelist_id': christmas_pricelist.id, + }) + + order_line = self.env['sale.order.line'].new({ + 'order_id': so.id, + 'product_id': computer_case.id, + }) + + # force compute uom and prices + order_line.product_id_change() + order_line.product_uom_change() + order_line._onchange_discount() + self.assertEqual(order_line.price_subtotal, 90, "Christmas discount pricelist rule not applied") + self.assertEqual(order_line.discount, 10, "Christmas discount not equalt to 10%") + order_line.product_uom = new_uom + order_line.product_uom_change() + order_line._onchange_discount() + self.assertEqual(order_line.price_subtotal, 900, "Christmas discount pricelist rule not applied") + self.assertEqual(order_line.discount, 10, "Christmas discount not equalt to 10%") + + def test_pricelist_based_on_other(self): + """ Test price and discount are correctly applied with a pricelist based on an other one""" + computer_case = self.env.ref('product.product_product_16') + computer_case.list_price = 100 + partner = self.res_partner_model.create(dict(name="George")) + categ_unit_id = self.ref('product.product_uom_unit') + goup_discount_id = self.ref('sale.group_discount_per_so_line') + self.env.user.write({'groups_id': [(4, goup_discount_id, 0)]}) + + first_pricelist = self.env['product.pricelist'].create({ + 'name': 'First pricelist', + 'discount_policy': 'without_discount', + 'item_ids': [(0, 0, { + 'compute_price': 'percentage', + 'base': 'list_price', + 'percent_price': 10, + 'applied_on': '3_global', + 'name': 'First discount' + })] + }) + + second_pricelist = self.env['product.pricelist'].create({ + 'name': 'Second pricelist', + 'discount_policy': 'without_discount', + 'item_ids': [(0, 0, { + 'compute_price': 'formula', + 'base': 'pricelist', + 'base_pricelist_id': first_pricelist.id, + 'price_discount': 10, + 'applied_on': '3_global', + 'name': 'Second discount' + })] + }) + + so = self.env['sale.order'].create({ + 'partner_id': partner.id, + 'date_order': '2018-07-11', + 'pricelist_id': second_pricelist.id, + }) + + order_line = self.env['sale.order.line'].new({ + 'order_id': so.id, + 'product_id': computer_case.id, + }) + + # force compute uom and prices + order_line.product_id_change() + order_line._onchange_discount() + self.assertEqual(order_line.price_subtotal, 81, "Second pricelist rule not applied") + self.assertEqual(order_line.discount, 19, "Second discount not applied") + + def test_pricelist_with_other_currency(self): + """ Test prices are correctly applied with a pricelist with an other currency""" + computer_case = self.env.ref('product.product_product_16') + computer_case.list_price = 100 + partner = self.res_partner_model.create(dict(name="George")) + categ_unit_id = self.ref('product.product_uom_unit') + other_currency = self.env['res.currency'].create({'name': 'other currency', + 'symbol': 'other'}) + self.env['res.currency.rate'].create({'name': '2018-07-11', + 'rate': 2.0, + 'currency_id': other_currency.id, + 'company_id': self.env.user.company_id.id}) + new_uom = self.env['product.uom'].create({ + 'name': '10 units', + 'factor_inv': 10, + 'uom_type': 'bigger', + 'rounding': 1.0, + 'category_id': categ_unit_id + }) + + # This pricelist doesn't show the discount + first_pricelist = self.env['product.pricelist'].create({ + 'name': 'First pricelist', + 'currency_id': other_currency.id, + 'discount_policy': 'with_discount', + 'item_ids': [(0, 0, { + 'compute_price': 'percentage', + 'base': 'list_price', + 'percent_price': 10, + 'applied_on': '3_global', + 'name': 'First discount' + })] + }) + + so = self.env['sale.order'].create({ + 'partner_id': partner.id, + 'date_order': '2018-07-12', + 'pricelist_id': first_pricelist.id, + }) + + order_line = self.env['sale.order.line'].new({ + 'order_id': so.id, + 'product_id': computer_case.id, + }) + + # force compute uom and prices + order_line.product_id_change() + self.assertEqual(order_line.price_unit, 180, "First pricelist rule not applied") + order_line.product_uom = new_uom + order_line.product_uom_change() + self.assertEqual(order_line.price_unit, 1800, "First pricelist rule not applied") From 755081cbf4d3df629a33565fc4e59bb4d020ad52 Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Thu, 12 Jul 2018 11:46:43 +0200 Subject: [PATCH 8/8] [FIX] account: reconciliation widget rounding Set the rounding method to Round Globally Create a tax A with rate = 1.1040 % Create a bank statement with one line having amount = 1153.960 Go on to reconcile that bank statement Reconcile with a new line on which the tax A applies Create the write-off Click on Reconcile Before this commit, an error was thrown by the server (Move unbalanced) This was because the amount of the write-off line sent by JS (which is the tax amount) was 12.7397 The server though computes the taxes by forcing the rounding and gives off a tax amount of 12.74 Hence the error After this commit, the JS will fetch the amount of the taxes by forcing the rounding there also And the reconciliation process goes well This fix is already present in v11.0 at 6fce298d7013379c4383e8368c40ee06d6146423 OPW 1859939 closes #25733 --- addons/account/static/src/js/account_reconciliation_widgets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account/static/src/js/account_reconciliation_widgets.js b/addons/account/static/src/js/account_reconciliation_widgets.js index a2b0f66d9b7..5d00984157c 100644 --- a/addons/account/static/src/js/account_reconciliation_widgets.js +++ b/addons/account/static/src/js/account_reconciliation_widgets.js @@ -1000,7 +1000,7 @@ var abstractReconciliationLine = Widget.extend({ var tax_id = self.tax_id_field.get("value"); if (amount && tax_id) { deferred_tax = self.model_tax - .call("json_friendly_compute_all", [[tax_id], amount, self.get("currency_id")]) + .call("json_friendly_compute_all", [[tax_id], amount, self.get("currency_id")], {context: {round: true}}) // just as the python will do .then(function(data){ line_created_being_edited.length = 1; // remove tax lines line_created_being_edited[0].amount_before_tax = amount;