From 72d7dd8994f21978379f6a80c0c21d56719f342f Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Tue, 11 Sep 2018 17:40:46 +0200 Subject: [PATCH 01/11] [FIX] *mail_thread: don't modify modified help The 'help' of window action is often fiddled with, adding thing before, after or arround it. For example in CRM leads, we add at the beginning "Click to add a new opportunity" with an arrow towards the button, and after if there is a mail alias: "All email incoming to * will automatically create new...". But for crm.lead, hr.expense, sale.order this would not take into account that the fiddled "help" can be edited, so if we edit 2 times help in studio or backend action editing, we would get: Click to add a new opportunity Click to add a new opportunity Click to add a new opportunity [Original help content] All email incoming to * will automatically create new... All email incoming to * will automatically create new... All email incoming to * will automatically create new... And see several "arrows" towards the button (in enterprise the additional ones are on same color background). With this commit we do what is done in "mail.thread" by default which is not fiddling with the `help` if has been fiddled before (if it contains "oe_view_nocontent_create" class). note: for 10.0 up to not including 11.0 which is fixed by #26911 opw-1877663 closes #26912 --- addons/crm/models/crm_lead.py | 6 ++++-- addons/hr_expense/models/hr_expense.py | 4 ++-- addons/im_livechat/models/mail_channel.py | 2 +- addons/mail/models/mail_thread.py | 4 +++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index 3c44753c8c0..a92dcf3499f 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -871,7 +871,7 @@ class Lead(FormatAddress, models.Model): @api.model def get_empty_list_help(self, help): - if help: + if help and help.find("oe_view_nocontent_create") == -1: alias_record = self.env.ref("crm.mail_alias_lead_info", raise_if_not_found=False) if alias_record and alias_record.alias_domain and alias_record.alias_name: email = '%s@%s' % (alias_record.alias_name, alias_record.alias_domain) @@ -879,7 +879,9 @@ class Lead(FormatAddress, models.Model): dynamic_help = _("""All email incoming to %s will automatically create new opportunity. Update your business card, phone book, social media,... Send an email right now and see it here.""") % (email_link,) - return '

%s

%s

%s

' % (_('Click to add a new opportunity'), help, dynamic_help) + return '

%s

%s

%s
%s

%s' % ( + return '

%s
%s

%s' % ( _('Click to add a new expense,'), _('or send receipts by email to %s.') % (link,), help_message) diff --git a/addons/im_livechat/models/mail_channel.py b/addons/im_livechat/models/mail_channel.py index 6153e7cb7d8..b0fbee82983 100644 --- a/addons/im_livechat/models/mail_channel.py +++ b/addons/im_livechat/models/mail_channel.py @@ -105,5 +105,5 @@ class MailChannel(models.Model): @api.model def get_empty_list_help(self, help): if help: - return '%s

' % (help) + return '

%s

' % (help) return super(MailChannel, self).get_empty_list_help(help) diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 0a4f718b677..9de945ece86 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -341,7 +341,9 @@ class MailThread(models.AbstractModel): 'email_link': email_link } } - return "%(static_help)s

%(dyn_help)s" % { + # do not add alias two times if it was added previously + if not help or help.find("oe_view_nocontent_alias") == -1: + return '%(static_help)s

%(dyn_help)s

' % { 'static_help': help or '', 'dyn_help': _("You could also add a new %(document)s by sending an email to: %(email_link)s.") % { 'document': document_name, From 60c9742dfbad6c8ce8875a7f373d26e2a0db0c01 Mon Sep 17 00:00:00 2001 From: Frederik Kramer Date: Tue, 28 Aug 2018 16:59:51 +0200 Subject: [PATCH 02/11] [CLA] backport signature for initos This is a backport of 5a53e5b8e9aed4 as requested at #26638 Added Florian Kantelberg to our team of contributors --- doc/cla/corporate/initos.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/cla/corporate/initos.md b/doc/cla/corporate/initos.md index 3c95fe2d07f..dd7fb8ea0e5 100644 --- a/doc/cla/corporate/initos.md +++ b/doc/cla/corporate/initos.md @@ -21,3 +21,4 @@ Peter Hahn peter.hahn@initos.com https://github.com/codingforfun Claudia Haida claudia.haida@initos.com Andreas Zöllner andreas.zoellner@initos.com https://github.com/azoellner Rami Alwafaie rami.alwafaie@initos.com https://github.com/rami-wafaie +Florian Kantelberg florian.kantelberg@initos.com https://github.com/fkantelberg From 4cbc9b8d611aba925676deb2763f829c7ae15bcb Mon Sep 17 00:00:00 2001 From: Mathieu Duckerts-Antoine Date: Fri, 7 Sep 2018 12:21:55 +0200 Subject: [PATCH 03/11] [FIX] account: translate default overdue_msg opw-1880132 Closes #26852 --- addons/account/i18n/account.pot | 13 +++++++++++++ addons/account/models/company.py | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/addons/account/i18n/account.pot b/addons/account/i18n/account.pot index 617430fad62..cf6c74c5edf 100644 --- a/addons/account/i18n/account.pot +++ b/addons/account/i18n/account.pot @@ -3263,6 +3263,19 @@ msgstr "" msgid "Day(s) after the invoice date" msgstr "" +#. module: account +#: code:addons/account/models/company.py:44 +#, python-format +msgid "Dear Sir/Madam,\n" +"\n" +"Our records indicate that some payments on your account are still due. Please find details below.\n" +"If the amount has already been paid, please disregard this notice. Otherwise, please forward us the total amount stated below.\n" +"If you have any queries regarding your account, Please contact us.\n" +"\n" +"Thank you in advance for your cooperation.\n" +"Best Regards," +msgstr "" + #. module: account #: model:ir.model.fields,field_description:account.field_account_move_line_debit #: model:ir.ui.view,arch_db:account.report_financial diff --git a/addons/account/models/company.py b/addons/account/models/company.py index 5dd2568d708..80cb1b4e31d 100644 --- a/addons/account/models/company.py +++ b/addons/account/models/company.py @@ -41,14 +41,14 @@ class ResCompany(models.Model): property_stock_valuation_account_id = fields.Many2one('account.account', string="Account Template for Stock Valuation") bank_journal_ids = fields.One2many('account.journal', 'company_id', domain=[('type', '=', 'bank')], string='Bank Journals') overdue_msg = fields.Text(string='Overdue Payments Message', translate=True, - default='''Dear Sir/Madam, + default=lambda t: _('''Dear Sir/Madam, Our records indicate that some payments on your account are still due. Please find details below. If the amount has already been paid, please disregard this notice. Otherwise, please forward us the total amount stated below. If you have any queries regarding your account, Please contact us. Thank you in advance for your cooperation. -Best Regards,''') +Best Regards,''')) @api.multi def _check_lock_dates(self, vals): From e6ca846c65bd94bf4c3ecb57847e93ae8ecaa9e9 Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Wed, 12 Sep 2018 17:05:44 +0200 Subject: [PATCH 04/11] [FIX] base_geolocalize, website_crm_partner_assign: API keys are now required Google Important Updated: API keys are now required We began enforcing the use of API keys, effective June 11th 2018. Keyless usage will result in a degraded experience, or an error. https://developers.google.com/maps/billing/important-updates --- addons/base_geolocalize/models/res_partner.py | 44 +++++++++++++------ .../models/crm_lead.py | 17 ++----- .../tests/test_partner_assign.py | 2 +- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/addons/base_geolocalize/models/res_partner.py b/addons/base_geolocalize/models/res_partner.py index 44fa8d9692f..4c36ffe9207 100644 --- a/addons/base_geolocalize/models/res_partner.py +++ b/addons/base_geolocalize/models/res_partner.py @@ -1,16 +1,26 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import json +import logging import urllib2 from odoo import api, fields, models, tools, _ from odoo.exceptions import UserError +_logger = logging.getLogger(__name__) -def geo_find(addr): + +def geo_find(addr, apikey=False): if not addr: return None - url = 'https://maps.googleapis.com/maps/api/geocode/json?sensor=false&address=' + + if not apikey: + raise UserError(_('''API key for GeoCoding (Places) required.\n + Save this key in System Parameters with key: google.api_key_geocode, value: + Visit https://developers.google.com/maps/documentation/geocoding/get-api-key for more information. + ''')) + + url = "https://maps.googleapis.com/maps/api/geocode/json?key=%s&sensor=false&address=" % apikey url += urllib2.quote(addr.encode('utf8')) try: @@ -19,6 +29,8 @@ def geo_find(addr): raise UserError(_('Cannot contact geolocation servers. Please make sure that your Internet connection is up and running (%s).') % e) if result['status'] != 'OK': + if result.get('error_message'): + _logger.error(result['error_message']) return None try: @@ -46,22 +58,26 @@ class ResPartner(models.Model): partner_longitude = fields.Float(string='Geo Longitude', digits=(16, 5)) date_localization = fields.Date(string='Geolocation Date') + @classmethod + def _geo_localize(cls, apikey, street='', zip='', city='', state='', country=''): + search = geo_query_address(street=street, zip=zip, city=city, state=state, country=country) + result = geo_find(search, apikey) + if result is None: + search = geo_query_address(city=city, state=state, country=country) + result = geo_find(search, apikey) + return result + @api.multi def geo_localize(self): # We need country names in English below + apikey = self.env['ir.config_parameter'].sudo().get_param('google.api_key_geocode') for partner in self.with_context(lang='en_US'): - result = geo_find(geo_query_address(street=partner.street, - zip=partner.zip, - city=partner.city, - state=partner.state_id.name, - country=partner.country_id.name)) - if result is None: - result = geo_find(geo_query_address( - city=partner.city, - state=partner.state_id.name, - country=partner.country_id.name - )) - + result = partner._geo_localize(apikey, + partner.street, + partner.zip, + partner.city, + partner.state_id.name, + partner.country_id.name) if result: partner.write({ 'partner_latitude': result[0], diff --git a/addons/website_crm_partner_assign/models/crm_lead.py b/addons/website_crm_partner_assign/models/crm_lead.py index 3e7651bbb2a..a927cb37e12 100644 --- a/addons/website_crm_partner_assign/models/crm_lead.py +++ b/addons/website_crm_partner_assign/models/crm_lead.py @@ -88,19 +88,10 @@ class CrmLead(models.Model): if lead.partner_latitude and lead.partner_longitude: continue if lead.country_id: - result = geo_find(geo_query_address(street=lead.street, - zip=lead.zip, - city=lead.city, - state=lead.state_id.name, - country=lead.country_id.name)) - - if result is None: - result = geo_find(geo_query_address( - city=lead.city, - state=lead.state_id.name, - country=lead.country_id.name - )) - + apikey = self.env['ir.config_parameter'].sudo().get_param('google.api_key_geocode') + result = self.env['res.partner']._geo_localize(apikey, + lead.street, lead.zip, lead.city, + lead.state_id.name, lead.country_id.name) if result: lead.write({ 'partner_latitude': result[0], diff --git a/addons/website_crm_partner_assign/tests/test_partner_assign.py b/addons/website_crm_partner_assign/tests/test_partner_assign.py index 6bf5e00af76..b3249fd3d7c 100644 --- a/addons/website_crm_partner_assign/tests/test_partner_assign.py +++ b/addons/website_crm_partner_assign/tests/test_partner_assign.py @@ -11,7 +11,7 @@ class TestPartnerAssign(TransactionCase): def setUp(self): super(TestPartnerAssign, self).setUp() - def geo_find(addr): + def geo_find(addr, apikey): return { 'Wavre, Belgium': (50.7158956, 4.6128075), 'Cannon Hill Park, B46 3AG Birmingham, United Kingdom': (52.45216, -1.898578), From 83292e522aabcf753fe1c55b76ce04eb961cd9ad Mon Sep 17 00:00:00 2001 From: len-odoo Date: Thu, 6 Sep 2018 11:34:02 +0200 Subject: [PATCH 05/11] [FIX] doc: add documentation for widget formatter support in pivot view Commit: https://github.com/odoo/odoo/commit/8c1204954ef571e51e94ddf4070043ec2d5de3c6 put back support in v11 for formatter widgets in pivot views. We document this feature. opw 1876445 --- doc/reference/views.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/reference/views.rst b/doc/reference/views.rst index d23a0db9da3..59ee7dd5571 100644 --- a/doc/reference/views.rst +++ b/doc/reference/views.rst @@ -808,6 +808,18 @@ element is ```` which can take the following attributes: The elements allowed within a pivot view are the same as for the graph view. +In Pivot view a ``field`` can have a ``widget`` attribute to dictate its format. +The widget should be a field formatter, of which the most interesting are +``date``, ``datetime``, ``float_time``, and ``monetary``. + +For instance a timesheet pivot view could be defined as:: + + + + + + + .. _reference/views/kanban: Kanban From cd62900878c6f5fe42f1f323f0aea79dd3a17fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Thu, 12 Apr 2018 09:39:44 +0200 Subject: [PATCH 06/11] [IMP] web: add fail fast feature to qunit test suite With this commit, we introduce a new 'fail fast' feature to our qunit test suite: when it is activated, the qunit test suite will immediately stop after the first failed test. It is accessible as a flag in the url (failfast), or by clicking the checkbox in the UI. It is currently not activated by default. Note that this commit also change the url for the runbot phantomjs test in order to activate this feature. This allows us to increase the global timeout for the js test suite without fear! Backport of 611c836a4633 --- .../web/static/tests/helpers/qunit_config.js | 21 +++++++++++++++++++ addons/web/tests/test_js.py | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/addons/web/static/tests/helpers/qunit_config.js b/addons/web/static/tests/helpers/qunit_config.js index 8d2b287ab88..1fb99da4e43 100644 --- a/addons/web/static/tests/helpers/qunit_config.js +++ b/addons/web/static/tests/helpers/qunit_config.js @@ -130,4 +130,25 @@ function _addSortButton() { }); } +/** + * We add here a 'fail fast' feature: we often want to stop the test suite after + * the first failed test. This is also useful for the runbot test suites. + */ + +QUnit.config.urlConfig.push({ + id: "failfast", + label: "Fail Fast", + tooltip: "Stop the test suite immediately after the first failed test." +}); + +QUnit.begin(function() { + if (QUnit.config.failfast) { + QUnit.testDone(function(details) { + if (details.failed > 0) { + QUnit.config.queue.length = 0; + } + }); + } +}); + })(); diff --git a/addons/web/tests/test_js.py b/addons/web/tests/test_js.py index 62bc73d67d6..5d0f961fe59 100644 --- a/addons/web/tests/test_js.py +++ b/addons/web/tests/test_js.py @@ -13,11 +13,11 @@ class WebSuite(odoo.tests.HttpCase): def test_01_js(self): # webclient desktop test suite - self.phantom_js('/web/tests?mod=web', "", "", login='admin', timeout=390) + self.phantom_js('/web/tests?mod=web&failfast', "", "", login='admin', timeout=1800) def test_02_js(self): # webclient mobile test suite - self.phantom_js('/web/tests/mobile?mod=web', "", "", login='admin', timeout=300) + self.phantom_js('/web/tests/mobile?mod=web&failfast', "", "", login='admin', timeout=1800) def test_check_suite(self): # verify no js test is using `QUnit.only` as it forbid any other test to be executed From daa6cea4397833ccd3e2987234d170d217006273 Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Thu, 13 Sep 2018 09:51:28 +0200 Subject: [PATCH 07/11] [FIX] mrp: Upating the quantity of a MO Steps to reproduce the bug: - Create three stockable products P1, P2, P3 - Create a BOM for P1 with P2 and P3 as components - Create a MO with 5 P1 - Check the availability and plan it - Go on the WO and process only 3 P1 - Go to the MO and update the quantity to 3 Bug: From this point, it was impossible to pass the MO to the state= 'done' With this fix, you can now, click on "Post inventory" and the button "Mark as Done" will appear. opw:1874227 --- addons/mrp/wizard/change_production_qty.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py index 4816981a1fe..cddc9101043 100644 --- a/addons/mrp/wizard/change_production_qty.py +++ b/addons/mrp/wizard/change_production_qty.py @@ -77,6 +77,8 @@ class ChangeProductionQty(models.TransientModel): wo.qty_producing = quantity if wo.qty_produced < wo.qty_production and wo.state == 'done': wo.state = 'progress' + if wo.qty_produced == wo.qty_production and wo.state == 'progress': + wo.state = 'done' # assign moves; last operation receive all unassigned moves # TODO: following could be put in a function as it is similar as code in _workorders_create # TODO: only needed when creating new moves From cbdb7383e9a88610ae6552ef0725a54f1aacd625 Mon Sep 17 00:00:00 2001 From: Richard Mathot Date: Thu, 13 Sep 2018 11:00:15 +0200 Subject: [PATCH 08/11] [REV] account: revert 8781286f1c35a7ee97bbb76c7933f24c8afaab43 Causes a traceback "Expected singleton" when opening the Reconciliation from the accounting dashboard --- addons/account/models/account_bank_statement.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index cd9d6d70717..e60c0dc09d4 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -298,7 +298,7 @@ class AccountBankStatement(models.Model): WHERE account_id IS NULL AND not exists (select 1 from account_move m where m.statement_line_id = stl.id) AND company_id = %s """ - params = (self.company_id.id,) + params = (self.env.user.company_id.id,) if statements: sql_query += ' AND stl.statement_id IN %s' params += (tuple(statements.ids),) @@ -323,7 +323,7 @@ class AccountBankStatement(models.Model): ) AND aml.ref IN %s """ - params = (self.company_id.id, (st_lines_left[0].journal_id.default_credit_account_id.id, st_lines_left[0].journal_id.default_debit_account_id.id), tuple(refs)) + params = (self.env.user.company_id.id, (st_lines_left[0].journal_id.default_credit_account_id.id, st_lines_left[0].journal_id.default_debit_account_id.id), tuple(refs)) if statements: sql_query += 'AND stl.id IN %s' params += (tuple(stl_to_assign_partner),) @@ -620,7 +620,7 @@ class AccountBankStatementLine(models.Model): st_line_currency = self.currency_id or self.journal_id.currency_id currency = (st_line_currency and st_line_currency != company_currency) and st_line_currency.id or False precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places - params = {'company_id': self.company_id.id, + params = {'company_id': self.env.user.company_id.id, 'account_payable_receivable': (self.journal_id.default_credit_account_id.id, self.journal_id.default_debit_account_id.id), 'amount': float_round(amount, precision_digits=precision), 'partner_id': self.partner_id.id, @@ -671,7 +671,7 @@ class AccountBankStatementLine(models.Model): st_line_currency = self.currency_id or self.journal_id.currency_id currency = (st_line_currency and st_line_currency != company_currency) and st_line_currency.id or False precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places - params = {'company_id': self.company_id.id, + params = {'company_id': self.env.user.company_id.id, 'account_payable_receivable': (self.journal_id.default_credit_account_id.id, self.journal_id.default_debit_account_id.id), 'amount': float_round(amount, precision_digits=precision), 'partner_id': self.partner_id.id, From ad584540d4a91c1e8b97680e1e84536d0b8a6d91 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 12 Sep 2018 11:05:31 +0200 Subject: [PATCH 09/11] [FIX] account: tax account Use the cash basis account for cash basis taxes. Follow-up of 1b475753723c81f46414357dfd449a47fe342dff opw-1858492 --- addons/account/models/account_move.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index ca1dfbd98a2..b3b026dc66f 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -1315,6 +1315,7 @@ class AccountMoveLine(models.Model): temp['amount_currency'] = bank.company_id.currency_id.with_context(ctx).compute(tax_vals['amount'], bank.currency_id, round=True) if vals.get('tax_exigible'): temp['tax_exigible'] = True + temp['account_id'] = tax.cash_basis_account.id or account_id tax_lines_vals.append(temp) #Toggle the 'tax_exigible' field to False in case it is not yet given and the tax in 'tax_line_id' or one of From dee5ce3be14cac5510c8b723ba2492b1b701a934 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 12 Sep 2018 12:21:43 +0200 Subject: [PATCH 10/11] [FIX] account: open balance account when partner change `line.amount` is `undefined`, leading to an incorrect account selected. opw-1878890 --- .../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 a975380d005..7c5134f8b9e 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -760,7 +760,7 @@ var StatementModel = BasicModel.extend({ }).then(function (result) { if (result.length > 0) { var line = self.getLine(handle); - self.lines[handle].st_line.open_balance_account_id = line.amount < 0 ? result[0]['property_account_payable_id'][0] : result[0]['property_account_receivable_id'][0]; + self.lines[handle].st_line.open_balance_account_id = line.balance.amount < 0 ? result[0]['property_account_payable_id'][0] : result[0]['property_account_receivable_id'][0]; } }); }, From 52c89adef7f49a2e12e9a4f6f9f45844bff964da Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 12 Sep 2018 14:47:15 +0200 Subject: [PATCH 11/11] [FIX] sale_stock, stock_dropshipping: ordered qty - Set a vendor on Product A - Create a SO with 1 Unit of A, set the Drop Ship route - Confirm the SO => a PO with 1 Unit is created - Change the oredered quantity to 2 The PO is updated with a total quantity of 3 instead of 2. Since the removal of procurements, the quantity is computed based on the stock moves. However, in this case, no stock move has been created since the PO is not yet confirmed. In this specific case, we fall back on the PO line quantity. The logic is similar to `_quantity_in_progress`. opw-1879118 --- addons/sale_stock/models/sale_order.py | 11 +++-- addons/stock_dropshipping/models/__init__.py | 3 +- addons/stock_dropshipping/models/sale.py | 21 +++++++++ addons/stock_dropshipping/tests/__init__.py | 2 +- .../stock_dropshipping/tests/test_dropship.py | 45 +++++++++++++++++++ 5 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 addons/stock_dropshipping/models/sale.py create mode 100644 addons/stock_dropshipping/tests/test_dropship.py diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py index bcf147c7cd4..3917e2782de 100644 --- a/addons/sale_stock/models/sale_order.py +++ b/addons/sale_stock/models/sale_order.py @@ -217,6 +217,13 @@ class SaleOrderLine(models.Model): }) return values + def _get_qty_procurement(self): + self.ensure_one() + qty = 0.0 + for move in self.move_ids.filtered(lambda r: r.state != 'cancel'): + qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') + return qty + @api.multi def _action_launch_procurement_rule(self): """ @@ -229,9 +236,7 @@ class SaleOrderLine(models.Model): for line in self: if line.state != 'sale' or not line.product_id.type in ('consu','product'): continue - qty = 0.0 - for move in line.move_ids.filtered(lambda r: r.state != 'cancel'): - qty += move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom, rounding_method='HALF-UP') + qty = line._get_qty_procurement() if float_compare(qty, line.product_uom_qty, precision_digits=precision) >= 0: continue diff --git a/addons/stock_dropshipping/models/__init__.py b/addons/stock_dropshipping/models/__init__.py index c617a28246d..7d6f1be055f 100644 --- a/addons/stock_dropshipping/models/__init__.py +++ b/addons/stock_dropshipping/models/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import purchase \ No newline at end of file +from . import purchase +from . import sale diff --git a/addons/stock_dropshipping/models/sale.py b/addons/stock_dropshipping/models/sale.py new file mode 100644 index 00000000000..81f975cbce6 --- /dev/null +++ b/addons/stock_dropshipping/models/sale.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, models, fields + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + purchase_line_ids = fields.One2many('purchase.order.line', 'sale_line_id') + + @api.multi + def _get_qty_procurement(self): + if not self.move_ids.filtered(lambda r: r.state != 'cancel') and self.purchase_line_ids.filtered(lambda r: r.state != 'cancel'): + qty = 0.0 + for po_line in self.purchase_line_ids.filtered(lambda r: r.state != 'cancel'): + qty += po_line.product_uom._compute_quantity(po_line.product_qty, self.product_uom, rounding_method='HALF-UP') + return qty + else: + return super(SaleOrderLine, self)._get_qty_procurement() + diff --git a/addons/stock_dropshipping/tests/__init__.py b/addons/stock_dropshipping/tests/__init__.py index 4df849a1468..7d0e80dc968 100644 --- a/addons/stock_dropshipping/tests/__init__.py +++ b/addons/stock_dropshipping/tests/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- +from . import test_dropship from . import test_stockvaluation - diff --git a/addons/stock_dropshipping/tests/test_dropship.py b/addons/stock_dropshipping/tests/test_dropship.py new file mode 100644 index 00000000000..7fb7a4ddf7c --- /dev/null +++ b/addons/stock_dropshipping/tests/test_dropship.py @@ -0,0 +1,45 @@ +from odoo.tests.common import TransactionCase + + +class TestDropship(TransactionCase): + def test_change_qty(self): + # enable the dropship and MTO route on the product + prod = self.env.ref('product.product_product_8') + dropshipping_route = self.env.ref('stock_dropshipping.route_drop_shipping') + mto_route = self.env.ref('stock.route_warehouse0_mto') + prod.write({'route_ids': [(6, 0, [dropshipping_route.id, mto_route.id])]}) + + # add a vendor + vendor1 = self.env['res.partner'].create({'name': 'vendor1'}) + seller1 = self.env['product.supplierinfo'].create({ + 'name': vendor1.id, + 'price': 8, + }) + prod.write({'seller_ids': [(6, 0, [seller1.id])]}) + + # sell one unit of this product + cust = self.env['res.partner'].create({'name': 'customer1'}) + so = self.env['sale.order'].create({ + 'partner_id': cust.id, + 'partner_invoice_id': cust.id, + 'partner_shipping_id': cust.id, + 'order_line': [(0, 0, { + 'name': prod.name, + 'product_id': prod.id, + 'product_uom_qty': 1.00, + 'product_uom': prod.uom_id.id, + 'price_unit': 12, + })], + 'pricelist_id': self.env.ref('product.list0').id, + 'picking_policy': 'direct', + }) + so.action_confirm() + po = self.env['purchase.order'].search([('group_id', '=', so.procurement_group_id.id)]) + po_line = po.order_line + + # Check the qty on the P0 + self.assertAlmostEqual(po_line.product_qty, 1.00) + + # Update qty on SO and check PO + so.order_line.product_uom_qty = 2.00 + self.assertAlmostEqual(po_line.product_qty, 2.00)